[
  {
    "path": ".devcontainer/Dockerfile",
    "content": "ARG VARIANT=\"nightly-bookworm-slim\"\nFROM rustlang/rust:${VARIANT}\n\nENV DEBIAN_FRONTEND=noninteractive\n\n# Install required system libraries and NPM\n# Reference: https://dioxuslabs.com/learn/0.7/beyond/contributing#before-you-contribute\nRUN apt-get update -qq \\\n    && apt-get install -y -qq \\\n    libgdk3.0-cil \\\n    libatk1.0-dev \\\n    libcairo2-dev \\\n    libpango1.0-dev \\\n    libgdk-pixbuf2.0-dev \\\n    libsoup-3.0-dev \\\n    libjavascriptcoregtk-4.1-dev \\\n    libwebkit2gtk-4.1-dev \\\n    npm \\\n    && rm -rf /var/lib/apt/lists/*\n\n# Set a shared folder for pre-installed browsers\nENV PLAYWRIGHT_BROWSERS_PATH=/ms-playwright\n\n# Temporarily install Playwright globally to install the browsers and their dependencies\nRUN npm install -g @playwright/test && \\\n    npx playwright install --with-deps && \\\n    npm uninstall -g @playwright/test\n"
  },
  {
    "path": ".devcontainer/README.md",
    "content": "# Dev Container\n\nA dev container in the most simple context allows one to create a consistent development environment within a docker container that can easily be opened locally or remotely via codespaces such that contributors don't need to install anything to contribute.\n\n## Useful Links\n\n- <https://code.visualstudio.com/docs/devcontainers/containers>\n- <https://containers.dev/>\n- <https://github.com/devcontainers>\n- <https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers>\n\n## Using A Dev Container\n\n### Locally\n\nTo use this dev container locally, make sure Docker is installed and in VSCode install the `ms-vscode-remote.remote-containers` extension. Then from the root of Dioxus you can type `Ctrl + Shift + P`, then choose `Dev Containers: Rebuild and Reopen in Devcontainer`.\n\n### Codespaces\n\n[Codespaces Setup](https://docs.github.com/en/codespaces/developing-in-codespaces/creating-a-codespace-for-a-repository#creating-a-codespace-for-a-repository)\n\n### Playwright Tests\nThe dev container comes with Playwright dependencies pre-installed.\nYou can run the tests located in `packages/playwright-tests` by using the VSCode extension or by executing commands such as:\n\n```bash\n# Run all tests\nnpx playwright test\n\n# Run tests using the UI mode\nnpx playwright test --ui-host=0.0.0.0\n```\n\n## Troubleshooting\n\nIf having difficulty commiting with github, and you use ssh or gpg keys, you may need to ensure that the keys are being shared properly between your host and VSCode.\n\nThough VSCode does a pretty good job sharing credentials between host and devcontainer, to save some time you can always just reopen the container locally to commit with `Ctrl + Shift + P`, then choose `Dev Containers: Reopen Folder Locally`\n"
  },
  {
    "path": ".devcontainer/devcontainer.json",
    "content": "{\n    \"name\": \"dioxus\",\n    \"remoteUser\": \"vscode\",\n    \"build\": {\n        \"dockerfile\": \"./Dockerfile\",\n        \"context\": \".\"\n    },\n    \"features\": {\n        \"ghcr.io/devcontainers/features/common-utils:2\": {\n            \"installZsh\": \"true\",\n            \"username\": \"vscode\",\n            \"uid\": \"1000\",\n            \"gid\": \"1000\",\n            \"upgradePackages\": \"true\"\n        }\n    },\n    \"containerEnv\": {\n        \"RUST_LOG\": \"INFO\"\n    },\n    \"customizations\": {\n        \"vscode\": {\n            \"settings\": {\n                \"files.watcherExclude\": {\n                    \"**/target/**\": true\n                },\n                \"[rust]\": {\n                    \"editor.formatOnSave\": true\n                }\n            },\n            \"extensions\": [\n                \"rust-lang.rust-analyzer\",\n                \"tamasfe.even-better-toml\",\n                \"fill-labs.dependi\",\n                \"ms-playwright.playwright\"\n            ]\n        }\n    }\n}"
  },
  {
    "path": ".github/CODEOWNERS",
    "content": "# borrowed from tauri - only allow core maintainers to approve PRs\n\n* @DioxusLabs/core\n\n.github @DioxusLabs/core\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "# These are supported funding model platforms\n\ngithub: DioxusLabs # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]\nopen_collective: dioxus-labs # Replace with a single Open Collective username\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Create a report to help us improve Dioxus\nlabels: bug\n---\n\n**Problem**\n\n<!-- A clear and concise description of what the bug is. -->\n\n**Steps To Reproduce**\n\nSteps to reproduce the behavior:\n\n- \n- \n- \n\n**Expected behavior**\n\n<!-- A clear and concise description of what you expected to happen. -->\n\n**Screenshots**\n\n<!-- If applicable, add screenshots to help explain your problem. -->\n\n**Environment:**\n\n- Dioxus version:  <!-- e.g. v0.17, main -->\n- Rust version:    <!-- e.g. 1.123.0, nightly -->\n- OS info:         <!-- e.g. macOS, NixOS 25.05 -->\n- App platform:    <!-- e.g. web, desktop -->\n\n**Questionnaire**\n\n<!-- If you feel up to the challenge, please uncomment applicable lines below: -->\n\n<!-- I'm interested in fixing this myself but don't know where to start. -->\n<!-- I would like to fix and I have a solution. -->\n<!-- I don't have time to fix this right now, but maybe later. -->\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_requst.md",
    "content": "---\nname: Feature Request\nabout: If you have any interesting advice, you can tell us.\nlabels: enhancement\n---\n\n## Feature Request\n\n<!--\nDescribe the issue in detail and why we should add it. To help us out,\nplease poke through our issue tracker and make sure it's not a duplicate issue.\n-->\n\n## Implement Suggestion\n\n<!--\nIf you have any suggestions on how to design this feature or any prior art,\nlist them here.\n-->\n"
  },
  {
    "path": ".github/actions/free-disk-space/action.yml",
    "content": "name: Free Disk Space\ndescription: Free up disk space on the runner\nruns:\n  using: composite\n  steps:\n  - name: Free Disk Space (Ubuntu)\n    if: runner.os == 'Linux'\n    shell: bash\n    run: |\n      echo \"Freeing up disk space...\"\n      sudo rm -rf /opt/ghc\n      sudo rm -rf /usr/share/dotnet\n      sudo rm -rf /usr/local/lib/android\n      sudo rm -rf /usr/share/swift\n      sudo docker image prune --all --force || true\n\n\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n\n  # Maintain dependencies for GitHub Actions\n  - package-ecosystem: \"github-actions\"\n    directory: \"/\"\n    schedule:\n      interval: \"weekly\"\n"
  },
  {
    "path": ".github/install.ps1",
    "content": "#!/usr/bin/env pwsh\n\n$ErrorActionPreference = 'Stop'\n\nif ($v) {\n  $Version = \"v${v}\"\n}\nif ($Args.Length -eq 1) {\n  $Version = $Args.Get(0)\n}\n\n$DxInstall = $env:DX_INSTALL\n$BinDir = if ($DxInstall) {\n  \"${DxInstall}\\bin\"\n} else {\n  \"${Home}\\.dx\\bin\"\n}\n\n$DxZip = \"$BinDir\\dx.zip\"\n$DxExe = \"$BinDir\\dx.exe\"\n$Target = 'x86_64-pc-windows-msvc'\n\n$DownloadUrl = if (!$Version) {\n    \"https://github.com/dioxuslabs/dioxus/releases/latest/download/dx-${target}.zip\"\n} else {\n    \"https://github.com/dioxuslabs/dioxus/releases/download/${Version}/dx-${target}.zip\"\n}\n\n\nif (!(Test-Path $BinDir)) {\n  New-Item $BinDir -ItemType Directory | Out-Null\n}\n\ncurl.exe --ssl-revoke-best-effort -Lo $DxZip $DownloadUrl\n\ntar.exe xf $DxZip -C $BinDir\n\nRemove-Item $DxZip\n\n$CargoBin = \"${Home}\\.cargo\\bin\"\n\nif (!(Test-Path $CargoBin)) {\n    New-Item $CargoBin -ItemType Directory | Out-Null\n}\n\nCopy-Item $DxExe \"$CargoBin\\dx.exe\" -Force\n\n# $User = [System.EnvironmentVariableTarget]::User\n# $Path = [System.Environment]::GetEnvironmentVariable('Path', $User)\n# if (!(\";${Path};\".ToLower() -like \"*;${BinDir};*\".ToLower())) {\n#   [System.Environment]::SetEnvironmentVariable('Path', \"${Path};${BinDir}\", $User)\n#   $Env:Path += \";${BinDir}\"\n# }\n\nWrite-Output \"dx was installed successfully! 💫\"\nWrite-Output \"Run 'dx --help' to get started\"\n"
  },
  {
    "path": ".github/install.sh",
    "content": "#!/bin/sh\nset -eo pipefail\n\n# Reset\nColor_Off=''\n\n# Regular Colors\nRed=''\nGreen=''\nDim='' # White\n\n# Bold\nBold_White=''\nBold_Green=''\n\nif [ -t 1 ]; then\n    # Reset\n    Color_Off='\\033[0m' # Text Reset\n\n    # Regular Colors\n    Red='\\033[0;31m'   # Red\n    Green='\\033[0;32m' # Green\n    Dim='\\033[0;2m'    # White\n\n    # Bold\n    Bold_Green='\\033[1;32m' # Bold Green\n    Bold_White='\\033[1m'    # Bold White\nfi\n\nerror() {\n    printf \"${Red}error${Color_Off}: %s\\n\" \"$*\" >&2\n    exit 1\n}\n\ninfo() {\n    printf \"${Dim}%s ${Color_Off}\\n\" \"$*\"\n}\n\ninfo_bold() {\n    printf \"${Bold_White}%s ${Color_Off}\\n\" \"$*\"\n}\n\nsuccess() {\n    printf \"${Green}%s ${Color_Off}\\n\" \"$*\"\n}\n\ncommand -v unzip >/dev/null ||\n    error 'unzip is required to install dx'\n\nif [ $# -gt 2 ]; then\n    error 'Too many arguments, only 2 are allowed. The first can be a specific tag of dx to install. (e.g. \"dx-v0.7.1\") or `nightly` or `pr <PR_NUMBER>` to install the latest nightly or PR build.'\nfi\n\nif [ \"$OS\" = \"Windows_NT\" ]; then\n    target=\"x86_64-pc-windows-msvc\"\nelse\n    case $(uname -sm) in\n    \"Darwin x86_64\") target=\"x86_64-apple-darwin\" ;;\n    \"Darwin arm64\") target=\"aarch64-apple-darwin\" ;;\n    \"Linux aarch64\")\n        if [ -f /etc/alpine-release ]; then\n            target=\"aarch64-unknown-linux-musl\"\n        else\n            target=\"aarch64-unknown-linux-gnu\"\n        fi\n        ;;\n    *)\n        if [ -f /etc/alpine-release ]; then\n            target=\"x86_64-unknown-linux-musl\"\n        else\n            target=\"x86_64-unknown-linux-gnu\"\n        fi\n        ;;\n    esac\nfi\n\nGITHUB=${GITHUB-\"https://github.com\"}\ngithub_repo=\"$GITHUB/dioxuslabs/dioxus\"\nexe_name=dx\n\nif [ $# = 0 ]; then\n    dx_uri=$github_repo/releases/latest/download/dx-$target.zip\nelse\n    dx_uri=$github_repo/releases/download/$1/dx-$target.zip\nfi\n\nif [ -n \"$DX_INSTALL\" ]; then\n    dx_install=\"$DX_INSTALL\"\nelif [ -n \"$XDG_DATA_HOME\" ]; then\n    dx_install=\"$XDG_DATA_HOME/dx\"\nelse\n    dx_install=\"$HOME/.dx\"\nfi\nbin_dir=\"$dx_install/bin\"\nexe=\"$bin_dir/dx\"\ncargo_bin_dir=\"${CARGO_HOME:-$HOME/.cargo}/bin\"\ncargo_bin_exe=\"$cargo_bin_dir/dx\"\n\nif [ ! -d \"$bin_dir\" ]; then\n\tmkdir -p \"$bin_dir\"\nfi\n\ncurl --fail --location --progress-bar --output \"$exe.zip\" \"$dx_uri\"\nif command -v unzip >/dev/null; then\n\tunzip -d \"$bin_dir\" -o \"$exe.zip\"\nelse\n\t7z x -o\"$bin_dir\" -y \"$exe.zip\"\nfi\nchmod +x \"$exe\"\ncp \"$exe\" \"$cargo_bin_exe\" || error \"Failed to copy dx to $cargo_bin_dir\"\nrm \"$exe.zip\"\necho \"  installed: $cargo_bin_exe\"\n\necho\necho \"dx was installed successfully! 💫\"\necho\n\nif command -v dx >/dev/null; then\n\techo \"Run 'dx --help' to get started\"\nelse\n\techo \"Run '$exe --help' to get started\"\nfi\n"
  },
  {
    "path": ".github/workflows/main.yml",
    "content": "# Whenever an open PR is updated, the workflow will be triggered\n#\n# This can get expensive, so we do a lot of caching and checks to prevent unnecessary runs\n\nname: Rust CI\n\non:\n  push:\n    branches:\n      - main\n    paths:\n      - packages/**\n      - examples/**\n      - docs/guide/**\n      - src/**\n      - .github/**\n      - lib.rs\n      - Cargo.toml\n      - Makefile.toml\n\n  pull_request:\n    types: [opened, synchronize, reopened, ready_for_review]\n    branches:\n      - main\n    paths:\n      - packages/**\n      - examples/**\n      - src/**\n      - .github/**\n      - lib.rs\n      - Cargo.toml\n\n# workflow_dispatch:\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}\n  cancel-in-progress: true\n\nenv:\n  CARGO_TERM_COLOR: always\n  CARGO_INCREMENTAL: 0 # todo(jon): cargo-cache wipes incremental artifacts, but we eventually want to cache them\n  RUST_BACKTRACE: 1\n  rust_nightly: nightly-2025-10-05\n\njobs:\n  check-msrv:\n    if: github.event.pull_request.draft == false\n    name: Check MSRV\n    runs-on: blacksmith-4vcpu-ubuntu-2404\n    steps:\n      - uses: actions/checkout@v5\n      - uses: dtolnay/rust-toolchain@1.88.0\n      - uses: Swatinem/rust-cache@v2\n        with:\n          cache-all-crates: \"true\"\n      # https://github.com/foresterre/cargo-msrv/blob/4345edfe3f4fc91cc8ae6c7d6804c0748fae92ae/.github/workflows/msrv.yml\n      - name: install_cargo_msrv\n        run: cargo install cargo-msrv --all-features --version 0.16.3 --locked\n      - name: version_of_cargo_msrv\n        run: cargo msrv --version\n      - name: run_cargo_msrv\n        run: cargo msrv --output-format json verify -- cargo check\n      - name: run_cargo_msrv_on_verify_failure\n        if: ${{ failure() }}\n        run: cargo msrv --output-format json -- cargo check\n\n  test:\n    if: github.event.pull_request.draft == false\n    name: Test Suite\n    runs-on: blacksmith-4vcpu-ubuntu-2404\n    steps:\n      - uses: actions/checkout@v5\n      - name: Free Disk Space\n        uses: ./.github/actions/free-disk-space\n      - uses: awalsh128/cache-apt-pkgs-action@latest\n        with:\n          packages: libwebkit2gtk-4.1-dev libgtk-3-dev libasound2-dev libudev-dev libayatana-appindicator3-dev libxdo-dev libglib2.0-dev\n          version: 1.0\n      - uses: dtolnay/rust-toolchain@1.88.0\n        with:\n          components: rustfmt, clippy\n      - uses: Swatinem/rust-cache@v2\n        with:\n          cache-all-crates: \"true\"\n      - uses: browser-actions/setup-firefox@latest\n      - run: cargo test --lib --bins --tests --examples --workspace --exclude dioxus-desktop\n\n  release-test:\n    if: github.event.pull_request.draft == false\n    name: Test Suite with Optimizations\n    runs-on: blacksmith-4vcpu-ubuntu-2404\n    steps:\n      - uses: actions/checkout@v5\n      - name: Free Disk Space\n        uses: ./.github/actions/free-disk-space\n      - uses: awalsh128/cache-apt-pkgs-action@latest\n        with:\n          packages: libwebkit2gtk-4.1-dev libgtk-3-dev libasound2-dev libudev-dev libayatana-appindicator3-dev libxdo-dev libglib2.0-dev\n          version: 1.0\n      - uses: dtolnay/rust-toolchain@1.88.0\n        with:\n          components: rustfmt, clippy\n      - uses: Swatinem/rust-cache@v2\n        with:\n          cache-all-crates: \"true\"\n      - uses: browser-actions/setup-firefox@latest\n      - run: cargo test --lib --bins --tests --examples --workspace --exclude dioxus-desktop --profile release-unoptimized\n\n  fmt:\n    if: github.event.pull_request.draft == false\n    name: Rustfmt\n    runs-on: blacksmith-4vcpu-ubuntu-2404\n    steps:\n      - uses: actions/checkout@v5\n      - uses: dtolnay/rust-toolchain@1.88.0\n        with:\n          components: rustfmt\n      - uses: Swatinem/rust-cache@v2\n        with:\n          cache-all-crates: \"true\"\n      - run: cargo fmt --all -- --check\n\n  schema:\n    if: github.event.pull_request.draft == false\n    name: Check Schema\n    runs-on: blacksmith-4vcpu-ubuntu-2404\n    steps:\n      - uses: actions/checkout@v5\n      - uses: dtolnay/rust-toolchain@1.88.0\n      - uses: Swatinem/rust-cache@v2\n        with:\n          cache-all-crates: \"true\"\n      - name: Generate schema\n        run: cargo run -p dioxus-cli -- config schema --out packages/cli/schema.json\n      - name: Check for uncommitted changes\n        run: |\n          if ! git diff --exit-code packages/cli/schema.json; then\n            echo \"::error::Schema is out of date. Run 'dx config schema --out packages/cli/schema.json' and commit the changes.\"\n            exit 1\n          fi\n\n  docs:\n    if: github.event.pull_request.draft == false\n    name: Docs\n    runs-on: blacksmith-4vcpu-ubuntu-2404\n    steps:\n      - uses: actions/checkout@v5\n      - uses: awalsh128/cache-apt-pkgs-action@latest\n        with:\n          packages: libwebkit2gtk-4.1-dev libgtk-3-dev libasound2-dev libudev-dev libayatana-appindicator3-dev libxdo-dev libglib2.0-dev\n          version: 1.0\n      - name: Install Rust ${{ env.rust_nightly }}\n        uses: dtolnay/rust-toolchain@nightly\n        with:\n          toolchain: ${{ env.rust_nightly }}\n      - uses: Swatinem/rust-cache@v2\n        with:\n          cache-all-crates: \"true\"\n      - name: \"doc --lib --all-features\"\n        run: |\n          cargo doc --workspace --no-deps --all-features --document-private-items\n        env:\n          RUSTDOCFLAGS: -Dwarnings --document-private-items\n\n  test-docs:\n    if: github.event.pull_request.draft == false\n    name: Test Docs\n    runs-on: blacksmith-4vcpu-ubuntu-2404\n    steps:\n      - uses: actions/checkout@v5\n      - uses: awalsh128/cache-apt-pkgs-action@latest\n        with:\n          packages: libwebkit2gtk-4.1-dev libgtk-3-dev libasound2-dev libudev-dev libayatana-appindicator3-dev libxdo-dev libglib2.0-dev\n          version: 1.0\n      - name: Install Rust ${{ env.rust_nightly }}\n        uses: dtolnay/rust-toolchain@nightly\n        with:\n          toolchain: ${{ env.rust_nightly }}\n      - uses: Swatinem/rust-cache@v2\n        with:\n          cache-all-crates: \"true\"\n      - name: \"doc --lib --all-features\"\n        run: |\n          cargo test --doc --workspace --all-features\n\n  check:\n    if: github.event.pull_request.draft == false\n    name: Check\n    runs-on: blacksmith-4vcpu-ubuntu-2404\n    steps:\n      - uses: actions/checkout@v5\n      - uses: awalsh128/cache-apt-pkgs-action@latest\n        with:\n          packages: libwebkit2gtk-4.1-dev libgtk-3-dev libasound2-dev libudev-dev libayatana-appindicator3-dev libxdo-dev libglib2.0-dev\n          version: 1.0\n      - uses: dtolnay/rust-toolchain@1.88.0\n      - uses: Swatinem/rust-cache@v2\n        with:\n          cache-all-crates: \"true\"\n      - run: cargo check --workspace --all-features --all-targets\n\n  clippy:\n    if: github.event.pull_request.draft == false\n    name: Clippy\n    runs-on: blacksmith-4vcpu-ubuntu-2404\n    steps:\n      - uses: actions/checkout@v5\n      - uses: awalsh128/cache-apt-pkgs-action@latest\n        with:\n          packages: libwebkit2gtk-4.1-dev libgtk-3-dev libasound2-dev libudev-dev libayatana-appindicator3-dev libxdo-dev libglib2.0-dev\n          version: 1.0\n      - uses: dtolnay/rust-toolchain@1.90.0\n        with:\n          components: rustfmt, clippy\n      - uses: Swatinem/rust-cache@v2\n        with:\n          cache-all-crates: \"true\"\n      - run: cargo clippy --workspace --examples --tests --all-features --all-targets -- -D warnings\n\n  nix:\n    if: github.event.pull_request.draft == false\n    runs-on: ${{ matrix.os }}\n    strategy:\n      matrix:\n        os: [blacksmith-4vcpu-ubuntu-2404, macos-latest]\n    steps:\n      - uses: actions/checkout@v5\n      - uses: nixbuild/nix-quick-install-action@master\n      - uses: nix-community/cache-nix-action@main\n        with:\n          primary-key: nix-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('**/*.nix', '**/flake.lock') }}\n          restore-prefixes-first-match: nix-${{ runner.os }}-${{ runner.arch }}\n      - name: Install omnix\n        run: nix --accept-flake-config profile install \"github:juspay/omnix\"\n      - name: Build all flake outputs\n        run: om ci\n      - name: Ensure devShell has all build deps\n        run: nix develop -c cargo build -p dioxus-cli --features no-downloads\n\n  playwright:\n    if: github.event.pull_request.draft == false\n    name: Playwright Tests\n    runs-on: ${{ matrix.os }}\n    timeout-minutes: 45\n    strategy:\n      matrix:\n        os: [blacksmith-4vcpu-windows-2025, blacksmith-8vcpu-ubuntu-2404]\n        platform:\n          - { toolchain: beta }\n          # - { toolchain: 1.86.0 }\n    steps:\n      # Do our best to cache the toolchain and node install steps\n      - uses: actions/checkout@v5\n      - name: Free Disk Space\n        if: ${{ matrix.os == 'ubuntu-24.04' || matrix.os == 'ubuntu-24.04-arm' || matrix.os == 'blacksmith-4vcpu-ubuntu-2404' || matrix.os == 'blacksmith-4vcpu-ubuntu-2404-arm' || matrix.os == 'blacksmith-8vcpu-ubuntu-2404' }}\n        uses: ./.github/actions/free-disk-space\n      - uses: awalsh128/cache-apt-pkgs-action@latest\n        if: ${{ matrix.os == 'ubuntu-24.04' || matrix.os == 'ubuntu-24.04-arm' || matrix.os == 'blacksmith-4vcpu-ubuntu-2404' || matrix.os == 'blacksmith-4vcpu-ubuntu-2404-arm' || matrix.os == 'blacksmith-8vcpu-ubuntu-2404' }}\n        with:\n          packages: glib-networking glib-networking-common glib-networking-services libaa1 libabsl20220623t64 libass9 libasyncns0 libavc1394-0 libavcodec60 libavfilter9 libavformat60 libavtp0 libavutil58 libblas3 libbluray2 libbs2b0 libcaca0 libcairo-script-interpreter2 libcdparanoia0 libchromaprint1 libcjson1 libcodec2-1.2 libdav1d7 libdc1394-25 libdca0 libdecor-0-0 libdirectfb-1.7-7t64 libdv4t64 libdvdnav4 libdvdread8t64 libfaad2 libflac12t64 libfluidsynth3 libfreeaptx0 libgav1-1 libgme0 libgraphene-1.0-0 libgsm1 libgssdp-1.6-0 libgstreamer-plugins-good1.0-0 libgtk-4-common libgupnp-1.6-0 libgupnp-igd-1.6-0 libhwy1t64 libiec61883-0 libimath-3-1-29t64 libinstpatch-1.0-2 libjack-jackd2-0 libjxl0.7 liblapack3 liblc3-1 libldacbt-enc2 liblilv-0-0 liblrdf0 libltc11 libmbedcrypto7t64 libmfx1 libmjpegutils-2.1-0t64 libmodplug1 libmp3lame0 libmpcdec6 libmpeg2encpp-2.1-0t64 libmpg123-0t64 libmplex2-2.1-0t64 libmysofa1 libneon27t64 libnice10 libogg0 libopenal-data libopenal1 libopenexr-3-1-30 libopenh264-7 libopenmpt0t64 libopenni2-0 liborc-0.4-0t64 libpipewire-0.3-0t64 libplacebo338 libpocketsphinx3 libpostproc57 libproxy1v5 libpulse0 libqrencode4 libraptor2-0 librav1e0 libraw1394-11 librist4 librubberband2 libsamplerate0 libsbc1 libsdl2-2.0-0 libsecret-common libserd-0-0 libshine3 libshout3 libsndfile1 libsndio7.0 libsord-0-0 libsoundtouch1 libsoup-3.0-0 libsoup-3.0-common libsoxr0 libspa-0.2-modules libspandsp2t64 libspeex1 libsphinxbase3t64 libsratom-0-0 libsrt1.5-gnutls libsrtp2-1 libssh-gcrypt-4 libsvtav1enc1d1 libswresample4 libswscale7 libtag1v5 libtag1v5-vanilla libtheora0 libtwolame0 libudfread0 libunibreak5 libv4l-0t64 libv4lconvert0t64 libva-drm2 libva-x11-2 libva2 libvdpau1 libvidstab1.1 libvisual-0.4-0 libvo-aacenc0 libvo-amrwbenc0 libvorbis0a libvorbisenc2 libvorbisfile3 libvpl2 libwavpack1 libwebrtc-audio-processing1 libwildmidi2 libx265-199 libxcb-xkb1 libxkbcommon-x11-0 libcups2t64 libxml2 libxml2-dev libxvidcore4 libyuv0 libzbar0t64 libzimg2 libzix-0-0 libzvbi-common libzvbi0t64 libzxing3 ocl-icd-libopencl1 timgm6mb-soundfont xfonts-encodings xfonts-utils binutils lld binutils-devel binutils-gold fonts-freefont-ttf fonts-ipafont-gothic fonts-tlwg-loma-otf fonts-unifont fonts-wqy-zenhei gstreamer1.0-libav gstreamer1.0-plugins-bad gstreamer1.0-plugins-good libavif16 libevent-2.1-7t64 libgstreamer-plugins-bad1.0-0 libharfbuzz-icu0 libhyphen0 libmanette-0.2-0 libsecret-1-0 libwoff1 xfonts-cyrillic xfonts-scalable fonts-ipafont-mincho fonts-tlwg-loma gstreamer1.0-x\n          version: 1.0\n      - uses: actions/setup-node@v4\n        with:\n          node-version: 24\n      - name: Install Rust\n        uses: dtolnay/rust-toolchain@master\n        with:\n          toolchain: ${{ matrix.platform.toolchain }}\n          targets: x86_64-unknown-linux-gnu,wasm32-unknown-unknown\n      - uses: Swatinem/rust-cache@v2\n        with:\n          key: \"playwright-${{ matrix.platform.toolchain }}-${{ runner.os }}\"\n          cache-all-crates: \"true\"\n          cache-on-failure: \"true\"\n      - name: Wipe dx cache\n        if: ${{ matrix.os == 'ubuntu-24.04' || matrix.os == 'ubuntu-24.04-arm' || matrix.os == 'blacksmith-4vcpu-ubuntu-2404' || matrix.os == 'blacksmith-4vcpu-ubuntu-2404-arm' || matrix.os == 'blacksmith-8vcpu-ubuntu-2404' }}\n        run: |\n          rm -rf ./target/dx\n      - name: Playwright\n        working-directory: ./packages/playwright-tests\n        env:\n          # The hot patch test requires incremental compilation\n          CARGO_INCREMENTAL: 1\n        run: |\n          npm ci\n          npm install -D @playwright/test\n          npx playwright install\n          npx playwright test\n      - uses: actions/upload-artifact@v6\n        if: always()\n        with:\n          name: playwright-report-${{ matrix.platform.toolchain }}-${{ runner.os }}\n          path: ./packages/playwright-tests/playwright-report/\n          retention-days: 30\n\n  matrix_test:\n    runs-on: ${{ matrix.platform.os }}\n    if: github.event.pull_request.draft == false\n    env:\n      RUST_CARGO_COMMAND: ${{ matrix.platform.cross == true && 'cross' || 'cargo' }}\n    strategy:\n      matrix:\n        platform:\n          - {\n              target: aarch64-apple-darwin,\n              os: macos-latest,\n              toolchain: \"1.88.0\",\n              cross: false,\n              command: \"test\",\n              args: \"--all --tests\",\n              platform: \"desktop\",\n            }\n          - {\n              target: aarch64-apple-ios,\n              os: macos-latest,\n              toolchain: \"1.88.0\",\n              cross: false,\n              command: \"build\",\n              args: \"--package dioxus-desktop\",\n              platform: \"ios\",\n            }\n          - {\n              target: aarch64-unknown-linux-gnu,\n              os: ubuntu-24.04-arm,\n              toolchain: \"1.88.0\",\n              cross: false,\n              command: \"build\",\n              args: \"--all --tests\",\n              platform: \"desktop\",\n            }\n          - {\n              target: aarch64-linux-android,\n              os: blacksmith-4vcpu-ubuntu-2404,\n              toolchain: \"1.88.0\",\n              cross: true,\n              command: \"build\",\n              args: \"--package dioxus-desktop\",\n              platform: \"android\",\n            }\n            # commented out because it's having issues with space on the device, but we already test it above\n          # - {\n          #     target: x86_64-unknown-linux-gnu,\n          #     os: ubuntu-24.04,\n          #     toolchain: \"1.88.0\",\n          #     cross: false,\n          #     command: \"build\",\n          #     args: \"--all --tests\",\n          #     platform: \"desktop\",\n          #   }\n\n    steps:\n      - uses: actions/checkout@v5\n      - name: Free Disk Space\n        if: ${{ matrix.platform.os == 'ubuntu-24.04' || matrix.platform.os == 'ubuntu-24.04-arm' || matrix.platform.os == 'blacksmith-4vcpu-ubuntu-2404' || matrix.platform.os == 'blacksmith-4vcpu-ubuntu-2404-arm' }}\n        uses: ./.github/actions/free-disk-space\n\n      - uses: awalsh128/cache-apt-pkgs-action@latest\n        if: ${{ matrix.platform.os == 'ubuntu-24.04' || matrix.platform.os == 'ubuntu-24.04-arm' || matrix.platform.os == 'blacksmith-4vcpu-ubuntu-2404' || matrix.platform.os == 'blacksmith-4vcpu-ubuntu-2404-arm' }}\n        with:\n          packages: libwebkit2gtk-4.1-dev libgtk-3-dev libasound2-dev libudev-dev libayatana-appindicator3-dev libxdo-dev libglib2.0-dev\n          version: ${{ matrix.platform.target }}-${{ matrix.platform.os }} # disambiguate since we're in a matrix and this caching action doesn't factor in these variables\n\n      - name: install stable\n        uses: dtolnay/rust-toolchain@master\n        with:\n          toolchain: ${{ matrix.platform.toolchain }}\n          targets: ${{ matrix.platform.target }}\n          components: rustfmt\n\n      - name: Install nasm for windows (tls)\n        if: ${{ matrix.platform.target == 'x86_64-pc-windows-msvc' }}\n        uses: ilammy/setup-nasm@v1\n\n      - name: Install cross\n        if: ${{ matrix.platform.cross == true }}\n        uses: taiki-e/install-action@cross\n\n      - uses: Swatinem/rust-cache@v2\n        with:\n          key: \"matrix-${{ matrix.platform.target }}\"\n          cache-all-crates: \"true\"\n\n      - name: test\n        run: |\n          ${{ env.RUST_CARGO_COMMAND }} ${{ matrix.platform.command }} ${{ matrix.platform.args }} --target ${{ matrix.platform.target }}\n\n  # borrowed from uv\n  # https://raw.githubusercontent.com/astral-sh/uv/refs/heads/main/.github/workflows/ci.yml\n  cargo-test-windows:\n    if: github.event.pull_request.draft == false\n    runs-on:\n      labels: \"blacksmith-4vcpu-windows-2025\"\n    name: \"cargo test | windows\"\n    steps:\n      - uses: actions/checkout@v5\n      - uses: dtolnay/rust-toolchain@1.88.0\n        with:\n          components: rustfmt, clippy\n      - uses: Swatinem/rust-cache@v2\n        with:\n          workspaces: ${{ env.UV_WORKSPACE }}\n          cache-all-crates: \"true\"\n\n      - name: \"Install Rust toolchain\"\n        working-directory: ${{ env.UV_WORKSPACE }}\n        run: rustup show\n\n      - name: \"Cargo test\"\n        working-directory: ${{ env.UV_WORKSPACE }}\n        run: |\n          cargo test --workspace --tests\n"
  },
  {
    "path": ".github/workflows/merge.yml",
    "content": "# Runs whenever a PR is merged:\n# - attempt to backports fixes\n# - upload nightly docs\n#\n# Future:\n# - upload nightly CLI builds\n# - upload nightly vscode extension\n# - upload benchmarks\n# - compute coverge\n#\n# Note that direct commits to master circumvent this workflow!\n\nname: Backport merged pull request\non:\n  pull_request_target:\n    types: [closed]\n\npermissions:\n  contents: write # so it can comment\n  pull-requests: write # so it can create pull requests\n\njobs:\n  # Attempt to backport a merged pull request to the latest stable release\n  backport:\n    name: Backport pull request\n    runs-on: blacksmith-4vcpu-ubuntu-2404\n\n    # Don't run on closed unmerged pull requests, or pull requests with the \"breaking\" label\n    if: github.event.pull_request.merged && !contains(github.event.pull_request.labels.*.name, 'breaking')\n    steps:\n      - uses: actions/checkout@v5\n      - name: Create backport pull requests\n        uses: korthout/backport-action@v3\n\n  # Upload nightly docs to the website\n  docs:\n    runs-on: blacksmith-4vcpu-ubuntu-2404\n    permissions:\n      contents: write\n    steps:\n      - uses: actions/checkout@v5\n      - run: sudo apt-get update\n      - run: sudo apt install libwebkit2gtk-4.1-dev libgtk-3-dev libayatana-appindicator3-dev libxdo-dev libglib2.0-dev\n      - uses: dtolnay/rust-toolchain@nightly\n        with:\n          toolchain: nightly-2024-02-01\n      - uses: Swatinem/rust-cache@v2\n        with:\n          cache-all-crates: \"true\"\n          save-if: ${{ github.ref == 'refs/heads/main' }}\n\n      - name: cargo doc\n        run: cargo doc --no-deps --workspace --all-features\n\n      - name: Deploy\n        uses: JamesIves/github-pages-deploy-action@v4.7.3\n        with:\n          branch: gh-pages\n          folder: target/doc\n          target-folder: api-docs/nightly\n          repository-name: dioxuslabs/docsite\n          clean: false\n          token: ${{ secrets.DEPLOY_KEY }}\n# Attempt to backport a merged pull request to the latest stable release\n#\n# If the backported PR is succesfully merged\n# Any PR without the \"breaking\" label will be attempted to be backported to the latest stable release\n\n# Coverage is disabled until we can fix it\n# coverage:\n#   name: Coverage\n#   runs-on: ubuntu-latest\n#   container:\n#     image: xd009642/tarpaulin:develop-nightly\n#     options: --security-opt seccomp=unconfined\n#   steps:\n#     - name: Checkout repository\n#       uses: actions/checkout@v5\n#     - name: Generate code coverage\n#       run: |\n#         apt-get update &&\\\n#         apt-get install build-essential &&\\\n#         apt install libwebkit2gtk-4.0-dev libgtk-3-dev libayatana-appindicator3-dev -y &&\\\n#         cargo +nightly tarpaulin --verbose --all-features --workspace --timeout 120 --out Xml\n#     - name: Upload to codecov.io\n#       uses: codecov/codecov-action@v2\n#       with:\n#         fail_ci_if_error: false\n"
  },
  {
    "path": ".github/workflows/promote.yml",
    "content": "# Promote the current main branch to a stable release.\n# This will not actually release anything, so you need to run the release workflow after this.\n#\n# IE if the current master version is 0.4.0-rc.7, this will create a PR to promote it to 0.4.0\n#\n# - update the version in the Cargo.toml to v0.4.0\n# - generate a v0.4 branch\n# - push the branch to the repository\n# - then bump 0.4.0-rc.1 to 0.5.0-rc.0\n#\n# This means main will never be a \"stable\" release, and we can always merge breaking changes to main\n# and backport them to the latest stable release\n#\n# This is configured to be ran manually, but could honestly just be a release workflow\n\nname: Promote main to stable branch\non:\n  workflow_dispatch:\n\npermissions:\n  actions: write\n\njobs:\n  promote:\n    runs-on: blacksmith-4vcpu-ubuntu-2404\n    steps:\n      - uses: actions/checkout@v5\n      - name: Publish the next pre-release\n        run: |\n          git config --global user.email \"github-actions[bot]@users.noreply.github.com\"\n          git config --global user.name \"github-actions[bot]\"\n\n          # go from eg 0.4.0-rc.7 to 0.4.0, committing the change\n          cargo workspaces version -y minor\n\n          # create a new branch for the release\n          RELEASE_BRANCH=$(cargo metadata --no-deps --format-version 1 | jq -r '.packages[0].version')\n          RELEASE_BRANCH=v$(echo $RELEASE_BRANCH | sed 's/\\.[0-9]*$//')\n          git branch $RELEASE_BRANCH\n\n          # go from 0.4.0 to 0.5.0-rc.0\n          cargo workspaces version -y preminor --pre-id rc\n\n          # push the new branch to the repository\n          git push origin $RELEASE_BRANCH\n\n          # push the new version to the repository\n          git push origin main\n"
  },
  {
    "path": ".github/workflows/publish.yml",
    "content": "# Release workflow\n#\n# We parallelize builds, dump all the artifacts into a release, and then publish the release\n# This guarantees everything is properly built and cached in case anything goes wrong\n#\n# The artifacts also need to get pushed to the various places\n# - the CLI goes to the releases page for binstall\n# - the extension goes to the marketplace\n# - the docs go to the website\n#\n# We need to be aware of the channel we're releasing\n# - prerelease is master\n# - stable is whatever the latest stable release is (ie 0.4 or 0.5 or 0.6 etc)\n#\n# It's intended that this workflow is run manually, and only when we're ready to release\n\nname: Publish CLI\non:\n  workflow_dispatch:\n    inputs:\n      post:\n        name: \"Release Post\"\n        required: true\n        description: Choose the release post to publish with. Must be a tag (eg v0.4.0)\n        type: string\n      channel:\n        name: \"CLI Binary Version\"\n        required: true\n        description: Choose the version number to publish with. Must be a tag (ie v0.4.0)\n        type: string\n\nenv:\n  # make sure we have the right version\n  # main is always a prepatch until we hit 1.0, and then this script needs to be updated\n  # note that we need to promote the prepatch to a minor bump when we actually do a release\n  # this means the version in git will always be one minor bump ahead of the actual release - basically meaning once\n  # we release a version, it's fair game to merge breaking changes to main since all semver-compatible changes will be\n  # backported automatically\n  # SEMVER: ${{ github.event.inputs.channel == 'main' && 'prerelease' || 'patch' }}\n  # PRERELEASE_TAG: ${{ github.event.inputs.channel == 'main' && '-pre' || '' }}\n  RELEASE_TAG: ${{ github.event.inputs.channel }}\n  RELEASE_POST: ${{ github.event.inputs.post }}\n\njobs:\n  release-cli:\n    permissions:\n      contents: write\n    runs-on: ${{ matrix.platform.os }}\n    strategy:\n      matrix:\n        platform:\n          - target: x86_64-pc-windows-msvc\n            os: windows-latest\n          - target: aarch64-pc-windows-msvc\n            os: windows-latest\n          - target: x86_64-apple-darwin\n            os: macos-15-intel\n          - target: aarch64-apple-darwin\n            os: macos-latest\n          - target: x86_64-unknown-linux-gnu\n            os: ubuntu-24.04\n          - target: aarch64-unknown-linux-gnu\n            os: ubuntu-24.04-arm\n          # os: blacksmith-4vcpu-ubuntu-2404\n          # os: blacksmith-4vcpu-ubuntu-2404\n          # - target: x86_64-unknown-linux-musl\n          #   os: ubuntu-24.04\n          # - target: aarch64-unknown-linux-musl\n          #   os: ubuntu-24.04-arm\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v5\n\n      - name: Install openssl on macos\n        if: matrix.platform.os == 'macos-latest'\n        run: brew install openssl\n\n      - name: Install nasm for windows (tls)\n        if: ${{ matrix.platform.target == 'x86_64-pc-windows-msvc' }}\n        uses: ilammy/setup-nasm@v1\n\n      - name: Free Disk Space\n        if: ${{ matrix.platform.os == 'ubuntu-24.04' || matrix.platform.os == 'ubuntu-24.04-arm' }}\n        uses: ./.github/actions/free-disk-space\n\n      - uses: awalsh128/cache-apt-pkgs-action@latest\n        if: ${{ matrix.platform.os == 'ubuntu-24.04' || matrix.platform.os == 'ubuntu-24.04-arm' }}\n        with:\n          packages: libwebkit2gtk-4.1-dev libgtk-3-dev libayatana-appindicator3-dev libxdo-dev libglib2.0-dev musl-tools\n          version: 1.0\n\n      - name: Install stable\n        uses: dtolnay/rust-toolchain@master\n        with:\n          toolchain: \"1.88.0\"\n          targets: ${{ matrix.platform.target }}\n\n      - uses: Swatinem/rust-cache@v2\n        with:\n          cache-all-crates: \"true\"\n          save-if: ${{ github.ref == 'refs/heads/main' }}\n\n      - name: Free Disk Space\n        uses: jlumbroso/free-disk-space@v1.3.1\n        with: # speed things up a bit\n          large-packages: false\n          docker-images: false\n          swap-storage: false\n\n      # Todo: we want `cargo install dx` to actually just use a prebuilt binary instead of building it\n      - name: Build and upload CLI binaries\n        uses: taiki-e/upload-rust-binary-action@v1\n        with:\n          bin: dx\n          token: ${{ secrets.GITHUB_TOKEN }}\n          target: ${{ matrix.platform.target }}\n          archive: $bin-$target\n          checksum: sha256\n          manifest_path: packages/cli/Cargo.toml\n          ref: refs/tags/${{ env.RELEASE_POST }}\n          zip: \"all\"\n\n  # todo: these things\n  # Run benchmarks, which we'll use to display on the website\n  # release-benchmarks:\n  # Build the vscode extension, uploading the artifact to the marketplace\n  # release-extension:\n\n  # First, run checks (clippy, tests, etc) and then publish the crates to crates.io\n  # release-crates:\n  #   steps:\n  #     # Checkout the right branch, and the nightly stuff\n  #     - uses: actions/checkout@v5\n  #       ref: ${{ github.event.inputs.channel }}\n  #     - run: sudo apt-get update\n  #     - run: sudo apt install libwebkit2gtk-4.1-dev libgtk-3-dev libayatana-appindicator3-dev libxdo-dev libglib2.0-dev\n  #     - uses: dtolnay/rust-toolchain@nightly\n  #       with:\n  #         toolchain: nightly-2024-02-01\n  #     - uses: Swatinem/rust-cache@v2\n  #       with:\n  #         cache-all-crates: \"true\"\n\n  #     - name: Free Disk Space (Ubuntu)\n  #       uses: jlumbroso/free-disk-space@v1.3.1\n  #       with: # speed things up a bit\n  #         large-packages: false\n  #         docker-images: false\n  #         swap-storage: false\n\n  #     # Just make sure clippy is happy before doing anything else\n  #     # Don't publish versions with clippy errors!\n  #     - name: Clippy\n  #       run: cargo clippy --workspace --all --examples --tests --all-features --all-targets -- -D warnings\n\n  #     # Build the docs here too before publishing, to ensure they're up to date\n  #     - name: cargo doc\n  #       run: cargo doc --no-deps --workspace --all-features\n\n  #     - name: Publish to crates.io\n  #       run: |\n  #         git config --global user.email \"github-actions[bot]@users.noreply.github.com\"\n  #         git config --global user.name \"github-actions[bot]\"\n  #         cargo workspaces version -y ${{ env.SEMVER }} --pre-id rc --no-git-commit\n\n  #         # todo: actually just publish!\n  #         # cargo workspaces publish -y ${{ github.event.inputs.semver }}\n\n  # this will be more useful when we publish the website with updated docs\n  # Build the docs.rs docs and publish them to the website under the right folder\n  # v0.4.x -> docs/0.4\n  # v0.5.x -> docs/0.5 etc\n  # main -> docs/nightly\n  # strip the v from the channel, and the .x from the end, and replace main with nightly\n  # - name: determine docs folder by channel\n  #   id: determine_docs_folder\n  #   run: echo \"::set-output name=folder::$(echo ${{ github.event.inputs.channel }} | sed 's/v//g' | sed 's/\\.x//g' | sed 's/main/nightly/g')\"\n"
  },
  {
    "path": ".github/workflows/setup-dev-drive.ps1",
    "content": "# This creates a 20GB dev drive, and exports all required environment\n# variables so that rustup, uv and others all use the dev drive as much\n# as possible.\n$Volume = New-VHD -Path C:/uv_dev_drive.vhdx -SizeBytes 20GB |\n\t\t\t\t\tMount-VHD -Passthru |\n\t\t\t\t\tInitialize-Disk -Passthru |\n\t\t\t\t\tNew-Partition -AssignDriveLetter -UseMaximumSize |\n\t\t\t\t\tFormat-Volume -FileSystem ReFS -Confirm:$false -Force\n\nWrite-Output $Volume\n\n$Drive = \"$($Volume.DriveLetter):\"\n$Tmp = \"$($Drive)/uv-tmp\"\n\n# Create the directory ahead of time in an attempt to avoid race-conditions\nNew-Item $Tmp -ItemType Directory\n\nWrite-Output `\n\t\"DEV_DRIVE=$($Drive)\" `\n\t\"TMP=$($Tmp)\" `\n\t\"TEMP=$($Tmp)\" `\n\t\"RUSTUP_HOME=$($Drive)/.rustup\" `\n\t\"CARGO_HOME=$($Drive)/.cargo\" `\n\t\"UV_WORKSPACE=$($Drive)/uv\" `\n\t\"PATH=$($Drive)/.cargo/bin;$env:PATH\" `\n\t>> $env:GITHUB_ENV"
  },
  {
    "path": ".github/workflows/typos.yml",
    "content": "# Whenever an open PR is updated, the workflow will be triggered\n\nname: Language Linting\n\non:\n  push:\n    branches:\n      - main\n\n  pull_request:\n    types: [opened, synchronize, reopened, ready_for_review]\n    branches:\n      - main\n\njobs:\n  typos:\n    if: github.event.pull_request.draft == false\n    name: Check for typos\n    runs-on: blacksmith-4vcpu-ubuntu-2404\n    steps:\n      - uses: actions/checkout@v5\n      - name: Check for typos\n        uses: crate-ci/typos@master\n\n  # Check for invalid links in the repository\n  link-check:\n    if: github.event.pull_request.draft == false\n    name: Check For Invalid Links\n    runs-on: blacksmith-4vcpu-ubuntu-2404\n    steps:\n      - uses: actions/checkout@v5\n      - name: Restore lychee cache\n        uses: actions/cache@v5\n        with:\n          path: .lycheecache\n          key: cache-lychee-${{ github.sha }}\n          restore-keys: cache-lychee-\n      - name: Run lychee\n        uses: lycheeverse/lychee-action@v2\n        with:\n          args: --config ./lychee.toml './**/*.md'\n          fail: true\n"
  },
  {
    "path": ".gitignore",
    "content": ".dioxus\n/target\n/packages/playwright-tests/cli-optimization/monaco-editor-0.52.2\n/packages/playwright-tests/web/dist\n/packages/playwright-tests/fullstack/dist\n/packages/playwright-tests/test-results\n/packages/playwright-tests/web-hot-patch-temp\n/packages/playwright-tests/web-hot-patch-fullstack-temp\n/packages/playwright-tests/web-hot-patch/Cargo.lock\n/packages/playwright-tests/web-hot-patch-fullstack/Cargo.lock\n/dist\n.DS_Store\n/examples/assets/test_video.mp4\n/examples/_assets/test_video.mp4\nstatic\n\n# new recommendation to keep the lockfile in for CI and reproducible builds\n# Cargo.lock\n\n.vscode/*\n!.vscode/settings.json\n!.vscode/tasks.json\n!.vscode/launch.json\n!.vscode/extensions.json\ntarpaulin-report.html\n\n# Jetbrain\n.idea/\nnode_modules/\n/test-results/\n/packages/playwright-report/\n/packages/playwright/.cache/\n# Allow geolocation plugin sources to be tracked\n/packages/geolocation/android/build/\n/packages/geolocation/android/.gradle/\n# Zed\n.zed/\n\n# ignore the output of tmps\ntmp/\nbundle/\n\n# in debugging we frequently dump wasm to wat with `wasm-tools print`\n*.wat\n"
  },
  {
    "path": ".vscode/settings.json",
    "content": "{\n  \"editor.formatOnSave\": true,\n  \"[toml]\": {\n    \"editor.formatOnSave\": false\n  },\n  \"[handlebars]\": {\n    \"editor.formatOnSave\": false\n  },\n  \"[javascript]\": {\n    \"editor.formatOnSave\": false\n  },\n  \"[html]\": {\n    \"editor.formatOnSave\": false\n  },\n  \"dioxus.formatOnSave\": \"disabled\",\n  // \"rust-analyzer.check.workspace\": true,\n  // \"rust-analyzer.check.workspace\": false,\n  // \"rust-analyzer.check.features\": \"all\",\n  // \"rust-analyzer.cargo.buildScripts.rebuildOnSave\": false,\n  // \"rust-analyzer.check.workspace\": false,\n  // \"rust-analyzer.check.allTargets\": true,\n  \"rust-analyzer.cargo.features\": \"all\",\n  \"rust-analyzer.check.features\": \"all\",\n  \"rust-analyzer.cargo.extraArgs\": [\n    \"--tests\"\n  ],\n}"
  },
  {
    "path": "AGENTS.md",
    "content": "# Dioxus Agent Guide\n\nDioxus is a cross-platform UI framework for Rust, similar to React. It compiles to web (WASM), desktop (webview), mobile (iOS/Android), and native (GPU-rendered).\n\n## Quick Overview\n\n- **Language**: Rust (stable toolchain)\n- **UI Model**: React-like with VirtualDOM, components, hooks, signals\n- **Syntax**: JSX-like `rsx!` macro for declaring UI\n- **Platforms**: Web, Desktop (Windows/macOS/Linux), Mobile, Native, LiveView (server-rendered)\n\n## Workspace Structure\n\n```\npackages/\n├── dioxus/           # Main re-export crate users depend on\n├── core/             # VirtualDOM, components, diffing, scheduling\n├── rsx/              # RSX macro parsing and code generation\n├── rsx-hotreload/    # Template diffing for hot-reload\n├── signals/          # Reactive state (Signal, Memo, Store)\n├── hooks/            # Built-in hooks (use_signal, use_effect, etc.)\n├── router/           # Type-safe routing with #[derive(Routable)]\n├── fullstack/        # SSR, hydration, #[server] functions\n├── cli/              # `dx` build tool, dev server, bundling\n├── web/              # WASM renderer\n├── desktop/          # Wry/Tao webview renderer\n├── native/           # Blitz/Vello GPU renderer\n├── liveview/         # WebSocket streaming renderer\n├── manganis/         # asset!() macro for compile-time assets\n├── subsecond/        # Hot-patching system (jump table indirection)\n├── devtools/         # Dev server communication protocol\n├── interpreter/      # Sledgehammer JS for DOM mutations\n└── wasm-split/       # WASM code splitting\n```\n\n## Architecture Documentation\n\nFor deeper understanding, see `notes/architecture/`:\n\n| When working on...                         | Read...            |\n| ------------------------------------------ | ------------------ |\n| VirtualDOM, components, diffing, events    | `01-CORE.md`       |\n| CLI, build system, bundling, dev server    | `02-CLI.md`        |\n| RSX macro, parsing, formatting             | `03-RSX.md`        |\n| Signals, state management, reactivity      | `04-SIGNALS.md`    |\n| Server functions, SSR, hydration           | `05-FULLSTACK.md`  |\n| Web/desktop/native/liveview renderers      | `06-RENDERERS.md`  |\n| Hot-reload, hot-patching, devtools         | `07-HOTRELOAD.md`  |\n| Asset macro, manganis, const serialization | `08-ASSETS.md`     |\n| Router, navigation, nested routes          | `09-ROUTER.md`     |\n| WASM code splitting                        | `10-WASM-SPLIT.md` |\n\n## Key Concepts\n\n- **VirtualDOM**: Tree of `VNode` with templates, dynamic nodes, and attributes\n- **Signals**: Copy-able reactive primitives via generational-box (generation-based validity)\n- **WriteMutations**: Trait that renderers implement to apply DOM changes\n- **RSX**: Proc macro that compiles JSX-like syntax to `VNode` construction\n- **Server Functions**: `#[server]` macro generates client RPC stubs and server handlers\n- **Subsecond**: Hot-patches Rust code via jump table indirection (no memory modification)\n- **Manganis**: `asset!(\"/main.css\")` macro for including assets by embedding data via linker symbols\n\n## Common Patterns\n\n**Component definition**:\n```rust\n#[component]\nfn MyComponent(name: String) -> Element {\n    let mut count = use_signal(|| 0);\n    rsx! {\n        button { onclick: move |_| count += 1, \"{name}: {count}\" }\n    }\n}\n```\n\n## Notes for Agents\n\n1. The `dioxus` crate re-exports from other crates - most implementation is in `packages/core`, `packages/signals`, etc.\n2. RSX macro expansion happens in `packages/rsx` - look there for syntax questions\n3. Each renderer implements `WriteMutations` differently - see `06-RENDERERS.md`\n4. Hot-reload has two systems: RSX template diffing (fast) and Subsecond code patching (full Rust)\n5. Assets use link sections and binary patching - the `asset!()` macro creates symbols the CLI processes\n"
  },
  {
    "path": "Cargo.toml",
    "content": "[workspace]\nresolver = \"2\"\nmembers = [\n    \"packages/dioxus\",\n    \"packages/core\",\n    \"packages/core-types\",\n    \"packages/cli\",\n    \"packages/cli-opt\",\n    \"packages/cli-config\",\n    \"packages/cli-telemetry\",\n    \"packages/core-macro\",\n    \"packages/config-macro\",\n    \"packages/router-macro\",\n    \"packages/extension\",\n    \"packages/router\",\n    \"packages/html\",\n    \"packages/html-internal-macro\",\n    \"packages/hooks\",\n    \"packages/web\",\n    \"packages/ssr\",\n    \"packages/desktop\",\n    \"packages/interpreter\",\n    \"packages/liveview\",\n    \"packages/autofmt\",\n    \"packages/check\",\n    \"packages/devtools-types\",\n    \"packages/devtools\",\n    \"packages/document\",\n    \"packages/fullstack\",\n    \"packages/fullstack-core\",\n    \"packages/fullstack-macro\",\n    \"packages/fullstack-server\",\n    \"packages/generational-box\",\n    \"packages/history\",\n    \"packages/lazy-js-bundle\",\n    \"packages/rsx-hotreload\",\n    \"packages/rsx-rosetta\",\n    \"packages/rsx\",\n    \"packages/signals\",\n    \"packages/stores\",\n    \"packages/stores-macro\",\n    \"packages/const-serialize\",\n    \"packages/const-serialize-macro\",\n    \"packages/dx-wire-format\",\n    \"packages/logger\",\n    \"packages/config-macros\",\n    \"packages/native\",\n    \"packages/native-dom\",\n    \"packages/asset-resolver\",\n    \"packages/depinfo\",\n    \"packages/component-manifest\",\n\n    # CLI harnesses, all included\n    \"packages/cli-harnesses/*\",\n\n    # Playwright tests\n    \"packages/playwright-tests/liveview\",\n    \"packages/playwright-tests/web\",\n    \"packages/playwright-tests/web-routing\",\n    \"packages/playwright-tests/web-hash-routing\",\n    \"packages/playwright-tests/barebones-template\",\n    \"packages/playwright-tests/fullstack\",\n    \"packages/playwright-tests/fullstack-errors\",\n    \"packages/playwright-tests/fullstack-mounted\",\n    \"packages/playwright-tests/fullstack-spread\",\n    \"packages/playwright-tests/fullstack-routing\",\n    \"packages/playwright-tests/fullstack-hydration-order\",\n    \"packages/playwright-tests/suspense-carousel\",\n    \"packages/playwright-tests/nested-suspense\",\n    \"packages/playwright-tests/cli-optimization\",\n    \"packages/playwright-tests/wasm-split-harness\",\n    \"packages/playwright-tests/default-features-disabled\",\n    \"packages/playwright-tests/fullstack-error-codes\",\n    \"packages/playwright-tests/windows-headless\",\n\n    # manganis\n    \"packages/manganis/manganis\",\n    \"packages/manganis/manganis-core\",\n    \"packages/manganis/manganis-macro\",\n    \"packages/manganis/manganis-07\",\n\n    # wasm-split\n    \"packages/wasm-split/wasm-split\",\n    \"packages/wasm-split/wasm-split-macro\",\n    \"packages/wasm-split/wasm-split-cli\",\n    \"packages/wasm-split/wasm-used\",\n\n    # subsecond\n    \"packages/subsecond/subsecond\",\n    \"packages/subsecond/subsecond-types\",\n    \"packages/subsecond/subsecond-tests/cross-tls-crate\",\n    \"packages/subsecond/subsecond-tests/cross-tls-crate-dylib\",\n    \"packages/subsecond/subsecond-tests/cross-tls-test\",\n\n    # Full project examples\n    \"examples/01-app-demos/hackernews\",\n    \"examples/01-app-demos/ecommerce-site\",\n    \"examples/01-app-demos/bluetooth-scanner\",\n    \"examples/01-app-demos/file-explorer\",\n    \"examples/01-app-demos/hotdog\",\n    \"examples/01-app-demos/geolocation-native-plugin\",\n\n    # Fullstack examples\n    \"examples/07-fullstack/hello-world\",\n    \"examples/07-fullstack/router\",\n    \"examples/07-fullstack/desktop\",\n    \"examples/07-fullstack/auth\",\n    \"examples/07-fullstack/ssr-only\",\n\n    # Integrations\n    \"examples/10-integrations/tailwind\",\n    \"examples/10-integrations/pwa\",\n    \"examples/10-integrations/wgpu-texture\",\n    \"examples/10-integrations/native-headless\",\n    \"examples/10-integrations/native-headless-in-bevy\",\n    \"examples/10-integrations/bevy\",\n]\n\n\n[workspace.package]\nversion = \"0.7.3\"\n\n# dependencies that are shared across packages\n[workspace.dependencies]\ndioxus = { path = \"packages/dioxus\", version = \"0.7.3\" }\ndioxus-core = { path = \"packages/core\", version = \"0.7.3\" }\ndioxus-core-types = { path = \"packages/core-types\", version = \"0.7.3\" }\ndioxus-core-macro = { path = \"packages/core-macro\", version = \"0.7.3\" }\ndioxus-config-macro = { path = \"packages/config-macro\", version = \"0.7.3\" }\ndioxus-router = { path = \"packages/router\", version = \"0.7.3\" }\ndioxus-router-macro = { path = \"packages/router-macro\", version = \"0.7.3\" }\ndioxus-document = { path = \"packages/document\", version = \"0.7.3\", default-features = false }\ndioxus-history = { path = \"packages/history\", version = \"0.7.3\", default-features = false }\ndioxus-html = { path = \"packages/html\", version = \"0.7.3\", default-features = false }\ndioxus-html-internal-macro = { path = \"packages/html-internal-macro\", version = \"0.7.3\" }\ndioxus-hooks = { path = \"packages/hooks\", version = \"0.7.3\" }\ndioxus-web = { path = \"packages/web\", version = \"0.7.3\", default-features = false }\ndioxus-ssr = { path = \"packages/ssr\", version = \"0.7.3\", default-features = false }\ndioxus-desktop = { path = \"packages/desktop\", version = \"0.7.3\", default-features = false }\ndioxus-interpreter-js = { path = \"packages/interpreter\", version = \"0.7.3\" }\ndioxus-liveview = { path = \"packages/liveview\", version = \"0.7.3\" }\ndioxus-autofmt = { path = \"packages/autofmt\", version = \"0.7.3\" }\ndioxus-check = { path = \"packages/check\", version = \"0.7.3\" }\ndioxus-rsx = { path = \"packages/rsx\", version = \"0.7.3\" }\ndioxus-rsx-hotreload = { path = \"packages/rsx-hotreload\", version = \"0.7.3\" }\ndioxus-rsx-rosetta = { path = \"packages/rsx-rosetta\", version = \"0.7.3\" }\ndioxus-signals = { path = \"packages/signals\", version = \"0.7.3\" }\ndioxus-stores = { path = \"packages/stores\", version = \"0.7.3\" }\ndioxus-stores-macro = { path = \"packages/stores-macro\", version = \"0.7.3\" }\ndioxus-devtools = { path = \"packages/devtools\", version = \"0.7.3\" }\ndioxus-devtools-types = { path = \"packages/devtools-types\", version = \"0.7.3\" }\ndioxus-fullstack = { path = \"packages/fullstack\", version = \"0.7.3\", default-features = false }\ndioxus-fullstack-core = { path = \"packages/fullstack-core\", version = \"0.7.3\", default-features = false }\ndioxus-fullstack-macro = { path = \"packages/fullstack-macro\", version = \"0.7.3\", default-features = false }\ndioxus-server = { path = \"packages/fullstack-server\", version = \"0.7.3\" }\ndioxus-dx-wire-format = { path = \"packages/dx-wire-format\", version = \"0.7.3\" }\ndioxus-logger = { path = \"packages/logger\", version = \"0.7.3\" }\ndioxus-native = { path = \"packages/native\", version = \"0.7.3\" }\ndioxus-native-dom = { path = \"packages/native-dom\", version = \"0.7.3\" }\ndioxus-asset-resolver = { path = \"packages/asset-resolver\", version = \"0.7.3\" }\ndioxus-config-macros = { path = \"packages/config-macros\", version = \"0.7.3\" }\ndioxus-component-manifest = { path = \"packages/component-manifest\", version = \"0.7.3\" }\ngenerational-box = { path = \"packages/generational-box\", version = \"0.7.3\" }\nlazy-js-bundle = { path = \"packages/lazy-js-bundle\", version = \"0.7.3\" }\n\n# cli\ndioxus-cli-opt = { path = \"packages/cli-opt\", version = \"0.7.3\" }\ndioxus-cli-telemetry = { path = \"packages/cli-telemetry\", version = \"0.7.3\" }\ndioxus-cli-config = { path = \"packages/cli-config\", version = \"0.7.3\" }\n\n# subsecond\nsubsecond-types = { path = \"packages/subsecond/subsecond-types\", version = \"0.7.3\" }\nsubsecond = { path = \"packages/subsecond/subsecond\", version = \"0.7.3\" }\n\n# wasm-split\nwasm-splitter = { path = \"packages/wasm-split/wasm-split\", version = \"0.7.3\" }\nwasm-split-macro = { path = \"packages/wasm-split/wasm-split-macro\", version = \"0.7.3\" }\nwasm-split-cli = { path = \"packages/wasm-split/wasm-split-cli\", version = \"0.7.3\" }\nwasm-used = { path = \"packages/wasm-split/wasm-used\", version = \"0.7.3\" }\n\n# our dep-info parsing crate\ndepinfo = { path = \"packages/depinfo\", version = \"0.7.3\" }\n\n# manganis\nmanganis = { path = \"packages/manganis/manganis\", version = \"0.7.3\" }\nmanganis-core = { path = \"packages/manganis/manganis-core\", version = \"0.7.3\" }\nmanganis-macro = { path = \"packages/manganis/manganis-macro\", version = \"0.7.3\" }\nmanganis-core-07 = { path = \"packages/manganis/manganis-07\", version = \"0.7.2\" }\n\n# const-serialize.\n# these are on \"alpha\" versions, but really, we are prepping for 0.8 but not committing to it yet\n# once the workspace moves onto 0.8, we can clean this up, moving the const-serialize stuff back to normal versions\nconst-serialize = { path = \"packages/const-serialize\", version = \"0.8.0-alpha.0\" }\nconst-serialize-macro = { path = \"packages/const-serialize-macro\", version = \"0.8.0-alpha.0\" }\n\nwarnings = { version = \"0.2.1\" }\n\n# blitz\nblitz-dom = { version = \"0.2.4\", default-features = false }\nblitz-net = { version = \"0.2\" }\nblitz-html = { version = \"0.2\" }\nblitz-paint = { version = \"0.2\" }\nblitz-traits = { version = \"0.2\" }\nblitz-shell = { version = \"0.2\", default-features = false }\nanyrender = { version = \"0.6.2\", default-features = false }\nanyrender_vello = { version = \"0.6\", default-features = false }\nanyrender_vello_cpu = { version = \"0.8\", default-features = false }\nwgpu_context = { version = \"0.1\", default-features = false }\nwgpu = { version = \"26.0\" }\nvello = \"0.6\"\nbevy = \"0.17\"\n\n\n# a fork of pretty please for tests - let's get off of this if we can!\nprettier-please = { version = \"0.3.0\", features = [\"verbatim\"] }\nanyhow = \"1.0.98\"\nclap = { version = \"4.5.40\" }\naskama_escape = \"0.13.0\"\ntracing = \"0.1.41\"\ntracing-futures = \"0.2.5\"\ntracing-subscriber = { version = \"0.3.19\", default-features = false }\ntoml = \"0.8\"\ntokio = \"1.48\"\ntokio-util = { version = \"0.7.15\" }\ntokio-stream = { version = \"0.1.17\" }\nslab = \"0.4.10\"\nslotmap = { version = \"1.0.7\", features = [\"serde\"] }\nfutures = \"0.3.31\"\nfutures-channel = \"0.3.31\"\nfutures-util = { version = \"0.3\", default-features = false }\nrustc-hash = \"2.1.1\"\nwasm-bindgen = \"0.2.105\"\nwasm-bindgen-futures = \"0.4.50\"\njs-sys = \"0.3\"\nweb-sys = { version = \"0.3.77\", default-features = false }\nhtml_parser = \"0.7.0\"\nthiserror = \"2.0.12\"\nprettyplease = { version = \"0.2.35\", features = [\"verbatim\"] }\nconst_format = \"0.2.34\"\ncargo_toml = { version = \"0.22.1\" }\ntauri-utils = { version = \"=2.5.0\" }\ntauri-macos-sign = { version = \"=2.2.0\" }\ntauri-bundler = { version = \"=2.5.0\" }\nlru = \"0.16.0\"\nasync-trait = \"0.1.88\"\naxum = { version = \"0.8.4\", default-features = false }\naxum-server = { version = \"0.7.3\", default-features = false }\nhttp-body = { version = \"1.0\" }\ntower = \"0.5.2\"\nhttp = \"1.3.1\"\nnotify = { version = \"8.1.0\" }\ntower-http = \"0.6.6\"\nhyper = \"1.6.0\"\nhyper-rustls = { version = \"0.27.7\", default-features = false, features = [\n    \"native-tokio\",\n    \"http1\",\n    \"http2\",\n    \"tls12\",\n    \"logging\",\n    \"ring\",\n] }\nrustls = { version = \"0.23.28\", default-features = false, features = [\n    \"logging\",\n    \"std\",\n    \"tls12\",\n    \"ring\",\n] }\nserde_json = \"1.0.140\"\nserde = \"1.0.219\"\nschemars = \"1.0\"\nsyn = \"2.0\"\nquote = \"1.0\"\naxum-core = \"0.5\"\nproc-macro2 = \"1.0.101\"\naxum_session = \"0.16.0\"\naxum_session_auth = \"0.16.0\"\naxum_session_sqlx = \"0.5.0\"\naxum-extra = \"0.10.1\"\nreqwest = { version = \"0.12.23\", default-features = false }\nowo-colors = \"4.2.2\"\nciborium = \"0.2.2\"\nbase64 = \"0.22.1\"\nuuid = \"1.17.0\"\nconvert_case = \"0.8.0\"\ntungstenite = { version = \"0.27.0\" }\ntokio-tungstenite = { version = \"0.27.0\" }\ngloo-timers = \"0.3.0\"\ninternment = { version = \"0.8.6\" }\nproc-macro2-diagnostics = { version = \"0.10\", default-features = false }\nenv_logger = \"0.11.8\"\nchrono = { version = \"0.4.39\" }\nrustversion = \"1.0.21\"\nrand = \"0.9\"\nlongest-increasing-subsequence = \"0.1.0\"\ntrybuild = \"1.0\"\ndirs = \"6.0.0\"\ncargo-config2 = \"0.1.34\"\ncriterion = { version = \"0.6\" }\ncargo_metadata = \"0.19.2\"\nparking_lot = \"0.12.4\"\ntracing-wasm = \"0.2.1\"\nbase16 = \"0.2.1\"\ndigest = \"0.10.7\"\nsha2 = \"0.10.9\"\nwalrus = { version = \"0.23.3\", features = [\"parallel\"] }\nid-arena = \"2.2.1\"\nasync-compression = { version = \"0.4\", features = [\n    \"futures-io\",\n    \"gzip\",\n    \"brotli\",\n] }\ngetrandom = { version = \"0.3.3\" }\nasync-once-cell = { version = \"0.5.4\" }\nrayon = \"1.10.0\"\nwasmparser = \"0.235.0\"\nitertools = \"0.14.0\"\nobject = { version = \"0.37.1\" }\ninventory = { version = \"0.3\" }\nmacro-string = \"0.1.4\"\nwalkdir = \"2.5.0\"\nurl = \"2\"\ndata-url = \"0.3.2\"\nseparator = \"0.4.1\"\npretty_assertions = \"1\"\nserde_repr = \"0.1\"\nhyper-util = \"0.1\"\nkrates = { version = \"0.17.5\" }\nlibloading = \"0.8.8\"\nlibc = \"0.2.174\"\nmemmap2 = \"0.9.5\"\nmemfd = \"0.6.4\"\nxxhash-rust = { version = \"0.8.15\", default-features = false }\nserde_qs = \"0.15.0\"\nmulter = \"3.1.0\"\nconst-str = \"0.7.0\"\nbytes = \"1.10\"\nsend_wrapper = \"0.6.0\"\npin-project = { version = \"1.1.10\" }\npostcard = { version = \"1.1.3\", default-features = false }\nserde_urlencoded = \"0.7\"\nform_urlencoded = \"1.2.1\"\nwinnow = \"0.7.14\"\n\n# desktop\nwry = { version = \"0.53.5\", default-features = false }\ntao = { version = \"0.34.0\", features = [\"rwh_05\"] }\ninfer = \"0.19.0\"\ndunce = \"1.0.5\"\npercent-encoding = \"2.3.1\"\nmuda = \"0.17.0\"\ntray-icon = \"0.21.0\"\nopen = \"5.3.2\"\nwebbrowser = \"1.0\"\n\n# web\ngloo-dialogs = \"0.2.0\"\n\n# tui stuff\nansi-to-tui = \"7.0\"\nansi-to-html = \"0.2.2\"\npath-absolutize = \"3.1\"\ncrossterm = { version = \"0.29.0\" }\nratatui = { version = \"0.29.0\" }\nshell-words = \"1.1.0\"\n\n# native\nkeyboard-types = { version = \"0.7\", default-features = false }\nwinit = { version = \"0.30.11\", features = [\"rwh_06\"] }\n\n# our release profile should be fast to compile and fast to run\n# when we ship our CI builds, we turn on LTO which improves perf leftover by turning on incremental\n[profile.release]\nincremental = true\n\n# crank up the opt level for wasm-split-cli in dev mode\n# important here that lto is on and the debug symbols are present (since they're used by wasm-opt)\n[profile.wasm-split-release]\ninherits = \"release\"\nopt-level = 'z'\nlto = true\ndebug = true\n\n# a profile for running the CLI that's also incremental\n[profile.cli-release-dev]\ninherits = \"release\"\nopt-level = 3\nincremental = true\n\n# crank up walrus since it's quite slow in dev mode\n[profile.dev.package.walrus]\nopt-level = 3\n\n# ensure we have adversarial setup for tls\n[profile.dev.package.cross-tls-crate]\nopt-level = 2\n\n[profile.dev.package.cross-tls-crate-dylib]\nopt-level = 2\n\n[profile.release-max-opt]\ninherits = \"release\"\nlto = true\ncodegen-units = 1\n\n# Disable debug assertions to check the released path of core and other packages, but build without optimizations to keep build times quick\n[profile.release-unoptimized]\ninherits = \"dev\"\ndebug-assertions = false\nincremental = true\n\n[profile.wasm-dev]\ninherits = \"dev\"\nopt-level = 1\n\n[profile.server-dev]\ninherits = \"dev\"\n\n[profile.android-dev]\ninherits = \"dev\"\n\n# This is a \"virtual package\"\n# It is not meant to be published, but is used so \"cargo run --example XYZ\" works properly\n[package]\nname = \"dioxus-examples\"\nauthors = [\"Jonathan Kelley\"]\nedition = \"2024\"\ndescription = \"Top level crate for the Dioxus repository\"\nlicense = \"MIT OR Apache-2.0\"\nrepository = \"https://github.com/DioxusLabs/dioxus/\"\nhomepage = \"https://dioxuslabs.com\"\ndocumentation = \"https://dioxuslabs.com\"\nkeywords = [\"dom\", \"ui\", \"gui\", \"react\", \"wasm\"]\nrust-version = \"1.85.0\"\npublish = false\nversion = \"0.7.0\"\n\n[dependencies]\nreqwest = { workspace = true, features = [\"json\"] }\nciborium = { workspace = true, optional = true }\nbase64 = { workspace = true, optional = true }\nhttp-range = { version = \"0.1.5\" }\nwgpu = { workspace = true, optional = true }\nwinit = { workspace = true, optional = true }\nouroboros = { version = \"*\", optional = true }\nwasm-splitter = { workspace = true }\n\nsqlx = { version = \"0.8.6\", features = [\n    \"macros\",\n    \"migrate\",\n    \"postgres\",\n    \"sqlite\",\n    \"_unstable-all-types\",\n    \"tls-native-tls\",\n    \"runtime-tokio\",\n], optional = true }\nwasm-streams = \"0.4.2\"\n\n[dev-dependencies]\ndioxus = { workspace = true, features = [\"router\", \"fullstack\"] }\ndioxus-html = { workspace = true, features = [\"serialize\"] }\ndioxus-stores = { workspace = true }\ndioxus-ssr = { workspace = true }\nfutures-util = { workspace = true }\nseparator = { workspace = true }\nserde = { workspace = true, features = [\"derive\"] }\nserde_json = { workspace = true }\nrand = { workspace = true, features = [\"small_rng\"] }\nform_urlencoded = \"1.2.1\"\nasync-std = \"1.13.1\"\nweb-time = \"1.1.0\"\nanyhow = { workspace = true }\nthiserror = { workspace = true }\nbytes = { workspace = true }\nfutures = { workspace = true }\naxum-core = { workspace = true }\nuuid = { workspace = true, features = [\"v4\", \"serde\"] }\ntower-http = { workspace = true, features = [\"timeout\"] }\npollster = \"0.4.0\"\n\n[target.'cfg(target_arch = \"wasm32\")'.dev-dependencies]\ngetrandom = { workspace = true, features = [\"wasm_js\"] }\ntokio = { version = \"1.48\", default-features = false, features = [\n    \"sync\",\n    \"macros\",\n    \"io-util\",\n    \"rt\",\n    \"time\",\n] }\nuuid = { workspace = true, features = [\"v4\", \"serde\", \"js\"] }\n\n[target.'cfg(not(target_arch = \"wasm32\"))'.dev-dependencies]\ntokio = { version = \"1.48\", features = [\"full\"] }\n\n# To make most examples faster to compile, we split out assets and http-related stuff\n# This trims off like 270 dependencies, leading to a significant speedup in compilation time\n[features]\ndefault = [\"desktop\"]\ndesktop = [\"dioxus/desktop\"]\nnative = [\"dioxus/native\", \"winit\"]\nliveview = [\"dioxus/liveview\"]\nserver = [\"dioxus/server\"]\nmobile = [\"dioxus/mobile\"]\nweb = [\"dioxus/web\"]\ngpu = [\"dep:ouroboros\", \"dep:wgpu\"]\n\n[[example]]\nname = \"websocket_chat\"\npath = \"examples/01-app-demos/websocket_chat.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"weather_app\"\npath = \"examples/01-app-demos/weather_app.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"crm\"\npath = \"examples/01-app-demos/crm.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"image_generator_openai\"\npath = \"examples/01-app-demos/image_generator_openai.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"todomvc\"\npath = \"examples/01-app-demos/todomvc.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"calculator_mutable\"\npath = \"examples/01-app-demos/calculator_mutable.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"hello_world\"\npath = \"examples/01-app-demos/hello_world.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"counters\"\npath = \"examples/01-app-demos/counters.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"todomvc_store\"\npath = \"examples/01-app-demos/todomvc_store.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"dog_app\"\npath = \"examples/01-app-demos/dog_app.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"calculator\"\npath = \"examples/01-app-demos/calculator.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"repo_readme\"\npath = \"examples/01-app-demos/repo_readme.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"nested_listeners\"\npath = \"examples/02-building-ui/nested_listeners.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"disabled\"\npath = \"examples/02-building-ui/disabled.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"svg\"\npath = \"examples/02-building-ui/svg.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"css_modules\"\npath = \"examples/03-assets-styling/css_modules.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"custom_assets\"\npath = \"examples/03-assets-styling/custom_assets.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"dynamic_assets\"\npath = \"examples/03-assets-styling/dynamic_assets.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"meta\"\npath = \"examples/03-assets-styling/meta.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"meta_elements\"\npath = \"examples/03-assets-styling/meta_elements.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"reducer\"\npath = \"examples/04-managing-state/reducer.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"memo_chain\"\npath = \"examples/04-managing-state/memo_chain.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"global\"\npath = \"examples/04-managing-state/global.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"context_api\"\npath = \"examples/04-managing-state/context_api.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"signals\"\npath = \"examples/04-managing-state/signals.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"errors\"\npath = \"examples/04-managing-state/error_handling.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"backgrounded_futures\"\npath = \"examples/05-using-async/backgrounded_futures.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"clock\"\npath = \"examples/05-using-async/clock.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"streams\"\npath = \"examples/05-using-async/streams.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"suspense\"\npath = \"examples/05-using-async/suspense.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"future\"\npath = \"examples/05-using-async/future.rs\"\ndoc-scrape-examples = true\n\n\n[[example]]\nname = \"simple_router\"\npath = \"examples/06-routing/simple_router.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"router_restore_scroll\"\npath = \"examples/06-routing/router_restore_scroll.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"link\"\npath = \"examples/06-routing/link.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"hash_fragment_state\"\nrequired-features = [\"ciborium\", \"base64\"]\npath = \"examples/06-routing/hash_fragment_state.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"router\"\npath = \"examples/06-routing/router.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"router_resource\"\npath = \"examples/06-routing/router_resource.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"query_segment_search\"\npath = \"examples/06-routing/query_segment_search.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"flat_router\"\npath = \"examples/06-routing/flat_router.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"query_params\"\npath = \"examples/07-fullstack/query_params.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"server_functions\"\npath = \"examples/07-fullstack/server_functions.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"middleware\"\npath = \"examples/07-fullstack/middleware.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"custom_error_page\"\npath = \"examples/07-fullstack/custom_error_page.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"redirect\"\npath = \"examples/07-fullstack/redirect.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"header_map\"\npath = \"examples/07-fullstack/header_map.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"login_form\"\npath = \"examples/07-fullstack/login_form.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"handling_errors\"\npath = \"examples/07-fullstack/handling_errors.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"dog_app_self_hosted\"\npath = \"examples/07-fullstack/dog_app_self_hosted.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"through_reqwest\"\npath = \"examples/07-fullstack/through_reqwest.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"streaming_file_upload\"\npath = \"examples/07-fullstack/streaming_file_upload.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"multipart_form\"\npath = \"examples/07-fullstack/multipart_form.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"full_request_access\"\npath = \"examples/07-fullstack/full_request_access.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"fullstack_hello_world\"\npath = \"examples/07-fullstack/fullstack_hello_world.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"server_sent_events\"\npath = \"examples/07-fullstack/server_sent_events.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"server_state\"\npath = \"examples/07-fullstack/server_state.rs\"\ndoc-scrape-examples = true\nrequired-features = [\"sqlx\"]\n\n[[example]]\nname = \"streaming\"\npath = \"examples/07-fullstack/streaming.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"custom_axum_serve\"\npath = \"examples/07-fullstack/custom_axum_serve.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"websocket\"\npath = \"examples/07-fullstack/websocket.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"drag_and_drop\"\npath = \"examples/08-apis/drag_and_drop.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"control_focus\"\npath = \"examples/08-apis/control_focus.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"window_popup\"\nrequired-features = [\"desktop\"]\npath = \"examples/08-apis/window_popup.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"custom_html\"\nrequired-features = [\"desktop\"]\npath = \"examples/08-apis/custom_html.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"multiwindow_with_tray_icon\"\nrequired-features = [\"desktop\"]\npath = \"examples/08-apis/multiwindow_with_tray_icon.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"window_event\"\nrequired-features = [\"desktop\"]\npath = \"examples/08-apis/window_event.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"read_size\"\npath = \"examples/08-apis/read_size.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"logging\"\npath = \"examples/08-apis/logging.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"overlay\"\nrequired-features = [\"desktop\"]\npath = \"examples/08-apis/overlay.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"ssr\"\npath = \"examples/08-apis/ssr.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"video_stream\"\nrequired-features = [\"desktop\"]\npath = \"examples/08-apis/video_stream.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"title\"\npath = \"examples/08-apis/title.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"file_upload\"\npath = \"examples/08-apis/file_upload.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"window_focus\"\nrequired-features = [\"desktop\"]\npath = \"examples/08-apis/window_focus.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"eval\"\npath = \"examples/08-apis/eval.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"shortcut\"\nrequired-features = [\"desktop\"]\npath = \"examples/08-apis/shortcut.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"scroll_to_offset\"\npath = \"examples/08-apis/scroll_to_offset.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"scroll_to_top\"\npath = \"examples/08-apis/scroll_to_top.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"wgpu_child_window\"\npath = \"examples/08-apis/wgpu_child_window.rs\"\nrequired-features = [\"gpu\", \"desktop\"]\ndoc-scrape-examples = true\n\n[[example]]\nname = \"multiwindow\"\nrequired-features = [\"desktop\"]\npath = \"examples/08-apis/multiwindow.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"window_zoom\"\nrequired-features = [\"desktop\"]\npath = \"examples/08-apis/window_zoom.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"custom_menu\"\nrequired-features = [\"desktop\"]\npath = \"examples/08-apis/custom_menu.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"on_resize\"\npath = \"examples/08-apis/on_resize.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"form\"\npath = \"examples/08-apis/form.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"on_visible\"\npath = \"examples/08-apis/on_visible.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"all_events\"\npath = \"examples/09-reference/all_events.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"xss_safety\"\npath = \"examples/09-reference/xss_safety.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"web_component\"\npath = \"examples/09-reference/web_component.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"generic_component\"\npath = \"examples/09-reference/generic_component.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"shorthand\"\npath = \"examples/09-reference/shorthand.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"simple_list\"\npath = \"examples/09-reference/simple_list.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"optional_props\"\npath = \"examples/09-reference/optional_props.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"rsx_usage\"\npath = \"examples/09-reference/rsx_usage.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"spread\"\npath = \"examples/09-reference/spread.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"__scrape_example_list\"\npath = \"examples/scripts/scrape_examples.rs\"\n"
  },
  {
    "path": "LICENSE-APACHE",
    "content": "                              Apache License\n                        Version 2.0, January 2004\n                     http://www.apache.org/licenses/\n\nTERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n1. Definitions.\n\n   \"License\" shall mean the terms and conditions for use, reproduction,\n   and distribution as defined by Sections 1 through 9 of this document.\n\n   \"Licensor\" shall mean the copyright owner or entity authorized by\n   the copyright owner that is granting the License.\n\n   \"Legal Entity\" shall mean the union of the acting entity and all\n   other entities that control, are controlled by, or are under common\n   control with that entity. For the purposes of this definition,\n   \"control\" means (i) the power, direct or indirect, to cause the\n   direction or management of such entity, whether by contract or\n   otherwise, or (ii) ownership of fifty percent (50%) or more of the\n   outstanding shares, or (iii) beneficial ownership of such entity.\n\n   \"You\" (or \"Your\") shall mean an individual or Legal Entity\n   exercising permissions granted by this License.\n\n   \"Source\" form shall mean the preferred form for making modifications,\n   including but not limited to software source code, documentation\n   source, and configuration files.\n\n   \"Object\" form shall mean any form resulting from mechanical\n   transformation or translation of a Source form, including but\n   not limited to compiled object code, generated documentation,\n   and conversions to other media types.\n\n   \"Work\" shall mean the work of authorship, whether in Source or\n   Object form, made available under the License, as indicated by a\n   copyright notice that is included in or attached to the work\n   (an example is provided in the Appendix below).\n\n   \"Derivative Works\" shall mean any work, whether in Source or Object\n   form, that is based on (or derived from) the Work and for which the\n   editorial revisions, annotations, elaborations, or other modifications\n   represent, as a whole, an original work of authorship. For the purposes\n   of this License, Derivative Works shall not include works that remain\n   separable from, or merely link (or bind by name) to the interfaces of,\n   the Work and Derivative Works thereof.\n\n   \"Contribution\" shall mean any work of authorship, including\n   the original version of the Work and any modifications or additions\n   to that Work or Derivative Works thereof, that is intentionally\n   submitted to Licensor for inclusion in the Work by the copyright owner\n   or by an individual or Legal Entity authorized to submit on behalf of\n   the copyright owner. For the purposes of this definition, \"submitted\"\n   means any form of electronic, verbal, or written communication sent\n   to the Licensor or its representatives, including but not limited to\n   communication on electronic mailing lists, source code control systems,\n   and issue tracking systems that are managed by, or on behalf of, the\n   Licensor for the purpose of discussing and improving the Work, but\n   excluding communication that is conspicuously marked or otherwise\n   designated in writing by the copyright owner as \"Not a Contribution.\"\n\n   \"Contributor\" shall mean Licensor and any individual or Legal Entity\n   on behalf of whom a Contribution has been received by Licensor and\n   subsequently incorporated within the Work.\n\n2. Grant of Copyright License. Subject to the terms and conditions of\n   this License, each Contributor hereby grants to You a perpetual,\n   worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n   copyright license to reproduce, prepare Derivative Works of,\n   publicly display, publicly perform, sublicense, and distribute the\n   Work and such Derivative Works in Source or Object form.\n\n3. Grant of Patent License. Subject to the terms and conditions of\n   this License, each Contributor hereby grants to You a perpetual,\n   worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n   (except as stated in this section) patent license to make, have made,\n   use, offer to sell, sell, import, and otherwise transfer the Work,\n   where such license applies only to those patent claims licensable\n   by such Contributor that are necessarily infringed by their\n   Contribution(s) alone or by combination of their Contribution(s)\n   with the Work to which such Contribution(s) was submitted. If You\n   institute patent litigation against any entity (including a\n   cross-claim or counterclaim in a lawsuit) alleging that the Work\n   or a Contribution incorporated within the Work constitutes direct\n   or contributory patent infringement, then any patent licenses\n   granted to You under this License for that Work shall terminate\n   as of the date such litigation is filed.\n\n4. Redistribution. You may reproduce and distribute copies of the\n   Work or Derivative Works thereof in any medium, with or without\n   modifications, and in Source or Object form, provided that You\n   meet the following conditions:\n\n   (a) You must give any other recipients of the Work or\n       Derivative Works a copy of this License; and\n\n   (b) You must cause any modified files to carry prominent notices\n       stating that You changed the files; and\n\n   (c) You must retain, in the Source form of any Derivative Works\n       that You distribute, all copyright, patent, trademark, and\n       attribution notices from the Source form of the Work,\n       excluding those notices that do not pertain to any part of\n       the Derivative Works; and\n\n   (d) If the Work includes a \"NOTICE\" text file as part of its\n       distribution, then any Derivative Works that You distribute must\n       include a readable copy of the attribution notices contained\n       within such NOTICE file, excluding those notices that do not\n       pertain to any part of the Derivative Works, in at least one\n       of the following places: within a NOTICE text file distributed\n       as part of the Derivative Works; within the Source form or\n       documentation, if provided along with the Derivative Works; or,\n       within a display generated by the Derivative Works, if and\n       wherever such third-party notices normally appear. The contents\n       of the NOTICE file are for informational purposes only and\n       do not modify the License. You may add Your own attribution\n       notices within Derivative Works that You distribute, alongside\n       or as an addendum to the NOTICE text from the Work, provided\n       that such additional attribution notices cannot be construed\n       as modifying the License.\n\n   You may add Your own copyright statement to Your modifications and\n   may provide additional or different license terms and conditions\n   for use, reproduction, or distribution of Your modifications, or\n   for any such Derivative Works as a whole, provided Your use,\n   reproduction, and distribution of the Work otherwise complies with\n   the conditions stated in this License.\n\n5. Submission of Contributions. Unless You explicitly state otherwise,\n   any Contribution intentionally submitted for inclusion in the Work\n   by You to the Licensor shall be under the terms and conditions of\n   this License, without any additional terms or conditions.\n   Notwithstanding the above, nothing herein shall supersede or modify\n   the terms of any separate license agreement you may have executed\n   with Licensor regarding such Contributions.\n\n6. Trademarks. This License does not grant permission to use the trade\n   names, trademarks, service marks, or product names of the Licensor,\n   except as required for reasonable and customary use in describing the\n   origin of the Work and reproducing the content of the NOTICE file.\n\n7. Disclaimer of Warranty. Unless required by applicable law or\n   agreed to in writing, Licensor provides the Work (and each\n   Contributor provides its Contributions) on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n   implied, including, without limitation, any warranties or conditions\n   of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n   PARTICULAR PURPOSE. You are solely responsible for determining the\n   appropriateness of using or redistributing the Work and assume any\n   risks associated with Your exercise of permissions under this License.\n\n8. Limitation of Liability. In no event and under no legal theory,\n   whether in tort (including negligence), contract, or otherwise,\n   unless required by applicable law (such as deliberate and grossly\n   negligent acts) or agreed to in writing, shall any Contributor be\n   liable to You for damages, including any direct, indirect, special,\n   incidental, or consequential damages of any character arising as a\n   result of this License or out of the use or inability to use the\n   Work (including but not limited to damages for loss of goodwill,\n   work stoppage, computer failure or malfunction, or any and all\n   other commercial damages or losses), even if such Contributor\n   has been advised of the possibility of such damages.\n\n9. Accepting Warranty or Additional Liability. While redistributing\n   the Work or Derivative Works thereof, You may choose to offer,\n   and charge a fee for, acceptance of support, warranty, indemnity,\n   or other liability obligations and/or rights consistent with this\n   License. However, in accepting such obligations, You may act only\n   on Your own behalf and on Your sole responsibility, not on behalf\n   of any other Contributor, and only if You agree to indemnify,\n   defend, and hold each Contributor harmless for any liability\n   incurred by, or claims asserted against, such Contributor by reason\n   of your accepting any such warranty or additional liability.\n\nEND OF TERMS AND CONDITIONS\n"
  },
  {
    "path": "LICENSE-MIT",
    "content": "Permission is hereby granted, free of charge, to any\nperson obtaining a copy of this software and associated\ndocumentation files (the \"Software\"), to deal in the\nSoftware without restriction, including without\nlimitation the rights to use, copy, modify, merge,\npublish, distribute, sublicense, and/or sell copies of\nthe Software, and to permit persons to whom the Software\nis furnished to do so, subject to the following\nconditions:\n\nThe above copyright notice and this permission notice\nshall be included in all copies or substantial portions\nof the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF\nANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED\nTO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\nPARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT\nSHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\nCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\nOF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR\nIN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\nDEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "<p>\n    <p align=\"center\" >\n      <!-- <img src=\"./notes/header-light-updated.svg#gh-light-mode-only\" >\n      <img src=\"./notes/header-dark-updated.svg#gh-dark-mode-only\" > -->\n      <!-- <a href=\"https://dioxuslabs.com\">\n          <img src=\"./notes/flat-splash.avif\">\n      </a> -->\n      <img src=\"./notes/splash-header-darkmode.svg#gh-dark-mode-only\" style=\"width: 80%; height: auto;\">\n      <img src=\"./notes/splash-header.svg#gh-light-mode-only\" style=\"width: 80%; height: auto;\">\n      <img src=\"./notes/image-splash.avif\">\n      <br>\n    </p>\n</p>\n<div align=\"center\">\n  <!-- Crates version -->\n  <a href=\"https://crates.io/crates/dioxus\">\n    <img src=\"https://img.shields.io/crates/v/dioxus.svg?style=flat-square\"\n    alt=\"Crates.io version\" />\n  </a>\n  <!-- Downloads -->\n  <a href=\"https://crates.io/crates/dioxus\">\n    <img src=\"https://img.shields.io/crates/d/dioxus.svg?style=flat-square\"\n      alt=\"Download\" />\n  </a>\n  <!-- docs -->\n  <a href=\"https://docs.rs/dioxus\">\n    <img src=\"https://img.shields.io/badge/docs-latest-blue.svg?style=flat-square\"\n      alt=\"docs.rs docs\" />\n  </a>\n  <!-- CI -->\n  <a href=\"https://github.com/jkelleyrtp/dioxus/actions\">\n    <img src=\"https://github.com/dioxuslabs/dioxus/actions/workflows/main.yml/badge.svg\"\n      alt=\"CI status\" />\n  </a>\n\n  <!--Awesome -->\n  <a href=\"https://dioxuslabs.com/awesome\">\n    <img src=\"https://cdn.rawgit.com/sindresorhus/awesome/d7305f38d29fed78fa85652e3a63e154dd8e8829/media/badge.svg\" alt=\"Awesome Page\" />\n  </a>\n  <!-- Discord -->\n  <a href=\"https://discord.gg/XgGxMSkvUM\">\n    <img src=\"https://img.shields.io/discord/899851952891002890.svg?logo=discord&style=flat-square\" alt=\"Discord Link\" />\n  </a>\n</div>\n\n<div align=\"center\">\n  <h3>\n    <a href=\"https://dioxuslabs.com\"> Website </a>\n    <span> | </span>\n    <a href=\"https://github.com/DioxusLabs/dioxus/tree/main/examples\"> Examples </a>\n    <span> | </span>\n    <a href=\"https://dioxuslabs.com/learn/0.7/tutorial\"> Tutorial </a>\n    <span> | </span>\n    <a href=\"https://github.com/DioxusLabs/dioxus/blob/main/notes/translations/zh-cn/README.md\"> 中文 </a>\n    <span> | </span>\n    <a href=\"https://github.com/DioxusLabs/dioxus/blob/main/notes/translations/pt-br/README.md\"> PT-BR </a>\n    <span> | </span>\n    <a href=\"https://github.com/DioxusLabs/dioxus/blob/main/notes/translations/ja-jp/README.md\"> 日本語 </a>\n    <span> | </span>\n    <a href=\"https://github.com/DioxusLabs/dioxus/blob/main/notes/translations/tr-tr\"> Türkçe </a>\n    <span> | </span>\n    <a href=\"https://github.com/DioxusLabs/dioxus/blob/main/notes/translations/ko-kr\"> 한국어 </a>\n  </h3>\n</div>\n<br>\n<p align=\"center\">\n  <a href=\"https://github.com/DioxusLabs/dioxus/releases/tag/v0.7.0\">✨ Dioxus 0.7 is out!!! ✨</a>\n</p>\n<br>\n\nBuild for web, desktop, and mobile, and more with a single codebase. Zero-config setup, integrated hot-reloading, and signals-based state management. Add backend functionality with Server Functions and bundle with our CLI.\n\n```rust\nfn app() -> Element {\n    let mut count = use_signal(|| 0);\n\n    rsx! {\n        h1 { \"High-Five counter: {count}\" }\n        button { onclick: move |_| count += 1, \"Up high!\" }\n        button { onclick: move |_| count -= 1, \"Down low!\" }\n    }\n}\n```\n\n## ⭐️ Unique features:\n\n- Cross-platform apps in three lines of code (web, desktop, mobile, server, and more)\n- [Ergonomic state management](https://dioxuslabs.com/blog/release-050) combines the best of React, Solid, and Svelte\n- Built-in featureful, type-safe, fullstack web framework\n- Integrated bundler for deploying to the web, macOS, Linux, and Windows\n- Subsecond Rust hot-patching and asset hot-reloading\n- And more! [Take a tour of Dioxus](https://dioxuslabs.com/learn/0.7/).\n\n## Instant hot-reloading\n\nWith one command, `dx serve` and your app is running. Edit your markup, styles, and see changes in milliseconds. Use our experimental `dx serve --hotpatch` to update Rust code in real time.\n\n<div align=\"center\">\n  <img src=\"https://raw.githubusercontent.com/DioxusLabs/screenshots/refs/heads/main/blitz/hotreload-video.webp\">\n  <!-- <video src=\"https://private-user-images.githubusercontent.com/10237910/386919031-6da371d5-3340-46da-84ff-628216851ba6.mov\" width=\"500\"></video> -->\n  <!-- <video src=\"https://private-user-images.githubusercontent.com/10237910/386919031-6da371d5-3340-46da-84ff-628216851ba6.mov\" width=\"500\"></video> -->\n</div>\n\n## Build Beautiful Apps\n\nDioxus apps are styled with HTML and CSS. Use the built-in TailwindCSS support or load your favorite CSS library. Easily call into native code (objective-c, JNI, Web-Sys) for a perfect native touch.\n\n<div align=\"center\">\n  <img src=\"./notes/ebou2.avif\">\n</div>\n\n\n\n## Truly fullstack applications\n\nDioxus deeply integrates with [axum](https://github.com/tokio-rs/axum) to provide powerful fullstack capabilities for both clients and servers. Pick from a wide array of built-in batteries like WebSockets, SSE, Streaming, File Upload/Download, Server-Side-Rendering, Forms, Middleware, and Hot-Reload, or go fully custom and integrate your existing axum backend.\n\n<div align=\"center\">\n  <img src=\"./notes/fullstack-websockets.avif\" width=\"700\">\n</div>\n\n## Experimental Native Renderer\n\nRender using web-sys, webview, server-side-rendering, liveview, or even with our experimental WGPU-based renderer. Embed Dioxus in Bevy, WGPU, or even run on embedded Linux!\n\n<div align=\"center\">\n  <img src=\"https://raw.githubusercontent.com/DioxusLabs/screenshots/refs/heads/main/blitz/native-blitz-wgpu.webp\">\n</div>\n\n\n## First-party primitive components\n\nGet started quickly with a complete set of primitives modeled after shadcn/ui and Radix-Primitives.\n\n<div align=\"center\">\n  <img src=\"./notes/primitive-components.avif\" width=\"700\">\n</div>\n\n## First-class Android and iOS support\n\nDioxus is the fastest way to build native mobile apps with Rust. Simply run `dx serve --platform android` and your app is running in an emulator or on device in seconds. Call directly into JNI and Native APIs.\n\n<div align=\"center\">\n  <img src=\"./notes/android_and_ios2.avif\" width=\"500\">\n</div>\n\n\n\n## Bundle for web, desktop, and mobile\n\nSimply run `dx bundle` and your app will be built and bundled with maximization optimizations. On the web, take advantage of [`.avif` generation, `.wasm` compression, minification](https://dioxuslabs.com/learn/0.7/tutorial/assets), and more. Build WebApps weighing [less than 50kb](https://github.com/ealmloff/tiny-dioxus/) and desktop/mobile apps less than 5mb.\n\n<div align=\"center\">\n  <img src=\"./notes/bundle.gif\">\n</div>\n\n\n## Fantastic documentation\n\nWe've put a ton of effort into building clean, readable, and comprehensive documentation. All html elements and listeners are documented with MDN docs, and our Docs runs continuous integration with Dioxus itself to ensure that the docs are always up to date. Check out the [Dioxus website](https://dioxuslabs.com/learn/0.7/) for guides, references, recipes, and more. Fun fact: we use the Dioxus website as a testbed for new Dioxus features - [check it out!](https://github.com/dioxusLabs/docsite)\n\n<div align=\"center\">\n  <img src=\"./notes/docs.avif\">\n</div>\n\n\n## Modular and Customizable\n\nBuild your own renderer. Use our modular components like RSX, VirtualDom, Blitz, Taffy, and Subsecond.\n\n## Community\n\nDioxus is a community-driven project, with a very active [Discord](https://discord.gg/XgGxMSkvUM) and [GitHub](https://github.com/DioxusLabs/dioxus/issues) community. We're always looking for help, and we're happy to answer questions and help you get started. [Our SDK](https://github.com/DioxusLabs/dioxus-std) is community-run and we even have a [GitHub organization](https://github.com/dioxus-community/) for the best Dioxus crates that receive free upgrades and support.\n\n<div align=\"center\">\n  <img src=\"./notes/dioxus-community.avif\">\n</div>\n\n## Full-time core team\n\nDioxus has grown from a side project to a small team of fulltime engineers. Thanks to the generous support of FutureWei, Satellite.im, the GitHub Accelerator program, we're able to work on Dioxus full-time. Our long term goal is for Dioxus to become self-sustaining by providing paid high-quality enterprise tools. If your company is interested in adopting Dioxus and would like to work with us, please reach out!\n\n## Supported Platforms\n\n<div align=\"center\">\n  <table style=\"width:100%\">\n    <tr>\n      <td>\n      <b>Web</b>\n      </td>\n      <td>\n        <ul>\n          <li>Render directly to the DOM using WebAssembly</li>\n          <li>Pre-render with SSR and rehydrate on the client</li>\n          <li>Simple \"hello world\" at about 50kb, comparable to React</li>\n          <li>Built-in dev server and hot reloading for quick iteration</li>\n        </ul>\n      </td>\n    </tr>\n    <tr>\n      <td>\n      <b>Desktop</b>\n      </td>\n      <td>\n        <ul>\n          <li>Render using Webview or - experimentally - with WGPU or <a href=\"https://freyaui.dev\">Freya</a> (Skia) </li>\n          <li>Zero-config setup. Simply `cargo run` or `dx serve` to build your app </li>\n          <li>Full support for native system access without IPC </li>\n          <li>Supports macOS, Linux, and Windows. Portable <3mb binaries </li>\n        </ul>\n      </td>\n    </tr>\n    <tr>\n      <td>\n      <b>Mobile</b>\n      </td>\n      <td>\n        <ul>\n          <li>Render using Webview or - experimentally - with WGPU or Skia </li>\n          <li>Build .ipa and .apk files for iOS and Android </li>\n          <li>Call directly into Java and Objective-C with minimal overhead</li>\n          <li>From \"hello world\" to running on device in seconds</li>\n        </ul>\n      </td>\n    </tr>\n    <tr>\n      <td>\n      <b>Server-side Rendering</b>\n      </td>\n      <td>\n        <ul>\n          <li>Suspense, hydration, and server-side rendering</li>\n          <li>Quickly drop in backend functionality with server functions</li>\n          <li>Extractors, middleware, and routing integrations</li>\n          <li>Static-site generation and incremental regeneration</li>\n        </ul>\n      </td>\n    </tr>\n  </table>\n</div>\n\n## Running the examples\n\n> The examples in the main branch of this repository target the git version of dioxus and the CLI. If you are looking for examples that work with the latest stable release of dioxus, check out the [0.6 branch](https://github.com/DioxusLabs/dioxus/tree/v0.6/examples).\n\nThe examples in the top level of this repository can be run with:\n\n```sh\ncargo run --example <example>\n```\n\nHowever, we encourage you to download the dioxus-cli to test out features like hot-reloading. To install the most recent binary CLI, you can use cargo binstall.\n\n```sh\ncargo binstall dioxus-cli@0.7.0 --force\n```\n\nIf this CLI is out-of-date, you can install it directly from git\n\n```sh\ncargo install --git https://github.com/DioxusLabs/dioxus dioxus-cli --locked\n```\n\nWith the CLI, you can also run examples with the web platform. You will need to disable the default desktop feature and enable the web feature with this command:\n\n```sh\ndx serve --example <example> --platform web -- --no-default-features\n```\n\n## Contributing\n\n- Check out the website [section on contributing](https://dioxuslabs.com/learn/0.7/beyond/contributing).\n- Report issues on our [issue tracker](https://github.com/dioxuslabs/dioxus/issues).\n- [Join](https://discord.gg/XgGxMSkvUM) the discord and ask questions!\n\n<a href=\"https://github.com/dioxuslabs/dioxus/graphs/contributors\">\n  <img src=\"https://contrib.rocks/image?repo=dioxuslabs/dioxus&max=30&columns=10\" />\n</a>\n\n## License\n\nThis project is licensed under either the [MIT license] or the [Apache-2 License].\n\n[apache-2 license]: https://github.com/DioxusLabs/dioxus/blob/master/LICENSE-APACHE\n[mit license]: https://github.com/DioxusLabs/dioxus/blob/master/LICENSE-MIT\n\nUnless you explicitly state otherwise, any contribution intentionally submitted\nfor inclusion in Dioxus by you, shall be licensed as MIT or Apache-2, without any additional\nterms or conditions.\n"
  },
  {
    "path": "_typos.toml",
    "content": "[default.extend-words]\n# https://ratatui.rs/\nratatui = \"ratatui\"\n# lits is short for literals\nlits = \"lits\"\n# https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/seeked_event\nseeked = \"seeked\"\n# https://developer.apple.com/forums/thread/108953\n# udid = unique device identifier\nudid = \"udid\"\n# Part of Blitz's API\nunparented = \"unparented\"\n\n[files]\nextend-exclude = [\"notes/translations/*\", \"CHANGELOG.md\", \"*.js\"]\n"
  },
  {
    "path": "codecov.yml",
    "content": "comment: false\nfail_ci_if_error: false\n"
  },
  {
    "path": "examples/01-app-demos/bluetooth-scanner/.gitignore",
    "content": "/target\n"
  },
  {
    "path": "examples/01-app-demos/bluetooth-scanner/Cargo.toml",
    "content": "[package]\nname = \"bluetooth-scanner\"\nversion = \"0.1.1\"\nedition = \"2021\"\npublish = false\n\n# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html\n\n[dependencies]\ntokio = { workspace = true, features = [\"full\"] }\ndioxus = { workspace = true }\nfutures-channel = { workspace = true }\nfutures = { workspace = true }\nbtleplug = \"0.11.8\"\n\n[features]\ndefault = [\"desktop\"]\ndesktop = [\"dioxus/desktop\"]\nnative = [\"dioxus/native\"]\n"
  },
  {
    "path": "examples/01-app-demos/bluetooth-scanner/README.md",
    "content": "# Bluetooth scanner app\n\nThis desktop app showcases the use of background threads.\n\n![Demo of app](./demo_small.png)\n"
  },
  {
    "path": "examples/01-app-demos/bluetooth-scanner/assets/tailwind.css",
    "content": "/*! tailwindcss v4.1.5 | MIT License | https://tailwindcss.com */\n@layer properties;\n@layer theme, base, components, utilities;\n@layer theme {\n  :root, :host {\n    --font-sans: ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol',\n    'Noto Color Emoji';\n    --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New',\n    monospace;\n    --color-green-500: oklch(72.3% 0.219 149.579);\n    --color-indigo-500: oklch(58.5% 0.233 277.117);\n    --color-indigo-600: oklch(51.1% 0.262 276.966);\n    --color-purple-50: oklch(97.7% 0.014 308.299);\n    --color-purple-500: oklch(62.7% 0.265 303.9);\n    --color-gray-50: oklch(98.5% 0.002 247.839);\n    --color-gray-500: oklch(55.1% 0.027 264.364);\n    --color-white: #fff;\n    --spacing: 0.25rem;\n    --text-xs: 0.75rem;\n    --text-xs--line-height: calc(1 / 0.75);\n    --text-2xl: 1.5rem;\n    --text-2xl--line-height: calc(2 / 1.5);\n    --font-weight-medium: 500;\n    --font-weight-bold: 700;\n    --default-transition-duration: 150ms;\n    --default-transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);\n    --default-font-family: var(--font-sans);\n    --default-mono-font-family: var(--font-mono);\n  }\n}\n@layer base {\n  *, ::after, ::before, ::backdrop, ::file-selector-button {\n    box-sizing: border-box;\n    margin: 0;\n    padding: 0;\n    border: 0 solid;\n  }\n  html, :host {\n    line-height: 1.5;\n    -webkit-text-size-adjust: 100%;\n    tab-size: 4;\n    font-family: var(--default-font-family, ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji');\n    font-feature-settings: var(--default-font-feature-settings, normal);\n    font-variation-settings: var(--default-font-variation-settings, normal);\n    -webkit-tap-highlight-color: transparent;\n  }\n  hr {\n    height: 0;\n    color: inherit;\n    border-top-width: 1px;\n  }\n  abbr:where([title]) {\n    -webkit-text-decoration: underline dotted;\n    text-decoration: underline dotted;\n  }\n  h1, h2, h3, h4, h5, h6 {\n    font-size: inherit;\n    font-weight: inherit;\n  }\n  a {\n    color: inherit;\n    -webkit-text-decoration: inherit;\n    text-decoration: inherit;\n  }\n  b, strong {\n    font-weight: bolder;\n  }\n  code, kbd, samp, pre {\n    font-family: var(--default-mono-font-family, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace);\n    font-feature-settings: var(--default-mono-font-feature-settings, normal);\n    font-variation-settings: var(--default-mono-font-variation-settings, normal);\n    font-size: 1em;\n  }\n  small {\n    font-size: 80%;\n  }\n  sub, sup {\n    font-size: 75%;\n    line-height: 0;\n    position: relative;\n    vertical-align: baseline;\n  }\n  sub {\n    bottom: -0.25em;\n  }\n  sup {\n    top: -0.5em;\n  }\n  table {\n    text-indent: 0;\n    border-color: inherit;\n    border-collapse: collapse;\n  }\n  :-moz-focusring {\n    outline: auto;\n  }\n  progress {\n    vertical-align: baseline;\n  }\n  summary {\n    display: list-item;\n  }\n  ol, ul, menu {\n    list-style: none;\n  }\n  img, svg, video, canvas, audio, iframe, embed, object {\n    display: block;\n    vertical-align: middle;\n  }\n  img, video {\n    max-width: 100%;\n    height: auto;\n  }\n  button, input, select, optgroup, textarea, ::file-selector-button {\n    font: inherit;\n    font-feature-settings: inherit;\n    font-variation-settings: inherit;\n    letter-spacing: inherit;\n    color: inherit;\n    border-radius: 0;\n    background-color: transparent;\n    opacity: 1;\n  }\n  :where(select:is([multiple], [size])) optgroup {\n    font-weight: bolder;\n  }\n  :where(select:is([multiple], [size])) optgroup option {\n    padding-inline-start: 20px;\n  }\n  ::file-selector-button {\n    margin-inline-end: 4px;\n  }\n  ::placeholder {\n    opacity: 1;\n  }\n  @supports (not (-webkit-appearance: -apple-pay-button))  or (contain-intrinsic-size: 1px) {\n    ::placeholder {\n      color: currentcolor;\n      @supports (color: color-mix(in lab, red, red)) {\n        color: color-mix(in oklab, currentcolor 50%, transparent);\n      }\n    }\n  }\n  textarea {\n    resize: vertical;\n  }\n  ::-webkit-search-decoration {\n    -webkit-appearance: none;\n  }\n  ::-webkit-date-and-time-value {\n    min-height: 1lh;\n    text-align: inherit;\n  }\n  ::-webkit-datetime-edit {\n    display: inline-flex;\n  }\n  ::-webkit-datetime-edit-fields-wrapper {\n    padding: 0;\n  }\n  ::-webkit-datetime-edit, ::-webkit-datetime-edit-year-field, ::-webkit-datetime-edit-month-field, ::-webkit-datetime-edit-day-field, ::-webkit-datetime-edit-hour-field, ::-webkit-datetime-edit-minute-field, ::-webkit-datetime-edit-second-field, ::-webkit-datetime-edit-millisecond-field, ::-webkit-datetime-edit-meridiem-field {\n    padding-block: 0;\n  }\n  :-moz-ui-invalid {\n    box-shadow: none;\n  }\n  button, input:where([type='button'], [type='reset'], [type='submit']), ::file-selector-button {\n    appearance: button;\n  }\n  ::-webkit-inner-spin-button, ::-webkit-outer-spin-button {\n    height: auto;\n  }\n  [hidden]:where(:not([hidden='until-found'])) {\n    display: none !important;\n  }\n}\n@layer utilities {\n  .container {\n    width: 100%;\n    @media (width >= 40rem) {\n      max-width: 40rem;\n    }\n    @media (width >= 48rem) {\n      max-width: 48rem;\n    }\n    @media (width >= 64rem) {\n      max-width: 64rem;\n    }\n    @media (width >= 80rem) {\n      max-width: 80rem;\n    }\n    @media (width >= 96rem) {\n      max-width: 96rem;\n    }\n  }\n  .mx-auto {\n    margin-inline: auto;\n  }\n  .mb-6 {\n    margin-bottom: calc(var(--spacing) * 6);\n  }\n  .flex {\n    display: flex;\n  }\n  .inline-block {\n    display: inline-block;\n  }\n  .table {\n    display: table;\n  }\n  .w-full {\n    width: 100%;\n  }\n  .table-auto {\n    table-layout: auto;\n  }\n  .overflow-x-auto {\n    overflow-x: auto;\n  }\n  .rounded {\n    border-radius: 0.25rem;\n  }\n  .rounded-full {\n    border-radius: calc(infinity * 1px);\n  }\n  .bg-gray-50 {\n    background-color: var(--color-gray-50);\n  }\n  .bg-green-500 {\n    background-color: var(--color-green-500);\n  }\n  .bg-indigo-500 {\n    background-color: var(--color-indigo-500);\n  }\n  .bg-purple-50 {\n    background-color: var(--color-purple-50);\n  }\n  .bg-white {\n    background-color: var(--color-white);\n  }\n  .p-4 {\n    padding: calc(var(--spacing) * 4);\n  }\n  .px-2 {\n    padding-inline: calc(var(--spacing) * 2);\n  }\n  .px-4 {\n    padding-inline: calc(var(--spacing) * 4);\n  }\n  .px-6 {\n    padding-inline: calc(var(--spacing) * 6);\n  }\n  .py-1 {\n    padding-block: calc(var(--spacing) * 1);\n  }\n  .py-3 {\n    padding-block: calc(var(--spacing) * 3);\n  }\n  .py-5 {\n    padding-block: calc(var(--spacing) * 5);\n  }\n  .py-8 {\n    padding-block: calc(var(--spacing) * 8);\n  }\n  .pb-3 {\n    padding-bottom: calc(var(--spacing) * 3);\n  }\n  .pl-6 {\n    padding-left: calc(var(--spacing) * 6);\n  }\n  .text-left {\n    text-align: left;\n  }\n  .text-2xl {\n    font-size: var(--text-2xl);\n    line-height: var(--tw-leading, var(--text-2xl--line-height));\n  }\n  .text-xs {\n    font-size: var(--text-xs);\n    line-height: var(--tw-leading, var(--text-xs--line-height));\n  }\n  .font-bold {\n    --tw-font-weight: var(--font-weight-bold);\n    font-weight: var(--font-weight-bold);\n  }\n  .font-medium {\n    --tw-font-weight: var(--font-weight-medium);\n    font-weight: var(--font-weight-medium);\n  }\n  .text-gray-500 {\n    color: var(--color-gray-500);\n  }\n  .text-purple-500 {\n    color: var(--color-purple-500);\n  }\n  .text-white {\n    color: var(--color-white);\n  }\n  .shadow {\n    --tw-shadow: 0 1px 3px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 1px 2px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1));\n    box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);\n  }\n  .transition {\n    transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to, opacity, box-shadow, transform, translate, scale, rotate, filter, -webkit-backdrop-filter, backdrop-filter, display, visibility, content-visibility, overlay, pointer-events;\n    transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));\n    transition-duration: var(--tw-duration, var(--default-transition-duration));\n  }\n  .duration-200 {\n    --tw-duration: 200ms;\n    transition-duration: 200ms;\n  }\n  .hover\\:bg-indigo-600 {\n    &:hover {\n      @media (hover: hover) {\n        background-color: var(--color-indigo-600);\n      }\n    }\n  }\n  .md\\:w-auto {\n    @media (width >= 48rem) {\n      width: auto;\n    }\n  }\n}\n@property --tw-font-weight {\n  syntax: \"*\";\n  inherits: false;\n}\n@property --tw-shadow {\n  syntax: \"*\";\n  inherits: false;\n  initial-value: 0 0 #0000;\n}\n@property --tw-shadow-color {\n  syntax: \"*\";\n  inherits: false;\n}\n@property --tw-shadow-alpha {\n  syntax: \"<percentage>\";\n  inherits: false;\n  initial-value: 100%;\n}\n@property --tw-inset-shadow {\n  syntax: \"*\";\n  inherits: false;\n  initial-value: 0 0 #0000;\n}\n@property --tw-inset-shadow-color {\n  syntax: \"*\";\n  inherits: false;\n}\n@property --tw-inset-shadow-alpha {\n  syntax: \"<percentage>\";\n  inherits: false;\n  initial-value: 100%;\n}\n@property --tw-ring-color {\n  syntax: \"*\";\n  inherits: false;\n}\n@property --tw-ring-shadow {\n  syntax: \"*\";\n  inherits: false;\n  initial-value: 0 0 #0000;\n}\n@property --tw-inset-ring-color {\n  syntax: \"*\";\n  inherits: false;\n}\n@property --tw-inset-ring-shadow {\n  syntax: \"*\";\n  inherits: false;\n  initial-value: 0 0 #0000;\n}\n@property --tw-ring-inset {\n  syntax: \"*\";\n  inherits: false;\n}\n@property --tw-ring-offset-width {\n  syntax: \"<length>\";\n  inherits: false;\n  initial-value: 0px;\n}\n@property --tw-ring-offset-color {\n  syntax: \"*\";\n  inherits: false;\n  initial-value: #fff;\n}\n@property --tw-ring-offset-shadow {\n  syntax: \"*\";\n  inherits: false;\n  initial-value: 0 0 #0000;\n}\n@property --tw-duration {\n  syntax: \"*\";\n  inherits: false;\n}\n@layer properties {\n  @supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))) {\n    *, ::before, ::after, ::backdrop {\n      --tw-font-weight: initial;\n      --tw-shadow: 0 0 #0000;\n      --tw-shadow-color: initial;\n      --tw-shadow-alpha: 100%;\n      --tw-inset-shadow: 0 0 #0000;\n      --tw-inset-shadow-color: initial;\n      --tw-inset-shadow-alpha: 100%;\n      --tw-ring-color: initial;\n      --tw-ring-shadow: 0 0 #0000;\n      --tw-inset-ring-color: initial;\n      --tw-inset-ring-shadow: 0 0 #0000;\n      --tw-ring-inset: initial;\n      --tw-ring-offset-width: 0px;\n      --tw-ring-offset-color: #fff;\n      --tw-ring-offset-shadow: 0 0 #0000;\n      --tw-duration: initial;\n    }\n  }\n}\n"
  },
  {
    "path": "examples/01-app-demos/bluetooth-scanner/src/main.rs",
    "content": "use dioxus::prelude::*;\n\nfn main() {\n    dioxus::launch(app)\n}\n\nfn app() -> Element {\n    let mut scan = use_action(|| async {\n        use btleplug::api::{Central, Manager as _, Peripheral, ScanFilter};\n\n        let manager = btleplug::platform::Manager::new().await?;\n\n        // get the first bluetooth adapter\n        let adapters = manager.adapters().await?;\n        let central = adapters\n            .into_iter()\n            .next()\n            .context(\"No Bluetooth adapter found\")?;\n\n        // start scanning for devices\n        central.start_scan(ScanFilter::default()).await?;\n        tokio::time::sleep(std::time::Duration::from_secs(2)).await;\n\n        // Return the list of peripherals after scanning\n        let mut devices = vec![];\n        for p in central.peripherals().await? {\n            if let Some(p) = p.properties().await? {\n                devices.push(p);\n            }\n        }\n\n        // Sort them by RSSI (signal strength)\n        devices.sort_by_key(|p| p.rssi.unwrap_or(-100));\n\n        dioxus::Ok(devices)\n    });\n\n    rsx! {\n        Stylesheet { href: asset!(\"/assets/tailwind.css\") }\n        div {\n            div { class: \"py-8 px-6\",\n                div { class: \"container px-4 mx-auto\",\n                    h2 { class: \"text-2xl font-bold\", \"Scan for Bluetooth Devices\" }\n                    button {\n                        class: \"inline-block w-full md:w-auto px-6 py-3 font-medium text-white bg-indigo-500 hover:bg-indigo-600 rounded transition duration-200\",\n                        disabled: scan.pending(),\n                        onclick: move |_| {\n                            scan.call();\n                        },\n                        if scan.pending() { \"Scanning\" } else { \"Scan\" }\n                    }\n                }\n            }\n\n            section { class: \"py-8\",\n                div { class: \"container px-4 mx-auto\",\n                    div { class: \"p-4 mb-6 bg-white shadow rounded overflow-x-auto\",\n                        table { class: \"table-auto w-full\",\n                            thead {\n                                tr { class: \"text-xs text-gray-500 text-left\",\n                                    th { class: \"pl-6 pb-3 font-medium\", \"Strength\" }\n                                    th { class: \"pb-3 font-medium\", \"Network\" }\n                                    th { class: \"pb-3 font-medium\", \"Channel\" }\n                                    th { class: \"pb-3 px-2 font-medium\", \"Security\" }\n                                }\n                            }\n                            match scan.value() {\n                                None if scan.pending() => rsx! { \"Scanning...\" },\n                                None => rsx! { \"Press Scan to start scanning\" },\n                                Some(Err(_err)) => rsx! { \"Failed to scan\" },\n                                Some(Ok(peripherals)) => rsx! {\n                                    tbody {\n                                        for peripheral in peripherals.read().iter().rev() {\n                                            tr { class: \"text-xs bg-gray-50\",\n                                                td { class: \"py-5 px-6 font-medium\", \"{peripheral.rssi.unwrap_or(-100)}\" }\n                                                td { class: \"flex py-3 font-medium\", \"{peripheral.local_name.clone().unwrap_or_default()}\" }\n                                                td { span { class: \"inline-block py-1 px-2 text-white bg-green-500 rounded-full\", \"{peripheral.address}\" } }\n                                                td {  span { class: \"inline-block py-1 px-2 text-purple-500 bg-purple-50 rounded-full\", \"{peripheral.tx_power_level.unwrap_or_default()}\" } }\n                                            }\n                                        }\n                                    }\n                                }\n                            }\n                        }\n                    }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "examples/01-app-demos/bluetooth-scanner/tailwind.css",
    "content": "@import \"tailwindcss\";\n@source \"./src/**/*.{rs,html,css}\";\n"
  },
  {
    "path": "examples/01-app-demos/calculator.rs",
    "content": "//! Calculator\n//!\n//! This example is a simple iOS-style calculator. Instead of wrapping the state in a single struct like the\n//! `calculate_mutable` example, this example uses several closures to manage actions with the state. Most\n//! components will start like this since it's the quickest way to start adding state to your app. The `Signal` type\n//! in Dioxus is `Copy` - meaning you don't need to clone it to use it in a closure.\n//!\n//! Notice how our logic is consolidated into just a few callbacks instead of a single struct. This is a rather organic\n//! way to start building state management in Dioxus, and it's a great way to start.\n\nuse dioxus::events::*;\nuse dioxus::html::input_data::keyboard_types::Key;\nuse dioxus::prelude::*;\n\nconst TITLE: &str = \"Calculator\";\nconst STYLE: Asset = asset!(\"/examples/assets/calculator.css\");\n\nfn main() {\n    dioxus::LaunchBuilder::new()\n        .with_cfg(desktop!({\n            use dioxus::desktop::{Config, LogicalSize, WindowBuilder};\n            Config::new().with_window(\n                WindowBuilder::default()\n                    .with_title(TITLE)\n                    .with_inner_size(LogicalSize::new(300.0, 525.0)),\n            )\n        }))\n        .with_cfg(native!({\n            use dioxus::native::{Config, LogicalSize, WindowAttributes};\n            Config::new().with_window_attributes(\n                WindowAttributes::default()\n                    .with_title(TITLE)\n                    .with_inner_size(LogicalSize::new(300.0, 525.0)),\n            )\n        }))\n        .launch(app);\n}\n\nfn app() -> Element {\n    let mut val = use_signal(|| String::from(\"0\"));\n\n    let mut input_digit = move |num: String| {\n        if val() == \"0\" {\n            val.set(String::new());\n        }\n        val.push_str(num.as_str());\n    };\n\n    let mut input_operator = move |key: &str| val.push_str(key);\n\n    let handle_key_down_event = move |evt: KeyboardEvent| match evt.key() {\n        Key::Backspace => {\n            if !val().is_empty() {\n                val.pop();\n            }\n        }\n        Key::Character(character) => match character.as_str() {\n            \"+\" | \"-\" | \"/\" | \"*\" => input_operator(&character),\n            \"0\" | \"1\" | \"2\" | \"3\" | \"4\" | \"5\" | \"6\" | \"7\" | \"8\" | \"9\" => input_digit(character),\n            _ => {}\n        },\n        _ => {}\n    };\n\n    rsx! {\n        Stylesheet { href: STYLE }\n        div { id: \"wrapper\",\n            div { class: \"app\",\n                div { class: \"calculator\", tabindex: \"0\", onkeydown: handle_key_down_event,\n                    div { class: \"calculator-display\",\n                        if val().is_empty() {\n                            \"0\"\n                        } else {\n                            \"{val}\"\n                        }\n                    }\n                    div { class: \"calculator-keypad\",\n                        div { class: \"input-keys\",\n                            div { class: \"function-keys\",\n                                button {\n                                    class: \"calculator-key key-clear\",\n                                    onclick: move |_| {\n                                        val.set(String::new());\n                                        if !val.cloned().is_empty() {\n                                            val.set(\"0\".into());\n                                        }\n                                    },\n                                    if val.cloned().is_empty() { \"C\" } else { \"AC\" }\n                                }\n                                button {\n                                    class: \"calculator-key key-sign\",\n                                    onclick: move |_| {\n                                        let new_val = calc_val(val.cloned().as_str());\n                                        if new_val > 0.0 {\n                                            val.set(format!(\"-{new_val}\"));\n                                        } else {\n                                            val.set(format!(\"{}\", new_val.abs()));\n                                        }\n                                    },\n                                    \"±\"\n                                }\n                                button {\n                                    class: \"calculator-key key-percent\",\n                                    onclick: move |_| val.set(format!(\"{}\", calc_val(val.cloned().as_str()) / 100.0)),\n                                    \"%\"\n                                }\n                            }\n                            div { class: \"digit-keys\",\n                                button {\n                                    class: \"calculator-key key-0\",\n                                    onclick: move |_| input_digit(0.to_string()),\n                                    \"0\"\n                                }\n                                button {\n                                    class: \"calculator-key key-dot\",\n                                    onclick: move |_| val.push('.'),\n                                    \"●\"\n                                }\n                                for k in 1..10 {\n                                    button {\n                                        class: \"calculator-key {k}\",\n                                        name: \"key-{k}\",\n                                        onclick: move |_| input_digit(k.to_string()),\n                                        \"{k}\"\n                                    }\n                                }\n                            }\n                        }\n                        div { class: \"operator-keys\",\n                            for (key, class) in [(\"/\", \"key-divide\"), (\"*\", \"key-multiply\"), (\"-\", \"key-subtract\"), (\"+\", \"key-add\")] {\n                                button {\n                                    class: \"calculator-key {class}\",\n                                    onclick: move |_| input_operator(key),\n                                    \"{key}\"\n                                }\n                            }\n                            button {\n                                class: \"calculator-key key-equals\",\n                                onclick: move |_| val.set(format!(\"{}\", calc_val(val.cloned().as_str()))),\n                                \"=\"\n                            }\n                        }\n                    }\n                }\n            }\n        }\n    }\n}\n\nfn calc_val(val: &str) -> f64 {\n    if val.is_empty() {\n        return 0.0;\n    }\n\n    let mut temp = String::new();\n    let mut operation = \"+\".to_string();\n\n    let mut start_index = 0;\n    let mut temp_value;\n    let mut fin_index = 0;\n\n    if val.len() > 1 && &val[0..1] == \"-\" {\n        temp_value = String::from(\"-\");\n        fin_index = 1;\n        start_index += 1;\n    } else {\n        temp_value = String::from(\"\");\n    }\n\n    for c in val[fin_index..].chars() {\n        if c == '+' || c == '-' || c == '*' || c == '/' {\n            break;\n        }\n        temp_value.push(c);\n        start_index += 1;\n    }\n\n    let mut result = temp_value.parse::<f64>().unwrap();\n\n    if start_index + 1 >= val.len() {\n        return result;\n    }\n\n    for c in val[start_index..].chars() {\n        if c == '+' || c == '-' || c == '*' || c == '/' {\n            if !temp.is_empty() {\n                match &operation as &str {\n                    \"+\" => result += temp.parse::<f64>().unwrap(),\n                    \"-\" => result -= temp.parse::<f64>().unwrap(),\n                    \"*\" => result *= temp.parse::<f64>().unwrap(),\n                    \"/\" => result /= temp.parse::<f64>().unwrap(),\n                    _ => unreachable!(),\n                };\n            }\n            operation = c.to_string();\n            temp = String::new();\n        } else {\n            temp.push(c);\n        }\n    }\n\n    if !temp.is_empty() {\n        match &operation as &str {\n            \"+\" => result += temp.parse::<f64>().unwrap(),\n            \"-\" => result -= temp.parse::<f64>().unwrap(),\n            \"*\" => result *= temp.parse::<f64>().unwrap(),\n            \"/\" => result /= temp.parse::<f64>().unwrap(),\n            _ => unreachable!(),\n        };\n    }\n\n    result\n}\n"
  },
  {
    "path": "examples/01-app-demos/calculator_mutable.rs",
    "content": "//! This example showcases a simple calculator using an approach to state management where the state is composed of only\n//! a single signal. Since Dioxus implements traditional React diffing, state can be consolidated into a typical Rust struct\n//! with methods that take `&mut self`. For many use cases, this is a simple way to manage complex state without wrapping\n//! everything in a signal.\n//!\n//! Generally, you'll want to split your state into several signals if you have a large application, but for small\n//! applications, or focused components, this is a great way to manage state.\n\nuse dioxus::html::MouseEvent;\nuse dioxus::html::input_data::keyboard_types::Key;\nuse dioxus::prelude::*;\n\nfn main() {\n    dioxus::LaunchBuilder::new()\n        .with_cfg(desktop! {{\n            use dioxus::desktop::{Config, LogicalSize, WindowBuilder};\n            Config::new().with_window(\n                WindowBuilder::new()\n                    .with_title(\"Calculator Demo\")\n                    .with_resizable(false)\n                    .with_inner_size(LogicalSize::new(320.0, 530.0)),\n            )\n        }})\n        .with_cfg(native! {{\n            use dioxus::native::{Config, LogicalSize, WindowAttributes};\n            Config::new().with_window_attributes(\n                WindowAttributes::default()\n                    .with_title(\"Calculator Demo\")\n                    .with_inner_size(LogicalSize::new(300.0, 525.0)),\n            )\n        }})\n        .launch(app);\n}\n\nfn app() -> Element {\n    let mut state = use_signal(Calculator::new);\n\n    rsx! {\n        Stylesheet { href: asset!(\"/examples/assets/calculator.css\") }\n        div { id: \"wrapper\",\n            div { class: \"app\",\n                div {\n                    class: \"calculator\",\n                    onkeypress: move |evt| state.write().handle_keydown(evt),\n                    div { class: \"calculator-display\", {state.read().formatted_display()} }\n                    div { class: \"calculator-keypad\",\n                        div { class: \"input-keys\",\n                            div { class: \"function-keys\",\n                                CalculatorKey {\n                                    name: \"key-clear\",\n                                    onclick: move |_| state.write().clear_display(),\n                                    if state.read().display_value == \"0\" {\n                                        \"C\"\n                                    } else {\n                                        \"AC\"\n                                    }\n                                }\n                                CalculatorKey {\n                                    name: \"key-sign\",\n                                    onclick: move |_| state.write().toggle_sign(),\n                                    \"±\"\n                                }\n                                CalculatorKey {\n                                    name: \"key-percent\",\n                                    onclick: move |_| state.write().toggle_percent(),\n                                    \"%\"\n                                }\n                            }\n                            div { class: \"digit-keys\",\n                                CalculatorKey {\n                                    name: \"key-0\",\n                                    onclick: move |_| state.write().input_digit(0),\n                                    \"0\"\n                                }\n                                CalculatorKey {\n                                    name: \"key-dot\",\n                                    onclick: move |_| state.write().input_dot(),\n                                    \"●\"\n                                }\n                                for k in 1..10 {\n                                    CalculatorKey {\n                                        key: \"{k}\",\n                                        name: \"key-{k}\",\n                                        onclick: move |_| state.write().input_digit(k),\n                                        \"{k}\"\n                                    }\n                                }\n                            }\n                        }\n                        div { class: \"operator-keys\",\n                            CalculatorKey {\n                                name: \"key-divide\",\n                                onclick: move |_| state.write().set_operator(Operator::Div),\n                                \"÷\"\n                            }\n                            CalculatorKey {\n                                name: \"key-multiply\",\n                                onclick: move |_| state.write().set_operator(Operator::Mul),\n                                \"×\"\n                            }\n                            CalculatorKey {\n                                name: \"key-subtract\",\n                                onclick: move |_| state.write().set_operator(Operator::Sub),\n                                \"−\"\n                            }\n                            CalculatorKey {\n                                name: \"key-add\",\n                                onclick: move |_| state.write().set_operator(Operator::Add),\n                                \"+\"\n                            }\n                            CalculatorKey {\n                                name: \"key-equals\",\n                                onclick: move |_| state.write().perform_operation(),\n                                \"=\"\n                            }\n                        }\n                    }\n                }\n            }\n        }\n    }\n}\n\n#[component]\nfn CalculatorKey(name: String, onclick: EventHandler<MouseEvent>, children: Element) -> Element {\n    rsx! {\n        button { class: \"calculator-key {name}\", onclick, {children} }\n    }\n}\n\nstruct Calculator {\n    display_value: String,\n    operator: Option<Operator>,\n    waiting_for_operand: bool,\n    cur_val: f64,\n}\n\n#[derive(Clone)]\nenum Operator {\n    Add,\n    Sub,\n    Mul,\n    Div,\n}\n\nimpl Calculator {\n    fn new() -> Self {\n        Calculator {\n            display_value: \"0\".to_string(),\n            operator: None,\n            waiting_for_operand: false,\n            cur_val: 0.0,\n        }\n    }\n    fn formatted_display(&self) -> String {\n        use separator::Separatable;\n        self.display_value\n            .parse::<f64>()\n            .unwrap()\n            .separated_string()\n    }\n    fn clear_display(&mut self) {\n        self.display_value = \"0\".to_string();\n    }\n    fn input_digit(&mut self, digit: u8) {\n        let content = digit.to_string();\n        if self.waiting_for_operand || self.display_value == \"0\" {\n            self.waiting_for_operand = false;\n            self.display_value = content;\n        } else {\n            self.display_value.push_str(content.as_str());\n        }\n    }\n    fn input_dot(&mut self) {\n        if !self.display_value.contains('.') {\n            self.display_value.push('.');\n        }\n    }\n    fn perform_operation(&mut self) {\n        if let Some(op) = &self.operator {\n            let rhs = self.display_value.parse::<f64>().unwrap();\n            let new_val = match op {\n                Operator::Add => self.cur_val + rhs,\n                Operator::Sub => self.cur_val - rhs,\n                Operator::Mul => self.cur_val * rhs,\n                Operator::Div => self.cur_val / rhs,\n            };\n            self.cur_val = new_val;\n            self.display_value = new_val.to_string();\n            self.operator = None;\n        }\n    }\n    fn toggle_sign(&mut self) {\n        if self.display_value.starts_with('-') {\n            self.display_value = self.display_value.trim_start_matches('-').to_string();\n        } else {\n            self.display_value = format!(\"-{}\", self.display_value);\n        }\n    }\n    fn toggle_percent(&mut self) {\n        self.display_value = (self.display_value.parse::<f64>().unwrap() / 100.0).to_string();\n    }\n    fn backspace(&mut self) {\n        if !self.display_value.as_str().eq(\"0\") {\n            self.display_value.pop();\n        }\n    }\n    fn set_operator(&mut self, operator: Operator) {\n        self.operator = Some(operator);\n        self.cur_val = self.display_value.parse::<f64>().unwrap();\n        self.waiting_for_operand = true;\n    }\n    fn handle_keydown(&mut self, evt: KeyboardEvent) {\n        match evt.key() {\n            Key::Backspace => self.backspace(),\n            Key::Character(c) => match c.as_str() {\n                \"0\" => self.input_digit(0),\n                \"1\" => self.input_digit(1),\n                \"2\" => self.input_digit(2),\n                \"3\" => self.input_digit(3),\n                \"4\" => self.input_digit(4),\n                \"5\" => self.input_digit(5),\n                \"6\" => self.input_digit(6),\n                \"7\" => self.input_digit(7),\n                \"8\" => self.input_digit(8),\n                \"9\" => self.input_digit(9),\n                \"+\" => self.operator = Some(Operator::Add),\n                \"-\" => self.operator = Some(Operator::Sub),\n                \"/\" => self.operator = Some(Operator::Div),\n                \"*\" => self.operator = Some(Operator::Mul),\n                _ => {}\n            },\n\n            _ => {}\n        }\n    }\n}\n"
  },
  {
    "path": "examples/01-app-demos/counters.rs",
    "content": "//! A simple counters example that stores a list of items in a vec and then iterates over them.\n\nuse dioxus::prelude::*;\n\nconst STYLE: Asset = asset!(\"/examples/assets/counter.css\");\n\nfn main() {\n    dioxus::launch(app);\n}\n\nfn app() -> Element {\n    // Store the counters in a signal\n    let mut counters = use_signal(|| vec![0, 0, 0]);\n\n    // Whenever the counters change, sum them up\n    let sum = use_memo(move || counters.read().iter().copied().sum::<i32>());\n\n    rsx! {\n        Stylesheet { href: STYLE }\n\n        div { id: \"controls\",\n            button { onclick: move |_| counters.push(0), \"Add counter\" }\n            button { onclick: move |_| { counters.pop(); }, \"Remove counter\" }\n        }\n\n        h3 { \"Total: {sum}\" }\n\n        // Calling `iter` on a Signal<Vec<>> gives you a GenerationalRef to each entry in the vec\n        // We enumerate to get the idx of each counter, which we use later to modify the vec\n        for (i, counter) in counters.iter().enumerate() {\n            // We need a key to uniquely identify each counter. You really shouldn't be using the index, so we're using\n            // the counter value itself.\n            //\n            // If we used the index, and a counter is removed, dioxus would need to re-write the contents of all following\n            // counters instead of simply removing the one that was removed\n            //\n            // You should use a stable identifier for the key, like a unique id or the value of the counter itself\n            li { key: \"{i}\",\n                button { onclick: move |_| counters.write()[i] -= 1, \"-1\" }\n                input {\n                    r#type: \"number\",\n                    value: \"{counter}\",\n                    oninput: move |e| {\n                        if let Ok(value) = e.parsed() {\n                            counters.write()[i] = value;\n                        }\n                    }\n                }\n                button { onclick: move |_| counters.write()[i] += 1, \"+1\" }\n                button { onclick: move |_| { counters.remove(i); }, \"x\" }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "examples/01-app-demos/crm.rs",
    "content": "//! Tiny CRM - A simple CRM app using the Router component and global signals\n//!\n//! This shows how to use the `Router` component to manage different views in your app. It also shows how to use global\n//! signals to manage state across the entire app.\n//!\n//! We could simply pass the state as a prop to each component, but this is a good example of how to use global state\n//! in a way that works across pages.\n//!\n//! We implement a number of important details here too, like focusing inputs, handling form submits, navigating the router,\n//! platform-specific configuration, and importing 3rd party CSS libraries.\n\nuse dioxus::prelude::*;\n\nfn main() {\n    dioxus::LaunchBuilder::new()\n        .with_cfg(desktop!({\n            use dioxus::desktop::{LogicalSize, WindowBuilder};\n            dioxus::desktop::Config::default()\n                .with_window(WindowBuilder::new().with_inner_size(LogicalSize::new(800, 600)))\n        }))\n        .launch(|| {\n            rsx! {\n                Stylesheet {\n                    href: \"https://unpkg.com/purecss@2.0.6/build/pure-min.css\",\n                    integrity: \"sha384-Uu6IeWbM+gzNVXJcM9XV3SohHtmWE+3VGi496jvgX1jyvDTXfdK+rfZc8C1Aehk5\",\n                    crossorigin: \"anonymous\",\n                }\n                Stylesheet { href: asset!(\"/examples/assets/crm.css\") }\n                h1 { \"Dioxus CRM Example\" }\n                Router::<Route> {}\n            }\n        });\n}\n\n/// We only have one list of clients for the whole app, so we can use a global signal.\nstatic CLIENTS: GlobalSignal<Vec<Client>> = Signal::global(Vec::new);\n\nstruct Client {\n    first_name: String,\n    last_name: String,\n    description: String,\n}\n\n/// The pages of the app, each with a route\n#[derive(Routable, Clone)]\nenum Route {\n    #[route(\"/\")]\n    List,\n\n    #[route(\"/new\")]\n    New,\n\n    #[route(\"/settings\")]\n    Settings,\n}\n\n#[component]\nfn List() -> Element {\n    rsx! {\n        h2 { \"List of Clients\" }\n        Link { to: Route::New, class: \"pure-button pure-button-primary\", \"Add Client\" }\n        Link { to: Route::Settings, class: \"pure-button\", \"Settings\" }\n        for client in CLIENTS.read().iter() {\n            div { class: \"client\", style: \"margin-bottom: 50px\",\n                p { \"Name: {client.first_name} {client.last_name}\" }\n                p { \"Description: {client.description}\" }\n            }\n        }\n    }\n}\n\n#[component]\nfn New() -> Element {\n    let mut first_name = use_signal(String::new);\n    let mut last_name = use_signal(String::new);\n    let mut description = use_signal(String::new);\n\n    let submit_client = move |_| {\n        // Write the client\n        CLIENTS.write().push(Client {\n            first_name: first_name(),\n            last_name: last_name(),\n            description: description(),\n        });\n\n        // And then navigate back to the client list\n        router().push(Route::List);\n    };\n\n    rsx! {\n        h2 { \"Add new Client\" }\n        form { class: \"pure-form pure-form-aligned\", onsubmit: submit_client,\n            fieldset {\n                div { class: \"pure-control-group\",\n                    label { r#for: \"first_name\", \"First Name\" }\n                    input {\n                        id: \"first_name\",\n                        r#type: \"text\",\n                        placeholder: \"First Name…\",\n                        required: true,\n                        value: \"{first_name}\",\n                        oninput: move |e| first_name.set(e.value()),\n\n                        // when the form mounts, focus the first name input\n                        onmounted: move |e| async move {\n                            _ = e.set_focus(true).await;\n                        },\n                    }\n                }\n\n                div { class: \"pure-control-group\",\n                    label { r#for: \"last_name\", \"Last Name\" }\n                    input {\n                        id: \"last_name\",\n                        r#type: \"text\",\n                        placeholder: \"Last Name…\",\n                        required: true,\n                        value: \"{last_name}\",\n                        oninput: move |e| last_name.set(e.value()),\n                    }\n                }\n\n                div { class: \"pure-control-group\",\n                    label { r#for: \"description\", \"Description\" }\n                    textarea {\n                        id: \"description\",\n                        placeholder: \"Description…\",\n                        value: \"{description}\",\n                        oninput: move |e| description.set(e.value()),\n                    }\n                }\n\n                div { class: \"pure-controls\",\n                    button {\n                        r#type: \"submit\",\n                        class: \"pure-button pure-button-primary\",\n                        \"Save\"\n                    }\n                    Link {\n                        to: Route::List,\n                        class: \"pure-button pure-button-primary red\",\n                        \"Cancel\"\n                    }\n                }\n            }\n        }\n    }\n}\n\n#[component]\nfn Settings() -> Element {\n    rsx! {\n        h2 { \"Settings\" }\n        button {\n            class: \"pure-button pure-button-primary red\",\n            onclick: move |_| {\n                CLIENTS.write().clear();\n                dioxus::router::router().push(Route::List);\n            },\n            \"Remove all Clients\"\n        }\n        Link { to: Route::List, class: \"pure-button\", \"Go back\" }\n    }\n}\n"
  },
  {
    "path": "examples/01-app-demos/dog_app.rs",
    "content": "//! This example demonstrates a simple app that fetches a list of dog breeds and displays a random dog.\n//!\n//! This app combines `use_loader` and `use_action` to fetch data from the Dog API.\n//! - `use_loader` automatically fetches the list of dog breeds when the component mounts.\n//! - `use_action` fetches a random dog image whenever the `.dispatch` method is called.\n\nuse dioxus::prelude::*;\nuse serde::{Deserialize, Serialize};\nuse std::collections::HashMap;\n\nfn main() {\n    dioxus::launch(app);\n}\n\nfn app() -> Element {\n    // Fetch the list of breeds from the Dog API, using the `?` syntax to suspend or throw errors\n    let breed_list = use_loader(move || async move {\n        #[derive(Deserialize, Serialize, Debug, PartialEq, Clone)]\n        struct ListBreeds {\n            message: HashMap<String, Vec<String>>,\n        }\n\n        reqwest::get(\"https://dog.ceo/api/breeds/list/all\")\n            .await?\n            .json::<ListBreeds>()\n            .await\n    })?;\n\n    // Whenever this action is called, it will re-run the future and return the result.\n    let mut breed = use_action(move |breed| async move {\n        #[derive(Deserialize, Serialize, Debug, PartialEq)]\n        struct DogApi {\n            message: String,\n        }\n\n        reqwest::get(format!(\"https://dog.ceo/api/breed/{breed}/images/random\"))\n            .await\n            .unwrap()\n            .json::<DogApi>()\n            .await\n    });\n\n    rsx! {\n        h1 { \"Doggo selector\" }\n        div { width: \"400px\",\n            for cur_breed in breed_list.read().message.keys().take(20).cloned() {\n                button {\n                    onclick: move |_| {\n                        breed.call(cur_breed.clone());\n                    },\n                    \"{cur_breed}\"\n                }\n            }\n        }\n        div {\n            match breed.value() {\n                None => rsx! { div { \"Click the button to fetch a dog!\" } },\n                Some(Err(_e)) => rsx! { div { \"Failed to fetch a dog, please try again.\" } },\n                Some(Ok(res)) => rsx! {\n                    img {\n                        max_width: \"500px\",\n                        max_height: \"500px\",\n                        src: \"{res.read().message}\"\n                    }\n                },\n            }\n        }\n\n    }\n}\n"
  },
  {
    "path": "examples/01-app-demos/ecommerce-site/.gitignore",
    "content": "/target\n"
  },
  {
    "path": "examples/01-app-demos/ecommerce-site/Cargo.toml",
    "content": "[package]\nname = \"ecommerce-site\"\nversion = \"0.1.1\"\nedition = \"2021\"\npublish = false\n# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html\n\n[dependencies]\ndioxus = { workspace = true, features = [\"fullstack\", \"router\"] }\nreqwest = { workspace = true, features = [\"json\"] }\nserde = { workspace = true }\n\n[target.'cfg(target_family = \"wasm\")'.dependencies]\nchrono = { workspace = true, features = [\"serde\", \"wasmbind\"] }\n\n[target.'cfg(not(target_family = \"wasm\"))'.dependencies]\nchrono = { workspace = true, features = [\"serde\"] }\n\n[features]\nweb = [\"dioxus/web\"]\nserver = [\"dioxus/server\"]\n"
  },
  {
    "path": "examples/01-app-demos/ecommerce-site/README.md",
    "content": "# Dioxus Example: An e-commerce site using the FakeStoreAPI\n\nThis example app is a fullstack web application leveraging the FakeStoreAPI and [Tailwind CSS](https://tailwindcss.com/).\n\n![Demo Image](demo.png)\n\n# Development\n\n1. Run the following commands to serve the application:\n\n```bash\ndx serve\n```\n\nNote that in Dioxus 0.7, the Tailwind watcher is initialized automatically if a `tailwind.css` file is find in your app's root.\n\n# Status\n\nThis is a work in progress. The following features are currently implemented:\n\n- [x] A homepage with a list of products dynamically fetched from the FakeStoreAPI (rendered using SSR)\n- [x] A product detail page with details about a product (rendered using LiveView)\n- [ ] A cart page\n- [ ] A checkout page\n- [ ] A login page\n"
  },
  {
    "path": "examples/01-app-demos/ecommerce-site/public/loading.css",
    "content": "@keyframes spin {\n    0% {\n        transform: rotate(0deg);\n    }\n\n    100% {\n        transform: rotate(360deg);\n    }\n}\n.spinner {\n    width: 10px;\n    height: 10px;\n    border: 4px solid #f3f3f3;\n    border-top: 4px solid #3498db;\n    border-radius: 50%;\n    animation: spin 2s linear infinite;\n}"
  },
  {
    "path": "examples/01-app-demos/ecommerce-site/public/tailwind.css",
    "content": "/*! tailwindcss v4.1.0 | MIT License | https://tailwindcss.com */\n@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))) {\n  @layer base {\n    *, ::before, ::after, ::backdrop {\n      --tw-translate-x: 0;\n      --tw-translate-y: 0;\n      --tw-translate-z: 0;\n      --tw-rotate-x: rotateX(0);\n      --tw-rotate-y: rotateY(0);\n      --tw-rotate-z: rotateZ(0);\n      --tw-skew-x: skewX(0);\n      --tw-skew-y: skewY(0);\n      --tw-border-style: solid;\n      --tw-font-weight: initial;\n      --tw-shadow: 0 0 #0000;\n      --tw-shadow-color: initial;\n      --tw-shadow-alpha: 100%;\n      --tw-inset-shadow: 0 0 #0000;\n      --tw-inset-shadow-color: initial;\n      --tw-inset-shadow-alpha: 100%;\n      --tw-ring-color: initial;\n      --tw-ring-shadow: 0 0 #0000;\n      --tw-inset-ring-color: initial;\n      --tw-inset-ring-shadow: 0 0 #0000;\n      --tw-ring-inset: initial;\n      --tw-ring-offset-width: 0px;\n      --tw-ring-offset-color: #fff;\n      --tw-ring-offset-shadow: 0 0 #0000;\n      --tw-duration: initial;\n    }\n  }\n}\n@layer theme, base, components, utilities;\n@layer theme {\n  :root, :host {\n    --font-sans: ui-sans-serif, system-ui, sans-serif, \"Apple Color Emoji\",\n      \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\";\n    --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\",\n      \"Courier New\", monospace;\n    --color-orange-300: oklch(83.7% 0.128 66.29);\n    --color-orange-400: oklch(75% 0.183 55.934);\n    --color-blue-300: oklch(80.9% 0.105 251.813);\n    --color-gray-50: oklch(98.5% 0.002 247.839);\n    --color-gray-100: oklch(96.7% 0.003 264.542);\n    --color-gray-200: oklch(92.8% 0.006 264.531);\n    --color-gray-400: oklch(70.7% 0.022 261.325);\n    --color-gray-500: oklch(55.1% 0.027 264.364);\n    --color-gray-600: oklch(44.6% 0.03 256.802);\n    --color-gray-700: oklch(37.3% 0.034 259.733);\n    --color-gray-800: oklch(27.8% 0.033 256.848);\n    --color-white: #fff;\n    --spacing: 0.25rem;\n    --container-sm: 24rem;\n    --container-md: 28rem;\n    --container-xl: 36rem;\n    --container-2xl: 42rem;\n    --text-xs: 0.75rem;\n    --text-xs--line-height: calc(1 / 0.75);\n    --text-2xl: 1.5rem;\n    --text-2xl--line-height: calc(2 / 1.5);\n    --text-3xl: 1.875rem;\n    --text-3xl--line-height: calc(2.25 / 1.875);\n    --text-5xl: 3rem;\n    --text-5xl--line-height: 1;\n    --text-6xl: 3.75rem;\n    --text-6xl--line-height: 1;\n    --font-weight-semibold: 600;\n    --font-weight-bold: 700;\n    --radius-md: 0.375rem;\n    --radius-lg: 0.5rem;\n    --default-transition-duration: 150ms;\n    --default-transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);\n    --default-font-family: var(--font-sans);\n    --default-mono-font-family: var(--font-mono);\n  }\n}\n@layer base {\n  *, ::after, ::before, ::backdrop, ::file-selector-button {\n    box-sizing: border-box;\n    margin: 0;\n    padding: 0;\n    border: 0 solid;\n  }\n  html, :host {\n    line-height: 1.5;\n    -webkit-text-size-adjust: 100%;\n    tab-size: 4;\n    font-family: var(--default-font-family, ui-sans-serif, system-ui, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\");\n    font-feature-settings: var(--default-font-feature-settings, normal);\n    font-variation-settings: var(--default-font-variation-settings, normal);\n    -webkit-tap-highlight-color: transparent;\n  }\n  hr {\n    height: 0;\n    color: inherit;\n    border-top-width: 1px;\n  }\n  abbr:where([title]) {\n    -webkit-text-decoration: underline dotted;\n    text-decoration: underline dotted;\n  }\n  h1, h2, h3, h4, h5, h6 {\n    font-size: inherit;\n    font-weight: inherit;\n  }\n  a {\n    color: inherit;\n    -webkit-text-decoration: inherit;\n    text-decoration: inherit;\n  }\n  b, strong {\n    font-weight: bolder;\n  }\n  code, kbd, samp, pre {\n    font-family: var(--default-mono-font-family, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace);\n    font-feature-settings: var(--default-mono-font-feature-settings, normal);\n    font-variation-settings: var(--default-mono-font-variation-settings, normal);\n    font-size: 1em;\n  }\n  small {\n    font-size: 80%;\n  }\n  sub, sup {\n    font-size: 75%;\n    line-height: 0;\n    position: relative;\n    vertical-align: baseline;\n  }\n  sub {\n    bottom: -0.25em;\n  }\n  sup {\n    top: -0.5em;\n  }\n  table {\n    text-indent: 0;\n    border-color: inherit;\n    border-collapse: collapse;\n  }\n  :-moz-focusring {\n    outline: auto;\n  }\n  progress {\n    vertical-align: baseline;\n  }\n  summary {\n    display: list-item;\n  }\n  ol, ul, menu {\n    list-style: none;\n  }\n  img, svg, video, canvas, audio, iframe, embed, object {\n    display: block;\n    vertical-align: middle;\n  }\n  img, video {\n    max-width: 100%;\n    height: auto;\n  }\n  button, input, select, optgroup, textarea, ::file-selector-button {\n    font: inherit;\n    font-feature-settings: inherit;\n    font-variation-settings: inherit;\n    letter-spacing: inherit;\n    color: inherit;\n    border-radius: 0;\n    background-color: transparent;\n    opacity: 1;\n  }\n  :where(select:is([multiple], [size])) optgroup {\n    font-weight: bolder;\n  }\n  :where(select:is([multiple], [size])) optgroup option {\n    padding-inline-start: 20px;\n  }\n  ::file-selector-button {\n    margin-inline-end: 4px;\n  }\n  ::placeholder {\n    opacity: 1;\n  }\n  @supports (not (-webkit-appearance: -apple-pay-button))  or (contain-intrinsic-size: 1px) {\n    ::placeholder {\n      color: color-mix(in oklab, currentColor 50%, transparent);\n    }\n  }\n  textarea {\n    resize: vertical;\n  }\n  ::-webkit-search-decoration {\n    -webkit-appearance: none;\n  }\n  ::-webkit-date-and-time-value {\n    min-height: 1lh;\n    text-align: inherit;\n  }\n  ::-webkit-datetime-edit {\n    display: inline-flex;\n  }\n  ::-webkit-datetime-edit-fields-wrapper {\n    padding: 0;\n  }\n  ::-webkit-datetime-edit, ::-webkit-datetime-edit-year-field, ::-webkit-datetime-edit-month-field, ::-webkit-datetime-edit-day-field, ::-webkit-datetime-edit-hour-field, ::-webkit-datetime-edit-minute-field, ::-webkit-datetime-edit-second-field, ::-webkit-datetime-edit-millisecond-field, ::-webkit-datetime-edit-meridiem-field {\n    padding-block: 0;\n  }\n  :-moz-ui-invalid {\n    box-shadow: none;\n  }\n  button, input:where([type=\"button\"], [type=\"reset\"], [type=\"submit\"]), ::file-selector-button {\n    appearance: button;\n  }\n  ::-webkit-inner-spin-button, ::-webkit-outer-spin-button {\n    height: auto;\n  }\n  [hidden]:where(:not([hidden=\"until-found\"])) {\n    display: none !important;\n  }\n}\n@layer utilities {\n  .absolute {\n    position: absolute;\n  }\n  .fixed {\n    position: fixed;\n  }\n  .relative {\n    position: relative;\n  }\n  .inset-0 {\n    inset: calc(var(--spacing) * 0);\n  }\n  .top-0 {\n    top: calc(var(--spacing) * 0);\n  }\n  .top-1\\/2 {\n    top: calc(1/2 * 100%);\n  }\n  .right-0 {\n    right: calc(var(--spacing) * 0);\n  }\n  .bottom-0 {\n    bottom: calc(var(--spacing) * 0);\n  }\n  .left-0 {\n    left: calc(var(--spacing) * 0);\n  }\n  .z-50 {\n    z-index: 50;\n  }\n  .container {\n    width: 100%;\n    @media (width >= 40rem) {\n      max-width: 40rem;\n    }\n    @media (width >= 48rem) {\n      max-width: 48rem;\n    }\n    @media (width >= 64rem) {\n      max-width: 64rem;\n    }\n    @media (width >= 80rem) {\n      max-width: 80rem;\n    }\n    @media (width >= 96rem) {\n      max-width: 96rem;\n    }\n  }\n  .m-0 {\n    margin: calc(var(--spacing) * 0);\n  }\n  .m-2 {\n    margin: calc(var(--spacing) * 2);\n  }\n  .-mx-4 {\n    margin-inline: calc(var(--spacing) * -4);\n  }\n  .mx-auto {\n    margin-inline: auto;\n  }\n  .mt-2 {\n    margin-top: calc(var(--spacing) * 2);\n  }\n  .mr-1 {\n    margin-right: calc(var(--spacing) * 1);\n  }\n  .mr-2 {\n    margin-right: calc(var(--spacing) * 2);\n  }\n  .mr-3 {\n    margin-right: calc(var(--spacing) * 3);\n  }\n  .mr-6 {\n    margin-right: calc(var(--spacing) * 6);\n  }\n  .mr-8 {\n    margin-right: calc(var(--spacing) * 8);\n  }\n  .mr-10 {\n    margin-right: calc(var(--spacing) * 10);\n  }\n  .mr-12 {\n    margin-right: calc(var(--spacing) * 12);\n  }\n  .mr-14 {\n    margin-right: calc(var(--spacing) * 14);\n  }\n  .mr-16 {\n    margin-right: calc(var(--spacing) * 16);\n  }\n  .mr-auto {\n    margin-right: auto;\n  }\n  .mb-4 {\n    margin-bottom: calc(var(--spacing) * 4);\n  }\n  .mb-6 {\n    margin-bottom: calc(var(--spacing) * 6);\n  }\n  .mb-8 {\n    margin-bottom: calc(var(--spacing) * 8);\n  }\n  .mb-10 {\n    margin-bottom: calc(var(--spacing) * 10);\n  }\n  .mb-12 {\n    margin-bottom: calc(var(--spacing) * 12);\n  }\n  .mb-14 {\n    margin-bottom: calc(var(--spacing) * 14);\n  }\n  .mb-16 {\n    margin-bottom: calc(var(--spacing) * 16);\n  }\n  .mb-24 {\n    margin-bottom: calc(var(--spacing) * 24);\n  }\n  .ml-8 {\n    margin-left: calc(var(--spacing) * 8);\n  }\n  .block {\n    display: block;\n  }\n  .flex {\n    display: flex;\n  }\n  .hidden {\n    display: none;\n  }\n  .inline-block {\n    display: inline-block;\n  }\n  .inline-flex {\n    display: inline-flex;\n  }\n  .h-2 {\n    height: calc(var(--spacing) * 2);\n  }\n  .h-6 {\n    height: calc(var(--spacing) * 6);\n  }\n  .h-8 {\n    height: calc(var(--spacing) * 8);\n  }\n  .h-9 {\n    height: calc(var(--spacing) * 9);\n  }\n  .h-40 {\n    height: calc(var(--spacing) * 40);\n  }\n  .h-full {\n    height: 100%;\n  }\n  .w-1\\/2 {\n    width: calc(1/2 * 100%);\n  }\n  .w-1\\/4 {\n    width: calc(1/4 * 100%);\n  }\n  .w-1\\/6 {\n    width: calc(1/6 * 100%);\n  }\n  .w-2 {\n    width: calc(var(--spacing) * 2);\n  }\n  .w-5\\/6 {\n    width: calc(5/6 * 100%);\n  }\n  .w-6 {\n    width: calc(var(--spacing) * 6);\n  }\n  .w-8 {\n    width: calc(var(--spacing) * 8);\n  }\n  .w-12 {\n    width: calc(var(--spacing) * 12);\n  }\n  .w-full {\n    width: 100%;\n  }\n  .max-w-2xl {\n    max-width: var(--container-2xl);\n  }\n  .max-w-md {\n    max-width: var(--container-md);\n  }\n  .max-w-sm {\n    max-width: var(--container-sm);\n  }\n  .max-w-xl {\n    max-width: var(--container-xl);\n  }\n  .shrink-0 {\n    flex-shrink: 0;\n  }\n  .translate-1\\/2 {\n    --tw-translate-x: calc(1/2 * 100%);\n    --tw-translate-y: calc(1/2 * 100%);\n    translate: var(--tw-translate-x) var(--tw-translate-y);\n  }\n  .transform {\n    transform: var(--tw-rotate-x) var(--tw-rotate-y) var(--tw-rotate-z) var(--tw-skew-x) var(--tw-skew-y);\n  }\n  .cursor-pointer {\n    cursor: pointer;\n  }\n  .flex-col {\n    flex-direction: column;\n  }\n  .flex-row {\n    flex-direction: row;\n  }\n  .flex-wrap {\n    flex-wrap: wrap;\n  }\n  .place-items-center {\n    place-items: center;\n  }\n  .items-center {\n    align-items: center;\n  }\n  .justify-between {\n    justify-content: space-between;\n  }\n  .self-center {\n    align-self: center;\n  }\n  .overflow-y-auto {\n    overflow-y: auto;\n  }\n  .rounded {\n    border-radius: 0.25rem;\n  }\n  .rounded-full {\n    border-radius: calc(infinity * 1px);\n  }\n  .rounded-lg {\n    border-radius: var(--radius-lg);\n  }\n  .rounded-md {\n    border-radius: var(--radius-md);\n  }\n  .border {\n    border-style: var(--tw-border-style);\n    border-width: 1px;\n  }\n  .border-0 {\n    border-style: var(--tw-border-style);\n    border-width: 0px;\n  }\n  .border-r {\n    border-right-style: var(--tw-border-style);\n    border-right-width: 1px;\n  }\n  .border-b {\n    border-bottom-style: var(--tw-border-style);\n    border-bottom-width: 1px;\n  }\n  .border-b-2 {\n    border-bottom-style: var(--tw-border-style);\n    border-bottom-width: 2px;\n  }\n  .border-l {\n    border-left-style: var(--tw-border-style);\n    border-left-width: 1px;\n  }\n  .border-gray-200 {\n    border-color: var(--color-gray-200);\n  }\n  .border-transparent {\n    border-color: transparent;\n  }\n  .bg-gray-50 {\n    background-color: var(--color-gray-50);\n  }\n  .bg-gray-100 {\n    background-color: var(--color-gray-100);\n  }\n  .bg-gray-800 {\n    background-color: var(--color-gray-800);\n  }\n  .bg-orange-300 {\n    background-color: var(--color-orange-300);\n  }\n  .bg-white {\n    background-color: var(--color-white);\n  }\n  .object-cover {\n    object-fit: cover;\n  }\n  .object-scale-down {\n    object-fit: scale-down;\n  }\n  .p-2 {\n    padding: calc(var(--spacing) * 2);\n  }\n  .p-10 {\n    padding: calc(var(--spacing) * 10);\n  }\n  .px-2 {\n    padding-inline: calc(var(--spacing) * 2);\n  }\n  .px-4 {\n    padding-inline: calc(var(--spacing) * 4);\n  }\n  .px-6 {\n    padding-inline: calc(var(--spacing) * 6);\n  }\n  .px-8 {\n    padding-inline: calc(var(--spacing) * 8);\n  }\n  .px-10 {\n    padding-inline: calc(var(--spacing) * 10);\n  }\n  .px-12 {\n    padding-inline: calc(var(--spacing) * 12);\n  }\n  .py-2 {\n    padding-block: calc(var(--spacing) * 2);\n  }\n  .py-4 {\n    padding-block: calc(var(--spacing) * 4);\n  }\n  .py-5 {\n    padding-block: calc(var(--spacing) * 5);\n  }\n  .py-6 {\n    padding-block: calc(var(--spacing) * 6);\n  }\n  .py-8 {\n    padding-block: calc(var(--spacing) * 8);\n  }\n  .py-20 {\n    padding-block: calc(var(--spacing) * 20);\n  }\n  .pr-10 {\n    padding-right: calc(var(--spacing) * 10);\n  }\n  .pb-10 {\n    padding-bottom: calc(var(--spacing) * 10);\n  }\n  .pl-4 {\n    padding-left: calc(var(--spacing) * 4);\n  }\n  .pl-6 {\n    padding-left: calc(var(--spacing) * 6);\n  }\n  .text-center {\n    text-align: center;\n  }\n  .text-left {\n    text-align: left;\n  }\n  .text-2xl {\n    font-size: var(--text-2xl);\n    line-height: var(--tw-leading, var(--text-2xl--line-height));\n  }\n  .text-3xl {\n    font-size: var(--text-3xl);\n    line-height: var(--tw-leading, var(--text-3xl--line-height));\n  }\n  .text-5xl {\n    font-size: var(--text-5xl);\n    line-height: var(--tw-leading, var(--text-5xl--line-height));\n  }\n  .text-xs {\n    font-size: var(--text-xs);\n    line-height: var(--tw-leading, var(--text-xs--line-height));\n  }\n  .font-bold {\n    --tw-font-weight: var(--font-weight-bold);\n    font-weight: var(--font-weight-bold);\n  }\n  .font-semibold {\n    --tw-font-weight: var(--font-weight-semibold);\n    font-weight: var(--font-weight-semibold);\n  }\n  .text-ellipsis {\n    text-overflow: ellipsis;\n  }\n  .text-blue-300 {\n    color: var(--color-blue-300);\n  }\n  .text-gray-400 {\n    color: var(--color-gray-400);\n  }\n  .text-gray-500 {\n    color: var(--color-gray-500);\n  }\n  .text-gray-600 {\n    color: var(--color-gray-600);\n  }\n  .text-white {\n    color: var(--color-white);\n  }\n  .uppercase {\n    text-transform: uppercase;\n  }\n  .placeholder-gray-400 {\n    &::placeholder {\n      color: var(--color-gray-400);\n    }\n  }\n  .opacity-25 {\n    opacity: 25%;\n  }\n  .shadow-2xl {\n    --tw-shadow: 0 25px 50px -12px var(--tw-shadow-color, rgb(0 0 0 / 0.25));\n    box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);\n  }\n  .shadow-lg {\n    --tw-shadow: 0 10px 15px -3px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 4px 6px -4px var(--tw-shadow-color, rgb(0 0 0 / 0.1));\n    box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);\n  }\n  .ring-1 {\n    --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentColor);\n    box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);\n  }\n  .transition {\n    transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to, opacity, box-shadow, transform, translate, scale, rotate, filter, -webkit-backdrop-filter, backdrop-filter;\n    transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));\n    transition-duration: var(--tw-duration, var(--default-transition-duration));\n  }\n  .transition-all {\n    transition-property: all;\n    transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));\n    transition-duration: var(--tw-duration, var(--default-transition-duration));\n  }\n  .duration-200 {\n    --tw-duration: 200ms;\n    transition-duration: 200ms;\n  }\n  .hover\\:bg-orange-400 {\n    &:hover {\n      @media (hover: hover) {\n        background-color: var(--color-orange-400);\n      }\n    }\n  }\n  .hover\\:text-gray-600 {\n    &:hover {\n      @media (hover: hover) {\n        color: var(--color-gray-600);\n      }\n    }\n  }\n  .hover\\:text-gray-700 {\n    &:hover {\n      @media (hover: hover) {\n        color: var(--color-gray-700);\n      }\n    }\n  }\n  .hover\\:shadow-2xl {\n    &:hover {\n      @media (hover: hover) {\n        --tw-shadow: 0 25px 50px -12px var(--tw-shadow-color, rgb(0 0 0 / 0.25));\n        box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);\n      }\n    }\n  }\n  .hover\\:ring-4 {\n    &:hover {\n      @media (hover: hover) {\n        --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(4px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentColor);\n        box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);\n      }\n    }\n  }\n  .focus\\:border-blue-300 {\n    &:focus {\n      border-color: var(--color-blue-300);\n    }\n  }\n  .focus\\:ring-blue-300 {\n    &:focus {\n      --tw-ring-color: var(--color-blue-300);\n    }\n  }\n  .focus\\:ring-transparent {\n    &:focus {\n      --tw-ring-color: transparent;\n    }\n  }\n  .focus\\:outline-hidden {\n    &:focus {\n      --tw-outline-style: none;\n      outline-style: none;\n      @media (forced-colors: active) {\n        outline: 2px solid transparent;\n        outline-offset: 2px;\n      }\n    }\n  }\n  .md\\:mb-0 {\n    @media (width >= 48rem) {\n      margin-bottom: calc(var(--spacing) * 0);\n    }\n  }\n  .md\\:w-1\\/2 {\n    @media (width >= 48rem) {\n      width: calc(1/2 * 100%);\n    }\n  }\n  .md\\:w-auto {\n    @media (width >= 48rem) {\n      width: auto;\n    }\n  }\n  .md\\:text-right {\n    @media (width >= 48rem) {\n      text-align: right;\n    }\n  }\n  .md\\:text-6xl {\n    @media (width >= 48rem) {\n      font-size: var(--text-6xl);\n      line-height: var(--tw-leading, var(--text-6xl--line-height));\n    }\n  }\n  .lg\\:pl-20 {\n    @media (width >= 64rem) {\n      padding-left: calc(var(--spacing) * 20);\n    }\n  }\n  .xl\\:mx-auto {\n    @media (width >= 80rem) {\n      margin-inline: auto;\n    }\n  }\n  .xl\\:mb-0 {\n    @media (width >= 80rem) {\n      margin-bottom: calc(var(--spacing) * 0);\n    }\n  }\n  .xl\\:block {\n    @media (width >= 80rem) {\n      display: block;\n    }\n  }\n  .xl\\:flex {\n    @media (width >= 80rem) {\n      display: flex;\n    }\n  }\n  .xl\\:hidden {\n    @media (width >= 80rem) {\n      display: none;\n    }\n  }\n  .xl\\:inline-block {\n    @media (width >= 80rem) {\n      display: inline-block;\n    }\n  }\n  .xl\\:w-2\\/3 {\n    @media (width >= 80rem) {\n      width: calc(2/3 * 100%);\n    }\n  }\n}\n@property --tw-translate-x {\n  syntax: \"*\";\n  inherits: false;\n  initial-value: 0;\n}\n@property --tw-translate-y {\n  syntax: \"*\";\n  inherits: false;\n  initial-value: 0;\n}\n@property --tw-translate-z {\n  syntax: \"*\";\n  inherits: false;\n  initial-value: 0;\n}\n@property --tw-rotate-x {\n  syntax: \"*\";\n  inherits: false;\n  initial-value: rotateX(0);\n}\n@property --tw-rotate-y {\n  syntax: \"*\";\n  inherits: false;\n  initial-value: rotateY(0);\n}\n@property --tw-rotate-z {\n  syntax: \"*\";\n  inherits: false;\n  initial-value: rotateZ(0);\n}\n@property --tw-skew-x {\n  syntax: \"*\";\n  inherits: false;\n  initial-value: skewX(0);\n}\n@property --tw-skew-y {\n  syntax: \"*\";\n  inherits: false;\n  initial-value: skewY(0);\n}\n@property --tw-border-style {\n  syntax: \"*\";\n  inherits: false;\n  initial-value: solid;\n}\n@property --tw-font-weight {\n  syntax: \"*\";\n  inherits: false;\n}\n@property --tw-shadow {\n  syntax: \"*\";\n  inherits: false;\n  initial-value: 0 0 #0000;\n}\n@property --tw-shadow-color {\n  syntax: \"*\";\n  inherits: false;\n}\n@property --tw-shadow-alpha {\n  syntax: \"<percentage>\";\n  inherits: false;\n  initial-value: 100%;\n}\n@property --tw-inset-shadow {\n  syntax: \"*\";\n  inherits: false;\n  initial-value: 0 0 #0000;\n}\n@property --tw-inset-shadow-color {\n  syntax: \"*\";\n  inherits: false;\n}\n@property --tw-inset-shadow-alpha {\n  syntax: \"<percentage>\";\n  inherits: false;\n  initial-value: 100%;\n}\n@property --tw-ring-color {\n  syntax: \"*\";\n  inherits: false;\n}\n@property --tw-ring-shadow {\n  syntax: \"*\";\n  inherits: false;\n  initial-value: 0 0 #0000;\n}\n@property --tw-inset-ring-color {\n  syntax: \"*\";\n  inherits: false;\n}\n@property --tw-inset-ring-shadow {\n  syntax: \"*\";\n  inherits: false;\n  initial-value: 0 0 #0000;\n}\n@property --tw-ring-inset {\n  syntax: \"*\";\n  inherits: false;\n}\n@property --tw-ring-offset-width {\n  syntax: \"<length>\";\n  inherits: false;\n  initial-value: 0px;\n}\n@property --tw-ring-offset-color {\n  syntax: \"*\";\n  inherits: false;\n  initial-value: #fff;\n}\n@property --tw-ring-offset-shadow {\n  syntax: \"*\";\n  inherits: false;\n  initial-value: 0 0 #0000;\n}\n@property --tw-duration {\n  syntax: \"*\";\n  inherits: false;\n}\n"
  },
  {
    "path": "examples/01-app-demos/ecommerce-site/src/api.rs",
    "content": "use dioxus::prelude::Result;\nuse serde::{Deserialize, Serialize};\nuse std::fmt::Display;\n\n// Cache up to 100 requests, invalidating them after 60 seconds\npub(crate) async fn fetch_product(product_id: usize) -> Result<Product> {\n    Ok(\n        reqwest::get(format!(\"https://fakestoreapi.com/products/{product_id}\"))\n            .await?\n            .json()\n            .await?,\n    )\n}\n\n// Cache up to 100 requests, invalidating them after 60 seconds\npub(crate) async fn fetch_products(count: usize, sort: Sort) -> Result<Vec<Product>> {\n    Ok(reqwest::get(format!(\n        \"https://fakestoreapi.com/products/?sort={sort}&limit={count}\"\n    ))\n    .await?\n    .json()\n    .await?)\n}\n\n#[derive(Serialize, Deserialize, PartialEq, Clone, Debug, Default)]\npub(crate) struct Product {\n    pub(crate) id: u32,\n    pub(crate) title: String,\n    pub(crate) price: f32,\n    pub(crate) description: String,\n    pub(crate) category: String,\n    pub(crate) image: String,\n    pub(crate) rating: Rating,\n}\n\n#[derive(Serialize, Deserialize, PartialEq, Clone, Debug, Default)]\npub(crate) struct Rating {\n    pub(crate) rate: f32,\n    pub(crate) count: u32,\n}\n\nimpl Display for Rating {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        let rounded = self.rate.round() as usize;\n        for _ in 0..rounded {\n            \"★\".fmt(f)?;\n        }\n        for _ in 0..(5 - rounded) {\n            \"☆\".fmt(f)?;\n        }\n\n        write!(f, \" ({:01}) ({} ratings)\", self.rate, self.count)?;\n\n        Ok(())\n    }\n}\n\n#[allow(unused)]\n#[derive(Clone, Copy, Hash, PartialEq, Eq, PartialOrd)]\npub(crate) enum Sort {\n    Descending,\n    Ascending,\n}\n\nimpl Display for Sort {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        match self {\n            Sort::Descending => write!(f, \"desc\"),\n            Sort::Ascending => write!(f, \"asc\"),\n        }\n    }\n}\n"
  },
  {
    "path": "examples/01-app-demos/ecommerce-site/src/components/error.rs",
    "content": "use dioxus::prelude::*;\n\n#[component]\npub fn error_page() -> Element {\n    rsx! {\n        section { class: \"py-20\",\n            div { class: \"container mx-auto px-4\",\n                div { class: \"flex flex-wrap -mx-4 mb-24 text-center\",\n                    \"An internal error has occurred\"\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "examples/01-app-demos/ecommerce-site/src/components/home.rs",
    "content": "// The homepage is statically rendered, so we don't need to a persistent websocket connection.\n\nuse crate::{\n    api::{fetch_products, Sort},\n    components::nav::Nav,\n    components::product_item::ProductItem,\n};\nuse dioxus::prelude::*;\n\npub(crate) fn Home() -> Element {\n    let products = use_loader(|| fetch_products(10, Sort::Ascending))?;\n\n    rsx! {\n        Nav {}\n        section { class: \"p-10\",\n            for product in products.iter() {\n                ProductItem {\n                    product: product.clone()\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "examples/01-app-demos/ecommerce-site/src/components/loading.rs",
    "content": "use dioxus::prelude::*;\n\n#[component]\npub(crate) fn ChildrenOrLoading(children: Element) -> Element {\n    rsx! {\n        Stylesheet { href: asset!(\"/public/loading.css\") }\n        SuspenseBoundary {\n            fallback: |_| rsx! { div { class: \"spinner\", } },\n            {children}\n        }\n    }\n}\n"
  },
  {
    "path": "examples/01-app-demos/ecommerce-site/src/components/nav.rs",
    "content": "use dioxus::prelude::*;\n\n#[component]\npub fn Nav() -> Element {\n    rsx! {\n        section { class: \"relative\",\n            nav { class: \"flex justify-between border-b\",\n                div { class: \"px-12 py-8 flex w-full items-center\",\n                    a { class: \"hidden xl:block mr-16\",\n                        href: \"/\",\n                        icons::cart_icon {}\n                    }\n                    ul { class: \"hidden xl:flex font-semibold font-heading\",\n                        li { class: \"mr-12\",\n                            a { class: \"hover:text-gray-600\",\n                                href: \"/\",\n                                \"Category\"\n                            }\n                        }\n                        li { class: \"mr-12\",\n                            a { class: \"hover:text-gray-600\",\n                                href: \"/\",\n                                \"Collection\"\n                            }\n                        }\n                        li { class: \"mr-12\",\n                            a { class: \"hover:text-gray-600\",\n                                href: \"/\",\n                                \"Story\"\n                            }\n                        }\n                        li {\n                            a { class: \"hover:text-gray-600\",\n                                href: \"/\",\n                                \"Brand\"\n                            }\n                        }\n                    }\n                    a { class: \"shrink-0 xl:mx-auto text-3xl font-bold font-heading\",\n                        href: \"/\",\n                        img { class: \"h-9\",\n                            width: \"auto\",\n                            alt: \"\",\n                            src: \"https://shuffle.dev/yofte-assets/logos/yofte-logo.svg\",\n                        }\n                    }\n                    div { class: \"hidden xl:inline-block mr-14\",\n                        input { class: \"py-5 px-8 w-full placeholder-gray-400 text-xs uppercase font-semibold font-heading bg-gray-50 border border-gray-200 focus:ring-blue-300 focus:border-blue-300 rounded-md\",\n                            placeholder: \"Search\",\n                            r#type: \"text\",\n                        }\n                    }\n                    div { class: \"hidden xl:flex items-center\",\n                        a { class: \"mr-10 hover:text-gray-600\",\n                            href: \"\",\n                            icons::icon_1 {}\n                        }\n                        a { class: \"flex items-center hover:text-gray-600\",\n                            href: \"/\",\n                            icons::icon_2 {}\n                            span { class: \"inline-block w-6 h-6 text-center bg-gray-50 rounded-full font-semibold font-heading\",\n                                \"3\"\n                            }\n                        }\n                    }\n                }\n                a { class: \"hidden xl:flex items-center px-12 border-l font-semibold font-heading hover:text-gray-600\",\n                    href: \"/\",\n                    icons::icon_3 {}\n                    span {\n                        \"Sign In\"\n                    }\n                }\n                a { class: \"xl:hidden flex mr-6 items-center text-gray-600\",\n                    href: \"/\",\n                    icons::icon_4 {}\n                    span { class: \"inline-block w-6 h-6 text-center bg-gray-50 rounded-full font-semibold font-heading\",\n                        \"3\"\n                    }\n                }\n                a { class: \"navbar-burger self-center mr-12 xl:hidden\",\n                    href: \"/\",\n                    icons::icon_5 {}\n                }\n            }\n            div { class: \"hidden navbar-menu fixed top-0 left-0 bottom-0 w-5/6 max-w-sm z-50\",\n                div { class: \"navbar-backdrop fixed inset-0 bg-gray-800 opacity-25\",\n                }\n                nav { class: \"relative flex flex-col py-6 px-6 w-full h-full bg-white border-r overflow-y-auto\",\n                    div { class: \"flex items-center mb-8\",\n                        a { class: \"mr-auto text-3xl font-bold font-heading\",\n                            href: \"/\",\n                            img { class: \"h-9\",\n                                src: \"https://shuffle.dev/yofte-assets/logos/yofte-logo.svg\",\n                                width: \"auto\",\n                                alt: \"\",\n                            }\n                        }\n                        button { class: \"navbar-close\",\n                            icons::icon_6 {}\n                        }\n                    }\n                    div { class: \"flex mb-8 justify-between\",\n                        a { class: \"inline-flex items-center font-semibold font-heading\",\n                            href: \"/\",\n                            icons::icon_7 {}\n                            span {\n                                \"Sign In\"\n                            }\n                        }\n                        div { class: \"flex items-center\",\n                            a { class: \"mr-10\",\n                                href: \"/\",\n                                icons::icon_8 {}\n                            }\n                            a { class: \"flex items-center\",\n                                href: \"/\",\n                                icons::icon_9 {}\n                                span { class: \"inline-block w-6 h-6 text-center bg-gray-100 rounded-full font-semibold font-heading\",\n                                    \"3\"\n                                }\n                            }\n                        }\n                    }\n                    input { class: \"block mb-10 py-5 px-8 bg-gray-100 rounded-md border-transparent focus:ring-blue-300 focus:border-blue-300 focus:outline-hidden\",\n                        r#type: \"search\",\n                        placeholder: \"Search\",\n                    }\n                    ul { class: \"text-3xl font-bold font-heading\",\n                        li { class: \"mb-8\",\n                            a {\n                                href: \"/\",\n                                \"Category\"\n                            }\n                        }\n                        li { class: \"mb-8\",\n                            a {\n                                href: \"/\",\n                                \"Collection\"\n                            }\n                        }\n                        li { class: \"mb-8\",\n                            a {\n                                href: \"/\",\n                                \"Story\"\n                            }\n                        }\n                        li {\n                            a {\n                                href: \"/\",\n                                \"Brand\"\n                            }\n                        }\n                    }\n                }\n            }\n        }\n    }\n}\n\nmod icons {\n    use super::*;\n\n    pub(super) fn cart_icon() -> Element {\n        rsx! {\n            svg { class: \"mr-3\",\n                fill: \"none\",\n                xmlns: \"http://www.w3.org/2000/svg\",\n                view_box: \"0 0 23 23\",\n                width: \"23\",\n                height: \"23\",\n                path {\n                    stroke_linejoin: \"round\",\n                    d: \"M18.1159 8.72461H2.50427C1.99709 8.72461 1.58594 9.12704 1.58594 9.62346V21.3085C1.58594 21.8049 1.99709 22.2074 2.50427 22.2074H18.1159C18.6231 22.2074 19.0342 21.8049 19.0342 21.3085V9.62346C19.0342 9.12704 18.6231 8.72461 18.1159 8.72461Z\",\n                    stroke: \"currentColor\",\n                    stroke_linecap: \"round\",\n                    stroke_width: \"1.5\",\n                }\n                path {\n                    stroke: \"currentColor\",\n                    stroke_linecap: \"round\",\n                    d: \"M6.34473 6.34469V4.95676C6.34473 3.85246 6.76252 2.79338 7.5062 2.01252C8.24988 1.23165 9.25852 0.792969 10.3102 0.792969C11.362 0.792969 12.3706 1.23165 13.1143 2.01252C13.858 2.79338 14.2758 3.85246 14.2758 4.95676V6.34469\",\n                    stroke_width: \"1.5\",\n                    stroke_linejoin: \"round\",\n                }\n            }\n        }\n    }\n\n    pub(super) fn icon_1() -> Element {\n        rsx! {\n            svg {\n                xmlns: \"http://www.w3.org/2000/svg\",\n                height: \"20\",\n                view_box: \"0 0 23 20\",\n                width: \"23\",\n                fill: \"none\",\n                path {\n                    d: \"M11.4998 19.2061L2.70115 9.92527C1.92859 9.14433 1.41864 8.1374 1.24355 7.04712C1.06847 5.95684 1.23713 4.8385 1.72563 3.85053V3.85053C2.09464 3.10462 2.63366 2.45803 3.29828 1.96406C3.9629 1.47008 4.73408 1.14284 5.5483 1.00931C6.36252 0.875782 7.19647 0.939779 7.98144 1.19603C8.7664 1.45228 9.47991 1.89345 10.0632 2.48319L11.4998 3.93577L12.9364 2.48319C13.5197 1.89345 14.2332 1.45228 15.0182 1.19603C15.8031 0.939779 16.6371 0.875782 17.4513 1.00931C18.2655 1.14284 19.0367 1.47008 19.7013 1.96406C20.3659 2.45803 20.905 3.10462 21.274 3.85053V3.85053C21.7625 4.8385 21.9311 5.95684 21.756 7.04712C21.581 8.1374 21.071 9.14433 20.2984 9.92527L11.4998 19.2061Z\",\n                    stroke: \"currentColor\",\n                    stroke_width: \"1.5\",\n                    stroke_linejoin: \"round\",\n                    stroke_linecap: \"round\",\n                }\n            }\n        }\n    }\n\n    pub(super) fn icon_2() -> Element {\n        rsx! {\n            svg { class: \"mr-3\",\n                fill: \"none\",\n                height: \"31\",\n                xmlns: \"http://www.w3.org/2000/svg\",\n                width: \"32\",\n                view_box: \"0 0 32 31\",\n                path {\n                    stroke_linejoin: \"round\",\n                    stroke_width: \"1.5\",\n                    d: \"M16.0006 16.3154C19.1303 16.3154 21.6673 13.799 21.6673 10.6948C21.6673 7.59064 19.1303 5.07422 16.0006 5.07422C12.871 5.07422 10.334 7.59064 10.334 10.6948C10.334 13.799 12.871 16.3154 16.0006 16.3154Z\",\n                    stroke_linecap: \"round\",\n                    stroke: \"currentColor\",\n                }\n                path {\n                    stroke_width: \"1.5\",\n                    d: \"M24.4225 23.8963C23.6678 22.3507 22.4756 21.0445 20.9845 20.1298C19.4934 19.2151 17.7647 18.7295 15.9998 18.7295C14.2349 18.7295 12.5063 19.2151 11.0152 20.1298C9.52406 21.0445 8.33179 22.3507 7.57715 23.8963\",\n                    stroke: \"currentColor\",\n                    stroke_linecap: \"round\",\n                    stroke_linejoin: \"round\",\n                }\n            }\n        }\n    }\n\n    pub(super) fn icon_3() -> Element {\n        rsx! {\n            svg { class: \"h-2 w-2 text-gray-500 cursor-pointer\",\n                height: \"10\",\n                width: \"10\",\n                xmlns: \"http://www.w3.org/2000/svg\",\n                fill: \"none\",\n                view_box: \"0 0 10 10\",\n                path {\n                    stroke_width: \"1.5\",\n                    stroke_linejoin: \"round\",\n                    d: \"M9.00002 1L1 9.00002M1.00003 1L9.00005 9.00002\",\n                    stroke: \"black\",\n                    stroke_linecap: \"round\",\n                }\n            }\n        }\n    }\n\n    pub(super) fn icon_4() -> Element {\n        rsx! {\n            svg {\n                view_box: \"0 0 20 12\",\n                fill: \"none\",\n                width: \"20\",\n                xmlns: \"http://www.w3.org/2000/svg\",\n                height: \"12\",\n                path {\n                    d: \"M1 2H19C19.2652 2 19.5196 1.89464 19.7071 1.70711C19.8946 1.51957 20 1.26522 20 1C20 0.734784 19.8946 0.48043 19.7071 0.292893C19.5196 0.105357 19.2652 0 19 0H1C0.734784 0 0.48043 0.105357 0.292893 0.292893C0.105357 0.48043 0 0.734784 0 1C0 1.26522 0.105357 1.51957 0.292893 1.70711C0.48043 1.89464 0.734784 2 1 2ZM19 10H1C0.734784 10 0.48043 10.1054 0.292893 10.2929C0.105357 10.4804 0 10.7348 0 11C0 11.2652 0.105357 11.5196 0.292893 11.7071C0.48043 11.8946 0.734784 12 1 12H19C19.2652 12 19.5196 11.8946 19.7071 11.7071C19.8946 11.5196 20 11.2652 20 11C20 10.7348 19.8946 10.4804 19.7071 10.2929C19.5196 10.1054 19.2652 10 19 10ZM19 5H1C0.734784 5 0.48043 5.10536 0.292893 5.29289C0.105357 5.48043 0 5.73478 0 6C0 6.26522 0.105357 6.51957 0.292893 6.70711C0.48043 6.89464 0.734784 7 1 7H19C19.2652 7 19.5196 6.89464 19.7071 6.70711C19.8946 6.51957 20 6.26522 20 6C20 5.73478 19.8946 5.48043 19.7071 5.29289C19.5196 5.10536 19.2652 5 19 5Z\",\n                    fill: \"#8594A5\",\n                }\n            }\n        }\n    }\n\n    pub(super) fn icon_5() -> Element {\n        rsx! {\n            svg { class: \"mr-2\",\n                fill: \"none\",\n                xmlns: \"http://www.w3.org/2000/svg\",\n                width: \"23\",\n                height: \"23\",\n                view_box: \"0 0 23 23\",\n                path {\n                    stroke_width: \"1.5\",\n                    stroke_linecap: \"round\",\n                    stroke_linejoin: \"round\",\n                    d: \"M18.1159 8.72461H2.50427C1.99709 8.72461 1.58594 9.12704 1.58594 9.62346V21.3085C1.58594 21.8049 1.99709 22.2074 2.50427 22.2074H18.1159C18.6231 22.2074 19.0342 21.8049 19.0342 21.3085V9.62346C19.0342 9.12704 18.6231 8.72461 18.1159 8.72461Z\",\n                    stroke: \"currentColor\",\n                }\n                path {\n                    d: \"M6.34473 6.34469V4.95676C6.34473 3.85246 6.76252 2.79338 7.5062 2.01252C8.24988 1.23165 9.25852 0.792969 10.3102 0.792969C11.362 0.792969 12.3706 1.23165 13.1143 2.01252C13.858 2.79338 14.2758 3.85246 14.2758 4.95676V6.34469\",\n                    stroke_linejoin: \"round\",\n                    stroke_width: \"1.5\",\n                    stroke_linecap: \"round\",\n                    stroke: \"currentColor\",\n                }\n            }\n        }\n    }\n\n    pub(super) fn icon_6() -> Element {\n        rsx! {\n            svg { class: \"mr-3\",\n                height: \"31\",\n                xmlns: \"http://www.w3.org/2000/svg\",\n                view_box: \"0 0 32 31\",\n                width: \"32\",\n                fill: \"none\",\n                path {\n                    stroke: \"currentColor\",\n                    stroke_width: \"1.5\",\n                    d: \"M16.0006 16.3154C19.1303 16.3154 21.6673 13.799 21.6673 10.6948C21.6673 7.59064 19.1303 5.07422 16.0006 5.07422C12.871 5.07422 10.334 7.59064 10.334 10.6948C10.334 13.799 12.871 16.3154 16.0006 16.3154Z\",\n                    stroke_linecap: \"round\",\n                    stroke_linejoin: \"round\",\n                }\n                path {\n                    stroke_linecap: \"round\",\n                    stroke_width: \"1.5\",\n                    stroke: \"currentColor\",\n                    stroke_linejoin: \"round\",\n                    d: \"M24.4225 23.8963C23.6678 22.3507 22.4756 21.0445 20.9845 20.1298C19.4934 19.2151 17.7647 18.7295 15.9998 18.7295C14.2349 18.7295 12.5063 19.2151 11.0152 20.1298C9.52406 21.0445 8.33179 22.3507 7.57715 23.8963\",\n                }\n            }\n        }\n    }\n\n    pub(super) fn icon_7() -> Element {\n        rsx! {\n            svg { class: \"mr-3\",\n                view_box: \"0 0 23 23\",\n                fill: \"none\",\n                height: \"23\",\n                width: \"23\",\n                xmlns: \"http://www.w3.org/2000/svg\",\n                path {\n                    stroke_linecap: \"round\",\n                    stroke: \"currentColor\",\n                    stroke_width: \"1.5\",\n                    stroke_linejoin: \"round\",\n                    d: \"M18.1159 8.72461H2.50427C1.99709 8.72461 1.58594 9.12704 1.58594 9.62346V21.3085C1.58594 21.8049 1.99709 22.2074 2.50427 22.2074H18.1159C18.6231 22.2074 19.0342 21.8049 19.0342 21.3085V9.62346C19.0342 9.12704 18.6231 8.72461 18.1159 8.72461Z\",\n                }\n                path {\n                    d: \"M6.34473 6.34469V4.95676C6.34473 3.85246 6.76252 2.79338 7.5062 2.01252C8.24988 1.23165 9.25852 0.792969 10.3102 0.792969C11.362 0.792969 12.3706 1.23165 13.1143 2.01252C13.858 2.79338 14.2758 3.85246 14.2758 4.95676V6.34469\",\n                    stroke_width: \"1.5\",\n                    stroke_linecap: \"round\",\n                    stroke: \"currentColor\",\n                    stroke_linejoin: \"round\",\n                }\n            }\n        }\n    }\n\n    pub(super) fn icon_8() -> Element {\n        rsx! {\n            svg {\n                height: \"20\",\n                width: \"23\",\n                fill: \"none\",\n                view_box: \"0 0 23 20\",\n                xmlns: \"http://www.w3.org/2000/svg\",\n                path {\n                    d: \"M11.4998 19.2061L2.70115 9.92527C1.92859 9.14433 1.41864 8.1374 1.24355 7.04712C1.06847 5.95684 1.23713 4.8385 1.72563 3.85053V3.85053C2.09464 3.10462 2.63366 2.45803 3.29828 1.96406C3.9629 1.47008 4.73408 1.14284 5.5483 1.00931C6.36252 0.875782 7.19647 0.939779 7.98144 1.19603C8.7664 1.45228 9.47991 1.89345 10.0632 2.48319L11.4998 3.93577L12.9364 2.48319C13.5197 1.89345 14.2332 1.45228 15.0182 1.19603C15.8031 0.939779 16.6371 0.875782 17.4513 1.00931C18.2655 1.14284 19.0367 1.47008 19.7013 1.96406C20.3659 2.45803 20.905 3.10462 21.274 3.85053V3.85053C21.7625 4.8385 21.9311 5.95684 21.756 7.04712C21.581 8.1374 21.071 9.14433 20.2984 9.92527L11.4998 19.2061Z\",\n                    stroke_linejoin: \"round\",\n                    stroke: \"currentColor\",\n                    stroke_width: \"1.5\",\n                    stroke_linecap: \"round\",\n                }\n            }\n        }\n    }\n\n    pub(super) fn icon_9() -> Element {\n        rsx! {\n            svg {\n                view_box: \"0 0 18 18\",\n                xmlns: \"http://www.w3.org/2000/svg\",\n                width: \"18\",\n                height: \"18\",\n                fill: \"none\",\n                path {\n                    fill: \"black\",\n                    d: \"M18 15.4688H0V17.7207H18V15.4688Z\",\n                }\n                path {\n                    fill: \"black\",\n                    d: \"M11.0226 7.87402H0V10.126H11.0226V7.87402Z\",\n                }\n                path {\n                    fill: \"black\",\n                    d: \"M18 0.279297H0V2.53127H18V0.279297Z\",\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "examples/01-app-demos/ecommerce-site/src/components/product_item.rs",
    "content": "use dioxus::prelude::*;\n\nuse crate::api::Product;\n\n#[component]\npub(crate) fn ProductItem(product: Product) -> Element {\n    let Product {\n        id,\n        title,\n        price,\n        category,\n        image,\n        rating,\n        ..\n    } = product;\n\n    rsx! {\n        section { class: \"h-40 p-2 m-2 shadow-lg ring-1 rounded-lg flex flex-row place-items-center hover:ring-4 hover:shadow-2xl transition-all duration-200\",\n            img {\n                class: \"object-scale-down w-1/6 h-full\",\n                src: \"{image}\",\n            }\n            div { class: \"pl-4 text-left text-ellipsis\",\n                a {\n                    href: \"/details/{id}\",\n                    class: \"w-full text-center\",\n                    \"{title}\"\n                }\n                p {\n                    class: \"w-full\",\n                    \"{rating}\"\n                }\n                p {\n                    class: \"w-full\",\n                    \"{category}\"\n                }\n                p {\n                    class: \"w-1/4\",\n                    \"${price}\"\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "examples/01-app-demos/ecommerce-site/src/components/product_page.rs",
    "content": "use std::{fmt::Display, str::FromStr};\n\nuse crate::api::{fetch_product, Product};\nuse dioxus::prelude::*;\n\n#[component]\npub fn ProductPage(product_id: ReadSignal<usize>) -> Element {\n    let mut quantity = use_signal(|| 1);\n    let mut size = use_signal(Size::default);\n    let product = use_loader(move || fetch_product(product_id()))?;\n\n    let Product {\n        title,\n        price,\n        description,\n        category,\n        image,\n        rating,\n        ..\n    } = product();\n\n    rsx! {\n        section { class: \"py-20\",\n            div { class: \"container mx-auto px-4\",\n                div { class: \"flex flex-wrap -mx-4 mb-24\",\n                    div { class: \"w-full md:w-1/2 px-4 mb-8 md:mb-0\",\n                        div { class: \"relative mb-10\",\n                            style: \"height: 564px;\",\n                            a { class: \"absolute top-1/2 left-0 ml-8 transform translate-1/2\",\n                                href: \"#\",\n                                icons::icon_0 {}\n                            }\n                            img { class: \"object-cover w-full h-full\",\n                                alt: \"\",\n                                src: \"{image}\",\n                            }\n                            a { class: \"absolute top-1/2 right-0 mr-8 transform translate-1/2\",\n                                href: \"#\",\n                                icons::icon_1 {}\n                            }\n                        }\n                    }\n                    div { class: \"w-full md:w-1/2 px-4\",\n                        div { class: \"lg:pl-20\",\n                            div { class: \"mb-10 pb-10 border-b\",\n                                h2 { class: \"mt-2 mb-6 max-w-xl text-5xl md:text-6xl font-bold font-heading\",\n                                    \"{title}\"\n                                }\n                                div { class: \"mb-8\",\n                                    \"{rating}\"\n                                }\n                                p { class: \"inline-block mb-8 text-2xl font-bold font-heading text-blue-300\",\n                                    span {\n                                        \"${price}\"\n                                    }\n                                }\n                                p { class: \"max-w-md text-gray-500\",\n                                    \"{description}\"\n                                }\n                            }\n                            div { class: \"flex mb-12\",\n                                div { class: \"mr-6\",\n                                    span { class: \"block mb-4 font-bold font-heading text-gray-400 uppercase\",\n                                        \"QTY\"\n                                    }\n                                    div { class: \"inline-flex items-center px-4 font-semibold font-heading text-gray-500 border border-gray-200 focus:ring-blue-300 focus:border-blue-300 rounded-md\",\n                                        button { class: \"py-2 hover:text-gray-700\",\n                                            onclick: move |_| quantity += 1,\n                                            icons::icon_2 {}\n                                        }\n                                        input { class: \"w-12 m-0 px-2 py-4 text-center md:text-right border-0 focus:ring-transparent focus:outline-hidden rounded-md\",\n                                            placeholder: \"1\",\n                                            r#type: \"number\",\n                                            value: \"{quantity}\",\n                                            oninput: move |evt| if let Ok(as_number) = evt.value().parse() { quantity.set(as_number) },\n                                        }\n                                        button { class: \"py-2 hover:text-gray-700\",\n                                            onclick: move |_| quantity -= 1,\n                                            icons::icon_3 {}\n                                        }\n                                    }\n                                }\n                                div {\n                                    span { class: \"block mb-4 font-bold font-heading text-gray-400 uppercase\",\n                                        \"Size\"\n                                    }\n                                    select { class: \"pl-6 pr-10 py-4 font-semibold font-heading text-gray-500 border border-gray-200 focus:ring-blue-300 focus:border-blue-300 rounded-md\",\n                                        id: \"\",\n                                        name: \"\",\n                                        onchange: move |evt| {\n                                            if let Ok(new_size) = evt.value().parse() {\n                                                size.set(new_size);\n                                            }\n                                        },\n                                        option {\n                                            value: \"1\",\n                                            \"Medium\"\n                                        }\n                                        option {\n                                            value: \"2\",\n                                            \"Small\"\n                                        }\n                                        option {\n                                            value: \"3\",\n                                            \"Large\"\n                                        }\n                                    }\n                                }\n                            }\n                            div { class: \"flex flex-wrap -mx-4 mb-14 items-center\",\n                                div { class: \"w-full xl:w-2/3 px-4 mb-4 xl:mb-0\",\n                                    a { class: \"block bg-orange-300 hover:bg-orange-400 text-center text-white font-bold font-heading py-5 px-8 rounded-md uppercase transition duration-200\",\n                                        href: \"#\",\n                                        \"Add to cart\"\n                                    }\n                                }\n                            }\n                            div { class: \"flex items-center\",\n                                span { class: \"mr-8 text-gray-500 font-bold font-heading uppercase\",\n                                    \"SHARE IT\"\n                                }\n                                a { class: \"mr-1 w-8 h-8\",\n                                    href: \"#\",\n                                    img {\n                                        alt: \"\",\n                                        src: \"https://shuffle.dev/yofte-assets/buttons/facebook-circle.svg\",\n                                    }\n                                }\n                                a { class: \"mr-1 w-8 h-8\",\n                                    href: \"#\",\n                                    img {\n                                        alt: \"\",\n                                        src: \"https://shuffle.dev/yofte-assets/buttons/instagram-circle.svg\",\n                                    }\n                                }\n                                a { class: \"w-8 h-8\",\n                                    href: \"#\",\n                                    img {\n                                        src: \"https://shuffle.dev/yofte-assets/buttons/twitter-circle.svg\",\n                                        alt: \"\",\n                                    }\n                                }\n                            }\n                        }\n                    }\n                }\n                div {\n                    ul { class: \"flex flex-wrap mb-16 border-b-2\",\n                        li { class: \"w-1/2 md:w-auto\",\n                            a { class: \"inline-block py-6 px-10 bg-white text-gray-500 font-bold font-heading shadow-2xl\",\n                                href: \"#\",\n                                \"Description\"\n                            }\n                        }\n                        li { class: \"w-1/2 md:w-auto\",\n                            a { class: \"inline-block py-6 px-10 text-gray-500 font-bold font-heading\",\n                                href: \"#\",\n                                \"Customer reviews\"\n                            }\n                        }\n                        li { class: \"w-1/2 md:w-auto\",\n                            a { class: \"inline-block py-6 px-10 text-gray-500 font-bold font-heading\",\n                                href: \"#\",\n                                \"Shipping &amp; returns\"\n                            }\n                        }\n                        li { class: \"w-1/2 md:w-auto\",\n                            a { class: \"inline-block py-6 px-10 text-gray-500 font-bold font-heading\",\n                                href: \"#\",\n                                \"Brand\"\n                            }\n                        }\n                    }\n                    h3 { class: \"mb-8 text-3xl font-bold font-heading text-blue-300\",\n                        \"{category}\"\n                    }\n                    p { class: \"max-w-2xl text-gray-500\",\n                        \"{description}\"\n                    }\n                }\n            }\n        }\n    }\n}\n\n#[derive(Default)]\nenum Size {\n    Small,\n    #[default]\n    Medium,\n    Large,\n}\n\nimpl Display for Size {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        match self {\n            Size::Small => \"small\".fmt(f),\n            Size::Medium => \"medium\".fmt(f),\n            Size::Large => \"large\".fmt(f),\n        }\n    }\n}\n\nimpl FromStr for Size {\n    type Err = ();\n\n    fn from_str(s: &str) -> Result<Self, Self::Err> {\n        use Size::*;\n        match s.to_lowercase().as_str() {\n            \"small\" => Ok(Small),\n            \"medium\" => Ok(Medium),\n            \"large\" => Ok(Large),\n            _ => Err(()),\n        }\n    }\n}\n\nmod icons {\n    use super::*;\n\n    pub(super) fn icon_0() -> Element {\n        rsx! {\n            svg { class: \"w-6 h-6\",\n                view_box: \"0 0 24 23\",\n                xmlns: \"http://www.w3.org/2000/svg\",\n                height: \"23\",\n                fill: \"none\",\n                width: \"24\",\n                path {\n                    stroke: \"black\",\n                    fill: \"black\",\n                    d: \"M2.01328 18.9877C2.05682 16.7902 2.71436 12.9275 6.3326 9.87096L6.33277 9.87116L6.33979 9.86454L6.3398 9.86452C6.34682 9.85809 8.64847 7.74859 13.4997 7.74859C13.6702 7.74859 13.8443 7.75111 14.0206 7.757L14.0213 7.75702L14.453 7.76978L14.6331 7.77511V7.59486V3.49068L21.5728 10.5736L14.6331 17.6562V13.6558V13.5186L14.4998 13.4859L14.1812 13.4077C14.1807 13.4075 14.1801 13.4074 14.1792 13.4072M2.01328 18.9877L14.1792 13.4072M2.01328 18.9877C7.16281 11.8391 14.012 13.3662 14.1792 13.4072M2.01328 18.9877L14.1792 13.4072M23.125 10.6961L23.245 10.5736L23.125 10.4512L13.7449 0.877527L13.4449 0.571334V1V6.5473C8.22585 6.54663 5.70981 8.81683 5.54923 8.96832C-0.317573 13.927 0.931279 20.8573 0.946581 20.938L0.946636 20.9383L1.15618 22.0329L1.24364 22.4898L1.47901 22.0885L2.041 21.1305L2.04103 21.1305C4.18034 17.4815 6.71668 15.7763 8.8873 15.0074C10.9246 14.2858 12.6517 14.385 13.4449 14.4935V20.1473V20.576L13.7449 20.2698L23.125 10.6961Z\",\n                    stroke_width: \"0.35\",\n                }\n            }\n        }\n    }\n\n    pub(super) fn icon_1() -> Element {\n        rsx! {\n            svg { class: \"w-6 h-6\",\n                height: \"27\",\n                view_box: \"0 0 27 27\",\n                fill: \"none\",\n                width: \"27\",\n                xmlns: \"http://www.w3.org/2000/svg\",\n                path {\n                    d: \"M13.4993 26.2061L4.70067 16.9253C3.9281 16.1443 3.41815 15.1374 3.24307 14.0471C3.06798 12.9568 3.23664 11.8385 3.72514 10.8505V10.8505C4.09415 10.1046 4.63318 9.45803 5.29779 8.96406C5.96241 8.47008 6.73359 8.14284 7.54782 8.00931C8.36204 7.87578 9.19599 7.93978 9.98095 8.19603C10.7659 8.45228 11.4794 8.89345 12.0627 9.48319L13.4993 10.9358L14.9359 9.48319C15.5192 8.89345 16.2327 8.45228 17.0177 8.19603C17.8026 7.93978 18.6366 7.87578 19.4508 8.00931C20.265 8.14284 21.0362 8.47008 21.7008 8.96406C22.3654 9.45803 22.9045 10.1046 23.2735 10.8505V10.8505C23.762 11.8385 23.9306 12.9568 23.7556 14.0471C23.5805 15.1374 23.0705 16.1443 22.298 16.9253L13.4993 26.2061Z\",\n                    stroke: \"black\",\n                    stroke_width: \"1.5\",\n                    stroke_linecap: \"round\",\n                    stroke_linejoin: \"round\",\n                }\n            }\n        }\n    }\n\n    pub(super) fn icon_2() -> Element {\n        rsx! {\n            svg {\n                view_box: \"0 0 12 12\",\n                height: \"12\",\n                width: \"12\",\n                fill: \"none\",\n                xmlns: \"http://www.w3.org/2000/svg\",\n                g {\n                    opacity: \"0.35\",\n                    rect {\n                        height: \"12\",\n                        x: \"5\",\n                        fill: \"currentColor\",\n                        width: \"2\",\n                    }\n                    rect {\n                        fill: \"currentColor\",\n                        width: \"2\",\n                        height: \"12\",\n                        x: \"12\",\n                        y: \"5\",\n                        transform: \"rotate(90 12 5)\",\n                    }\n                }\n            }\n        }\n    }\n\n    pub(super) fn icon_3() -> Element {\n        rsx! {\n            svg {\n                width: \"12\",\n                fill: \"none\",\n                view_box: \"0 0 12 2\",\n                height: \"2\",\n                xmlns: \"http://www.w3.org/2000/svg\",\n                g {\n                    opacity: \"0.35\",\n                    rect {\n                        transform: \"rotate(90 12 0)\",\n                        height: \"12\",\n                        fill: \"currentColor\",\n                        x: \"12\",\n                        width: \"2\",\n                    }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "examples/01-app-demos/ecommerce-site/src/main.rs",
    "content": "#![allow(non_snake_case)]\n\nuse components::home::Home;\nuse components::loading::ChildrenOrLoading;\nuse dioxus::prelude::*;\n\nmod components {\n    pub mod error;\n    pub mod home;\n    pub mod loading;\n    pub mod nav;\n    pub mod product_item;\n    pub mod product_page;\n}\nmod api;\n\nfn main() {\n    dioxus::launch(|| {\n        rsx! {\n            document::Link {\n                rel: \"stylesheet\",\n                href: asset!(\"/public/tailwind.css\")\n            }\n\n            ChildrenOrLoading {\n                Router::<Route> {}\n            }\n        }\n    });\n}\n\n#[derive(Clone, Routable, Debug, PartialEq)]\nenum Route {\n    #[route(\"/\")]\n    Home {},\n\n    #[route(\"/details/:product_id\")]\n    Details { product_id: usize },\n}\n\n#[component]\n/// Render a more sophisticated page with ssr\nfn Details(product_id: usize) -> Element {\n    rsx! {\n        div {\n            components::nav::Nav {}\n            components::product_page::ProductPage {\n                product_id\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "examples/01-app-demos/ecommerce-site/tailwind.css",
    "content": "@import \"tailwindcss\";\n@source \"./src/**/*.{rs,html,css}\";\n"
  },
  {
    "path": "examples/01-app-demos/file-explorer/.gitignore",
    "content": "# Generated by Cargo\n# will have compiled files and executables\n/target/\n/dist/\n/static/\n/.dioxus/\n\n# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries\n# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html\nCargo.lock\n\n# These are backup files generated by rustfmt\n**/*.rs.bk\n"
  },
  {
    "path": "examples/01-app-demos/file-explorer/Cargo.toml",
    "content": "[package]\nname = \"file-explorer\"\nedition = \"2021\"\nversion = \"0.1.0\"\npublish = false\n\n# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html\n\n[dependencies]\ndioxus = { workspace = true }\nopen = { workspace = true }\n\n[features]\ndefault = [\"desktop\"]\ndesktop = [\"dioxus/desktop\"]\nnative = [\"dioxus/native\"]\n"
  },
  {
    "path": "examples/01-app-demos/file-explorer/Dioxus.toml",
    "content": "[application]\n\n# App (Project) Name\nname = \"file-explorer\"\n\n# Dioxus App Default Platform\n# desktop, web\ndefault_platform = \"desktop\"\n\n# `build` & `serve` dist path\nout_dir = \"dist\"\n\n# assets file folder\nasset_dir = \"assets\"\n\n[web.app]\n\n# HTML title tag content\ntitle = \"file-explorer\"\n\n[web.watcher]\n\n# when watcher trigger, regenerate the `index.html`\nreload_html = true\n\n# which files or dirs will be watcher monitoring\nwatch_path = [\"src\", \"assets\"]\n\n# include `assets` in web platform\n[web.resource]\n\n# CSS style file\n\nstyle = []\n\n# Javascript code file\nscript = []\n\n[web.resource.dev]\n\n# Javascript code file\n# serve: [dev-server] only\nscript = []\n"
  },
  {
    "path": "examples/01-app-demos/file-explorer/README.md",
    "content": "# File-explorer with Rust and Dioxus\n\nThis example shows how a Dioxus App can directly leverage system calls and libraries to bridge native functionality with the WebView renderer.\n\n![example](./assets/image.png)\n\n\n## To run this example:\n\n```\ndx serve\n```\n\n"
  },
  {
    "path": "examples/01-app-demos/file-explorer/assets/fileexplorer.css",
    "content": "* {\n    margin: 0;\n    padding: 0;\n    font-family: 'Roboto', sans-serif;\n    user-select: none;\n    transition: .2s all;\n}\n\nbody {\n    padding-top: 77px;\n}\n\n\n/* header {\n  position: fixed;\n  top: 0;\n  left: 0;\n  right: 0;\n  z-index: 10;\n  padding: 20px;\n  background-color: #2196F3;\n  color: white;\n}\nheader h1 {\n  float: left;\n  font-size: 20px;\n  font-weight: 400;\n}\nheader .material-icons {\n  float: right;\n  cursor: pointer;\n}\nheader .icon-menu {\n  float: left;\n  margin-right: 20px;\n} */\n\nmain {\n    padding: 20px 50px;\n}\n\n.folder * {\n    width: 100px;\n}\n\n.folder {\n    float: left;\n    width: 100px;\n    height: 152px;\n    /* //padding: 20px; */\n    margin-right: 50px;\n    margin-bottom: 70px;\n    border-radius: 2px;\n    /* //overflow: hidden; */\n    cursor: pointer;\n}\n\n.folder:hover h1 {\n    display: none;\n}\n\n.folder:hover p.cooltip {\n    opacity: 1;\n    top: 0;\n}\n\n.folder * {\n    text-align: center;\n}\n\n.folder i {\n    margin: 0;\n    font-size: 100px;\n    color: #607D8B;\n}\n\n.folder h1 {\n    position: relative;\n    display: block;\n    top: -37px;\n    font-size: 20px;\n    font-weight: 400;\n}\n\n.folder p.cooltip {\n    position: relative;\n    top: 5px;\n    left: -50%;\n    margin-left: 35px;\n    background: #212121;\n    font-size: 15px;\n    color: white;\n    border-radius: 4px;\n    padding: 10px 20px;\n    padding-right: 30px;\n    width: 100px;\n    opacity: 0;\n}\n\n.folder p.cooltip:before {\n    content: '';\n    position: absolute;\n    display: block;\n    top: -4px;\n    left: 50%;\n    margin-left: -5px;\n    height: 10px;\n    width: 10px;\n    border-radius: 2px;\n    background-color: #212121;\n    transform: rotate(45deg);\n}\n\ndiv.properties {\n    position: fixed;\n    top: 0;\n    right: 0;\n    bottom: 0;\n    z-index: 10;\n    width: 300px;\n    background-color: white;\n}\n\ndiv.properties:before {\n    content: '';\n    position: fixed;\n    top: 0;\n    left: 0;\n    right: 300px;\n    bottom: 0;\n    background-color: #212121;\n    opacity: .5;\n    overflow: hidden;\n}\n\ndiv.properties img {\n    position: relative;\n    top: -1px;\n    left: -1px;\n    width: 110%;\n    height: 200px;\n    filter: blur(2px);\n}\n\ndiv.properties h1 {\n    position: relative;\n    width: 100%;\n    text-align: left;\n    margin-left: 20px;\n    color: white;\n}\n\nheader {\n    position: fixed;\n    top: 0;\n    left: 0;\n    right: 0;\n    padding: 20px;\n    background-color: #2196F3;\n    color: white;\n    display: flex;\n    align-items: center;\n}\n\nheader h1 {\n    font-weight: 400;\n}\n\nheader span {\n    flex: 1;\n}\n\nheader i {\n    margin: 0 10px;\n    cursor: pointer;\n}\n\nheader i:nth-child(1) {\n    margin: 0 20px;\n}"
  },
  {
    "path": "examples/01-app-demos/file-explorer/src/main.rs",
    "content": "//! Example: File Explorer\n//!\n//! This is a fun little desktop application that lets you explore the file system.\n//!\n//! This example is interesting because it's mixing filesystem operations and GUI, which is typically hard for UI to do.\n//! We store the state entirely in a single signal, making the explorer logic fairly easy to reason about.\n\nuse std::env::current_dir;\nuse std::path::PathBuf;\n\nuse dioxus::prelude::*;\n\nfn main() {\n    dioxus::launch(app);\n}\n\nfn app() -> Element {\n    let mut files = use_signal(Files::new);\n\n    rsx! {\n        Stylesheet { href: asset!(\"/assets/fileexplorer.css\") }\n        Stylesheet { href: \"https://fonts.googleapis.com/icon?family=Material+Icons\" }\n        div {\n            header {\n                i { class: \"material-icons icon-menu\", \"menu\" }\n                h1 { \"Files: \" {files.read().current()} }\n                span { }\n                i { class: \"material-icons\", onclick: move |_| files.write().go_up(), \"logout\" }\n            }\n            main {\n                for (dir_id, path) in files.read().path_names.iter().enumerate() {\n                    {\n                        let path_end = path.components().next_back().map(|p|p.as_os_str()).unwrap_or(path.as_os_str()).to_string_lossy();\n                        let path_display = path.display();\n                        let is_file = path.is_file();\n                        rsx! {\n                            div { class: \"folder\", key: \"{path_display}\",\n                                i { class: \"material-icons\",\n                                    onclick: move |_| files.write().enter_dir(dir_id),\n                                    if is_file {\n                                        \"description\"\n                                    } else {\n                                        \"folder\"\n                                    }\n                                    p { class: \"cooltip\", \"0 folders / 0 files\" }\n                                }\n                                h1 { \"{path_end}\" }\n                            }\n                        }\n                    }\n                }\n                if let Some(err) = files.read().err.as_ref() {\n                    div {\n                        code { \"{err}\" }\n                        button { onclick: move |_| files.write().clear_err(), \"x\" }\n                    }\n                }\n            }\n        }\n    }\n}\n\n/// A simple little struct to hold the file explorer state\n///\n/// We don't use any fancy signals or memoization here - Dioxus is so fast that even a file explorer can be done with a\n/// single signal.\nstruct Files {\n    current_path: PathBuf,\n    path_names: Vec<PathBuf>,\n    err: Option<String>,\n}\n\nimpl Files {\n    fn new() -> Self {\n        let mut files = Self {\n            current_path: std::path::absolute(current_dir().unwrap()).unwrap(),\n            path_names: vec![],\n            err: None,\n        };\n\n        files.reload_path_list();\n\n        files\n    }\n\n    fn reload_path_list(&mut self) {\n        let paths = match std::fs::read_dir(&self.current_path) {\n            Ok(e) => e,\n            Err(err) => {\n                let err = format!(\"An error occurred: {err:?}\");\n                self.err = Some(err);\n                return;\n            }\n        };\n        let collected = paths.collect::<Vec<_>>();\n\n        // clear the current state\n        self.clear_err();\n        self.path_names.clear();\n\n        for path in collected {\n            self.path_names.push(path.unwrap().path().to_path_buf());\n        }\n    }\n\n    fn go_up(&mut self) {\n        self.current_path = match self.current_path.parent() {\n            Some(path) => path.to_path_buf(),\n            None => {\n                self.err = Some(\"Cannot go up from the root directory\".to_string());\n                return;\n            }\n        };\n        self.reload_path_list();\n    }\n\n    fn enter_dir(&mut self, dir_id: usize) {\n        let path = &self.path_names[dir_id];\n        if !path.is_dir() {\n            return;\n        }\n        self.current_path.clone_from(path);\n        self.reload_path_list();\n    }\n\n    fn current(&self) -> String {\n        self.current_path.display().to_string()\n    }\n\n    fn clear_err(&mut self) {\n        self.err = None;\n    }\n}\n"
  },
  {
    "path": "examples/01-app-demos/geolocation-native-plugin/Cargo.toml",
    "content": "[package]\nname = \"geolocation-native-plugin\"\nversion = \"0.1.0\"\nauthors = [\"Sabin Regmi <get2sabin@gmail.com>\"]\nedition = \"2021\"\npublish = false\n\n# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html\n\n[dependencies]\ndioxus = { workspace = true, features = [] }\nmanganis = { workspace = true }\nserde = { workspace = true, features = [\"derive\"] }\nserde_json = { workspace = true }\nthiserror = { workspace = true }\n\n[features]\ndefault = [\"mobile\"]\nweb = [\"dioxus/web\"]\ndesktop = [\"dioxus/desktop\"]\nmobile = [\"dioxus/mobile\"]\n"
  },
  {
    "path": "examples/01-app-demos/geolocation-native-plugin/Dioxus.toml",
    "content": "#:schema ../../../packages/cli/schema.json\n\n[bundle]\nidentifier = \"com.dioxuslabs.geolocation\"\npublisher = \"Dioxus Labs\"\n\n[ios]\ndeployment_target = \"16.2\"\nbackground_modes = [\"location\"]\n\n[ios.plist]\nNSSupportsLiveActivities = true\n\n[[ios.widget_extensions]]\nsource = \"src/ios/widget\"\ndisplay_name = \"Location Widget\"\nbundle_id_suffix = \"location-widget\"\ndeployment_target = \"16.2\"\nmodule_name = \"GeolocationPlugin\"\n\n[android]\nmin_sdk = 24\ntarget_sdk = 34\nfeatures = [\"android.hardware.location.gps\"]\n\n[permissions]\nlocation = { precision = \"fine\", description = \"Access your precise location to provide location-based services\" }\n"
  },
  {
    "path": "examples/01-app-demos/geolocation-native-plugin/README.md",
    "content": "# Geolocation demo\n\nA minimal Dioxus application that implements a native plugin.\n\nThe plugin demonstrated here makes it possible to access the user's geolocation. It does a few things:\n\n- Inspect and request location permissions using the native Android/iOS dialogs.\n- Configure one-shot position requests (high-accuracy toggle + maximum cached age).\n- Inspect the last reported coordinates, accuracy, altitude, heading, and speed.\n\nThe example shares the same metadata pipeline as any plugin crate: the native Gradle/Swift\nartifacts are embedded via linker symbols and bundled automatically by `dx`.\n\n## Running the example\n\n```bash\n# Inside the repository root\ndx serve --project examples/01-app-demos/geolocation --platform mobile\n```\n\nFor Android/iOS you’ll need the respective toolchains installed (Android SDK/NDK, Xcode) so the\ngeolocation crate’s `build.rs` can build the native modules. The UI also works on desktop/web,\nbut location calls will return an error because the plugin only supports mobile targets—those\nerrors are shown inline in the demo.\n\n## Things to try\n\n1. Tap **Check permissions** to see the current OS state (granted/denied/prompt).\n2. Tap **Request permissions** to trigger the native dialog from within the app.\n3. Toggle *High accuracy* and set a *Max cached age* before requesting the current position.\n4. Observe the coordinate grid update whenever a new reading arrives, or the error banner if the\n   operation fails (e.g., permissions denied or running on an unsupported platform).\n"
  },
  {
    "path": "examples/01-app-demos/geolocation-native-plugin/assets/main.css",
    "content": "body {\n  background-color: #05060a;\n  color: #f4f4f5;\n  font-family: 'Inter', 'Segoe UI', sans-serif;\n  margin: 0;\n  min-height: 100vh;\n  display: flex;\n  justify-content: center;\n  padding: calc(16px + env(safe-area-inset-top, 0px)) 0 40px;\n}\n\n.app {\n  width: min(960px, 100%);\n  padding: 0 20px;\n  box-sizing: border-box;\n}\n\n.hero {\n  display: flex;\n  gap: 24px;\n  align-items: center;\n  margin-bottom: 32px;\n  flex-wrap: wrap;\n}\n\n.hero img {\n  width: 200px;\n  max-width: 35%;\n  border-radius: 16px;\n  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.4);\n}\n\n.hero__copy h1 {\n  margin: 0 0 8px;\n  font-size: clamp(28px, 6vw, 36px);\n}\n\n.hero__copy p {\n  margin: 0;\n  line-height: 1.5;\n  color: #c8cad7;\n}\n\n.cards {\n  display: grid;\n  grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));\n  gap: 24px;\n  margin-bottom: 16px;\n}\n\n.card {\n  background: linear-gradient(165deg, rgba(17, 20, 32, 0.95), rgba(6, 7, 16, 0.98));\n  border: 1px solid #222534;\n  border-radius: 16px;\n  padding: 24px;\n  box-shadow: 0 25px 45px rgba(0, 0, 0, 0.4);\n}\n\n.card h2 {\n  margin-top: 0;\n  font-size: 1.5rem;\n}\n\n.muted {\n  color: #a5a7b6;\n  font-size: 0.95rem;\n}\n\n.button-row {\n  display: flex;\n  flex-wrap: wrap;\n  gap: 12px;\n  margin-top: 16px;\n}\n\nbutton {\n  border: none;\n  border-radius: 999px;\n  padding: 10px 18px;\n  font-size: 0.95rem;\n  cursor: pointer;\n  transition: background 0.2s ease;\n}\n\nbutton.primary,\nbutton {\n  background: linear-gradient(135deg, #8f63ff, #4d8dff);\n  color: white;\n  box-shadow: 0 10px 25px rgba(77, 141, 255, 0.25);\n}\n\nbutton.secondary {\n  background: transparent;\n  color: #b3b7cf;\n  border: 1px solid #2f3244;\n}\n\nbutton.full-width {\n  width: 100%;\n  margin-top: 16px;\n}\n\nbutton.toggle {\n  width: fit-content;\n  background: #1a1d29;\n  border: 1px solid #2c2f40;\n  color: #d8d9e5;\n}\n\nbutton.toggle--active {\n  background: #23304d;\n  border-color: #4b6cff;\n  color: #ffffff;\n}\n\n.settings {\n  display: flex;\n  flex-direction: column;\n  gap: 12px;\n  margin-top: 16px;\n}\n\n.field {\n  display: flex;\n  flex-direction: column;\n  gap: 6px;\n}\n\n.field input {\n  background: #0b0d13;\n  border: 1px solid #26293a;\n  border-radius: 10px;\n  padding: 10px 12px;\n  color: white;\n}\n\n.status-grid {\n  margin-top: 20px;\n  display: grid;\n  gap: 14px;\n}\n\n.permission-row {\n  display: flex;\n  justify-content: space-between;\n  align-items: center;\n}\n\n.badge {\n  padding: 4px 10px;\n  border-radius: 999px;\n  font-size: 0.85rem;\n  text-transform: uppercase;\n}\n\n.badge--granted {\n  background: rgba(70, 221, 154, 0.15);\n  color: #7efac6;\n  border: 1px solid rgba(70, 221, 154, 0.4);\n}\n\n.badge--denied {\n  background: rgba(255, 98, 98, 0.16);\n  color: #ff8ea0;\n  border: 1px solid rgba(255, 98, 98, 0.4);\n}\n\n.badge--prompt {\n  background: rgba(255, 205, 112, 0.16);\n  color: #ffd27e;\n  border: 1px solid rgba(255, 205, 112, 0.35);\n}\n\n.position {\n  margin-top: 20px;\n}\n\n.position__grid {\n  margin-top: 14px;\n  display: grid;\n  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));\n  gap: 10px;\n}\n\n.coordinate-row {\n  display: flex;\n  flex-direction: column;\n  gap: 2px;\n  padding: 10px;\n  background: #080a11;\n  border-radius: 12px;\n  border: 1px solid #1c1f2b;\n}\n\n.error-banner {\n  margin-top: 24px;\n  padding: 14px 18px;\n  background: #3c1017;\n  border: 1px solid #a44856;\n  border-radius: 12px;\n  color: #ffe6ea;\n}\n\n@media (max-width: 640px) {\n  .hero {\n    flex-direction: column;\n    text-align: center;\n  }\n\n  .hero img {\n    max-width: 60%;\n  }\n\n  .button-row {\n    flex-direction: column;\n  }\n\n  button {\n    width: 100%;\n    text-align: center;\n  }\n}\n"
  },
  {
    "path": "examples/01-app-demos/geolocation-native-plugin/src/android/build.gradle.kts",
    "content": "import org.gradle.api.tasks.bundling.AbstractArchiveTask\n\nplugins {\n    id(\"com.android.library\") version \"8.4.2\"\n    kotlin(\"android\") version \"1.9.24\"\n}\n\nandroid {\n    namespace = \"com.dioxus.geolocation\"\n    compileSdk = 34\n\n    defaultConfig {\n        minSdk = 24\n        targetSdk = 34\n        consumerProguardFiles(\"consumer-rules.pro\")\n    }\n\n    buildTypes {\n        getByName(\"release\") {\n            isMinifyEnabled = false\n        }\n        getByName(\"debug\") {\n            isMinifyEnabled = false\n        }\n    }\n\n    compileOptions {\n        sourceCompatibility = JavaVersion.VERSION_17\n        targetCompatibility = JavaVersion.VERSION_17\n    }\n\n    kotlinOptions {\n        jvmTarget = \"17\"\n    }\n}\n\ndependencies {\n    implementation(\"androidx.core:core-ktx:1.12.0\")\n    implementation(\"com.google.android.gms:play-services-location:21.3.0\")\n}\n\ntasks.withType<AbstractArchiveTask>().configureEach {\n    archiveBaseName.set(\"geolocation-plugin\")\n}\n"
  },
  {
    "path": "examples/01-app-demos/geolocation-native-plugin/src/android/consumer-rules.pro",
    "content": "# Intentionally empty; no consumer Proguard rules required for the geolocation plugin.\n"
  },
  {
    "path": "examples/01-app-demos/geolocation-native-plugin/src/android/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <uses-permission android:name=\"android.permission.ACCESS_COARSE_LOCATION\" />\n    <uses-permission android:name=\"android.permission.ACCESS_FINE_LOCATION\" />\n    <!-- Background location is optional; uncomment if needed by host app -->\n    <!-- <uses-permission android:name=\"android.permission.ACCESS_BACKGROUND_LOCATION\" /> -->\n\n    <application />\n\n</manifest>\n"
  },
  {
    "path": "examples/01-app-demos/geolocation-native-plugin/src/android/src/main/kotlin/com/dioxus/geolocation/Geolocation.kt",
    "content": "// Copyright 2019-2023 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\npackage com.dioxus.geolocation\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.location.Location\nimport android.location.LocationManager\nimport android.os.SystemClock\nimport androidx.core.location.LocationManagerCompat\nimport android.util.Log\nimport com.google.android.gms.common.ConnectionResult\nimport com.google.android.gms.common.GoogleApiAvailability\nimport com.google.android.gms.location.LocationServices\nimport com.google.android.gms.location.Priority\n\nclass Geolocation(private val context: Context) {\n  fun isLocationServicesEnabled(): Boolean {\n    val lm = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager\n    return LocationManagerCompat.isLocationEnabled(lm)\n  }\n\n  @SuppressWarnings(\"MissingPermission\")\n  fun sendLocation(\n    enableHighAccuracy: Boolean,\n    successCallback: (location: Location) -> Unit,\n    errorCallback: (error: String) -> Unit,\n  ) {\n    val resultCode = GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(context)\n    if (resultCode == ConnectionResult.SUCCESS) {\n      val lm = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager\n\n      if (this.isLocationServicesEnabled()) {\n        var networkEnabled = false\n\n                try {\n                    networkEnabled = lm.isProviderEnabled(LocationManager.NETWORK_PROVIDER)\n                } catch (_: Exception) {\n                    Log.e(\"Geolocation\", \"isProviderEnabled failed\")\n                }\n\n        val lowPrio =\n          if (networkEnabled) Priority.PRIORITY_BALANCED_POWER_ACCURACY else Priority.PRIORITY_LOW_POWER\n        val prio = if (enableHighAccuracy) Priority.PRIORITY_HIGH_ACCURACY else lowPrio\n\n                Log.d(\"Geolocation\", \"Using priority $prio\")\n\n        LocationServices\n          .getFusedLocationProviderClient(context)\n          .getCurrentLocation(prio, null)\n          .addOnFailureListener { e -> e.message?.let { errorCallback(it) } }\n          .addOnSuccessListener { location ->\n            if (location == null) {\n              errorCallback(\"Location unavailable.\")\n            } else {\n              successCallback(location)\n            }\n          }\n      } else {\n        errorCallback(\"Location disabled.\")\n      }\n    } else {\n      errorCallback(\"Google Play Services unavailable.\")\n    }\n  }\n\n  @SuppressLint(\"MissingPermission\")\n  fun getLastLocation(maximumAge: Long): Location? {\n    var lastLoc: Location? = null\n    val lm = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager\n\n    for (provider in lm.allProviders) {\n      val tmpLoc = lm.getLastKnownLocation(provider)\n      if (tmpLoc != null) {\n        val locationAge = SystemClock.elapsedRealtimeNanos() - tmpLoc.elapsedRealtimeNanos\n        val maxAgeNano = maximumAge * 1_000_000L\n        if (locationAge <= maxAgeNano && (lastLoc == null || lastLoc.elapsedRealtimeNanos > tmpLoc.elapsedRealtimeNanos)) {\n          lastLoc = tmpLoc\n        }\n      }\n    }\n\n    return lastLoc\n  }\n}\n"
  },
  {
    "path": "examples/01-app-demos/geolocation-native-plugin/src/android/src/main/kotlin/com/dioxus/geolocation/GeolocationPlugin.kt",
    "content": "// Copyright 2019-2023 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\npackage com.dioxus.geolocation\n\nimport android.Manifest\nimport android.app.Activity\nimport android.content.pm.PackageManager\nimport android.location.Location\nimport android.os.Handler\nimport android.os.Looper\nimport android.webkit.WebView\nimport androidx.core.app.ActivityCompat\nimport androidx.core.content.ContextCompat\nimport org.json.JSONObject\nimport java.util.concurrent.CountDownLatch\nimport java.util.concurrent.TimeUnit\nimport java.util.Timer\nimport kotlin.concurrent.schedule\n\nclass GeolocationPlugin(private val activity: Activity) {\n  private val geolocation = Geolocation(activity)\n\n  fun checkPermissions(): Map<String, String> {\n    val response = mutableMapOf<String, String>()\n    val coarseStatus = ContextCompat.checkSelfPermission(activity, Manifest.permission.ACCESS_COARSE_LOCATION)\n    val fineStatus = ContextCompat.checkSelfPermission(activity, Manifest.permission.ACCESS_FINE_LOCATION)\n\n    response[\"location\"] = permissionToStatus(fineStatus)\n    response[\"coarseLocation\"] = permissionToStatus(coarseStatus)\n\n    return response\n  }\n\n  fun requestPermissions(callback: (Map<String, String>) -> Unit) {\n    val permissionsToRequest = mutableListOf<String>()\n\n    if (ContextCompat.checkSelfPermission(activity, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {\n      permissionsToRequest.add(Manifest.permission.ACCESS_FINE_LOCATION)\n    }\n\n    if (ContextCompat.checkSelfPermission(activity, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {\n      permissionsToRequest.add(Manifest.permission.ACCESS_COARSE_LOCATION)\n    }\n\n    if (permissionsToRequest.isEmpty()) {\n      callback(checkPermissions())\n    } else {\n      ActivityCompat.requestPermissions(activity, permissionsToRequest.toTypedArray(), 1001)\n      Handler(Looper.getMainLooper()).postDelayed({ callback(checkPermissions()) }, 1000)\n    }\n  }\n\n  fun getCurrentPosition(\n    enableHighAccuracy: Boolean,\n    timeout: Long,\n    maximumAge: Long,\n    successCallback: (Location) -> Unit,\n    errorCallback: (String) -> Unit,\n  ) {\n    val lastLocation = geolocation.getLastLocation(maximumAge)\n    if (lastLocation != null) {\n      successCallback(lastLocation)\n      return\n    }\n\n    val timer = Timer()\n    timer.schedule(timeout) {\n      activity.runOnUiThread { errorCallback(\"Timeout waiting for location.\") }\n    }\n\n    geolocation.sendLocation(\n      enableHighAccuracy,\n      { location ->\n        timer.cancel()\n        successCallback(location)\n      },\n      { error ->\n        timer.cancel()\n        errorCallback(error)\n      },\n    )\n  }\n\n  private fun permissionToStatus(value: Int): String =\n    when (value) {\n      PackageManager.PERMISSION_GRANTED -> \"granted\"\n      PackageManager.PERMISSION_DENIED -> \"denied\"\n      else -> \"prompt\"\n    }\n\n  // ---- Platform bridge helpers expected by Rust JNI layer ----\n\n  // Called by Rust after constructing the plugin. No-op placeholder to match signature.\n  fun load(webView: WebView?) { /* no-op */ }\n\n  // Serialize current permission status as JSON string\n  fun checkPermissionsJson(): String {\n    val status = checkPermissions()\n    val json = JSONObject()\n    json.put(\"location\", status[\"location\"]) // granted|denied|prompt\n    json.put(\"coarseLocation\", status[\"coarseLocation\"]) // granted|denied|prompt\n    return json.toString()\n  }\n\n  // Request permissions and return resulting status JSON (waits briefly for result)\n  fun requestPermissionsJson(permissionsJson: String?): String {\n    val latch = CountDownLatch(1)\n    var result: String = checkPermissionsJson()\n\n    requestPermissions { status ->\n      val json = JSONObject()\n      json.put(\"location\", status[\"location\"])\n      json.put(\"coarseLocation\", status[\"coarseLocation\"])\n      result = json.toString()\n      latch.countDown()\n    }\n\n    // Wait up to 5 seconds for the permission result, then return whatever we have\n    latch.await(5, TimeUnit.SECONDS)\n    return result\n  }\n\n  // Convert a Location to the Position JSON expected by Rust side\n  private fun locationToPositionJson(location: Location): String {\n    val coords = JSONObject()\n    coords.put(\"latitude\", location.latitude)\n    coords.put(\"longitude\", location.longitude)\n    coords.put(\"accuracy\", location.accuracy.toDouble())\n    if (location.hasAltitude()) coords.put(\"altitude\", location.altitude)\n    if (android.os.Build.VERSION.SDK_INT >= 26) {\n      val vAcc = try { location.verticalAccuracyMeters } catch (_: Exception) { null }\n      if (vAcc != null) coords.put(\"altitudeAccuracy\", vAcc.toDouble())\n    }\n    if (location.hasSpeed()) coords.put(\"speed\", location.speed.toDouble())\n    if (location.hasBearing()) coords.put(\"heading\", location.bearing.toDouble())\n\n    val obj = JSONObject()\n    obj.put(\"timestamp\", System.currentTimeMillis())\n    obj.put(\"coords\", coords)\n    return obj.toString()\n  }\n\n  // Synchronous wrapper returning JSON for getCurrentPosition\n  // Accepts a JSON string with options: {\"enableHighAccuracy\": bool, \"timeout\": number, \"maximumAge\": number}\n  fun getCurrentPositionJson(optionsJson: String?): String {\n    val options = try {\n      if (optionsJson.isNullOrEmpty()) JSONObject() else JSONObject(optionsJson)\n    } catch (e: Exception) {\n      JSONObject()\n    }\n    val enableHighAccuracy = options.optBoolean(\"enableHighAccuracy\", false)\n    val timeout = options.optLong(\"timeout\", 10000L)\n    val maximumAge = options.optLong(\"maximumAge\", 0L)\n\n    var output: String? = null\n    val latch = CountDownLatch(1)\n\n    getCurrentPosition(\n      enableHighAccuracy,\n      timeout,\n      maximumAge,\n      { location ->\n        output = locationToPositionJson(location)\n        latch.countDown()\n      },\n      { error ->\n        output = JSONObject(mapOf(\"error\" to error)).toString()\n        latch.countDown()\n      },\n    )\n\n    // Wait up to the timeout + 2s buffer\n    latch.await(timeout + 2000, TimeUnit.MILLISECONDS)\n    return output ?: JSONObject(mapOf(\"error\" to \"Timeout waiting for location.\")).toString()\n  }\n\n}\n"
  },
  {
    "path": "examples/01-app-demos/geolocation-native-plugin/src/ios/.gitignore",
    "content": ".DS_Store\n/.build\n/Packages\n/*.xcodeproj\nxcuserdata/\nDerivedData/\n.swiftpm/config/registries.json\n.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata\n.netrc\nPackage.resolved\n"
  },
  {
    "path": "examples/01-app-demos/geolocation-native-plugin/src/ios/plugin/Package.swift",
    "content": "// swift-tools-version:5.9\n// Copyright 2019-2023 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nimport PackageDescription\n\nlet package = Package(\n    name: \"GeolocationPlugin\",\n    platforms: [\n        .iOS(.v17),  // iOS 17+ for latest ActivityKit APIs\n        .macOS(.v14),\n    ],\n    products: [\n        .library(\n            name: \"GeolocationPlugin\",\n            type: .static,\n            targets: [\"GeolocationPlugin\"]\n        )\n    ],\n    dependencies: [],\n    targets: [\n        .target(\n            name: \"GeolocationPlugin\",\n            path: \"Sources\",\n            linkerSettings: [\n                .linkedFramework(\"CoreLocation\"),\n                .linkedFramework(\"Foundation\"),\n                .linkedFramework(\"ActivityKit\", .when(platforms: [.iOS])),\n            ]\n        )\n    ]\n)\n"
  },
  {
    "path": "examples/01-app-demos/geolocation-native-plugin/src/ios/plugin/Sources/GeolocationPlugin.swift",
    "content": "// Copyright 2019-2023 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nimport CoreLocation\nimport Foundation\nimport Dispatch\nimport ActivityKit\n\n/**\n * Simplified GeolocationPlugin for Dioxus that works without Tauri dependencies.\n * This can be shared with Tauri plugins with minimal changes.\n */\n@objc(GeolocationPlugin)\npublic class GeolocationPlugin: NSObject, CLLocationManagerDelegate {\n  private let locationManager = CLLocationManager()\n  private var positionCallbacks: [String: (String) -> Void] = [:]\n\n  override init() {\n    super.init()\n    locationManager.delegate = self\n  }\n\n  /**\n   * Get current position as JSON string (called from ObjC/Rust)\n   */\n  @objc public func getCurrentPositionJson(_ optionsJson: String) -> String {\n    // Parse options from JSON\n    guard let optionsData = optionsJson.data(using: .utf8),\n          let optionsDict = try? JSONSerialization.jsonObject(with: optionsData) as? [String: Any] else {\n      let error = [\"error\": \"Invalid options JSON\"]\n      return (try? JSONSerialization.data(withJSONObject: error))?.base64EncodedString() ?? \"\"\n    }\n\n    let enableHighAccuracy = optionsDict[\"enableHighAccuracy\"] as? Bool ?? false\n    let timeoutMs = optionsDict[\"timeout\"] as? Double ?? 10000\n    let maximumAgeMs = optionsDict[\"maximumAge\"] as? Double ?? 0\n\n    // If we have a recent cached location, return it immediately\n    if let lastLocation = self.locationManager.location {\n      let ageMs = Date().timeIntervalSince(lastLocation.timestamp) * 1000\n      if maximumAgeMs <= 0 || ageMs <= maximumAgeMs {\n        return self.convertLocationToJson(lastLocation)\n      }\n    }\n\n    let callbackId = UUID().uuidString\n    let semaphore = DispatchSemaphore(value: 0)\n    var responseJson: String?\n\n    self.positionCallbacks[callbackId] = { result in\n      responseJson = result\n      semaphore.signal()\n    }\n\n    if enableHighAccuracy {\n      self.locationManager.desiredAccuracy = kCLLocationAccuracyBest\n    } else {\n      self.locationManager.desiredAccuracy = kCLLocationAccuracyKilometer\n    }\n\n    if CLLocationManager.authorizationStatus() == .notDetermined {\n      self.locationManager.requestWhenInUseAuthorization()\n    } else {\n      self.locationManager.requestLocation()\n    }\n\n    let timeoutSeconds = max(timeoutMs / 1000.0, 0.1)\n    let deadline = Date().addingTimeInterval(timeoutSeconds)\n    while responseJson == nil && Date() < deadline {\n      let _ = RunLoop.current.run(mode: .default, before: Date().addingTimeInterval(0.05))\n      if semaphore.wait(timeout: .now()) == .success {\n        break\n      }\n    }\n\n    if let json = responseJson {\n      return json\n    } else {\n      // Timed out waiting for location\n      self.positionCallbacks.removeValue(forKey: callbackId)\n      let error = [\"error\": \"Timeout waiting for location\"]\n      return (try? JSONSerialization.data(withJSONObject: error)).flatMap {\n        String(data: $0, encoding: .utf8)\n      } ?? \"{\\\"error\\\":\\\"Timeout waiting for location\\\"}\"\n    }\n  }\n\n  /**\n   * Check permissions and return JSON string (called from ObjC/Rust)\n   */\n  @objc public func checkPermissionsJson() -> String {\n    var status: String = \"\"\n\n    if CLLocationManager.locationServicesEnabled() {\n      switch CLLocationManager.authorizationStatus() {\n      case .notDetermined:\n        status = \"prompt\"\n      case .restricted, .denied:\n        status = \"denied\"\n      case .authorizedAlways, .authorizedWhenInUse:\n        status = \"granted\"\n      @unknown default:\n        status = \"prompt\"\n      }\n    } else {\n      let error = [\"error\": \"Location services are not enabled\"]\n      return (try? JSONSerialization.data(withJSONObject: error))?.base64EncodedString() ?? \"\"\n    }\n\n    let result: [String: String] = [\"location\": status, \"coarseLocation\": status]\n\n    if let jsonData = try? JSONSerialization.data(withJSONObject: result),\n       let jsonString = String(data: jsonData, encoding: .utf8) {\n      return jsonString\n    }\n\n    return \"\"\n  }\n\n  /**\n   * Request permissions and return JSON string (called from ObjC/Rust)\n   */\n  @objc public func requestPermissionsJson(_ permissionsJson: String) -> String {\n    if CLLocationManager.locationServicesEnabled() {\n      if CLLocationManager.authorizationStatus() == .notDetermined {\n        DispatchQueue.main.async {\n          self.locationManager.requestWhenInUseAuthorization()\n        }\n        // Return current status - actual result comes via delegate\n        return self.checkPermissionsJson()\n      } else {\n        return self.checkPermissionsJson()\n      }\n    } else {\n      let error = [\"error\": \"Location services are not enabled\"]\n      if let jsonData = try? JSONSerialization.data(withJSONObject: error),\n         let jsonString = String(data: jsonData, encoding: .utf8) {\n        return jsonString\n      }\n      return \"\"\n    }\n  }\n\n  //\n  // CLLocationManagerDelegate methods\n  //\n\n  public func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {\n    let errorMessage = error.localizedDescription\n\n    // Notify all position callbacks\n    for (_, callback) in self.positionCallbacks {\n      let errorJson = \"{\\\"error\\\":\\\"\\(errorMessage)\\\"}\"\n      callback(errorJson)\n    }\n    self.positionCallbacks.removeAll()\n\n  }\n\n  public func locationManager(\n    _ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]\n  ) {\n    guard let location = locations.last else {\n      return\n    }\n\n    let resultJson = self.convertLocationToJson(location)\n\n    // Notify all position callbacks\n    for (_, callback) in self.positionCallbacks {\n      callback(resultJson)\n    }\n    self.positionCallbacks.removeAll()\n\n  }\n\n  public func locationManager(\n    _ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus\n  ) {\n    if !self.positionCallbacks.isEmpty {\n      self.locationManager.requestLocation()\n    }\n  }\n\n  //\n  // Internal/Helper methods\n  //\n\n  private func convertLocationToJson(_ location: CLLocation) -> String {\n    var ret: [String: Any] = [:]\n    var coords: [String: Any] = [:]\n\n    coords[\"latitude\"] = location.coordinate.latitude\n    coords[\"longitude\"] = location.coordinate.longitude\n    coords[\"accuracy\"] = location.horizontalAccuracy\n    coords[\"altitude\"] = location.altitude\n    coords[\"altitudeAccuracy\"] = location.verticalAccuracy\n    coords[\"speed\"] = location.speed\n    coords[\"heading\"] = location.course\n    ret[\"timestamp\"] = Int((location.timestamp.timeIntervalSince1970 * 1000))\n    ret[\"coords\"] = coords\n\n    if let jsonData = try? JSONSerialization.data(withJSONObject: ret),\n       let jsonString = String(data: jsonData, encoding: .utf8) {\n      return jsonString\n    }\n\n    return \"{\\\"error\\\":\\\"Failed to serialize location\\\"}\"\n  }\n\n  //\n  // Live Activity methods\n  //\n\n  /// Start a Live Activity showing current location\n  /// Returns JSON with activity ID or error\n  @objc public func startLiveActivityJson() -> String {\n    if #available(iOS 16.2, *) {\n      // Check if Live Activities are enabled\n      guard ActivityAuthorizationInfo().areActivitiesEnabled else {\n        return \"{\\\"error\\\":\\\"Live Activities are not enabled\\\"}\"\n      }\n\n      // Get current location\n      guard let location = self.locationManager.location else {\n        return \"{\\\"error\\\":\\\"No location available. Request location first.\\\"}\"\n      }\n\n      let attributes = LocationPermissionAttributes(appName: \"Geolocation Demo\")\n      let contentState = LocationPermissionAttributes.ContentState(\n        latitude: location.coordinate.latitude,\n        longitude: location.coordinate.longitude,\n        accuracy: location.horizontalAccuracy,\n        speed: location.speed >= 0 ? location.speed : nil,\n        heading: location.course >= 0 ? location.course : nil,\n        lastUpdated: Date()\n      )\n\n      do {\n        let activity = try Activity.request(\n          attributes: attributes,\n          content: .init(state: contentState, staleDate: nil),\n          pushType: nil\n        )\n\n        let result: [String: Any] = [\n          \"activityId\": activity.id,\n          \"latitude\": location.coordinate.latitude,\n          \"longitude\": location.coordinate.longitude,\n          \"accuracy\": location.horizontalAccuracy\n        ]\n\n        if let jsonData = try? JSONSerialization.data(withJSONObject: result),\n           let jsonString = String(data: jsonData, encoding: .utf8) {\n          return jsonString\n        }\n        return \"{\\\"error\\\":\\\"Failed to serialize result\\\"}\"\n      } catch {\n        return \"{\\\"error\\\":\\\"Failed to start Live Activity: \\(error.localizedDescription)\\\"}\"\n      }\n    } else {\n      return \"{\\\"error\\\":\\\"Live Activities require iOS 16.2+\\\"}\"\n    }\n  }\n\n  /// Update the Live Activity with current location\n  @objc public func updateLiveActivityJson(_ statusJson: String) -> String {\n    if #available(iOS 16.2, *) {\n      // Get current location\n      guard let location = self.locationManager.location else {\n        return \"{\\\"error\\\":\\\"No location available\\\"}\"\n      }\n\n      let contentState = LocationPermissionAttributes.ContentState(\n        latitude: location.coordinate.latitude,\n        longitude: location.coordinate.longitude,\n        accuracy: location.horizontalAccuracy,\n        speed: location.speed >= 0 ? location.speed : nil,\n        heading: location.course >= 0 ? location.course : nil,\n        lastUpdated: Date()\n      )\n\n      // Update all running activities of this type\n      Task {\n        for activity in Activity<LocationPermissionAttributes>.activities {\n          await activity.update(\n            ActivityContent(state: contentState, staleDate: nil)\n          )\n        }\n      }\n\n      let result: [String: Any] = [\n        \"latitude\": location.coordinate.latitude,\n        \"longitude\": location.coordinate.longitude,\n        \"accuracy\": location.horizontalAccuracy\n      ]\n      if let jsonData = try? JSONSerialization.data(withJSONObject: result),\n         let jsonString = String(data: jsonData, encoding: .utf8) {\n        return jsonString\n      }\n      return \"{\\\"error\\\":\\\"Failed to serialize result\\\"}\"\n    } else {\n      return \"{\\\"error\\\":\\\"Live Activities require iOS 16.2+\\\"}\"\n    }\n  }\n\n  /// End all Live Activities\n  @objc public func endLiveActivityJson() -> String {\n    if #available(iOS 16.2, *) {\n      Task {\n        for activity in Activity<LocationPermissionAttributes>.activities {\n          await activity.end(nil, dismissalPolicy: .immediate)\n        }\n      }\n      return \"{\\\"success\\\":true}\"\n    } else {\n      return \"{\\\"error\\\":\\\"Live Activities require iOS 16.2+\\\"}\"\n    }\n  }\n\n}\n"
  },
  {
    "path": "examples/01-app-demos/geolocation-native-plugin/src/ios/plugin/Sources/LocationActivityAttributes.swift",
    "content": "// Shared ActivityAttributes for Live Activities\n// This MUST be the single source of truth for both the main app and widget extension\n\nimport Foundation\nimport ActivityKit\n\n/// Live Activity attributes for displaying current location.\n///\n/// This struct is shared between the main app (which starts/updates activities)\n/// and the widget extension (which renders them on the lock screen).\npublic struct LocationPermissionAttributes: ActivityAttributes {\n    /// Dynamic content that can be updated while the activity is running\n    public struct ContentState: Codable, Hashable {\n        /// Current latitude\n        public var latitude: Double\n        /// Current longitude\n        public var longitude: Double\n        /// Horizontal accuracy in meters\n        public var accuracy: Double\n        /// Current speed in m/s (nil if not available)\n        public var speed: Double?\n        /// Current heading in degrees (nil if not available)\n        public var heading: Double?\n        /// Last update timestamp\n        public var lastUpdated: Date\n\n        public init(\n            latitude: Double,\n            longitude: Double,\n            accuracy: Double,\n            speed: Double? = nil,\n            heading: Double? = nil,\n            lastUpdated: Date\n        ) {\n            self.latitude = latitude\n            self.longitude = longitude\n            self.accuracy = accuracy\n            self.speed = speed\n            self.heading = heading\n            self.lastUpdated = lastUpdated\n        }\n    }\n\n    /// Static data set when the activity is started (cannot change)\n    public var appName: String\n\n    public init(appName: String) {\n        self.appName = appName\n    }\n}\n"
  },
  {
    "path": "examples/01-app-demos/geolocation-native-plugin/src/ios/plugin/Tests/PluginTests/PluginTests.swift",
    "content": "// Copyright 2019-2023 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nimport XCTest\n@testable import ExamplePlugin\n\nfinal class ExamplePluginTests: XCTestCase {\n    func testExample() throws {\n        let plugin = ExamplePlugin()\n    }\n}\n"
  },
  {
    "path": "examples/01-app-demos/geolocation-native-plugin/src/ios/widget/Package.swift",
    "content": "// swift-tools-version:5.9\n// Widget Extension for displaying location permission status on lock screen\n//\n// IMPORTANT: The target name MUST match the main app's Swift module name.\n// The plugin uses \"GeolocationPlugin\" as its package/target name, so the\n// widget must also use \"GeolocationPlugin\" for ActivityKit type matching.\n\nimport PackageDescription\n\nlet package = Package(\n    name: \"GeolocationPlugin\",\n    platforms: [\n        .iOS(.v17),  // iOS 17+ for latest ActivityKit APIs\n    ],\n    products: [\n        // Executable name must be \"widget\" for the build system to find it\n        // But the TARGET name determines the Swift module name\n        .executable(\n            name: \"widget\",\n            targets: [\"GeolocationPlugin\"]\n        )\n    ],\n    dependencies: [],\n    targets: [\n        // Target name = Swift module name = \"GeolocationPlugin\"\n        // This MUST match the main app's Swift plugin module name!\n        .executableTarget(\n            name: \"GeolocationPlugin\",\n            path: \"Sources\",\n            linkerSettings: [\n                .linkedFramework(\"WidgetKit\"),\n                .linkedFramework(\"SwiftUI\"),\n                .linkedFramework(\"ActivityKit\"),\n            ]\n        )\n    ]\n)\n"
  },
  {
    "path": "examples/01-app-demos/geolocation-native-plugin/src/ios/widget/Sources/LocationActivityAttributes.swift",
    "content": "// Shared ActivityAttributes for Live Activities\n// This MUST be the single source of truth for both the main app and widget extension\n\nimport Foundation\nimport ActivityKit\n\n/// Live Activity attributes for displaying current location.\n///\n/// This struct is shared between the main app (which starts/updates activities)\n/// and the widget extension (which renders them on the lock screen).\npublic struct LocationPermissionAttributes: ActivityAttributes {\n    /// Dynamic content that can be updated while the activity is running\n    public struct ContentState: Codable, Hashable {\n        /// Current latitude\n        public var latitude: Double\n        /// Current longitude\n        public var longitude: Double\n        /// Horizontal accuracy in meters\n        public var accuracy: Double\n        /// Current speed in m/s (nil if not available)\n        public var speed: Double?\n        /// Current heading in degrees (nil if not available)\n        public var heading: Double?\n        /// Last update timestamp\n        public var lastUpdated: Date\n\n        public init(\n            latitude: Double,\n            longitude: Double,\n            accuracy: Double,\n            speed: Double? = nil,\n            heading: Double? = nil,\n            lastUpdated: Date\n        ) {\n            self.latitude = latitude\n            self.longitude = longitude\n            self.accuracy = accuracy\n            self.speed = speed\n            self.heading = heading\n            self.lastUpdated = lastUpdated\n        }\n    }\n\n    /// Static data set when the activity is started (cannot change)\n    public var appName: String\n\n    public init(appName: String) {\n        self.appName = appName\n    }\n}\n"
  },
  {
    "path": "examples/01-app-demos/geolocation-native-plugin/src/ios/widget/Sources/LocationWidget.swift",
    "content": "// Widget Extension for Live Activity only\n// Note: Having multiple widgets in the same bundle can cause Live Activities to show black\n// See: https://developer.apple.com/forums/thread/807726\nimport ActivityKit\nimport SwiftUI\nimport WidgetKit\n\n@main\nstruct LocationWidgetBundle: WidgetBundle {\n    var body: some Widget {\n        // Only include the Live Activity - other widgets can cause rendering issues\n        LocationPermissionLiveActivity()\n    }\n}\n\n// Helper to get accuracy color\nfunc accuracyColor(_ accuracy: Double) -> Color {\n    if accuracy < 10 {\n        return .green\n    } else if accuracy < 50 {\n        return .yellow\n    } else if accuracy < 100 {\n        return .orange\n    } else {\n        return .red\n    }\n}\n\n// Helper to format coordinates with degree symbol\nfunc formatCoord(_ value: Double, isLat: Bool) -> String {\n    let direction = isLat ? (value >= 0 ? \"N\" : \"S\") : (value >= 0 ? \"E\" : \"W\")\n    return String(format: \"%.5f° %@\", abs(value), direction)\n}\n\n// Live Activity widget\nstruct LocationPermissionLiveActivity: Widget {\n    var body: some WidgetConfiguration {\n        ActivityConfiguration(for: LocationPermissionAttributes.self) { context in\n            // Lock screen view - flashy gradient design\n            ZStack {\n                // Gradient background\n                LinearGradient(\n                    colors: [\n                        Color(red: 0.1, green: 0.1, blue: 0.2),\n                        Color(red: 0.05, green: 0.15, blue: 0.25)\n                    ],\n                    startPoint: .topLeading,\n                    endPoint: .bottomTrailing\n                )\n\n                VStack(spacing: 12) {\n                    // Header with pulsing indicator\n                    HStack(spacing: 12) {\n                        // Animated location icon with glow\n                        ZStack {\n                            Circle()\n                                .fill(accuracyColor(context.state.accuracy).opacity(0.3))\n                                .frame(width: 44, height: 44)\n                            Circle()\n                                .fill(accuracyColor(context.state.accuracy).opacity(0.6))\n                                .frame(width: 32, height: 32)\n                            Image(systemName: \"location.fill\")\n                                .font(.system(size: 18, weight: .bold))\n                                .foregroundColor(.white)\n                        }\n\n                        VStack(alignment: .leading, spacing: 2) {\n                            Text(context.attributes.appName)\n                                .font(.headline)\n                                .fontWeight(.bold)\n                                .foregroundColor(.white)\n                            HStack(spacing: 4) {\n                                Image(systemName: \"antenna.radiowaves.left.and.right\")\n                                    .font(.caption2)\n                                Text(\"LIVE\")\n                                    .font(.caption2)\n                                    .fontWeight(.bold)\n                            }\n                            .foregroundColor(accuracyColor(context.state.accuracy))\n                        }\n\n                        Spacer()\n\n                        // Accuracy with animated ring\n                        VStack(spacing: 2) {\n                            Text(\"\\(Int(context.state.accuracy))\")\n                                .font(.system(size: 24, weight: .bold, design: .rounded))\n                                .foregroundColor(accuracyColor(context.state.accuracy))\n                                .contentTransition(.numericText())\n                            Text(\"meters\")\n                                .font(.caption2)\n                                .foregroundColor(.gray)\n                        }\n                        .padding(.horizontal, 12)\n                        .padding(.vertical, 8)\n                        .background(\n                            RoundedRectangle(cornerRadius: 12)\n                                .fill(accuracyColor(context.state.accuracy).opacity(0.15))\n                                .overlay(\n                                    RoundedRectangle(cornerRadius: 12)\n                                        .strokeBorder(accuracyColor(context.state.accuracy).opacity(0.5), lineWidth: 1)\n                                )\n                        )\n                    }\n\n                    // Coordinates in stylish cards\n                    HStack(spacing: 8) {\n                        // Latitude card\n                        VStack(alignment: .leading, spacing: 4) {\n                            HStack(spacing: 4) {\n                                Image(systemName: \"arrow.up.arrow.down\")\n                                    .font(.caption2)\n                                Text(\"LATITUDE\")\n                                    .font(.caption2)\n                                    .fontWeight(.semibold)\n                            }\n                            .foregroundColor(.cyan.opacity(0.8))\n\n                            Text(formatCoord(context.state.latitude, isLat: true))\n                                .font(.system(.callout, design: .monospaced))\n                                .fontWeight(.medium)\n                                .foregroundColor(.white)\n                                .contentTransition(.numericText())\n                        }\n                        .frame(maxWidth: .infinity, alignment: .leading)\n                        .padding(10)\n                        .background(\n                            RoundedRectangle(cornerRadius: 10)\n                                .fill(Color.cyan.opacity(0.1))\n                        )\n\n                        // Longitude card\n                        VStack(alignment: .leading, spacing: 4) {\n                            HStack(spacing: 4) {\n                                Image(systemName: \"arrow.left.arrow.right\")\n                                    .font(.caption2)\n                                Text(\"LONGITUDE\")\n                                    .font(.caption2)\n                                    .fontWeight(.semibold)\n                            }\n                            .foregroundColor(.purple.opacity(0.8))\n\n                            Text(formatCoord(context.state.longitude, isLat: false))\n                                .font(.system(.callout, design: .monospaced))\n                                .fontWeight(.medium)\n                                .foregroundColor(.white)\n                                .contentTransition(.numericText())\n                        }\n                        .frame(maxWidth: .infinity, alignment: .leading)\n                        .padding(10)\n                        .background(\n                            RoundedRectangle(cornerRadius: 10)\n                                .fill(Color.purple.opacity(0.1))\n                        )\n                    }\n\n                    // Speed and heading row (if available)\n                    if let speed = context.state.speed, speed >= 0 {\n                        HStack(spacing: 16) {\n                            // Speed\n                            HStack(spacing: 6) {\n                                Image(systemName: \"speedometer\")\n                                    .foregroundColor(.orange)\n                                Text(String(format: \"%.1f m/s\", speed))\n                                    .font(.system(.caption, design: .monospaced))\n                                    .fontWeight(.medium)\n                                    .foregroundColor(.white)\n                                    .contentTransition(.numericText())\n                            }\n\n                            // Heading if available\n                            if let heading = context.state.heading, heading >= 0 {\n                                HStack(spacing: 6) {\n                                    Image(systemName: \"safari\")\n                                        .foregroundColor(.mint)\n                                        .rotationEffect(.degrees(heading))\n                                    Text(String(format: \"%.0f°\", heading))\n                                        .font(.system(.caption, design: .monospaced))\n                                        .fontWeight(.medium)\n                                        .foregroundColor(.white)\n                                        .contentTransition(.numericText())\n                                }\n                            }\n\n                            Spacer()\n\n                            // Last updated\n                            Text(context.state.lastUpdated, style: .relative)\n                                .font(.caption2)\n                                .foregroundColor(.gray)\n                        }\n                    }\n                }\n                .padding()\n            }\n            .activitySystemActionForegroundColor(.white)\n\n        } dynamicIsland: { context in\n            DynamicIsland {\n                // Expanded regions\n                DynamicIslandExpandedRegion(.leading) {\n                    VStack(alignment: .leading, spacing: 4) {\n                        ZStack {\n                            Circle()\n                                .fill(accuracyColor(context.state.accuracy).opacity(0.3))\n                                .frame(width: 36, height: 36)\n                            Image(systemName: \"location.fill\")\n                                .foregroundColor(accuracyColor(context.state.accuracy))\n                                .font(.system(size: 16, weight: .bold))\n                        }\n                        Text(\"\\(Int(context.state.accuracy))m\")\n                            .font(.caption2)\n                            .fontWeight(.bold)\n                            .foregroundColor(accuracyColor(context.state.accuracy))\n                            .contentTransition(.numericText())\n                    }\n                }\n\n                DynamicIslandExpandedRegion(.center) {\n                    VStack(spacing: 6) {\n                        // Latitude\n                        HStack(spacing: 4) {\n                            Text(\"LAT\")\n                                .font(.caption2)\n                                .foregroundColor(.cyan.opacity(0.7))\n                            Text(String(format: \"%.5f°\", context.state.latitude))\n                                .font(.system(.caption, design: .monospaced))\n                                .fontWeight(.semibold)\n                                .foregroundColor(.cyan)\n                                .contentTransition(.numericText())\n                        }\n                        // Longitude\n                        HStack(spacing: 4) {\n                            Text(\"LON\")\n                                .font(.caption2)\n                                .foregroundColor(.purple.opacity(0.7))\n                            Text(String(format: \"%.5f°\", context.state.longitude))\n                                .font(.system(.caption, design: .monospaced))\n                                .fontWeight(.semibold)\n                                .foregroundColor(.purple)\n                                .contentTransition(.numericText())\n                        }\n                    }\n                }\n\n                DynamicIslandExpandedRegion(.trailing) {\n                    if let speed = context.state.speed, speed >= 0 {\n                        VStack(alignment: .trailing, spacing: 4) {\n                            Image(systemName: \"speedometer\")\n                                .foregroundColor(.orange)\n                                .font(.caption)\n                            Text(String(format: \"%.1f\", speed))\n                                .font(.system(.caption, design: .rounded))\n                                .fontWeight(.bold)\n                                .foregroundColor(.white)\n                                .contentTransition(.numericText())\n                            Text(\"m/s\")\n                                .font(.caption2)\n                                .foregroundColor(.gray)\n                        }\n                    } else {\n                        // Show heading compass if no speed\n                        if let heading = context.state.heading, heading >= 0 {\n                            VStack(spacing: 2) {\n                                Image(systemName: \"safari\")\n                                    .font(.title3)\n                                    .foregroundColor(.mint)\n                                    .rotationEffect(.degrees(heading))\n                                Text(String(format: \"%.0f°\", heading))\n                                    .font(.caption2)\n                                    .foregroundColor(.white)\n                            }\n                        }\n                    }\n                }\n\n                DynamicIslandExpandedRegion(.bottom) {\n                    HStack {\n                        Text(context.attributes.appName)\n                            .font(.caption2)\n                            .foregroundColor(.gray)\n                        Spacer()\n                        Text(context.state.lastUpdated, style: .relative)\n                            .font(.caption2)\n                            .foregroundColor(.gray)\n                    }\n                }\n\n            } compactLeading: {\n                ZStack {\n                    Circle()\n                        .fill(accuracyColor(context.state.accuracy).opacity(0.3))\n                        .frame(width: 24, height: 24)\n                    Image(systemName: \"location.fill\")\n                        .foregroundColor(accuracyColor(context.state.accuracy))\n                        .font(.caption)\n                }\n            } compactTrailing: {\n                Text(String(format: \"%.4f°\", context.state.latitude))\n                    .font(.system(.caption2, design: .monospaced))\n                    .fontWeight(.medium)\n                    .foregroundColor(.cyan)\n                    .contentTransition(.numericText())\n            } minimal: {\n                ZStack {\n                    Circle()\n                        .fill(accuracyColor(context.state.accuracy).opacity(0.3))\n                        .frame(width: 22, height: 22)\n                    Image(systemName: \"location.fill\")\n                        .foregroundColor(accuracyColor(context.state.accuracy))\n                        .font(.system(size: 10, weight: .bold))\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "examples/01-app-demos/geolocation-native-plugin/src/main.rs",
    "content": "//! A simple Dioxus app demonstrating how to build a native plugin using manganis.\n//!\n//! This example shows how to use the `#[manganis::ffi]` macro to automatically generate\n//! FFI bindings between Rust and native platforms (Swift/Kotlin).\n//!\n//! It also demonstrates how to use the widget!() macro to bundle a Widget Extension\n//! for Live Activities on iOS.\n\nuse dioxus::prelude::*;\n\n// Import the local plugin module\nmod plugin;\n#[cfg(target_os = \"ios\")]\nuse plugin::LiveActivityResult;\nuse plugin::{Geolocation, PermissionState, PermissionStatus, Position, PositionOptions};\n\nfn main() {\n    dioxus::launch(App);\n}\n\n#[component]\nfn App() -> Element {\n    let mut geolocation = use_signal(Geolocation::new);\n    let mut permission_status = use_signal(|| None::<PermissionStatus>);\n    let mut last_position = use_signal(|| None::<Position>);\n    let mut error = use_signal(|| None::<String>);\n    let mut use_high_accuracy = use_signal(|| true);\n    let mut max_age_input = use_signal(|| String::from(\"0\"));\n\n    let on_check_permissions = {\n        move |_| match geolocation.write().check_permissions() {\n            Ok(status) => {\n                permission_status.set(Some(status));\n                error.set(None);\n            }\n            Err(err) => error.set(Some(err.to_string())),\n        }\n    };\n\n    let on_request_permissions = move |_| {\n        let mut geo = geolocation.write();\n        match geo.request_permissions(None) {\n            Ok(_) => match geo.check_permissions() {\n                Ok(status) => {\n                    permission_status.set(Some(status));\n                    error.set(None);\n                }\n                Err(err) => error.set(Some(err.to_string())),\n            },\n            Err(err) => error.set(Some(err.to_string())),\n        }\n    };\n\n    let on_toggle_accuracy = move |_| use_high_accuracy.toggle();\n\n    let on_max_age_input = move |evt: FormEvent| max_age_input.set(evt.value());\n\n    let on_fetch_position = move |_| {\n        let maximum_age = max_age_input.read().trim().parse::<u32>().unwrap_or(0);\n\n        let options = PositionOptions {\n            enable_high_accuracy: use_high_accuracy(),\n            timeout: 10_000,\n            maximum_age,\n        };\n\n        match geolocation.write().get_current_position(Some(options)) {\n            Ok(position) => {\n                last_position.set(Some(position));\n                error.set(None);\n            }\n            Err(err) => error.set(Some(err.to_string())),\n        }\n    };\n\n    let accuracy_label = if use_high_accuracy() {\n        \"High accuracy: on\"\n    } else {\n        \"High accuracy: off\"\n    };\n\n    rsx! {\n        Stylesheet { href: asset!(\"/assets/main.css\") }\n\n        main { class: \"app\",\n            header { class: \"hero\",\n                div { class: \"hero__copy\",\n                    h1 { \"Geolocation plugin demo\" }\n                    p { \"One-shot location fetching through the Dioxus geolocation plugin.\n                        Measure permissions, request access, and inspect the last fix received from the device.\" }\n                }\n            }\n\n            div { class: \"cards\",\n                section { class: \"card\",\n                    h2 { \"Permissions\" }\n                    p { class: \"muted\",\n                        \"First, inspect what the OS currently allows this app to do. \\\n                        On Android & iOS these calls talk to the native permission dialog APIs.\" }\n                    div { class: \"button-row\",\n                        button { onclick: on_check_permissions, \"Check permissions\" }\n                        button { class: \"secondary\", onclick: on_request_permissions, \"Request permissions\" }\n                    }\n                    match permission_status() {\n                        Some(status) => rsx! {\n                            div { class: \"status-grid\",\n                                PermissionBadge { label: \"Location\".to_string(), state: status.location }\n                                PermissionBadge { label: \"Coarse location\".to_string(), state: status.coarse_location }\n                            }\n                        },\n                        None => rsx!(p { class: \"muted\", \"Tap “Check permissions” to see the current status.\" }),\n                    }\n                }\n\n                section { class: \"card\",\n                    h2 { \"Current position\" }\n                    p { class: \"muted\",\n                        \"The plugin resolves the device location once per request (no background watch). \\\n                        Configure the query and then fetch the coordinates.\" }\n                    div { class: \"settings\",\n                        button {\n                            class: if use_high_accuracy() { \"toggle toggle--active\" } else { \"toggle\" },\n                            onclick: on_toggle_accuracy,\n                            \"{accuracy_label}\"\n                        }\n                        label { class: \"field\",\n                            span { \"Max cached age (ms)\" }\n                            input {\n                                r#type: \"number\",\n                                inputmode: \"numeric\",\n                                min: \"0\",\n                                placeholder: \"0\",\n                                value: \"{max_age_input()}\",\n                                oninput: on_max_age_input,\n                            }\n                        }\n                    }\n                    button { class: \"primary full-width\", onclick: on_fetch_position, \"Get current position\" }\n\n                    match last_position() {\n                        Some(position) => {\n                            let snapshot = position.clone();\n                            let coords = snapshot.coords.clone();\n                            rsx! {\n                                div { class: \"position\",\n                                    h3 { \"Latest reading\" }\n                                    p { class: \"muted\", \"Timestamp: {snapshot.timestamp} ms since Unix epoch\" }\n                                    div { class: \"position__grid\",\n                                        CoordinateRow { label: \"Latitude\".to_string(), value: format!(\"{:.6}\", coords.latitude) }\n                                        CoordinateRow { label: \"Longitude\".to_string(), value: format!(\"{:.6}\", coords.longitude) }\n                                        CoordinateRow { label: \"Accuracy (m)\".to_string(), value: format!(\"{:.1}\", coords.accuracy) }\n                                        CoordinateRow { label: \"Altitude (m)\".to_string(), value: format_optional(coords.altitude) }\n                                        CoordinateRow { label: \"Altitude accuracy (m)\".to_string(), value: format_optional(coords.altitude_accuracy) }\n                                        CoordinateRow { label: \"Speed (m/s)\".to_string(), value: format_optional(coords.speed) }\n                                        CoordinateRow { label: \"Heading (°)\".to_string(), value: format_optional(coords.heading) }\n                                    }\n                                }\n                            }\n                        }\n                        None => rsx!(p { class: \"muted\", \"No location fetched yet.\" }),\n                    }\n                }\n\n                // Live Activity card (iOS only)\n                LiveActivityCard { geolocation, error }\n            }\n\n            if let Some(message) = error() {\n                div { class: \"error-banner\", \"Last error: {message}\" }\n            }\n        }\n    }\n}\n\n#[component]\nfn PermissionBadge(label: String, state: PermissionState) -> Element {\n    let (text, class) = match state {\n        PermissionState::Granted => (\"Granted\", \"badge badge--granted\"),\n        PermissionState::Denied => (\"Denied\", \"badge badge--denied\"),\n        PermissionState::Prompt | PermissionState::PromptWithRationale => {\n            (\"Needs prompt\", \"badge badge--prompt\")\n        }\n    };\n\n    rsx! {\n        div { class: \"permission-row\",\n            span { class: \"muted\", \"{label}\" }\n            span { class: class, \"{text}\" }\n        }\n    }\n}\n\n#[component]\nfn CoordinateRow(label: String, value: String) -> Element {\n    rsx! {\n        div { class: \"coordinate-row\",\n            span { class: \"muted\", \"{label}\" }\n            strong { \"{value}\" }\n        }\n    }\n}\n\nfn format_optional(value: Option<f64>) -> String {\n    value\n        .map(|inner| format!(\"{inner:.2}\"))\n        .unwrap_or_else(|| \"—\".to_string())\n}\n\n#[cfg(target_os = \"ios\")]\n#[component]\nfn LiveActivityCard(\n    mut geolocation: Signal<Geolocation>,\n    mut error: Signal<Option<String>>,\n) -> Element {\n    let mut live_activity = use_signal(|| None::<LiveActivityResult>);\n\n    let on_start_live_activity = move |_| match geolocation.write().start_live_activity() {\n        Ok(result) => {\n            live_activity.set(Some(result));\n            error.set(None);\n        }\n        Err(err) => error.set(Some(err.to_string())),\n    };\n\n    let on_update_live_activity = move |_| match geolocation.write().update_live_activity() {\n        Ok(_) => error.set(None),\n        Err(err) => error.set(Some(err.to_string())),\n    };\n\n    let on_end_live_activity = move |_| match geolocation.write().end_live_activity() {\n        Ok(_) => {\n            live_activity.set(None);\n            error.set(None);\n        }\n        Err(err) => error.set(Some(err.to_string())),\n    };\n\n    rsx! {\n        section { class: \"card\",\n            h2 { \"Live Activity\" }\n            p { class: \"muted\",\n                \"Start a Live Activity to show your current location on the lock screen. \\\n                Fetch your position first, then start the activity.\" }\n            div { class: \"button-row\",\n                button { onclick: on_start_live_activity, \"Start Activity\" }\n                button { class: \"secondary\", onclick: on_update_live_activity, \"Update\" }\n                button { class: \"secondary\", onclick: on_end_live_activity, \"End\" }\n            }\n            match live_activity() {\n                Some(activity) => rsx! {\n                    div { class: \"status-grid\",\n                        div { class: \"permission-row\",\n                            span { class: \"muted\", \"Activity ID\" }\n                            span { \"{activity.activity_id}\" }\n                        }\n                        div { class: \"permission-row\",\n                            span { class: \"muted\", \"Latitude\" }\n                            span { \"{activity.latitude:.6}\" }\n                        }\n                        div { class: \"permission-row\",\n                            span { class: \"muted\", \"Longitude\" }\n                            span { \"{activity.longitude:.6}\" }\n                        }\n                        div { class: \"permission-row\",\n                            span { class: \"muted\", \"Accuracy\" }\n                            span { class: \"badge badge--granted\", \"{activity.accuracy:.1}m\" }\n                        }\n                    }\n                },\n                None => rsx! {\n                    p { class: \"muted\", \"No Live Activity running.\" }\n                },\n            }\n        }\n    }\n}\n\n#[cfg(not(target_os = \"ios\"))]\n#[component]\nfn LiveActivityCard(geolocation: Signal<Geolocation>, error: Signal<Option<String>>) -> Element {\n    VNode::empty()\n}\n"
  },
  {
    "path": "examples/01-app-demos/geolocation-native-plugin/src/plugin/error.rs",
    "content": "// Copyright 2019-2023 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse serde::{ser::Serializer, Serialize};\n\npub type Result<T> = std::result::Result<T, Error>;\n\n#[derive(Debug, thiserror::Error)]\npub enum Error {\n    /// JSON serialization/deserialization error\n    #[error(\"JSON error: {0}\")]\n    Json(#[from] serde_json::Error),\n\n    /// Platform bridge error\n    #[error(\"Platform bridge error: {0}\")]\n    PlatformBridge(String),\n\n    /// Location unavailable\n    #[error(\"Location unavailable: {0}\")]\n    LocationUnavailable(String),\n\n    /// Live Activity error (iOS 16.1+)\n    #[cfg(target_os = \"ios\")]\n    #[error(\"Live Activity error: {0}\")]\n    LiveActivity(String),\n}\n\nimpl Serialize for Error {\n    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>\n    where\n        S: Serializer,\n    {\n        serializer.serialize_str(self.to_string().as_ref())\n    }\n}\n\nimpl From<&str> for Error {\n    fn from(s: &str) -> Self {\n        Error::PlatformBridge(s.to_string())\n    }\n}\n\nimpl From<String> for Error {\n    fn from(s: String) -> Self {\n        Error::PlatformBridge(s)\n    }\n}\n"
  },
  {
    "path": "examples/01-app-demos/geolocation-native-plugin/src/plugin/mod.rs",
    "content": "#![allow(non_snake_case)]\n// Copyright 2019-2023 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n//! Dioxus Geolocation Plugin\n//!\n//! This plugin provides APIs for getting and tracking the device's current position\n//! on Android and iOS mobile platforms.\n//!\n//! This example demonstrates the use of the `#[manganis::ffi]` macro for automatic\n//! FFI binding generation between Rust and native platforms.\n\npub use models::*;\n\nmod error;\nmod models;\n\npub use error::{Error, Result};\n\n// Note: Permissions are now declared in Dioxus.toml using the unified manifest system.\n// See Dioxus.toml in the project root for the permission configuration:\n//\n// [permissions]\n// location = { precision = \"fine\", description = \"Access your precise location...\" }\n//\n// The CLI automatically maps these to platform-specific identifiers:\n// - Android: ACCESS_FINE_LOCATION in AndroidManifest.xml\n// - iOS: NSLocationWhenInUseUsageDescription in Info.plist\n\n/// Access to the geolocation APIs.\n///\n/// This struct provides a unified interface for accessing geolocation functionality\n/// on both Android and iOS platforms. It uses the `#[manganis::ffi]` macro for\n/// automatic FFI binding generation.\n///\n/// # Example\n///\n/// ```rust,no_run\n/// use plugin::{Geolocation, PermissionState, PositionOptions};\n///\n/// let mut geolocation = Geolocation::new();\n///\n/// // Check permissions\n/// let status = geolocation.check_permissions()?;\n/// if status.location == PermissionState::Prompt {\n///     let new_status = geolocation.request_permissions(None)?;\n/// }\n///\n/// // Get current position\n/// let options = PositionOptions {\n///     enable_high_accuracy: true,\n///     timeout: 10000,\n///     maximum_age: 0,\n/// };\n/// let position = geolocation.get_current_position(Some(options))?;\n/// println!(\"Latitude: {}, Longitude: {}\", position.coords.latitude, position.coords.longitude);\n///\n/// # Ok::<(), plugin::Error>(())\n/// ```\npub struct Geolocation {\n    plugin: Option<GeolocationPlugin>,\n}\n\nimpl Geolocation {\n    /// Create a new Geolocation instance\n    pub fn new() -> Self {\n        Self { plugin: None }\n    }\n\n    /// Get or initialize the plugin instance\n    fn get_plugin(&mut self) -> Result<&GeolocationPlugin> {\n        if self.plugin.is_none() {\n            self.plugin = Some(GeolocationPlugin::new()?);\n        }\n        Ok(self.plugin.as_ref().unwrap())\n    }\n\n    /// Get the device's current position.\n    ///\n    /// # Arguments\n    ///\n    /// * `options` - Optional position options. If `None`, default options are used.\n    ///\n    /// # Returns\n    ///\n    /// Returns the current position or an error if the location cannot be obtained.\n    pub fn get_current_position(&mut self, options: Option<PositionOptions>) -> Result<Position> {\n        let options = options.unwrap_or_default();\n        let options_json = serde_json::to_string(&options).map_err(Error::Json)?;\n\n        let plugin = self.get_plugin()?;\n        let result_json = getCurrentPositionJson(plugin, options_json)?;\n\n        // Check for error in response\n        let json_value: serde_json::Value =\n            serde_json::from_str(&result_json).map_err(Error::Json)?;\n        if let Some(error_msg) = json_value.get(\"error\") {\n            return Err(Error::LocationUnavailable(\n                error_msg.as_str().unwrap_or(\"Unknown error\").to_string(),\n            ));\n        }\n\n        let position: Position = serde_json::from_str(&result_json).map_err(Error::Json)?;\n        Ok(position)\n    }\n\n    /// Check the current permission status.\n    ///\n    /// # Returns\n    ///\n    /// Returns the permission status for location and coarse location permissions.\n    pub fn check_permissions(&mut self) -> Result<PermissionStatus> {\n        let plugin = self.get_plugin()?;\n        let result_json = checkPermissionsJson(plugin)?;\n        let status: PermissionStatus = serde_json::from_str(&result_json).map_err(Error::Json)?;\n        Ok(status)\n    }\n\n    /// Request location permissions from the user.\n    ///\n    /// # Arguments\n    ///\n    /// * `permissions` - Optional list of specific permission types to request.\n    ///   If `None`, requests all location permissions.\n    ///\n    /// # Returns\n    ///\n    /// Returns the permission status after the user responds to the permission request.\n    pub fn request_permissions(\n        &mut self,\n        permissions: Option<Vec<PermissionType>>,\n    ) -> Result<PermissionStatus> {\n        let perms_json = serde_json::to_string(&permissions).map_err(Error::Json)?;\n        let plugin = self.get_plugin()?;\n        let result_json = requestPermissionsJson(plugin, perms_json)?;\n        let status: PermissionStatus = serde_json::from_str(&result_json).map_err(Error::Json)?;\n        Ok(status)\n    }\n\n    // =========================================================================\n    // Live Activity methods (iOS 16.1+)\n    // =========================================================================\n\n    /// Start a Live Activity showing the current permission status.\n    ///\n    /// Live Activities appear on the lock screen and Dynamic Island (on supported devices).\n    /// Requires iOS 16.1+ and a Widget Extension for the UI (see docs).\n    ///\n    /// # Returns\n    ///\n    /// Returns the activity ID and current permission status, or an error.\n    #[cfg(target_os = \"ios\")]\n    pub fn start_live_activity(&mut self) -> Result<LiveActivityResult> {\n        let plugin = self.get_plugin()?;\n        let result_json = startLiveActivityJson(plugin)?;\n\n        // Check for error in response\n        let json_value: serde_json::Value =\n            serde_json::from_str(&result_json).map_err(Error::Json)?;\n        if let Some(error_msg) = json_value.get(\"error\") {\n            return Err(Error::LiveActivity(\n                error_msg.as_str().unwrap_or(\"Unknown error\").to_string(),\n            ));\n        }\n\n        let result: LiveActivityResult = serde_json::from_str(&result_json).map_err(Error::Json)?;\n        Ok(result)\n    }\n\n    /// Update the Live Activity with the current permission status.\n    ///\n    /// Call this after permission changes to reflect the new state.\n    #[cfg(target_os = \"ios\")]\n    pub fn update_live_activity(&mut self) -> Result<LiveActivityUpdate> {\n        let plugin = self.get_plugin()?;\n        let result_json = updateLiveActivityJson(plugin, \"{}\".to_string())?;\n\n        let json_value: serde_json::Value =\n            serde_json::from_str(&result_json).map_err(Error::Json)?;\n        if let Some(error_msg) = json_value.get(\"error\") {\n            return Err(Error::LiveActivity(\n                error_msg.as_str().unwrap_or(\"Unknown error\").to_string(),\n            ));\n        }\n\n        let result: LiveActivityUpdate = serde_json::from_str(&result_json).map_err(Error::Json)?;\n        Ok(result)\n    }\n\n    /// End all Live Activities for this app.\n    #[cfg(target_os = \"ios\")]\n    pub fn end_live_activity(&mut self) -> Result<()> {\n        let plugin = self.get_plugin()?;\n        let result_json = endLiveActivityJson(plugin)?;\n\n        let json_value: serde_json::Value =\n            serde_json::from_str(&result_json).map_err(Error::Json)?;\n        if let Some(error_msg) = json_value.get(\"error\") {\n            return Err(Error::LiveActivity(\n                error_msg.as_str().unwrap_or(\"Unknown error\").to_string(),\n            ));\n        }\n\n        Ok(())\n    }\n}\n\nimpl Default for Geolocation {\n    fn default() -> Self {\n        Self::new()\n    }\n}\n\n/// iOS/macOS native bindings - the macro generates all FFI code automatically.\n/// The path \"src/ios/plugin\" points to the SwiftPM package containing GeolocationPlugin.swift\n#[cfg(any(target_os = \"ios\", target_os = \"macos\"))]\n#[manganis::ffi(\"src/ios/plugin\")]\nextern \"Swift\" {\n    /// The native GeolocationPlugin class\n    pub type GeolocationPlugin;\n\n    /// Get current position as JSON string\n    /// Swift signature: func getCurrentPositionJson(_ optionsJson: String) -> String\n    pub fn getCurrentPositionJson(this: &GeolocationPlugin, optionsJson: String) -> String;\n\n    /// Check permissions and return status as JSON\n    /// Swift signature: func checkPermissionsJson() -> String\n    pub fn checkPermissionsJson(this: &GeolocationPlugin) -> String;\n\n    /// Request permissions with optional types list as JSON, return status as JSON\n    /// Swift signature: func requestPermissionsJson(_ permissionsJson: String) -> String\n    pub fn requestPermissionsJson(this: &GeolocationPlugin, permissionsJson: String) -> String;\n\n    /// Start a Live Activity showing permission status (iOS 16.1+)\n    /// Swift signature: func startLiveActivityJson() -> String\n    pub fn startLiveActivityJson(this: &GeolocationPlugin) -> String;\n\n    /// Update the Live Activity with current permission status\n    /// Swift signature: func updateLiveActivityJson(_ statusJson: String) -> String\n    pub fn updateLiveActivityJson(this: &GeolocationPlugin, statusJson: String) -> String;\n\n    /// End all Live Activities\n    /// Swift signature: func endLiveActivityJson() -> String\n    pub fn endLiveActivityJson(this: &GeolocationPlugin) -> String;\n}\n\n/// Android native bindings - the macro generates all JNI code automatically.\n/// The path \"src/android\" points to the Gradle project containing GeolocationPlugin.kt\n#[cfg(target_os = \"android\")]\n#[manganis::ffi(\"src/android\")]\nextern \"Kotlin\" {\n    /// The native GeolocationPlugin class\n    pub type GeolocationPlugin;\n\n    /// Get current position as JSON string\n    /// Kotlin signature: fun getCurrentPositionJson(optionsJson: String): String\n    pub fn getCurrentPositionJson(this: &GeolocationPlugin, optionsJson: String) -> String;\n\n    /// Check permissions and return status as JSON\n    /// Kotlin signature: fun checkPermissionsJson(): String\n    pub fn checkPermissionsJson(this: &GeolocationPlugin) -> String;\n\n    /// Request permissions with optional types list as JSON, return status as JSON\n    /// Kotlin signature: fun requestPermissionsJson(permissionsJson: String): String\n    pub fn requestPermissionsJson(this: &GeolocationPlugin, permissionsJson: String) -> String;\n}\n\n// =============================================================================\n// Stub for non-native platforms (web, Linux desktop, etc.)\n// =============================================================================\n#[cfg(not(any(\n    all(any(target_os = \"ios\", target_os = \"macos\")),\n    all(target_os = \"android\")\n)))]\nuse fallback::*;\n\n#[cfg(not(any(\n    all(any(target_os = \"ios\", target_os = \"macos\")),\n    all(target_os = \"android\")\n)))]\nmod fallback {\n    #![allow(non_snake_case)]\n    use super::{Error, Result};\n\n    pub struct GeolocationPlugin;\n\n    impl GeolocationPlugin {\n        pub fn new() -> Result<Self> {\n            Err(Error::PlatformBridge(\n                \"Geolocation is only supported on Android, iOS, and macOS\".to_string(),\n            ))\n        }\n    }\n\n    pub fn getCurrentPositionJson(_: &GeolocationPlugin, _: String) -> Result<String> {\n        Err(Error::PlatformBridge(\n            \"Geolocation is only supported on Android, iOS, and macOS\".to_string(),\n        ))\n    }\n\n    pub fn checkPermissionsJson(_: &GeolocationPlugin) -> Result<String> {\n        Err(Error::PlatformBridge(\n            \"Geolocation is only supported on Android, iOS, and macOS\".to_string(),\n        ))\n    }\n\n    pub fn requestPermissionsJson(_: &GeolocationPlugin, _: String) -> Result<String> {\n        Err(Error::PlatformBridge(\n            \"Geolocation is only supported on Android, iOS, and macOS\".to_string(),\n        ))\n    }\n}\n"
  },
  {
    "path": "examples/01-app-demos/geolocation-native-plugin/src/plugin/models.rs",
    "content": "// Copyright 2019-2023 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse serde::{Deserialize, Serialize};\n\n/// Permission state for geolocation permissions\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]\n#[serde(rename_all = \"kebab-case\")]\n#[derive(Default)]\npub enum PermissionState {\n    /// Permission not yet determined (user hasn't been asked)\n    #[default]\n    Prompt,\n\n    /// Permission prompt shown with rationale (Android 12+)\n    PromptWithRationale,\n\n    /// Permission granted\n    Granted,\n\n    /// Permission denied\n    Denied,\n}\n\n#[derive(Debug, Clone, Default, Serialize, Deserialize)]\n#[serde(rename_all = \"camelCase\")]\npub struct PermissionStatus {\n    /// Permission state for the location alias.\n    ///\n    /// On Android it requests/checks both ACCESS_COARSE_LOCATION and ACCESS_FINE_LOCATION permissions.\n    ///\n    /// On iOS it requests/checks location permissions.\n    pub location: PermissionState,\n\n    /// Permissions state for the coarseLocation alias.\n    ///\n    /// On Android it requests/checks ACCESS_COARSE_LOCATION.\n    ///\n    /// On Android 12+, users can choose between Approximate location (ACCESS_COARSE_LOCATION) and Precise location (ACCESS_FINE_LOCATION).\n    ///\n    /// On iOS it will have the same value as the `location` alias.\n    pub coarse_location: PermissionState,\n}\n\n#[derive(Debug, Clone, Default, Serialize, Deserialize)]\n#[serde(rename_all = \"camelCase\")]\npub struct PositionOptions {\n    /// High accuracy mode (such as GPS, if available)\n    /// Will be ignored on Android 12+ if users didn't grant the ACCESS_FINE_LOCATION permission.\n    pub enable_high_accuracy: bool,\n    /// The maximum wait time in milliseconds for location updates.\n    /// Default: 10000\n    /// On Android the timeout gets ignored for getCurrentPosition.\n    /// Ignored on iOS.\n    // TODO: Handle Infinity and default to it.\n    // TODO: Should be u64+ but specta doesn't like that?\n    pub timeout: u32,\n    /// The maximum age in milliseconds of a possible cached position that is acceptable to return.\n    /// Default: 0\n    /// Ignored on iOS.\n    // TODO: Handle Infinity.\n    // TODO: Should be u64+ but specta doesn't like that?\n    pub maximum_age: u32,\n}\n\n#[derive(Debug, Clone, Serialize, Deserialize)]\n#[serde(rename_all = \"camelCase\")]\npub enum PermissionType {\n    Location,\n    CoarseLocation,\n}\n\n#[derive(Debug, Clone, Default, Serialize, Deserialize)]\n#[serde(rename_all = \"camelCase\")]\npub struct Coordinates {\n    /// Latitude in decimal degrees.\n    pub latitude: f64,\n    /// Longitude in decimal degrees.\n    pub longitude: f64,\n    /// Accuracy level of the latitude and longitude coordinates in meters.\n    pub accuracy: f64,\n    /// Accuracy level of the altitude coordinate in meters, if available.\n    /// Available on all iOS versions and on Android 8 and above.\n    pub altitude_accuracy: Option<f64>,\n    /// The altitude the user is at, if available.\n    pub altitude: Option<f64>,\n    // The speed the user is traveling, if available.\n    pub speed: Option<f64>,\n    /// The heading the user is facing, if available.\n    pub heading: Option<f64>,\n}\n\n#[derive(Debug, Clone, Default, Serialize, Deserialize)]\n#[serde(rename_all = \"camelCase\")]\npub struct Position {\n    /// Creation time for these coordinates.\n    // TODO: Check if we're actually losing precision.\n    pub timestamp: u64,\n    /// The GPS coordinates along with the accuracy of the data.\n    pub coords: Coordinates,\n}\n\n// =============================================================================\n// Live Activity types (iOS 16.1+)\n// =============================================================================\n\n/// Result from starting a Live Activity\n#[cfg(target_os = \"ios\")]\n#[derive(Debug, Clone, Serialize, Deserialize)]\n#[serde(rename_all = \"camelCase\")]\npub struct LiveActivityResult {\n    /// Unique identifier for the activity\n    pub activity_id: String,\n    /// Current latitude displayed in the activity\n    pub latitude: f64,\n    /// Current longitude displayed in the activity\n    pub longitude: f64,\n    /// Horizontal accuracy in meters\n    pub accuracy: f64,\n}\n\n/// Result from updating a Live Activity\n#[cfg(target_os = \"ios\")]\n#[derive(Debug, Clone, Serialize, Deserialize)]\n#[serde(rename_all = \"camelCase\")]\npub struct LiveActivityUpdate {\n    /// Current latitude after update\n    pub latitude: f64,\n    /// Current longitude after update\n    pub longitude: f64,\n    /// Horizontal accuracy in meters\n    pub accuracy: f64,\n}\n"
  },
  {
    "path": "examples/01-app-demos/hackernews/.gitignore",
    "content": "/static\n/dist"
  },
  {
    "path": "examples/01-app-demos/hackernews/Cargo.toml",
    "content": "[package]\nname = \"fullstack-hackernews-example\"\nversion = \"0.1.0\"\nauthors = [\"Evan Almloff <evanalmloff@gmail.com>\"]\nedition = \"2021\"\npublish = false\n\n# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html\n\n[dependencies]\ndioxus = { workspace = true, features = [\"fullstack\", \"router\"] }\nchrono = { workspace = true, features = [\"serde\"] }\nreqwest = { workspace= true, features = [\"json\"] }\nserde = { workspace = true, features = [\"derive\"] }\n\n[features]\ndefault = []\nserver = [\"dioxus/server\"]\nweb = [\"dioxus/web\"]\n"
  },
  {
    "path": "examples/01-app-demos/hackernews/assets/hackernews.css",
    "content": "@keyframes spin {\n    0% {\n        transform: rotate(0deg);\n    }\n\n    100% {\n        transform: rotate(360deg);\n    }\n}\n.spinner {\n    width: 10px;\n    height: 10px;\n    border: 4px solid #f3f3f3;\n    border-top: 4px solid #3498db;\n    border-radius: 50%;\n    animation: spin 2s linear infinite;\n}"
  },
  {
    "path": "examples/01-app-demos/hackernews/src/main.rs",
    "content": "#![allow(non_snake_case, unused)]\nuse dioxus::prelude::*;\n// Define the Hackernews API and types\nuse chrono::{DateTime, Utc};\nuse serde::{Deserialize, Serialize};\nuse std::{\n    fmt::{Display, Formatter},\n    num::ParseIntError,\n    str::FromStr,\n};\nuse svg_attributes::to;\n\nfn main() {\n    LaunchBuilder::new()\n        .with_cfg(server_only! {\n            dioxus::server::ServeConfig::builder().enable_out_of_order_streaming()\n        })\n        .launch(|| {\n            rsx! {\n                Stylesheet { href: asset!(\"/assets/hackernews.css\") }\n                Router::<Route> {}\n            }\n        });\n}\n\n#[derive(Clone, Routable)]\nenum Route {\n    #[route(\"/story&:story\")]\n    StoryPreview { story: Option<i64> },\n}\n\n#[component]\nfn StoryPreview(story: ReadSignal<Option<i64>>) -> Element {\n    rsx! {\n        div { display: \"flex\", flex_direction: \"row\", width: \"100%\",\n            div { width: \"50%\",\n                SuspenseBoundary { fallback: |context| rsx! { \"Loading...\" },\n                    Stories {}\n                }\n            }\n            div { width: \"50%\",\n                SuspenseBoundary { fallback: |context| rsx! { \"Loading preview...\" },\n                    if let Some(story) = story() {\n                        Preview { story_id: story }\n                    } else {\n                        div { padding: \"0.5rem\", \"Select a story to preview\" }\n                    }\n                }\n            }\n        }\n    }\n}\n\n#[component]\nfn Stories() -> Element {\n    let stories = use_loader(move || async move {\n        let stories_ids = reqwest::get(&format!(\"{}topstories.json\", BASE_API_URL))\n            .await?\n            .json::<Vec<i64>>()\n            .await?\n            .into_iter()\n            .take(30)\n            .collect::<Vec<i64>>();\n        dioxus::Ok(stories_ids)\n    })?;\n\n    rsx! {\n        div {\n            for story in stories() {\n                ChildrenOrLoading { key: \"{story}\",\n                    StoryListing { story }\n                }\n            }\n        }\n    }\n}\n\n#[component]\nfn StoryListing(story: ReadSignal<i64>) -> Element {\n    let story = use_loader(move || get_story(story()))?;\n    let StoryItem {\n        title,\n        url,\n        by,\n        score,\n        time,\n        kids,\n        id,\n        ..\n    } = story().item;\n\n    let url = url.as_deref().unwrap_or_default();\n    let hostname = url\n        .trim_start_matches(\"https://\")\n        .trim_start_matches(\"http://\")\n        .trim_start_matches(\"www.\");\n    let score = format!(\"{score} {}\", if score == 1 { \" point\" } else { \" points\" });\n    let comments = format!(\n        \"{} {}\",\n        kids.len(),\n        if kids.len() == 1 {\n            \" comment\"\n        } else {\n            \" comments\"\n        }\n    );\n    let time = time.format(\"%D %l:%M %p\");\n\n    rsx! {\n        div {\n            padding: \"0.5rem\",\n            position: \"relative\",\n            div { font_size: \"1.5rem\",\n                Link {\n                    to: Route::StoryPreview { story: Some(id) },\n                    \"{title}\"\n                }\n                a {\n                    color: \"gray\",\n                    href: \"https://news.ycombinator.com/from?site={hostname}\",\n                    text_decoration: \"none\",\n                    \" ({hostname})\"\n                }\n            }\n            div { display: \"flex\", flex_direction: \"row\", color: \"gray\",\n                div { \"{score}\" }\n                div { padding_left: \"0.5rem\", \"by {by}\" }\n                div { padding_left: \"0.5rem\", \"{time}\" }\n                div { padding_left: \"0.5rem\", \"{comments}\" }\n            }\n        }\n    }\n}\n\n#[component]\nfn Preview(story_id: ReadSignal<i64>) -> Element {\n    let story = use_loader(move || get_story(story_id()))?.cloned();\n    rsx! {\n        div { padding: \"0.5rem\",\n            div { font_size: \"1.5rem\", a { href: story.item.url, \"{story.item.title}\" } }\n            if let Some(text) = &story.item.text { div { dangerous_inner_html: \"{text}\" } }\n            for comment in story.item.kids.iter().copied() {\n                ChildrenOrLoading {\n                    key: \"{comment}\",\n                    Comment { comment }\n                }\n            }\n        }\n    }\n}\n\n#[component]\nfn Comment(comment: ReadSignal<i64>) -> Element {\n    let comment = use_loader(move || async move {\n        let mut comment = reqwest::get(&format!(\"{}{}{}.json\", BASE_API_URL, ITEM_API, comment))\n            .await?\n            .json::<CommentData>()\n            .await?;\n        dioxus::Ok(comment)\n    })?;\n\n    let CommentData {\n        by,\n        time,\n        text,\n        id,\n        kids,\n        ..\n    } = comment();\n\n    rsx! {\n        div { padding: \"0.5rem\",\n            div { color: \"gray\", \"by {by}\" }\n            div { dangerous_inner_html: \"{text}\" }\n            for comment in kids.iter().copied() {\n                ChildrenOrLoading {\n                    key: \"{comment}\",\n                    Comment { comment }\n                }\n            }\n        }\n    }\n}\n\npub static BASE_API_URL: &str = \"https://hacker-news.firebaseio.com/v0/\";\npub static ITEM_API: &str = \"item/\";\npub static USER_API: &str = \"user/\";\nconst COMMENT_DEPTH: i64 = 1;\n\n#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]\npub struct StoryPageData {\n    #[serde(flatten)]\n    pub item: StoryItem,\n    #[serde(default)]\n    pub comments: Vec<CommentData>,\n}\n\n#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]\npub struct CommentData {\n    pub id: i64,\n    /// there will be no by field if the comment was deleted\n    #[serde(default)]\n    pub by: String,\n    #[serde(default)]\n    pub text: String,\n    #[serde(with = \"chrono::serde::ts_seconds\")]\n    pub time: DateTime<Utc>,\n    #[serde(default)]\n    pub kids: Vec<i64>,\n    pub r#type: String,\n}\n\n#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]\npub struct StoryItem {\n    pub id: i64,\n    pub title: String,\n    pub url: Option<String>,\n    pub text: Option<String>,\n    #[serde(default)]\n    pub by: String,\n    #[serde(default)]\n    pub score: i64,\n    #[serde(default)]\n    pub descendants: i64,\n    #[serde(with = \"chrono::serde::ts_seconds\")]\n    pub time: DateTime<Utc>,\n    #[serde(default)]\n    pub kids: Vec<i64>,\n    pub r#type: String,\n}\n\npub async fn get_story(id: i64) -> Result<StoryPageData> {\n    Ok(\n        reqwest::get(&format!(\"{}{}{}.json\", BASE_API_URL, ITEM_API, id))\n            .await?\n            .json::<StoryPageData>()\n            .await?,\n    )\n}\n\n#[component]\nfn ChildrenOrLoading(children: Element) -> Element {\n    rsx! {\n        SuspenseBoundary {\n            fallback: |_| rsx! { div { class: \"spinner\", } },\n            children\n        }\n    }\n}\n"
  },
  {
    "path": "examples/01-app-demos/hello_world.rs",
    "content": "//! The simplest example of a Dioxus app.\n//!\n//! In this example we:\n//! - import a number of important items from the prelude (launch, Element, rsx, div, etc.)\n//! - define a main function that calls the launch function with our app function\n//! - define an app function that returns a div element with the text \"Hello, world!\"\n//!\n//! The `launch` function is the entry point for all Dioxus apps. It takes a function that returns an Element. This function\n//! calls \"launch\" on the currently-configured renderer you have. So if the `web` feature is enabled, it will launch a web\n//! app, and if the `desktop` feature is enabled, it will launch a desktop app.\n\nuse dioxus::prelude::*;\n\nfn main() {\n    dioxus::launch(app);\n}\n\nfn app() -> Element {\n    rsx! {\n        div { \"Hello, world!\" }\n    }\n}\n"
  },
  {
    "path": "examples/01-app-demos/hotdog/.gitignore",
    "content": "hotdogdb/*\n"
  },
  {
    "path": "examples/01-app-demos/hotdog/Cargo.toml",
    "content": "[package]\nname = \"hotdog\"\nversion = \"0.1.0\"\nauthors = [\"Dioxus Labs\"]\nedition = \"2021\"\npublish = false\n\n[dependencies]\ndioxus = { workspace = true,  features = [\"fullstack\", \"router\"] }\nreqwest = { workspace = true, features = [\"json\"] }\nserde = { workspace = true, features = [\"derive\"] }\nserde_json = { workspace = true }\nrusqlite = { version = \"0.32.0\", optional = true }\nanyhow = { workspace = true }\n\n[features]\ndefault = [\"web\", \"server\"]\nweb = [\"dioxus/web\"]\ndesktop = [\"dioxus/desktop\"]\nnative = [\"dioxus/native\"]\nmobile = [\"dioxus/mobile\"]\nserver = [\"dioxus/server\", \"dep:rusqlite\"]\n"
  },
  {
    "path": "examples/01-app-demos/hotdog/Dioxus.toml",
    "content": "[application]\n\n# App (Project) Name\nname = \"hot_dog\"\n\n[bundle]\nidentifier = \"com.dioxuslabs\"\npublisher = \"Dioxus Labs\"\n"
  },
  {
    "path": "examples/01-app-demos/hotdog/Dockerfile",
    "content": "FROM rust:1 AS chef\nRUN cargo install cargo-chef\nWORKDIR /app\n\nFROM chef AS planner\nCOPY . .\nRUN cargo chef prepare --recipe-path recipe.json\n\nFROM chef AS builder\nCOPY --from=planner /app/recipe.json recipe.json\nRUN cargo chef cook --release --recipe-path recipe.json\nCOPY . .\nRUN curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/DioxusLabs/dioxus/refs/heads/main/.github/install.sh | bash\nRUN /.cargo/bin/dx bundle --platform web\n\nFROM chef AS runtime\nCOPY --from=builder /app/target/dx/hotdog/release/web/ /usr/local/app\n\nENV PORT=8080\nENV IP=0.0.0.0\nEXPOSE 8080\n\nWORKDIR /usr/local/app\nENTRYPOINT [ \"/usr/local/app/server\" ]\n"
  },
  {
    "path": "examples/01-app-demos/hotdog/README.md",
    "content": "# Hot diggity dog!\n\nA Dioxus demo app for the new tutorial!\n\n![Demo](assets/screenshot.png)\n\n## To run\n\nMake sure you cd to this directory (dioxus/hotdog) and then `serve` any platform:\n\n```rust\ndx serve --platform web\ndx serve --platform desktop\ndx serve --platform ios\ndx serve --platform android\n```\n"
  },
  {
    "path": "examples/01-app-demos/hotdog/assets/main.css",
    "content": "/* App-wide styling */\nhtml, body {\n    background-color: #0e0e0e;\n    color: white;\n    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;\n    height: 100%;\n    width: 100%;\n    overflow: hidden;\n    margin: 0;\n}\n\n#main {\n    display: flex;\n    flex-direction: column;\n    height: 100%;\n    justify-content: space-between;\n}\n\n#dogview {\n    max-height: 80vh;\n    flex-grow: 1;\n    width: 100%;\n    display: flex;\n    flex-direction: column;\n    align-items: center;\n    justify-content: center;\n}\n\n#dogimg {\n    display: block;\n    max-width: 50%;\n    max-height: 50%;\n    transform: scale(1.8);\n    border-radius: 5px;\n    border: 1px solid rgb(233, 233, 233);\n    box-shadow: 0px 0px 5px 1px rgb(216, 216, 216, 0.5);\n}\n\n#title {\n    text-align: center;\n    padding-top: 10px;\n    border-bottom: 1px solid #a8a8a8;\n    display: flex;\n    flex-direction: row;\n    justify-content: space-evenly;\n    align-items: center;\n}\n\n#title a {\n    text-decoration: none;\n    color: white;\n}\n\n#heart {\n    background-color: white;\n    padding: 5px;\n    border-radius: 5px;\n}\n\n#title span {\n    width: 20px;\n}\n\n#title h1 {\n    margin: 0.25em;\n    font-style: italic;\n}\n\n#buttons {\n    display: flex;\n    flex-direction: row;\n    justify-content: center;\n    gap: 20px;\n    /* padding-top: 20px; */\n    padding-bottom: 20px;\n}\n\n#skip { background-color: gray }\n#save { background-color: green; }\n\n#skip, #save {\n    padding: 5px 30px 5px 30px;\n    border-radius: 3px;\n    font-size: 2rem;\n    font-weight: bold;\n    color: rgb(230, 230, 230)\n}\n\n#navbar {\n    border: 1px solid rgb(233, 233, 233);\n    border-width: 1px 0px 0px 0px;\n    display: flex;\n    flex-direction: row;\n    justify-content: space-evenly;\n    padding: 20px;\n    gap: 20px;\n}\n\n#navbar a {\n    background-color: #a8a8a8;\n    border-radius: 5px;\n    border: 1px solid black;\n    text-decoration: none;\n    color: black;\n    padding: 10px 30px 10px 30px;\n}\n\n#favorites {\n    flex-grow: 1;\n    overflow: hidden;\n    display: flex;\n    flex-direction: column;\n    padding: 10px;\n}\n\n#favorites-container {\n    overflow-y: auto;\n    overflow-x: hidden;\n    display: flex;\n    flex-direction: row;\n    flex-wrap: wrap;\n    justify-content: center;\n    gap: 10px;\n    padding: 10px;\n}\n\n.favorite-dog {\n    max-height: 180px;\n    max-width: 60%;\n    position: relative;\n}\n\n.favorite-dog img {\n    max-height: 150px;\n    border-radius: 5px;\n    margin: 5px;\n}\n\n.favorite-dog:hover button {\n    display: block;\n}\n\n.favorite-dog button {\n    display: none;\n    position: absolute;\n    bottom: 10px;\n    left: 10px;\n    z-index: 10;\n}\n"
  },
  {
    "path": "examples/01-app-demos/hotdog/fly.toml",
    "content": "# fly.toml app configuration file generated for hot-dog on 2024-12-19T18:23:45-08:00\n#\n# See https://fly.io/docs/reference/configuration/ for information about how to use this file.\n#\n\napp = 'hot-dog'\nprimary_region = 'sjc'\n\n[build]\n\n[http_service]\n  internal_port = 8080\n  force_https = true\n  auto_stop_machines = 'stop'\n  auto_start_machines = true\n  min_machines_running = 0\n  processes = ['app']\n\n[[vm]]\n  memory = '1gb'\n  cpu_kind = 'shared'\n  cpus = 1\n\n[mounts]\n  source = \"hotdogdb\"\n  destination = \"/usr/local/app/hotdogdb\"\n"
  },
  {
    "path": "examples/01-app-demos/hotdog/src/backend.rs",
    "content": "use anyhow::Result;\nuse dioxus::prelude::*;\n\n#[cfg(feature = \"server\")]\nthread_local! {\n    static DB: std::sync::LazyLock<rusqlite::Connection> = std::sync::LazyLock::new(|| {\n        std::fs::create_dir(\"hotdogdb\").unwrap();\n        let conn = rusqlite::Connection::open(\"hotdogdb/hotdog.db\").expect(\"Failed to open database\");\n\n        conn.execute_batch(\n            \"CREATE TABLE IF NOT EXISTS dogs (\n                id INTEGER PRIMARY KEY,\n                url TEXT NOT NULL\n            );\",\n        )\n        .unwrap();\n\n        conn\n    });\n}\n\n#[get(\"/api/dogs\")]\npub async fn list_dogs() -> Result<Vec<(usize, String)>> {\n    DB.with(|db| {\n        Ok(db\n            .prepare(\"SELECT id, url FROM dogs ORDER BY id DESC LIMIT 10\")?\n            .query_map([], |row| Ok((row.get(0)?, row.get(1)?)))?\n            .collect::<Result<Vec<(usize, String)>, rusqlite::Error>>()?)\n    })\n}\n\n#[delete(\"/api/dogs/{id}\")]\npub async fn remove_dog(id: usize) -> Result<()> {\n    DB.with(|db| db.execute(\"DELETE FROM dogs WHERE id = ?1\", [id]))?;\n    Ok(())\n}\n\n#[post(\"/api/dogs\")]\npub async fn save_dog(image: String) -> Result<()> {\n    DB.with(|db| db.execute(\"INSERT INTO dogs (url) VALUES (?1)\", [&image]))?;\n    Ok(())\n}\n"
  },
  {
    "path": "examples/01-app-demos/hotdog/src/frontend.rs",
    "content": "use dioxus::prelude::*;\nuse serde::{Deserialize, Serialize};\n\nuse crate::{\n    backend::{list_dogs, remove_dog, save_dog},\n    Route,\n};\n\n#[component]\npub fn Favorites() -> Element {\n    let mut favorites = use_loader(list_dogs)?;\n\n    rsx! {\n        div { id: \"favorites\",\n            for (id , url) in favorites.cloned() {\n                div { class: \"favorite-dog\", key: \"{id}\",\n                    img { src: \"{url}\" }\n                    button {\n                        onclick: move |_| async move {\n                            _ = remove_dog(id).await;\n                            favorites.restart();\n                        },\n                        \"❌\"\n                    }\n                }\n            }\n        }\n    }\n}\n\n#[component]\npub fn NavBar() -> Element {\n    rsx! {\n        div { id: \"title\",\n            span {}\n            Link { to: Route::DogView, h1 { \"🌭 HotDog! \" } }\n            Link { to: Route::Favorites, id: \"heart\", \"♥️\" }\n        }\n        Outlet::<Route> {}\n    }\n}\n\n#[component]\npub fn DogView() -> Element {\n    let mut img_src = use_loader(|| async move {\n        #[derive(Deserialize, Serialize, Debug, PartialEq)]\n        struct DogApi {\n            message: String,\n        }\n        let json = reqwest::get(\"https://dog.ceo/api/breeds/image/random\")\n            .await?\n            .json::<DogApi>()\n            .await?;\n        let url = json.message;\n\n        dioxus::Ok(url)\n    })?;\n\n    rsx! {\n        div { id: \"dogview\",\n            img { id: \"dogimg\", src: \"{img_src}\" }\n        }\n        div { id: \"buttons\",\n            button {\n                id: \"skip\",\n                onclick: move |_| img_src.restart(),\n                \"skip\"\n            }\n            button {\n                id: \"save\",\n                onclick: move |_| async move { _ = save_dog(img_src()).await },\n                \"save!\"\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "examples/01-app-demos/hotdog/src/main.rs",
    "content": "mod backend;\nmod frontend;\n\nuse dioxus::prelude::*;\nuse frontend::*;\n\n#[derive(Routable, PartialEq, Clone)]\nenum Route {\n    #[layout(NavBar)]\n    #[route(\"/\")]\n    DogView,\n\n    #[route(\"/favorites\")]\n    Favorites,\n}\n\nfn main() {\n    #[cfg(not(feature = \"server\"))]\n    dioxus::fullstack::set_server_url(\"https://hot-dog.fly.dev\");\n\n    dioxus::launch(app);\n}\n\nfn app() -> Element {\n    rsx! {\n        Stylesheet { href: asset!(\"/assets/main.css\") }\n        Router::<Route> {}\n    }\n}\n"
  },
  {
    "path": "examples/01-app-demos/image_generator_openai.rs",
    "content": "use dioxus::prelude::*;\n\nfn main() {\n    dioxus::launch(app)\n}\n\nfn app() -> Element {\n    let mut api_key = use_signal(|| \"\".to_string());\n    let mut prompt = use_signal(|| \"\".to_string());\n    let mut num_images = use_signal(|| 1.to_string());\n\n    let mut image = use_action(move || async move {\n        #[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq, Props, Clone, Default)]\n        struct ImageResponse {\n            created: i32,\n            data: Vec<UrlImage>,\n        }\n\n        #[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq, Props, Clone)]\n        struct UrlImage {\n            url: String,\n        }\n\n        if api_key.peek().is_empty() || prompt.peek().is_empty() || num_images.peek().is_empty() {\n            return dioxus::Ok(ImageResponse::default());\n        }\n\n        let res = reqwest::Client::new()\n            .post(\"https://api.openai.com/v1/images/generations\")\n            .json(&serde_json::json!({\n                \"prompt\":  prompt.cloned(),\n                \"n\": num_images.cloned().parse::<i32>().unwrap_or(1),\n                \"size\":\"1024x1024\",\n            }))\n            .bearer_auth(api_key)\n            .send()\n            .await?\n            .json::<ImageResponse>()\n            .await?;\n\n        Ok(res)\n    });\n\n    rsx! {\n        Stylesheet { href: \"https://unpkg.com/bulma@0.9.0/css/bulma.min.css\" }\n        div { class: \"container\",\n            div { class: \"columns\",\n                div { class: \"column\",\n                    input { class: \"input is-primary mt-4\",\n                        value: \"{api_key}\",\n                        r#type: \"text\",\n                        placeholder: \"Your OpenAI API Key\",\n                        oninput: move |evt| api_key.set(evt.value()),\n                    }\n                    input { class: \"input is-primary mt-4\",\n                        placeholder: \"MAX 1000 Dgts\",\n                        r#type: \"text\",\n                        value:\"{prompt}\",\n                        oninput: move |evt| prompt.set(evt.value())\n                    }\n                    input { class: \"input is-primary mt-4\",\n                        r#type: \"number\",\n                        min:\"1\",\n                        max:\"10\",\n                        value:\"{num_images}\",\n                        oninput: move |evt| num_images.set(evt.value()),\n                    }\n                }\n            }\n            button {\n                class: \"button is-primary\",\n                class: if image.pending() { \"is-loading\" },\n                onclick: move |_| {\n                    image.call();\n                },\n                \"Generate image\"\n            }\n            if let Some(Ok(image)) = image.value() {\n                for image in image.read().data.as_slice() {\n                    section { class: \"is-flex\",\n                        div { class: \"container is-fluid\",\n                            div { class: \"container has-text-centered\",\n                                div { class: \"is-justify-content-center\",\n                                    div { class: \"level\",\n                                        div { class: \"level-item\",\n                                            figure { class: \"image\", img { alt: \"\", src: \"{image.url}\", } }\n                                        }\n                                    }\n                                }\n                            }\n                        }\n                    }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "examples/01-app-demos/repo_readme.rs",
    "content": "//! The example from the readme!\n//!\n//! This example demonstrates how to create a simple counter app with dioxus. The `Signal` type wraps inner values,\n//! making them `Copy`, allowing them to be freely used in closures and async functions. `Signal` also provides\n//! helper methods like AddAssign, SubAssign, toggle, etc, to make it easy to update the value without running\n//! into lock issues.\n\nuse dioxus::prelude::*;\n\nfn main() {\n    dioxus::launch(app);\n}\n\nfn app() -> Element {\n    let mut count = use_signal(|| 0);\n\n    rsx! {\n        h1 { \"High-Five counter: {count}\" }\n        button { onclick: move |_| count += 1, \"Up high!\" }\n        button { onclick: move |_| count -= 1, \"Down low!\" }\n    }\n}\n"
  },
  {
    "path": "examples/01-app-demos/todomvc.rs",
    "content": "//! The typical TodoMVC app, implemented in Dioxus.\n\nuse dioxus::prelude::*;\nuse std::collections::HashMap;\n\nconst STYLE: Asset = asset!(\"/examples/assets/todomvc.css\");\n\nfn main() {\n    dioxus::launch(app);\n}\n\n#[derive(PartialEq, Eq, Clone, Copy)]\nenum FilterState {\n    All,\n    Active,\n    Completed,\n}\n\nstruct TodoItem {\n    checked: bool,\n    contents: String,\n}\n\nfn app() -> Element {\n    // We store the todos in a HashMap in a Signal.\n    // Each key is the id of the todo, and the value is the todo itself.\n    let mut todos = use_signal(HashMap::<u32, TodoItem>::new);\n\n    let filter = use_signal(|| FilterState::All);\n\n    // We use a simple memoized signal to calculate the number of active todos.\n    // Whenever the todos change, the active_todo_count will be recalculated.\n    let active_todo_count =\n        use_memo(move || todos.read().values().filter(|item| !item.checked).count());\n\n    // We use a memoized signal to filter the todos based on the current filter state.\n    // Whenever the todos or filter change, the filtered_todos will be recalculated.\n    // Note that we're only storing the IDs of the todos, not the todos themselves.\n    let filtered_todos = use_memo(move || {\n        let mut filtered_todos = todos\n            .read()\n            .iter()\n            .filter(|(_, item)| match filter() {\n                FilterState::All => true,\n                FilterState::Active => !item.checked,\n                FilterState::Completed => item.checked,\n            })\n            .map(|f| *f.0)\n            .collect::<Vec<_>>();\n\n        filtered_todos.sort_unstable();\n\n        filtered_todos\n    });\n\n    // Toggle all the todos to the opposite of the current state.\n    // If all todos are checked, uncheck them all. If any are unchecked, check them all.\n    let toggle_all = move |_| {\n        let check = active_todo_count() != 0;\n        for (_, item) in todos.write().iter_mut() {\n            item.checked = check;\n        }\n    };\n\n    rsx! {\n        Stylesheet { href: STYLE }\n        section { class: \"todoapp\",\n            TodoHeader { todos }\n            section { class: \"main\",\n                if !todos.read().is_empty() {\n                    input {\n                        id: \"toggle-all\",\n                        class: \"toggle-all\",\n                        r#type: \"checkbox\",\n                        onchange: toggle_all,\n                        checked: active_todo_count() == 0\n                    }\n                    label { r#for: \"toggle-all\" }\n                }\n\n                // Render the todos using the filtered_todos signal\n                // We pass the ID into the TodoEntry component so it can access the todo from the todos signal.\n                // Since we store the todos in a signal too, we also need to send down the todo list\n                ul { class: \"todo-list\",\n                    for id in filtered_todos() {\n                        TodoEntry { key: \"{id}\", id, todos }\n                    }\n                }\n\n                // We only show the footer if there are todos.\n                if !todos.read().is_empty() {\n                    ListFooter { active_todo_count, todos, filter }\n                }\n            }\n        }\n\n        // A simple info footer\n        footer { class: \"info\",\n            p { \"Double-click to edit a todo\" }\n            p {\n                \"Created by \"\n                a { href: \"http://github.com/jkelleyrtp/\", \"jkelleyrtp\" }\n            }\n            p {\n                \"Part of \"\n                a { href: \"http://todomvc.com\", \"TodoMVC\" }\n            }\n        }\n    }\n}\n\n#[component]\nfn TodoHeader(mut todos: WriteSignal<HashMap<u32, TodoItem>>) -> Element {\n    let mut draft = use_signal(|| \"\".to_string());\n    let mut todo_id = use_signal(|| 0);\n\n    let onkeydown = move |evt: KeyboardEvent| {\n        if evt.key() == Key::Enter && !draft.is_empty() {\n            let id = todo_id();\n            let todo = TodoItem {\n                checked: false,\n                contents: draft.to_string(),\n            };\n            todos.insert(id, todo);\n            todo_id += 1;\n            draft.set(\"\".to_string());\n        }\n    };\n\n    rsx! {\n        header { class: \"header\",\n            h1 { \"todos\" }\n            input {\n                class: \"new-todo\",\n                placeholder: \"What needs to be done?\",\n                value: \"{draft}\",\n                autofocus: \"true\",\n                oninput: move |evt| draft.set(evt.value()),\n                onkeydown\n            }\n        }\n    }\n}\n\n/// A single todo entry\n/// This takes the ID of the todo and the todos signal as props\n/// We can use these together to memoize the todo contents and checked state\n#[component]\nfn TodoEntry(mut todos: WriteSignal<HashMap<u32, TodoItem>>, id: u32) -> Element {\n    let mut is_editing = use_signal(|| false);\n\n    // To avoid re-rendering this component when the todo list changes, we isolate our reads to memos\n    // This way, the component will only re-render when the contents of the todo change, or when the editing state changes.\n    // This does involve taking a local clone of the todo contents, but it allows us to prevent this component from re-rendering\n    let checked = use_memo(move || todos.read().get(&id).unwrap().checked);\n    let contents = use_memo(move || todos.read().get(&id).unwrap().contents.clone());\n\n    rsx! {\n        li {\n            // Dioxus lets you use if statements in rsx to conditionally render attributes\n            // These will get merged into a single class attribute\n            class: if checked() { \"completed\" },\n            class: if is_editing() { \"editing\" },\n\n            // Some basic controls for the todo\n            div { class: \"view\",\n                input {\n                    class: \"toggle\",\n                    r#type: \"checkbox\",\n                    id: \"cbg-{id}\",\n                    checked: \"{checked}\",\n                    oninput: move |evt| todos.get_mut(&id).unwrap().checked = evt.checked()\n                }\n                label {\n                    r#for: \"cbg-{id}\",\n                    ondoubleclick: move |_| is_editing.set(true),\n                    onclick: |evt| evt.prevent_default(),\n                    \"{contents}\"\n                }\n                button {\n                    class: \"destroy\",\n                    onclick: move |evt| {\n                        evt.prevent_default();\n                        todos.remove(&id);\n                    },\n                }\n            }\n\n            // Only render the actual input if we're editing\n            if is_editing() {\n                input {\n                    class: \"edit\",\n                    value: \"{contents}\",\n                    oninput: move |evt| todos.get_mut(&id).unwrap().contents = evt.value(),\n                    autofocus: \"true\",\n                    onfocusout: move |_| is_editing.set(false),\n                    onkeydown: move |evt| {\n                        match evt.key() {\n                            Key::Enter | Key::Escape | Key::Tab => is_editing.set(false),\n                            _ => {}\n                        }\n                    }\n                }\n            }\n        }\n    }\n}\n\n#[component]\nfn ListFooter(\n    mut todos: WriteSignal<HashMap<u32, TodoItem>>,\n    active_todo_count: ReadSignal<usize>,\n    mut filter: WriteSignal<FilterState>,\n) -> Element {\n    // We use a memoized signal to calculate whether we should show the \"Clear completed\" button.\n    // This will recompute whenever the todos change, and if the value is true, the button will be shown.\n    let show_clear_completed = use_memo(move || todos.read().values().any(|todo| todo.checked));\n\n    rsx! {\n        footer { class: \"footer\",\n            span { class: \"todo-count\",\n                strong { \"{active_todo_count} \" }\n                span {\n                    match active_todo_count() {\n                        1 => \"item\",\n                        _ => \"items\",\n                    }\n                    \" left\"\n                }\n            }\n            ul { class: \"filters\",\n                for (state , state_text , url) in [\n                    (FilterState::All, \"All\", \"#/\"),\n                    (FilterState::Active, \"Active\", \"#/active\"),\n                    (FilterState::Completed, \"Completed\", \"#/completed\"),\n                ] {\n                    li {\n                        a {\n                            href: url,\n                            class: if filter() == state { \"selected\" },\n                            onclick: move |evt| {\n                                evt.prevent_default();\n                                filter.set(state)\n                            },\n                            {state_text}\n                        }\n                    }\n                }\n            }\n            if show_clear_completed() {\n                button {\n                    class: \"clear-completed\",\n                    onclick: move |_| todos.retain(|_, todo| !todo.checked),\n                    \"Clear completed\"\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "examples/01-app-demos/todomvc_store.rs",
    "content": "//! The typical TodoMVC app, implemented in Dioxus with stores. Stores let us\n//! share nested reactive state between components. They let us keep our todomvc\n//! state in a single struct without wrapping every type in a signal while still\n//! maintaining fine grained reactivity.\n\nuse dioxus::prelude::*;\nuse std::{collections::HashMap, vec};\n\nconst STYLE: Asset = asset!(\"/examples/assets/todomvc.css\");\n\n/// Deriving the store macro on a struct will automatically generate an extension trait\n/// for Store<TodoState> with method to zoom into the fields of the struct.\n///\n/// For this struct, the macro derives the following methods for Store<TodoState>:\n/// - `todos(self) -> Store<HashMap<u32, TodoItem>, _>`\n/// - `filter(self) -> Store<FilterState, _>`\n#[derive(Store, PartialEq, Clone, Debug)]\nstruct TodoState {\n    todos: HashMap<u32, TodoItem>,\n    filter: FilterState,\n}\n\n// We can also add custom methods to the store by using the `store` attribute on an impl block.\n// The store attribute turns the impl block into an extension trait for Store<TodoState>.\n// Methods that take &self will automatically get a bound that Lens: Readable<Target = TodoState>\n// Methods that take &mut self will automatically get a bound that Lens: Writable<Target = TodoState>\n#[store]\nimpl<Lens> Store<TodoState, Lens> {\n    fn active_items(&self) -> Vec<u32> {\n        let filter = self.filter().cloned();\n        let mut active_ids: Vec<u32> = self\n            .todos()\n            .iter()\n            .filter_map(|(id, item)| item.active(filter).then_some(id))\n            .collect();\n        active_ids.sort_unstable();\n        active_ids\n    }\n\n    fn incomplete_count(&self) -> usize {\n        self.todos()\n            .values()\n            .filter(|item| item.incomplete())\n            .count()\n    }\n\n    fn toggle_all(&mut self) {\n        let check = self.incomplete_count() != 0;\n        for item in self.todos().values() {\n            item.checked().set(check);\n        }\n    }\n\n    fn has_todos(&self) -> bool {\n        !self.todos().is_empty()\n    }\n}\n\n#[derive(PartialEq, Eq, Clone, Copy, Debug)]\nenum FilterState {\n    All,\n    Active,\n    Completed,\n}\n\n#[derive(Store, PartialEq, Clone, Debug)]\nstruct TodoItem {\n    checked: bool,\n    contents: String,\n}\n\nimpl TodoItem {\n    fn new(contents: impl ToString) -> Self {\n        Self {\n            checked: false,\n            contents: contents.to_string(),\n        }\n    }\n}\n\n#[store]\nimpl<Lens> Store<TodoItem, Lens> {\n    fn complete(&self) -> bool {\n        self.checked().cloned()\n    }\n\n    fn incomplete(&self) -> bool {\n        !self.complete()\n    }\n\n    fn active(&self, filter: FilterState) -> bool {\n        match filter {\n            FilterState::All => true,\n            FilterState::Active => self.incomplete(),\n            FilterState::Completed => self.complete(),\n        }\n    }\n}\n\nfn main() {\n    dioxus::launch(app);\n}\n\nfn app() -> Element {\n    // We store the state of our todo list in a store to use throughout the app.\n    let mut todos = use_store(|| TodoState {\n        todos: HashMap::new(),\n        filter: FilterState::All,\n    });\n\n    // We use a simple memo to calculate the number of active todos.\n    // Whenever the todos change, the active_todo_count will be recalculated.\n    let active_todo_count = use_memo(move || todos.incomplete_count());\n\n    // We use a memo to filter the todos based on the current filter state.\n    // Whenever the todos or filter change, the filtered_todos will be recalculated.\n    // Note that we're only storing the IDs of the todos, not the todos themselves.\n    let filtered_todos = use_memo(move || todos.active_items());\n\n    // Toggle all the todos to the opposite of the current state.\n    // If all todos are checked, uncheck them all. If any are unchecked, check them all.\n    let toggle_all = move |_| {\n        todos.toggle_all();\n    };\n\n    rsx! {\n        Stylesheet { href: STYLE }\n        section { class: \"todoapp\",\n            TodoHeader { todos }\n            section { class: \"main\",\n                if todos.has_todos() {\n                    input {\n                        id: \"toggle-all\",\n                        class: \"toggle-all\",\n                        r#type: \"checkbox\",\n                        onchange: toggle_all,\n                        checked: active_todo_count() == 0\n                    }\n                    label { r#for: \"toggle-all\" }\n                }\n\n                // Render the todos using the filtered_todos memo\n                // We pass the ID along with the hashmap into the TodoEntry component so it can access the todo from the todos store.\n                ul { class: \"todo-list\",\n                    for id in filtered_todos() {\n                        TodoEntry { key: \"{id}\", id, todos }\n                    }\n                }\n\n                // We only show the footer if there are todos.\n                if todos.has_todos() {\n                    ListFooter { active_todo_count, todos }\n                }\n            }\n        }\n\n        // A simple info footer\n        footer { class: \"info\",\n            p { \"Double-click to edit a todo\" }\n            p {\n                \"Created by \"\n                a { href: \"http://github.com/jkelleyrtp/\", \"jkelleyrtp\" }\n            }\n            p {\n                \"Part of \"\n                a { href: \"http://todomvc.com\", \"TodoMVC\" }\n            }\n        }\n    }\n}\n\n#[component]\nfn TodoHeader(mut todos: Store<TodoState>) -> Element {\n    let mut draft = use_signal(|| \"\".to_string());\n    let mut todo_id = use_signal(|| 0);\n\n    let onkeydown = move |evt: KeyboardEvent| {\n        if evt.key() == Key::Enter && !draft.is_empty() {\n            let id = todo_id();\n            let todo = TodoItem::new(draft.take());\n            todos.todos().insert(id, todo);\n            todo_id += 1;\n        }\n    };\n\n    rsx! {\n        header { class: \"header\",\n            h1 { \"todos\" }\n            input {\n                class: \"new-todo\",\n                placeholder: \"What needs to be done?\",\n                value: \"{draft}\",\n                autofocus: \"true\",\n                oninput: move |evt| draft.set(evt.value()),\n                onkeydown\n            }\n        }\n    }\n}\n\n/// A single todo entry\n/// This takes the ID of the todo and the todos store as props\n/// We can use these together to memoize the todo contents and checked state\n#[component]\nfn TodoEntry(mut todos: Store<TodoState>, id: u32) -> Element {\n    let mut is_editing = use_signal(|| false);\n\n    // When we get an item out of the store, it will only subscribe to that specific item.\n    // Since we only get the single todo item, the component will only rerender when that item changes.\n    let entry = todos.todos().get(id).unwrap();\n    let checked = entry.checked();\n    let contents = entry.contents();\n\n    rsx! {\n        li {\n            // Dioxus lets you use if statements in rsx to conditionally render attributes\n            // These will get merged into a single class attribute\n            class: if checked() { \"completed\" },\n            class: if is_editing() { \"editing\" },\n\n            // Some basic controls for the todo\n            div { class: \"view\",\n                input {\n                    class: \"toggle\",\n                    r#type: \"checkbox\",\n                    id: \"cbg-{id}\",\n                    checked: \"{checked}\",\n                    oninput: move |evt| entry.checked().set(evt.checked())\n                }\n                label {\n                    r#for: \"cbg-{id}\",\n                    ondoubleclick: move |_| is_editing.set(true),\n                    onclick: |evt| evt.prevent_default(),\n                    \"{contents}\"\n                }\n                button {\n                    class: \"destroy\",\n                    onclick: move |evt| {\n                        evt.prevent_default();\n                        todos.todos().remove(&id);\n                    },\n                }\n            }\n\n            // Only render the actual input if we're editing\n            if is_editing() {\n                input {\n                    class: \"edit\",\n                    value: \"{contents}\",\n                    oninput: move |evt| entry.contents().set(evt.value()),\n                    autofocus: \"true\",\n                    onfocusout: move |_| is_editing.set(false),\n                    onkeydown: move |evt| {\n                        match evt.key() {\n                            Key::Enter | Key::Escape | Key::Tab => is_editing.set(false),\n                            _ => {}\n                        }\n                    }\n                }\n            }\n        }\n    }\n}\n\n#[component]\nfn ListFooter(mut todos: Store<TodoState>, active_todo_count: ReadSignal<usize>) -> Element {\n    // We use a memo to calculate whether we should show the \"Clear completed\" button.\n    // This will recompute whenever the number of todos change or the checked state of an existing\n    // todo changes\n    let show_clear_completed = use_memo(move || todos.todos().values().any(|todo| todo.complete()));\n    let mut filter = todos.filter();\n\n    rsx! {\n        footer { class: \"footer\",\n            span { class: \"todo-count\",\n                strong { \"{active_todo_count} \" }\n                span {\n                    match active_todo_count() {\n                        1 => \"item\",\n                        _ => \"items\",\n                    }\n                    \" left\"\n                }\n            }\n            ul { class: \"filters\",\n                for (state , state_text , url) in [\n                    (FilterState::All, \"All\", \"#/\"),\n                    (FilterState::Active, \"Active\", \"#/active\"),\n                    (FilterState::Completed, \"Completed\", \"#/completed\"),\n                ] {\n                    li {\n                        a {\n                            href: url,\n                            class: if filter() == state { \"selected\" },\n                            onclick: move |evt| {\n                                evt.prevent_default();\n                                filter.set(state)\n                            },\n                            {state_text}\n                        }\n                    }\n                }\n            }\n            if show_clear_completed() {\n                button {\n                    class: \"clear-completed\",\n                    onclick: move |_| todos.todos().retain(|_, todo| !todo.checked),\n                    \"Clear completed\"\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "examples/01-app-demos/weather_app.rs",
    "content": "#![allow(non_snake_case)]\n\nuse dioxus::{fullstack::Loading, prelude::*};\nuse serde::{Deserialize, Serialize};\nuse std::fmt::Display;\n\nfn main() {\n    dioxus::launch(app);\n}\n\nfn app() -> Element {\n    let country = use_signal(|| WeatherLocation {\n        name: \"Berlin\".to_string(),\n        country: \"Germany\".to_string(),\n        latitude: 52.5244,\n        longitude: 13.4105,\n        id: 2950159,\n    });\n\n    let current_weather = use_loader(move || get_weather(country()));\n\n    rsx! {\n        Stylesheet { href: asset!(\"/examples/assets/weatherapp.css\") }\n        div { class: \"mx-auto p-4 bg-gray-100 h-screen flex justify-center\",\n            div { class: \"flex items-center justify-center flex-row\",\n                div { class: \"flex items-start justify-center flex-row\",\n                    SearchBox { country }\n                    div { class: \"flex flex-wrap w-full px-2\",\n                        div { class: \"bg-gray-900 text-white relative min-w-0 break-words rounded-lg overflow-hidden shadow-sm mb-4 w-full dark:bg-gray-600\",\n                            div { class: \"px-6 py-6 relative\",\n                                match current_weather {\n                                    Ok(weather) => rsx! {\n                                        CountryData {\n                                            country: country.read().clone(),\n                                            weather: weather.cloned(),\n                                        }\n                                        Forecast { weather: weather.cloned() }\n                                        div { height: \"20px\", margin_top: \"10px\",\n                                            if weather.loading() {\n                                                \"Fetching weather data...\"\n                                            }\n                                        }\n                                    },\n                                    Err(Loading::Pending(_)) => rsx! {\n                                        div { \"Loading weather data...\" }\n                                    },\n                                    Err(Loading::Failed(_)) => rsx! {\n                                        div { \"Failed to load weather data.\" }\n                                    }\n                                }\n                            }\n                        }\n                    }\n                }\n            }\n        }\n    }\n}\n\n#[allow(non_snake_case)]\n#[component]\nfn CountryData(weather: WeatherResponse, country: WeatherLocation) -> Element {\n    let today = \"Today\";\n    let max_temp = weather.daily.temperature_2m_max[0];\n    let min_temp = weather.daily.temperature_2m_min[0];\n\n    rsx! {\n        div { class: \"flex mb-4 justify-between items-center\",\n            div {\n                h5 { class: \"mb-0 font-medium text-xl\", \"{country.name} 🏞️\" }\n                h6 { class: \"mb-0\", \"{today}\" }\n            }\n            div {\n                div { class: \"flex items-center\",\n                    span { \"Temp min\" }\n                    span { class: \"px-2 inline-block\", \"👉 {min_temp}°\" }\n                }\n                div { class: \"flex items-center\",\n                    span { \"Temp max\" }\n                    span { class: \"px-2 inline-block \", \"👉 {max_temp}º\" }\n                }\n            }\n        }\n    }\n}\n\n#[allow(non_snake_case)]\n#[component]\nfn Forecast(weather: WeatherResponse) -> Element {\n    let today = (weather.daily.temperature_2m_max[0] + weather.daily.temperature_2m_max[0]) / 2.0;\n    let tomorrow =\n        (weather.daily.temperature_2m_max[1] + weather.daily.temperature_2m_max[1]) / 2.0;\n    let past_tomorrow =\n        (weather.daily.temperature_2m_max[2] + weather.daily.temperature_2m_max[2]) / 2.0;\n\n    rsx! {\n        div { class: \"px-6 pt-4 relative\",\n            div { class: \"w-full h-px bg-gray-100 mb-4\" }\n            div { p { class: \"text-center w-full mb-4\", \"👇 Forecast 📆\" } }\n            div { class: \"text-center justify-between items-center flex\",\n                div { class: \"text-center mb-0 flex items-center justify-center flex-col mx-4 w-16\",\n                    span { class: \"block my-1\", \"Today\" }\n                    span { class: \"block my-1\", \"{today}°\" }\n                }\n                div { class: \"text-center mb-0 flex items-center justify-center flex-col mx-8 w-16\",\n                    span { class: \"block my-1\", \"Tomorrow\" }\n                    span { class: \"block my-1\", \"{tomorrow}°\" }\n                }\n                div { class: \"text-center mb-0 flex items-center justify-center flex-col mx-2 w-30\",\n                    span { class: \"block my-1\", \"Past Tomorrow\" }\n                    span { class: \"block my-1\", \"{past_tomorrow}°\" }\n                }\n            }\n        }\n    }\n}\n\n#[component]\nfn SearchBox(mut country: WriteSignal<WeatherLocation>) -> Element {\n    let mut input = use_signal(|| \"\".to_string());\n    let locations = use_loader(move || get_locations(input()));\n\n    rsx! {\n        div {\n            div { class: \"inline-flex flex-col justify-center relative text-gray-500\",\n                div { class: \"relative\",\n                    input {\n                        class: \"p-2 pl-8 rounded-lg border border-gray-200 bg-gray-200 focus:bg-white focus:outline-none focus:ring-2 focus:ring-yellow-600 focus:border-transparent\",\n                        placeholder: \"Country name\",\n                        \"type\": \"text\",\n                        autofocus: true,\n                        oninput: move |e: FormEvent| input.set(e.value())\n                    }\n                    svg {\n                        class: \"w-4 h-4 absolute left-2.5 top-3.5\",\n                        \"viewBox\": \"0 0 24 24\",\n                        fill: \"none\",\n                        stroke: \"currentColor\",\n                        xmlns: \"http://www.w3.org/2000/svg\",\n                        path {\n                            d: \"M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z\",\n                            \"stroke-linejoin\": \"round\",\n                            \"stroke-linecap\": \"round\",\n                            \"stroke-width\": \"2\"\n                        }\n                    }\n                }\n                ul { class: \"bg-white border border-gray-100 w-full mt-2 max-h-72 overflow-auto\",\n                    match locations {\n                        Ok(locs) if locs.is_empty() => rsx! {\n                            li { class: \"pl-8 pr-2 py-1 border-b-2 border-gray-100 relative\",\n                                \"No locations found\"\n                            }\n                        },\n                        Ok(locs) => rsx! {\n                            for wl in locs.read().iter().take(5).cloned() {\n                                li { class: \"pl-8 pr-2 py-1 border-b-2 border-gray-100 relative cursor-pointer hover:bg-yellow-50 hover:text-gray-900\",\n                                    onclick: move |_| country.set(wl.clone()),\n                                    MapIcon {}\n                                    b { \"{wl.name}\" }\n                                    \" · {wl.country}\"\n                                }\n                            }\n                        },\n                        Err(Loading::Pending(_)) => rsx! {\n                            li { class: \"pl-8 pr-2 py-1 border-b-2 border-gray-100 relative\",\n                                \"Searching...\"\n                            }\n                        },\n                        Err(Loading::Failed(handle)) => rsx! {\n                            li { class: \"pl-8 pr-2 py-1 border-b-2 border-gray-100 relative\",\n                                \"Failed to search: {handle.error():?}\"\n                            }\n                        }\n                    }\n                }\n            }\n        }\n    }\n}\n\nfn MapIcon() -> Element {\n    rsx! {\n        svg {\n            class: \"stroke-current absolute w-4 h-4 left-2 top-2\",\n            stroke: \"currentColor\",\n            xmlns: \"http://www.w3.org/2000/svg\",\n            \"viewBox\": \"0 0 24 24\",\n            fill: \"none\",\n            path {\n                \"stroke-linejoin\": \"round\",\n                \"stroke-width\": \"2\",\n                \"stroke-linecap\": \"round\",\n                d: \"M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z\"\n            }\n            path {\n                \"stroke-linecap\": \"round\",\n                \"stroke-linejoin\": \"round\",\n                d: \"M15 11a3 3 0 11-6 0 3 3 0 016 0z\",\n                \"stroke-width\": \"2\"\n            }\n        }\n    }\n}\n\n#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Clone)]\nstruct WeatherLocation {\n    id: usize,\n    name: String,\n    latitude: f32,\n    longitude: f32,\n    country: String,\n}\n\ntype WeatherLocations = Vec<WeatherLocation>;\n\n#[derive(Debug, Default, Serialize, Deserialize)]\nstruct SearchResponse {\n    #[serde(default)]\n    results: WeatherLocations,\n}\n\nasync fn get_locations(input: impl Display) -> Result<WeatherLocations> {\n    let res = reqwest::get(&format!(\n        \"https://geocoding-api.open-meteo.com/v1/search?name={input}\"\n    ))\n    .await?\n    .json::<SearchResponse>()\n    .await?;\n\n    Ok(res.results)\n}\n\n#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Clone)]\nstruct WeatherResponse {\n    daily: DailyWeather,\n    hourly: HourlyWeather,\n}\n\n#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Clone)]\nstruct HourlyWeather {\n    time: Vec<String>,\n    temperature_2m: Vec<f32>,\n}\n\n#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Clone)]\nstruct DailyWeather {\n    temperature_2m_min: Vec<f32>,\n    temperature_2m_max: Vec<f32>,\n}\n\nasync fn get_weather(location: WeatherLocation) -> reqwest::Result<WeatherResponse> {\n    reqwest::get(&format!(\"https://api.open-meteo.com/v1/forecast?latitude={}&longitude={}&hourly=temperature_2m&daily=temperature_2m_max,temperature_2m_min,apparent_temperature_max,apparent_temperature_min&timezone=GMT\", location.latitude, location.longitude))\n        .await?\n        .json::<WeatherResponse>()\n        .await\n}\n"
  },
  {
    "path": "examples/01-app-demos/websocket_chat.rs",
    "content": "//! A websocket chat demo using Dioxus' built-in websocket support.\n//!\n//! We setup an endpoint at `/api/chat` that accepts a `name` and `user_id` query parameter.\n//! Each client connects to that endpoint, and we use a `tokio::broadcast` channel\n//! to send messages to all connected clients.\n//!\n//! In practice, you'd use a distributed messaging system (Redis PubSub / Kafka / etc) to coordinate\n//! between multiple server instances and an additional database to persist chat history.\n\nuse dioxus::fullstack::{WebSocketOptions, Websocket, use_websocket};\nuse dioxus::prelude::*;\nuse serde::{Deserialize, Serialize};\nuse uuid::Uuid;\n\nfn main() {\n    dioxus::launch(app);\n}\n\nfn app() -> Element {\n    // store the user's current input\n    let mut input = use_signal(|| \"\".to_string());\n\n    // Select a unique id for the user, and then use that entropy to pick a random name\n    let user_id = use_signal(uuid::Uuid::new_v4);\n    let user_name = use_signal(|| {\n        match user_id.read().as_bytes()[0] % 7 {\n            0 => \"Alice\",\n            1 => \"Bob\",\n            2 => \"Eve\",\n            3 => \"Mallory\",\n            4 => \"Trent\",\n            5 => \"Peggy\",\n            6 => \"Victor\",\n            _ => \"Charlie\",\n        }\n        .to_string()\n    });\n\n    // Store the messages we've received from the server\n    let mut message_list = use_signal(Vec::<ChatMessage>::new);\n\n    // Connect to the websocket endpoint\n    let mut socket =\n        use_websocket(move || uppercase_ws(user_name(), user_id(), Default::default()));\n\n    use_future(move || async move {\n        while let Ok(msg) = socket.recv().await {\n            match msg {\n                ServerEvent::ReceiveMessage(message) => message_list.push(message),\n                ServerEvent::Connected { messages } => message_list.set(messages),\n            }\n        }\n    });\n\n    rsx! {\n        h1 { \"WebSocket Chat\" }\n        p { \"Connection status: {socket.status():?} as {user_name}\" }\n        input {\n            placeholder: \"Type a message\",\n            value: \"{input}\",\n            oninput: move |e| async move { input.set(e.value()) },\n            onkeydown: move |e| async move {\n                if e.key() == Key::Enter {\n                    _ = socket.send(ClientEvent::SendMessage(input.read().clone())).await;\n                    input.set(\"\".to_string());\n                }\n            }\n        }\n\n        div {\n            for message in message_list.read().iter().rev() {\n                pre { \"{message.name}: {message.message}\" }\n            }\n        }\n    }\n}\n\n/// The events that the client can send to the server\n#[derive(Serialize, Deserialize, Debug)]\nenum ClientEvent {\n    SendMessage(String),\n}\n\n/// The events that the server can send to the client\n#[derive(Serialize, Deserialize, Debug)]\nenum ServerEvent {\n    Connected { messages: Vec<ChatMessage> },\n    ReceiveMessage(ChatMessage),\n}\n\n#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]\nstruct ChatMessage {\n    user_id: Uuid,\n    name: String,\n    message: String,\n}\n\n#[get(\"/api/chat?name&user_id\")]\nasync fn uppercase_ws(\n    name: String,\n    user_id: Uuid,\n    options: WebSocketOptions,\n) -> Result<Websocket<ClientEvent, ServerEvent>> {\n    use std::sync::LazyLock;\n    use tokio::sync::{\n        Mutex,\n        broadcast::{self, Sender},\n    };\n\n    // Every chat app needs a chat room! For this demo, we just use a tokio broadcast channel and a mutex-protected\n    // list of messages to store chat history.\n    //\n    // We place these types in the body of this serverfn since they're not used on the client, only the server.\n    static MESSAGES: LazyLock<Mutex<Vec<ChatMessage>>> = LazyLock::new(|| Mutex::new(Vec::new()));\n    static BROADCAST: LazyLock<Sender<ChatMessage>> = LazyLock::new(|| broadcast::channel(100).0);\n\n    Ok(options.on_upgrade(move |mut socket| async move {\n        // Send back all the messages from the room to the new client\n        let messages = MESSAGES.lock().await.clone();\n        _ = socket.send(ServerEvent::Connected { messages }).await;\n\n        // Subscriber to the broadcast channel\n        let sender = BROADCAST.clone();\n        let mut broadcast = sender.subscribe();\n\n        // Announce that we've joined\n        let _ = sender.send(ChatMessage {\n            message: format!(\"{name} has connected.\"),\n            user_id,\n            name: \"[CONSOLE]\".to_string(),\n        });\n\n        // Loop poll the broadcast receiver and the websocket for new messages\n        // If we receive a message from the broadcast channel, send it to the client\n        // If we receive a message from the client, broadcast it to all other clients and save it to the message list\n        loop {\n            tokio::select! {\n                Ok(msg) = broadcast.recv() => {\n                    let _ = socket.send(ServerEvent::ReceiveMessage(msg)).await;\n                }\n                Ok(ClientEvent::SendMessage(message)) = socket.recv() => {\n                    let chat_message = ChatMessage {\n                        user_id,\n                        name: name.clone(),\n                        message,\n                    };\n                    let _ = sender.send(chat_message.clone());\n                    MESSAGES.lock().await.push(chat_message.clone());\n                },\n                else => break,\n            }\n        }\n\n        _ = sender.send(ChatMessage {\n            name: \"[CONSOLE]\".to_string(),\n            message: format!(\"{name} has disconnected.\"),\n            user_id,\n        });\n    }))\n}\n"
  },
  {
    "path": "examples/02-building-ui/disabled.rs",
    "content": "//! A simple demonstration of how to set attributes on buttons to disable them.\n//!\n//! This example also showcases the shorthand syntax for attributes, and how signals themselves implement IntoAttribute\n\nuse dioxus::prelude::*;\n\nfn main() {\n    dioxus::launch(app);\n}\n\nfn app() -> Element {\n    let mut disabled = use_signal(|| false);\n\n    rsx! {\n        div { text_align: \"center\", margin: \"20px\", display: \"flex\", flex_direction: \"column\", align_items: \"center\",\n            button {\n                onclick: move |_| disabled.toggle(),\n                \"click to \"\n                if disabled() { \"enable\" } else { \"disable\" }\n                \" the lower button\"\n            }\n            button { disabled, \"lower button\" }\n        }\n    }\n}\n"
  },
  {
    "path": "examples/02-building-ui/nested_listeners.rs",
    "content": "//! Nested Listeners\n//!\n//! This example showcases how to control event bubbling from child to parents.\n//!\n//! Both web and desktop support bubbling and bubble cancellation.\n\nuse dioxus::prelude::*;\n\nfn main() {\n    dioxus::launch(app);\n}\n\nfn app() -> Element {\n    rsx! {\n        div {\n            onclick: move |_| println!(\"clicked! top\"),\n            \"- div\"\n            button {\n                onclick: move |_| println!(\"clicked! bottom propagate\"),\n                \"Propagate\"\n            }\n            button {\n                onclick: move |evt| {\n                    println!(\"clicked! bottom no bubbling\");\n                    evt.stop_propagation();\n                },\n                \"Dont propagate\"\n            }\n            button {\n                \"Does not handle clicks - only propagate\"\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "examples/02-building-ui/svg.rs",
    "content": "//! Thanks to @japsu and their project https://github.com/japsu/jatsi for the example!\n//!\n//! This example shows how to create a simple dice rolling app using SVG and Dioxus.\n//! The `svg` element and its children have a custom namespace, and are attached using different methods than regular\n//! HTML elements. Any element can specify a custom namespace by using the `namespace` meta attribute.\n//!\n//! If you `go-to-definition` on the `svg` element, you'll see its custom namespace.\n\nuse dioxus::prelude::*;\nuse rand::{Rng, rng};\n\nfn main() {\n    dioxus::launch(|| {\n        rsx! {\n            div { user_select: \"none\", webkit_user_select: \"none\", margin_left: \"10%\", margin_right: \"10%\",\n                h1 { \"Click die to generate a new value\" }\n                div { cursor: \"pointer\", height: \"100%\", width: \"100%\", Dice {} }\n            }\n        }\n    });\n}\n\n#[component]\nfn Dice() -> Element {\n    const Y: bool = true;\n    const N: bool = false;\n    const DOTS: [(i64, i64); 7] = [(-1, -1), (-1, -0), (-1, 1), (1, -1), (1, 0), (1, 1), (0, 0)];\n    const DOTS_FOR_VALUE: [[bool; 7]; 6] = [\n        [N, N, N, N, N, N, Y],\n        [N, N, Y, Y, N, N, N],\n        [N, N, Y, Y, N, N, Y],\n        [Y, N, Y, Y, N, Y, N],\n        [Y, N, Y, Y, N, Y, Y],\n        [Y, Y, Y, Y, Y, Y, N],\n    ];\n\n    let mut value = use_signal(|| 5);\n    let active_dots = use_memo(move || &DOTS_FOR_VALUE[(value() - 1) as usize]);\n\n    rsx! {\n        svg {\n            view_box: \"-1000 -1000 2000 2000\",\n            onclick: move |event| {\n                event.prevent_default();\n                value.set(rng().random_range(1..=6))\n            },\n            rect { x: -1000, y: -1000, width: 2000, height: 2000, rx: 200, fill: \"#aaa\" }\n            for ((x, y), _) in DOTS.iter().zip(active_dots.read().iter()).filter(|(_, active)| **active) {\n                circle {\n                    cx: *x * 600,\n                    cy: *y * 600,\n                    r: 200,\n                    fill: \"#333\"\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "examples/03-assets-styling/css_modules.rs",
    "content": "//! This example shows how to use css modules with the `css_module` macro. Css modules convert css\n//! class names to unique names to avoid class name collisions.\n\nuse dioxus::prelude::*;\n\nfn main() {\n    dioxus::launch(app);\n}\n\nfn app() -> Element {\n    // Each `css_module` macro will expand the annotated struct in the current scope\n    #[css_module(\"/examples/assets/css_module1.css\")]\n    struct Styles;\n\n    #[css_module(\n        \"/examples/assets/css_module2.css\",\n        // `css_module` can take `AssetOptions` as well\n        AssetOptions::css_module()\n            .with_minify(true)\n            .with_preload(false)\n    )]\n    struct OtherStyles;\n\n    rsx! {\n        div { class: Styles::container,\n            div { class: OtherStyles::test, \"Hello, world!\" }\n            div { class: OtherStyles::highlight, \"This is highlighted\" }\n            div { class: Styles::global_class, \"This uses a global class (no hash)\" }\n        }\n    }\n}\n"
  },
  {
    "path": "examples/03-assets-styling/custom_assets.rs",
    "content": "//! A simple example on how to use assets loading from the filesystem.\n//!\n//! Dioxus provides the asset!() macro which is a convenient way to load assets from the filesystem.\n//! This ensures the asset makes it into the bundle through dependencies and is accessible in environments\n//! like web and android where assets are lazily loaded using platform-specific APIs.\n\nuse dioxus::prelude::*;\n\nstatic ASSET_PATH: Asset = asset!(\"/examples/assets/logo.png\");\n\nfn main() {\n    dioxus::launch(app);\n}\n\nfn app() -> Element {\n    rsx! {\n        div {\n            h1 { \"This should show an image:\" }\n            img { src: ASSET_PATH }\n        }\n    }\n}\n"
  },
  {
    "path": "examples/03-assets-styling/dynamic_assets.rs",
    "content": "//! This example shows how to load in custom assets with the use_asset_handler hook.\n//!\n//! This hook is currently only available on desktop and allows you to intercept any request made by the webview\n//! and respond with your own data. You could use this to load in custom videos, streams, stylesheets, images,\n//! or any asset that isn't known at compile time.\n\nuse dioxus::desktop::{use_asset_handler, wry::http::Response};\nuse dioxus::prelude::*;\n\nconst STYLE: Asset = asset!(\"/examples/assets/custom_assets.css\");\n\nfn main() {\n    dioxus::LaunchBuilder::desktop().launch(app);\n}\n\nfn app() -> Element {\n    use_asset_handler(\"logos\", |request, response| {\n        // We get the original path - make sure you handle that!\n        if request.uri().path() != \"/logos/logo.png\" {\n            return;\n        }\n\n        response.respond(Response::new(include_bytes!(\"../assets/logo.png\").to_vec()));\n    });\n\n    rsx! {\n        Stylesheet { href: STYLE }\n        h1 { \"Dynamic Assets\" }\n        img { src: \"/logos/logo.png\" }\n    }\n}\n"
  },
  {
    "path": "examples/03-assets-styling/meta.rs",
    "content": "//! This example shows how to add metadata to the page with the Meta component\n\nuse dioxus::prelude::*;\n\nfn main() {\n    dioxus::launch(app);\n}\n\nfn app() -> Element {\n    rsx! {\n        // You can use the Meta component to render a meta tag into the head of the page\n        // Meta tags are useful to provide information about the page to search engines and social media sites\n        // This example sets up meta tags for the open graph protocol for social media previews\n        document::Meta {\n            property: \"og:title\",\n            content: \"My Site\",\n        }\n        document::Meta {\n            property: \"og:type\",\n            content: \"website\",\n        }\n        document::Meta {\n            property: \"og:url\",\n            content: \"https://www.example.com\",\n        }\n        document::Meta {\n            property: \"og:image\",\n            content: \"https://example.com/image.jpg\",\n        }\n        document::Meta {\n            name: \"description\",\n            content: \"My Site is a site\",\n        }\n    }\n}\n"
  },
  {
    "path": "examples/03-assets-styling/meta_elements.rs",
    "content": "//! This example shows how to add metadata to the page with the Meta component\n\nuse dioxus::prelude::*;\n\nfn main() {\n    dioxus::launch(app);\n}\n\nfn app() -> Element {\n    rsx! {\n        // You can use the Meta component to render a meta tag into the head of the page\n        // Meta tags are useful to provide information about the page to search engines and social media sites\n        // This example sets up meta tags for the open graph protocol for social media previews\n        Meta { property: \"og:title\", content: \"My Site\" }\n        Meta { property: \"og:type\", content: \"website\" }\n        Meta { property: \"og:url\", content: \"https://www.example.com\" }\n        Meta { property: \"og:image\", content: \"https://example.com/image.jpg\" }\n        Meta { name: \"description\", content: \"My Site is a site\" }\n    }\n}\n"
  },
  {
    "path": "examples/04-managing-state/context_api.rs",
    "content": "//! Demonstrates cross-component state sharing using Dioxus' Context API\n//!\n//! Features:\n//! - Context provider initialization\n//! - Nested component consumption\n//! - Reactive state updates\n//! - Error handling for missing context\n//! - Platform-agnostic implementation\n\nuse dioxus::prelude::*;\n\nconst STYLE: Asset = asset!(\"/examples/assets/context_api.css\");\n\nfn main() {\n    launch(app);\n}\n\n#[component]\nfn app() -> Element {\n    // Provide theme context at root level\n    use_context_provider(|| Signal::new(Theme::Light));\n\n    rsx! {\n        Stylesheet { href: STYLE }\n        main {\n            class: \"main-container\",\n\n            h1 { \"Theme Switcher\" }\n            ThemeControls {}\n            ThemeDisplay {}\n        }\n    }\n}\n\n#[derive(Clone, Copy, PartialEq, Debug)]\nenum Theme {\n    Light,\n    Dark,\n}\n\nimpl Theme {\n    fn stylesheet(&self) -> &'static str {\n        match self {\n            Theme::Light => \"light-theme\",\n            Theme::Dark => \"dark-theme\",\n        }\n    }\n}\n\n#[component]\nfn ThemeControls() -> Element {\n    let mut theme = use_theme_context();\n    let current_theme = *theme.read();\n    rsx! {\n        div {\n            class: \"controls\",\n            button {\n                class: \"btn\",\n                onclick: move |_| theme.set(Theme::Light),\n                disabled: current_theme== Theme::Light,\n                \"Switch to Light\"\n            }\n            button {\n                class: \"btn\",\n                onclick: move |_| theme.set(Theme::Dark),\n                disabled: current_theme == Theme::Dark,\n                \"Switch to Dark\"\n            }\n        }\n    }\n}\n\n#[component]\nfn ThemeDisplay() -> Element {\n    let theme = use_theme_context();\n\n    rsx! {\n        div {\n            class: \"display {theme.read().stylesheet()}\",\n            p { \"Current theme: {theme:?}\" }\n            p { \"Try switching themes using the buttons above!\" }\n        }\n    }\n}\n\nfn use_theme_context() -> Signal<Theme> {\n    try_use_context::<Signal<Theme>>()\n        .expect(\"Theme context not found. Ensure <App> is the root component.\")\n}\n"
  },
  {
    "path": "examples/04-managing-state/error_handling.rs",
    "content": "//! This example showcases how to use the ErrorBoundary component to handle errors in your app.\n//!\n//! The ErrorBoundary component is a special component that can be used to catch panics and other errors that occur.\n//! By default, Dioxus will catch panics during rendering, async, and handlers, and bubble them up to the nearest\n//! error boundary. If no error boundary is present, it will be caught by the root error boundary and the app will\n//! render the error message as just a string.\n//!\n//! NOTE: In wasm, panics can currently not be caught by the error boundary. This is a limitation of WASM in rust.\n#![allow(non_snake_case)]\n\nuse dioxus::prelude::*;\n\nfn main() {\n    dioxus::launch(|| rsx! { Router::<Route> {} });\n}\n\n/// You can use an ErrorBoundary to catch errors in children and display a warning\nfn Simple() -> Element {\n    rsx! {\n        GoBackButton { \"Home\" }\n        ErrorBoundary {\n            handle_error: |error: ErrorContext| rsx! {\n                h1 { \"An error occurred\" }\n                pre { \"{error:#?}\" }\n            },\n            ParseNumber {}\n        }\n    }\n}\n\n#[component]\nfn ParseNumber() -> Element {\n    rsx! {\n        h1 { \"Error handler demo\" }\n        button {\n            onclick: move |_| {\n                // You can return a result from an event handler which lets you easily quit rendering early if something fails\n                let data: i32 = \"0.5\".parse()?;\n\n                println!(\"parsed {data}\");\n\n                Ok(())\n            },\n            \"Click to throw an error\"\n        }\n    }\n}\n\n// You can provide additional context for the Error boundary to visualize\nfn Show() -> Element {\n    rsx! {\n        GoBackButton { \"Home\" }\n        div {\n            ErrorBoundary {\n                handle_error: |errors: ErrorContext| {\n                    rsx! {\n                        for error in errors.error() {\n                            // You can downcast the error to see if it's a specific type and render something specific for it\n                            if let Some(_error) = error.downcast_ref::<std::num::ParseIntError>() {\n                                div {\n                                    background_color: \"red\",\n                                    border: \"black\",\n                                    border_width: \"2px\",\n                                    border_radius: \"5px\",\n                                    p { \"Failed to parse data\" }\n                                    Link {\n                                        to: Route::Home {},\n                                        \"Go back to the homepage\"\n                                    }\n                                }\n                            } else {\n                                pre {\n                                    color: \"red\",\n                                    \"{error}\"\n                                }\n                            }\n                        }\n                    }\n                },\n                ParseNumberWithShow {}\n            }\n        }\n    }\n}\n\n#[component]\nfn ParseNumberWithShow() -> Element {\n    rsx! {\n        h1 { \"Error handler demo\" }\n        button {\n            onclick: move |_| {\n                let request_data = \"0.5\";\n                let data: i32 = request_data.parse()?;\n                println!(\"parsed {data}\");\n                Ok(())\n            },\n            \"Click to throw an error\"\n        }\n    }\n}\n\n// On desktop, dioxus will catch panics in components and insert an error automatically\nfn Panic() -> Element {\n    rsx! {\n        GoBackButton { \"Home\" }\n        ErrorBoundary {\n            handle_error: |errors: ErrorContext| rsx! {\n                h1 { \"Another error occurred\" }\n                pre { \"{errors:#?}\" }\n            },\n            ComponentPanic {}\n        }\n    }\n}\n\n#[component]\nfn ComponentPanic() -> Element {\n    panic!(\"This component panics\")\n}\n\n#[derive(Routable, Clone, Debug, PartialEq)]\nenum Route {\n    #[route(\"/\")]\n    Home {},\n    #[route(\"/simple\")]\n    Simple {},\n    #[route(\"/panic\")]\n    Panic {},\n    #[route(\"/show\")]\n    Show {},\n}\n\nfn Home() -> Element {\n    rsx! {\n        ul {\n            li {\n                Link {\n                    to: Route::Simple {},\n                    \"Simple errors\"\n                }\n            }\n            li {\n                Link {\n                    to: Route::Panic {},\n                    \"Capture panics\"\n                }\n            }\n            li {\n                Link {\n                    to: Route::Show {},\n                    \"Show errors\"\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "examples/04-managing-state/global.rs",
    "content": "//! Example: Global signals and memos\n//!\n//! This example demonstrates how to use global signals and memos to share state across your app.\n//! Global signals are simply signals that live on the root of your app and are accessible from anywhere. To access a\n//! global signal, simply use its methods like a regular signal. Calls to `read` and `write` will be forwarded to the\n//! signal at the root of your app using the `static`'s address.\n\nuse dioxus::prelude::*;\n\nconst STYLE: Asset = asset!(\"/examples/assets/counter.css\");\n\nstatic COUNT: GlobalSignal<i32> = Signal::global(|| 0);\nstatic DOUBLED_COUNT: GlobalMemo<i32> = Memo::global(|| COUNT() * 2);\n\nfn main() {\n    dioxus::launch(app);\n}\n\nfn app() -> Element {\n    rsx! {\n        Stylesheet { href: STYLE }\n        Increment {}\n        Decrement {}\n        Reset {}\n        Display {}\n    }\n}\n\n#[component]\nfn Increment() -> Element {\n    rsx! {\n        button { onclick: move |_| *COUNT.write() += 1, \"Up high!\" }\n    }\n}\n\n#[component]\nfn Decrement() -> Element {\n    rsx! {\n        button { onclick: move |_| *COUNT.write() -= 1, \"Down low!\" }\n    }\n}\n\n#[component]\nfn Display() -> Element {\n    rsx! {\n        p { \"Count: {COUNT}\" }\n        p { \"Doubled: {DOUBLED_COUNT}\" }\n    }\n}\n\n#[component]\nfn Reset() -> Element {\n    // Not all write methods are available on global signals since `write` requires a mutable reference. In these cases,\n    // We can simply pull out the actual signal using the signal() method.\n    let mut as_signal = use_hook(|| COUNT.resolve());\n\n    rsx! {\n        button { onclick: move |_| as_signal.set(0), \"Reset\" }\n    }\n}\n"
  },
  {
    "path": "examples/04-managing-state/memo_chain.rs",
    "content": "//! This example shows how you can chain memos together to create a tree of memoized values.\n//!\n//! Memos will also pause when their parent component pauses, so if you have a memo that depends on a signal, and the\n//! signal pauses, the memo will pause too.\n\nuse dioxus::prelude::*;\n\nfn main() {\n    dioxus::launch(app);\n}\n\nfn app() -> Element {\n    let mut value = use_signal(|| 0);\n    let mut depth = use_signal(|| 0_usize);\n    let items = use_memo(move || (0..depth()).map(|f| f as _).collect::<Vec<isize>>());\n    let state = use_memo(move || value() + 1);\n\n    println!(\"rendering app\");\n\n    rsx! {\n        button { onclick: move |_| value += 1, \"Increment\" }\n        button { onclick: move |_| depth += 1, \"Add depth\" }\n        button { onclick: move |_| depth -= 1, \"Remove depth\" }\n        if depth() > 0 {\n            Child { depth, items, state }\n        }\n    }\n}\n\n#[component]\nfn Child(state: Memo<isize>, items: Memo<Vec<isize>>, depth: ReadSignal<usize>) -> Element {\n    // These memos don't get re-computed when early returns happen\n    let state = use_memo(move || state() + 1);\n    let item = use_memo(move || items()[depth() - 1]);\n    let depth = use_memo(move || depth() - 1);\n\n    println!(\"rendering child: {}\", depth());\n\n    rsx! {\n        h3 { \"Depth({depth})-Item({item}): {state}\"}\n        if depth() > 0 {\n            Child { depth, state, items }\n        }\n    }\n}\n"
  },
  {
    "path": "examples/04-managing-state/reducer.rs",
    "content": "//! Example: Reducer Pattern\n//! -----------------\n//!\n//! This example shows how to encapsulate state in dioxus components with the reducer pattern.\n//! This pattern is very useful when a single component can handle many types of input that can\n//! be represented by an enum.\n\nuse dioxus::prelude::*;\n\nconst STYLE: Asset = asset!(\"/examples/assets/radio.css\");\n\nfn main() {\n    dioxus::launch(app);\n}\n\nfn app() -> Element {\n    let mut state = use_signal(|| PlayerState { is_playing: false });\n\n    rsx!(\n        Stylesheet { href: STYLE }\n        h1 {\"Select an option\"}\n\n        // Add some cute animations if the radio is playing!\n        div { class: if state.read().is_playing { \"bounce\" },\n            \"The radio is... \" {state.read().is_playing()} \"!\"\n        }\n\n        button { id: \"play\", onclick: move |_| state.write().reduce(PlayerAction::Pause), \"Pause\" }\n        button { id: \"pause\", onclick: move |_| state.write().reduce(PlayerAction::Play), \"Play\" }\n    )\n}\n\nenum PlayerAction {\n    Pause,\n    Play,\n}\n\n#[derive(Clone)]\nstruct PlayerState {\n    is_playing: bool,\n}\n\nimpl PlayerState {\n    fn reduce(&mut self, action: PlayerAction) {\n        match action {\n            PlayerAction::Pause => self.is_playing = false,\n            PlayerAction::Play => self.is_playing = true,\n        }\n    }\n    fn is_playing(&self) -> &'static str {\n        match self.is_playing {\n            true => \"currently playing!\",\n            false => \"not currently playing\",\n        }\n    }\n}\n"
  },
  {
    "path": "examples/04-managing-state/signals.rs",
    "content": "//! A simple example demonstrating how to use signals to modify state from several different places.\n//!\n//! This simple example implements a counter that can be incremented, decremented, and paused. It also demonstrates\n//! that background tasks in use_futures can modify the value as well.\n//!\n//! Most signals implement Into<ReadSignal<T>>, making ReadSignal a good default type when building new\n//! library components that don't need to modify their values.\n\nuse async_std::task::sleep;\nuse dioxus::prelude::*;\n\nfn main() {\n    dioxus::launch(app);\n}\n\nfn app() -> Element {\n    let mut running = use_signal(|| true);\n    let mut count = use_signal(|| 0);\n    let mut saved_values = use_signal(|| vec![0.to_string()]);\n\n    // use_memo will recompute the value of the signal whenever the captured signals change\n    let doubled_count = use_memo(move || count() * 2);\n\n    // use_effect will subscribe to any changes in the signal values it captures\n    // effects will always run after first mount and then whenever the signal values change\n    use_effect(move || println!(\"Count changed to {count}\"));\n\n    // We can do early returns and conditional rendering which will pause all futures that haven't been polled\n    if count() > 30 {\n        return rsx! {\n            h1 { \"Count is too high!\" }\n            button { onclick: move |_| count.set(0), \"Press to reset\" }\n        };\n    }\n\n    // use_future will spawn an infinitely running future that can be started and stopped\n    use_future(move || async move {\n        loop {\n            if running() {\n                count += 1;\n            }\n            sleep(std::time::Duration::from_millis(400)).await;\n        }\n    });\n\n    // use_resource will spawn a future that resolves to a value\n    let _slow_count = use_resource(move || async move {\n        sleep(std::time::Duration::from_millis(200)).await;\n        count() * 2\n    });\n\n    rsx! {\n        h1 { \"High-Five counter: {count}\" }\n        button { onclick: move |_| count += 1, \"Up high!\" }\n        button { onclick: move |_| count -= 1, \"Down low!\" }\n        button { onclick: move |_| running.toggle(), \"Toggle counter\" }\n        button { onclick: move |_| saved_values.push(count.to_string()), \"Save this value\" }\n        button { onclick: move |_| saved_values.clear(), \"Clear saved values\" }\n\n        // We can do boolean operations on the current signal value\n        if count() > 5 {\n            h2 { \"High five!\" }\n        }\n\n        // We can cleanly map signals with iterators\n        for value in saved_values.iter() {\n            h3 { \"Saved value: {value}\" }\n        }\n\n        // We can also use the signal value as a slice\n        if let [first, .., last] = saved_values.read().as_slice() {\n            li { \"First and last: {first}, {last}\" }\n        } else {\n            \"No saved values\"\n        }\n\n        // You can pass a value directly to any prop that accepts a signal\n        Child { count: doubled_count() }\n        Child { count: doubled_count }\n    }\n}\n\n#[component]\nfn Child(mut count: ReadSignal<i32>) -> Element {\n    println!(\"rendering child with count {count}\");\n\n    rsx! {\n        h1 { \"{count}\" }\n    }\n}\n"
  },
  {
    "path": "examples/05-using-async/backgrounded_futures.rs",
    "content": "//! Backgrounded futures example\n//!\n//! This showcases how use_future, use_memo, and use_effect will stop running if the component returns early.\n//! Generally you should avoid using early returns around hooks since most hooks are not properly designed to\n//! handle early returns. However, use_future *does* pause the future when the component returns early, and so\n//! hooks that build on top of it like use_memo and use_effect will also pause.\n//!\n//! This example is more of a demonstration of the behavior than a practical use case, but it's still interesting to see.\n\nuse async_std::task::sleep;\nuse dioxus::prelude::*;\n\nfn main() {\n    dioxus::launch(app);\n}\n\nfn app() -> Element {\n    let mut show_child = use_signal(|| true);\n    let mut count = use_signal(|| 0);\n\n    let child = use_memo(move || {\n        rsx! {\n            Child { count }\n        }\n    });\n\n    rsx! {\n        // Some toggle/controls to show the child or increment the count\n        button { onclick: move |_| show_child.toggle(), \"Toggle child\" }\n        button { onclick: move |_| count += 1, \"Increment count\" }\n\n        if show_child() {\n            {child()}\n        }\n    }\n}\n\n#[component]\nfn Child(count: WriteSignal<i32>) -> Element {\n    let mut early_return = use_signal(|| false);\n\n    let early = rsx! {\n        button { onclick: move |_| early_return.toggle(), \"Toggle {early_return} early return\" }\n    };\n\n    if early_return() {\n        return early;\n    }\n\n    use_future(move || async move {\n        loop {\n            sleep(std::time::Duration::from_millis(100)).await;\n            println!(\"Child\")\n        }\n    });\n\n    use_effect(move || println!(\"Child count: {}\", count()));\n\n    rsx! {\n        div {\n            \"Child component\"\n            {early}\n        }\n    }\n}\n"
  },
  {
    "path": "examples/05-using-async/clock.rs",
    "content": "//! A simple little clock that updates the time every few milliseconds.\n\nuse async_std::task::sleep;\nuse dioxus::prelude::*;\nuse web_time::Instant;\n\nfn main() {\n    dioxus::launch(app);\n}\n\nfn app() -> Element {\n    let mut millis = use_signal(|| 0);\n\n    use_future(move || async move {\n        // Save our initial time\n        let start = Instant::now();\n\n        loop {\n            sleep(std::time::Duration::from_millis(27)).await;\n\n            // Update the time, using a more precise approach of getting the duration since we started the timer\n            millis.set(start.elapsed().as_millis() as i64);\n        }\n    });\n\n    // Format the time as a string\n    // This is rather cheap so it's fine to leave it in the render function\n    let time = format!(\n        \"{:02}:{:02}:{:03}\",\n        millis() / 1000 / 60 % 60,\n        millis() / 1000 % 60,\n        millis() % 1000\n    );\n\n    rsx! {\n        document::Stylesheet { href: asset!(\"/examples/assets/clock.css\") }\n        div { id: \"app\",\n            div { id: \"title\", \"Carpe diem 🎉\" }\n            div { id: \"clock-display\", \"{time}\" }\n        }\n    }\n}\n"
  },
  {
    "path": "examples/05-using-async/future.rs",
    "content": "//! A simple example that shows how to use the use_future hook to run a background task.\n//!\n//! use_future won't return a value, analogous to use_effect.\n//! If you want to return a value from a future, use use_resource instead.\n\nuse async_std::task::sleep;\nuse dioxus::prelude::*;\n\nfn main() {\n    dioxus::launch(app);\n}\n\nfn app() -> Element {\n    let mut count = use_signal(|| 0);\n\n    // use_future is a non-reactive hook that simply runs a future in the background.\n    // You can use the UseFuture handle to pause, resume, restart, or cancel the future.\n    use_future(move || async move {\n        loop {\n            sleep(std::time::Duration::from_millis(200)).await;\n            count += 1;\n        }\n    });\n\n    // use_effect is a reactive hook that runs a future when signals captured by its reactive context\n    // are modified. This is similar to use_effect in React and is useful for running side effects\n    // that depend on the state of your component.\n    //\n    // Generally, we recommend performing async work in event as a reaction to a user event.\n    use_effect(move || {\n        spawn(async move {\n            sleep(std::time::Duration::from_secs(5)).await;\n            count.set(100);\n        });\n    });\n\n    // You can run futures directly from event handlers as well. Note that if the event handler is\n    // fired multiple times, the future will be spawned multiple times.\n    rsx! {\n        h1 { \"Current count: {count}\" }\n        button {\n            onclick: move |_| async move {\n                sleep(std::time::Duration::from_millis(200)).await;\n                count.set(0);\n            },\n            \"Slowly reset the count\"\n        }\n    }\n}\n"
  },
  {
    "path": "examples/05-using-async/streams.rs",
    "content": "//! Handle async streams using use_future and awaiting the next value.\n\nuse async_std::task::sleep;\nuse dioxus::prelude::*;\nuse futures_util::{Stream, StreamExt, future, stream};\n\nfn main() {\n    dioxus::launch(app);\n}\n\nfn app() -> Element {\n    let mut count = use_signal(|| 10);\n\n    use_future(move || async move {\n        // Create the stream.\n        // This could be a network request, a file read, or any other async operation.\n        let mut stream = some_stream();\n\n        // Await the next value from the stream.\n        while let Some(second) = stream.next().await {\n            count.set(second);\n        }\n    });\n\n    rsx! {\n        h1 { \"High-Five counter: {count}\" }\n    }\n}\n\nfn some_stream() -> std::pin::Pin<Box<dyn Stream<Item = i32>>> {\n    Box::pin(\n        stream::once(future::ready(0)).chain(stream::iter(1..).then(|second| async move {\n            sleep(std::time::Duration::from_secs(1)).await;\n            second\n        })),\n    )\n}\n"
  },
  {
    "path": "examples/05-using-async/suspense.rs",
    "content": "//! Suspense in Dioxus\n//!\n//! Suspense allows components to bubble up loading states to parent components, simplifying data fetching.\n\nuse dioxus::prelude::*;\n\nfn main() {\n    dioxus::launch(app)\n}\n\nfn app() -> Element {\n    rsx! {\n        div {\n            h1 { \"Dogs are very important\" }\n            p {\n                \"The dog or domestic dog (Canis familiaris[4][5] or Canis lupus familiaris[5])\"\n                \"is a domesticated descendant of the wolf which is characterized by an upturning tail.\"\n                \"The dog derived from an ancient, extinct wolf,[6][7] and the modern grey wolf is the\"\n                \"dog's nearest living relative.[8] The dog was the first species to be domesticated,[9][8]\"\n                \"by hunter–gatherers over 15,000 years ago,[7] before the development of agriculture.[1]\"\n            }\n            h3 { \"Illustrious Dog Photo\" }\n            ErrorBoundary { handle_error: |_| rsx! { p { \"Error loading doggos\" } },\n                SuspenseBoundary { fallback: move |_| rsx! { \"Loading doggos...\" },\n                    Doggo {}\n                }\n            }\n        }\n    }\n}\n\n#[component]\nfn Doggo() -> Element {\n    // `use_loader` returns a Result<Loader<T>, Loading>. Loading can either be \"Pending\" or \"Failed\".\n    // When we use the `?` operator, the pending/error state will be thrown to the nearest Suspense or Error boundary.\n    //\n    // During SSR, `use_loader` will serialize the contents of the fetch, and during hydration, the client will\n    // use the pre-fetched data instead of re-fetching to render.\n    let mut dog = use_loader(move || async move {\n        #[derive(serde::Deserialize, serde::Serialize, PartialEq)]\n        struct DogApi {\n            message: String,\n        }\n\n        reqwest::get(\"https://dog.ceo/api/breeds/image/random/\")\n            .await?\n            .json::<DogApi>()\n            .await\n    })?;\n\n    rsx! {\n        button { onclick: move |_| dog.restart(), \"Click to fetch another doggo\" }\n        div {\n            img {\n                max_width: \"500px\",\n                max_height: \"500px\",\n                src: \"{dog.read().message}\"\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "examples/06-routing/flat_router.rs",
    "content": "//! This example shows how to use the `Router` component to create a simple navigation system.\n//! The more complex router example uses all of the router features, while this simple example showcases\n//! just the `Layout` and `Route` features.\n//!\n//! Layouts let you wrap chunks of your app with a component. This is useful for things like a footers, heeaders, etc.\n//! Routes are enum variants with that match the name of a component in scope. This way you can create a new route\n//! in your app simply by adding the variant to the enum and creating a new component with the same name. You can\n//! override this of course.\n\nuse dioxus::prelude::*;\n\nconst STYLE: Asset = asset!(\"/examples/assets/flat_router.css\");\n\nfn main() {\n    dioxus::launch(|| {\n        rsx! {\n            Stylesheet { href: STYLE }\n            Router::<Route> {}\n        }\n    })\n}\n\n#[derive(Routable, Clone)]\n#[rustfmt::skip]\nenum Route {\n    #[layout(Footer)] // wrap the entire app in a footer\n        #[route(\"/\")]\n        Home {},\n\n        #[route(\"/games\")]\n        Games {},\n\n        #[route(\"/play\")]\n        Play {},\n\n        #[route(\"/settings\")]\n        Settings {},\n}\n\n#[component]\nfn Footer() -> Element {\n    rsx! {\n        nav {\n            Link { to: Route::Home {}, class: \"nav-btn\", \"Home\" }\n            Link { to: Route::Games {}, class: \"nav-btn\", \"Games\" }\n            Link { to: Route::Play {}, class: \"nav-btn\", \"Play\" }\n            Link { to: Route::Settings {}, class: \"nav-btn\", \"Settings\" }\n        }\n        div { id: \"content\",\n            Outlet::<Route> {}\n        }\n    }\n}\n\n#[component]\nfn Home() -> Element {\n    rsx!(\n        h1 { \"Home\" }\n        p { \"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\" }\n    )\n}\n\n#[component]\nfn Games() -> Element {\n    rsx!(\n        h1 { \"Games\" }\n        // Dummy text that talks about video games\n        p { \"Lorem games are sit amet  Sed do eiusmod tempor et dolore magna aliqua.\" }\n    )\n}\n\n#[component]\nfn Play() -> Element {\n    rsx!(\n        h1 { \"Play\" }\n        p { \"Always play with your full heart adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\" }\n    )\n}\n\n#[component]\nfn Settings() -> Element {\n    rsx!(\n        h1 { \"Settings\" }\n        p { \"Settings are consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\" }\n    )\n}\n"
  },
  {
    "path": "examples/06-routing/hash_fragment_state.rs",
    "content": "//! This example shows how to use the hash segment to store state in the url.\n//!\n//! You can set up two way data binding between the url hash and signals.\n//!\n//! Run this example on desktop with  \n//! ```sh\n//! dx serve --example hash_fragment_state --features=ciborium,base64\n//! ```\n//! Or on web with\n//! ```sh\n//! dx serve --platform web --features web --example hash_fragment_state --features=ciborium,base64 -- --no-default-features\n//! ```\n\nuse std::{fmt::Display, str::FromStr};\n\nuse base64::engine::general_purpose::STANDARD;\nuse base64::Engine;\nuse dioxus::prelude::*;\nuse serde::{Deserialize, Serialize};\n\nfn main() {\n    dioxus::launch(|| {\n        rsx! {\n            Router::<Route> {}\n        }\n    });\n}\n\n#[derive(Routable, Clone, Debug, PartialEq)]\n#[rustfmt::skip]\nenum Route {\n    #[route(\"/#:url_hash\")]\n    Home {\n        url_hash: State,\n    },\n}\n\n// You can use a custom type with the hash segment as long as it implements Display, FromStr and Default\n#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq)]\nstruct State {\n    counters: Vec<usize>,\n}\n\n// Display the state in a way that can be parsed by FromStr\nimpl Display for State {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        let mut serialized = Vec::new();\n        if ciborium::into_writer(self, &mut serialized).is_ok() {\n            write!(f, \"{}\", STANDARD.encode(serialized))?;\n        }\n        Ok(())\n    }\n}\n\nenum StateParseError {\n    DecodeError(base64::DecodeError),\n    CiboriumError(ciborium::de::Error<std::io::Error>),\n}\n\nimpl std::fmt::Display for StateParseError {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        match self {\n            Self::DecodeError(err) => write!(f, \"Failed to decode base64: {}\", err),\n            Self::CiboriumError(err) => write!(f, \"Failed to deserialize: {}\", err),\n        }\n    }\n}\n\n// Parse the state from a string that was created by Display\nimpl FromStr for State {\n    type Err = StateParseError;\n\n    fn from_str(s: &str) -> Result<Self, Self::Err> {\n        let decompressed = STANDARD\n            .decode(s.as_bytes())\n            .map_err(StateParseError::DecodeError)?;\n        let parsed = ciborium::from_reader(std::io::Cursor::new(decompressed))\n            .map_err(StateParseError::CiboriumError)?;\n        Ok(parsed)\n    }\n}\n\n#[component]\nfn Home(url_hash: ReadSignal<State>) -> Element {\n    // The initial state of the state comes from the url hash\n    let mut state = use_signal(&*url_hash);\n\n    // Change the state signal when the url hash changes\n    use_memo(move || {\n        if *state.peek() != *url_hash.read() {\n            state.set(url_hash());\n        }\n    });\n\n    // Change the url hash when the state changes\n    use_memo(move || {\n        if *state.read() != *url_hash.peek() {\n            navigator().replace(Route::Home { url_hash: state() });\n        }\n    });\n\n    rsx! {\n        button {\n            onclick: move |_| state.write().counters.clear(),\n            \"Reset\"\n        }\n        button {\n            onclick: move |_| {\n                state.write().counters.push(0);\n            },\n            \"Add Counter\"\n        }\n        for counter in 0..state.read().counters.len() {\n            div {\n                button {\n                    onclick: move |_| {\n                        state.write().counters.remove(counter);\n                    },\n                    \"Remove\"\n                }\n                button {\n                    onclick: move |_| {\n                        state.write().counters[counter] += 1;\n                    },\n                    \"Count: {state.read().counters[counter]}\"\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "examples/06-routing/link.rs",
    "content": "//! How to use links in Dioxus\n//!\n//! The `router` crate gives us a `Link` component which is a much more powerful version of the standard HTML link.\n//! However, you can use the traditional `<a>` tag if you want to build your own `Link` component.\n//!\n//! The `Link` component integrates with the Router and is smart enough to detect if the link is internal or external.\n//! It also allows taking any `Route` as a target, making your links typesafe\n\nuse dioxus::prelude::*;\n\nconst STYLE: Asset = asset!(\"/examples/assets/links.css\");\n\nfn main() {\n    dioxus::launch(app);\n}\n\nfn app() -> Element {\n    rsx! (\n        Stylesheet { href: STYLE }\n        Router::<Route> {}\n    )\n}\n\n#[derive(Routable, Clone)]\n#[rustfmt::skip]\nenum Route {\n    #[layout(Header)]\n        #[route(\"/\")]\n        Home {},\n\n        #[route(\"/default-links\")]\n        DefaultLinks {},\n\n        #[route(\"/settings\")]\n        Settings {},\n}\n\n#[component]\nfn Header() -> Element {\n    rsx! {\n        h1 { \"Your app here\" }\n        nav { id: \"nav\",\n            Link { to: Route::Home {}, \"home\" }\n            Link { to: Route::DefaultLinks {}, \"default links\" }\n            Link { to: Route::Settings {}, \"settings\" }\n        }\n        Outlet::<Route> {}\n    }\n}\n\n#[component]\nfn Home() -> Element {\n    rsx!( h1 { \"Home\" } )\n}\n\n#[component]\nfn Settings() -> Element {\n    rsx!( h1 { \"Settings\" } )\n}\n\n#[component]\nfn DefaultLinks() -> Element {\n    rsx! {\n        // Just some default links\n        div { id: \"external-links\",\n            // This link will open in a webbrowser\n            a { href: \"http://dioxuslabs.com/\", \"Default link - links outside of your app\" }\n\n            // This link will do nothing - we're preventing the default behavior\n            // It will just log \"Hello Dioxus\" to the console\n            a {\n                href: \"http://dioxuslabs.com/\",\n                onclick: |event| {\n                    event.prevent_default();\n                    println!(\"Hello Dioxus\")\n                },\n                \"Custom event link - links inside of your app\"\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "examples/06-routing/query_segment_search.rs",
    "content": "//! This example shows how to access and use query segments present in an url on the web.\n//!\n//! The enum router makes it easy to use your route as state in your app. This example shows how to use the router to encode search text into the url and decode it back into a string.\n//!\n//! Run this example on desktop with\n//! ```sh\n//! dx serve --example query_segment_search\n//! ```\n//! Or on web with\n//! ```sh\n//! dx serve --platform web --features web --example query_segment_search -- --no-default-features\n//! ```\n\nuse dioxus::prelude::*;\n\nfn main() {\n    dioxus::launch(|| {\n        rsx! {\n            Router::<Route> {}\n        }\n    });\n}\n\n#[derive(Routable, Clone, Debug, PartialEq)]\n#[rustfmt::skip]\nenum Route {\n    #[route(\"/\")]\n    Home {},\n\n    // The each query segment must implement <https://docs.rs/dioxus-router/latest/dioxus_router/routable/trait.FromQueryArgument.html> and Display.\n    // You can use multiple query segments separated by `&`s.\n    #[route(\"/search?:query&:word_count\")]\n    Search {\n        query: String,\n        word_count: usize,\n    },\n}\n\n#[component]\nfn Home() -> Element {\n    // Display a list of example searches in the home page\n    rsx! {\n        ul {\n            li {\n                Link {\n                    to: Route::Search {\n                        query: \"hello\".to_string(),\n                        word_count: 1\n                    },\n                    \"Search for results containing 'hello' and at least one word\"\n                }\n            }\n            li {\n                Link {\n                    to: Route::Search {\n                        query: \"dioxus\".to_string(),\n                        word_count: 2\n                    },\n                    \"Search for results containing 'dioxus' and at least two word\"\n                }\n            }\n        }\n    }\n}\n\n// Instead of accepting String and usize directly, we use ReadSignal to make the parameters `Copy` and let us subscribe to them automatically inside the meme\n#[component]\nfn Search(query: ReadSignal<String>, word_count: ReadSignal<usize>) -> Element {\n    const ITEMS: &[&str] = &[\n        \"hello\",\n        \"world\",\n        \"hello world\",\n        \"hello dioxus\",\n        \"hello dioxus-router\",\n    ];\n\n    // Find all results that contain the query and the right number of words\n    // This memo will automatically rerun when the query or word count changes because we read the signals inside the closure\n    let results = use_memo(move || {\n        ITEMS\n            .iter()\n            .filter(|item| {\n                item.contains(&*query.read()) && item.split_whitespace().count() >= word_count()\n            })\n            .collect::<Vec<_>>()\n    });\n\n    rsx! {\n        h1 { \"Search for {query}\" }\n        input {\n            oninput: move |e| {\n                // Every time the query changes, we change the current route to the new query\n                navigator().replace(Route::Search {\n                    query: e.value(),\n                    word_count: word_count(),\n                });\n            },\n            value: \"{query}\",\n        }\n        input {\n            r#type: \"number\",\n            oninput: move |e| {\n                // Every time the word count changes, we change the current route to the new query\n                if let Ok(word_count) = e.value().parse() {\n                    navigator().replace(Route::Search {\n                        query: query(),\n                        word_count,\n                    });\n                }\n            },\n            value: \"{word_count}\",\n        }\n        for result in results.read().iter() {\n            div { \"{result}\" }\n        }\n    }\n}\n"
  },
  {
    "path": "examples/06-routing/router.rs",
    "content": "//! An advanced usage of the router with nested routes and redirects.\n//!\n//! Dioxus implements an enum-based router, which allows you to define your routes in a type-safe way.\n//! However, since we need to bake quite a bit of logic into the enum, we have to add some extra syntax.\n//!\n//! Note that you don't need to use advanced features like nest, redirect, etc, since these can all be implemented\n//! manually, but they are provided as a convenience.\n\nuse dioxus::prelude::*;\n\nconst STYLE: Asset = asset!(\"/examples/assets/router.css\");\n\nfn main() {\n    dioxus::launch(|| {\n        rsx! {\n            Stylesheet { href: STYLE }\n            Router::<Route> {}\n        }\n    });\n}\n\n// Turn off rustfmt since we're doing layouts and routes in the same enum\n#[derive(Routable, Clone, Debug, PartialEq)]\n#[rustfmt::skip]\n#[allow(clippy::empty_line_after_outer_attr)]\nenum Route {\n    // Wrap Home in a Navbar Layout\n    #[layout(NavBar)]\n        // The default route is always \"/\" unless otherwise specified\n        #[route(\"/\")]\n        Home {},\n\n        // Wrap the next routes in a layout and a nest\n        #[nest(\"/blog\")]\n        #[layout(Blog)]\n            // At \"/blog\", we want to show a list of blog posts\n            #[route(\"/\")]\n            BlogList {},\n\n            // At \"/blog/:name\", we want to show a specific blog post, using the name slug\n            #[route(\"/:name\")]\n            BlogPost { name: String },\n\n        // We need to end the blog layout and nest\n        // Note we don't need either - we could've just done `/blog/` and `/blog/:name` without nesting,\n        // but it's a bit cleaner this way\n        #[end_layout]\n        #[end_nest]\n\n    // And the regular page layout\n    #[end_layout]\n\n    // Add some redirects for the `/myblog` route\n    #[nest(\"/myblog\")]\n        #[redirect(\"/\", || Route::BlogList {})]\n        #[redirect(\"/:name\", |name: String| Route::BlogPost { name })]\n    #[end_nest]\n\n    // Finally, we need to handle the 404 page\n    #[route(\"/:..route\")]\n    PageNotFound {\n        route: Vec<String>,\n    },\n}\n\n#[component]\nfn NavBar() -> Element {\n    rsx! {\n        nav { id: \"navbar\",\n            Link { to: Route::Home {}, \"Home\" }\n            Link { to: Route::BlogList {}, \"Blog\" }\n        }\n        Outlet::<Route> {}\n    }\n}\n\n#[component]\nfn Home() -> Element {\n    rsx! { h1 { \"Welcome to the Dioxus Blog!\" } }\n}\n\n#[component]\nfn Blog() -> Element {\n    rsx! {\n        h1 { \"Blog\" }\n        Outlet::<Route> {}\n    }\n}\n\n#[component]\nfn BlogList() -> Element {\n    rsx! {\n        h2 { \"Choose a post\" }\n        div { id: \"blog-list\",\n            Link { to: Route::BlogPost { name: \"Blog post 1\".into() },\n                \"Read the first blog post\"\n            }\n            Link { to: Route::BlogPost { name: \"Blog post 2\".into() },\n                \"Read the second blog post\"\n            }\n        }\n    }\n}\n\n// We can use the `name` slug to show a specific blog post\n// In theory we could read from the filesystem or a database here\n#[component]\nfn BlogPost(name: String) -> Element {\n    let contents = match name.as_str() {\n        \"Blog post 1\" => \"This is the first blog post. It's not very interesting.\",\n        \"Blog post 2\" => \"This is the second blog post. It's not very interesting either.\",\n        _ => \"This blog post doesn't exist.\",\n    };\n\n    rsx! {\n        h2 { \"{name}\" }\n        p { \"{contents}\" }\n    }\n}\n\n#[component]\nfn PageNotFound(route: Vec<String>) -> Element {\n    rsx! {\n        h1 { \"Page not found\" }\n        p { \"We are terribly sorry, but the page you requested doesn't exist.\" }\n        pre { color: \"red\", \"log:\\nattempted to navigate to: {route:?}\" }\n    }\n}\n"
  },
  {
    "path": "examples/06-routing/router_resource.rs",
    "content": "//! Example: Updating components with use_resource\n//! -----------------\n//!\n//! This example shows how to use ReadSignal to make props reactive\n//! when linking to it from the same component, when using use_resource\n\nuse dioxus::prelude::*;\n\n#[derive(Clone, Routable, Debug, PartialEq)]\nenum Route {\n    #[route(\"/\")]\n    Home {},\n    #[route(\"/blog/:id\")]\n    Blog { id: i32 },\n}\n\nfn main() {\n    dioxus::launch(App);\n}\n\n#[component]\nfn App() -> Element {\n    rsx! {\n        Router::<Route> {}\n    }\n}\n\n// We use id: ReadSignal<i32> instead of id: i32 to make id work with reactive hooks\n// Any i32 we pass in will automatically be converted into a ReadSignal<i32>\n#[component]\nfn Blog(id: ReadSignal<i32>) -> Element {\n    async fn future(n: i32) -> i32 {\n        n\n    }\n\n    // Because we accept ReadSignal<i32> instead of i32, the resource will automatically subscribe to the id when we read it\n    let res = use_resource(move || future(id()));\n\n    match res() {\n        Some(id) => rsx! {\n            div {\n                \"Blog post {id}\"\n            }\n            for i in 0..10 {\n                div {\n                    Link { to: Route::Blog { id: i }, \"Go to Blog {i}\" }\n                }\n            }\n        },\n        None => rsx! {},\n    }\n}\n\n#[component]\nfn Home() -> Element {\n    rsx! {\n        Link {\n            to: Route::Blog {\n                id: 0\n            },\n            \"Go to blog\"\n        }\n    }\n}\n"
  },
  {
    "path": "examples/06-routing/router_restore_scroll.rs",
    "content": "use std::rc::Rc;\n\nuse dioxus::html::geometry::PixelsVector2D;\nuse dioxus::prelude::*;\n\n#[derive(Clone, Routable, Debug, PartialEq)]\nenum Route {\n    #[route(\"/\")]\n    Home {},\n    #[route(\"/blog/:id\")]\n    Blog { id: i32 },\n}\n\nfn main() {\n    dioxus::launch(App);\n}\n\n#[component]\nfn App() -> Element {\n    use_context_provider(|| Signal::new(Scroll::default()));\n\n    rsx! {\n        Router::<Route> {}\n    }\n}\n\n#[component]\nfn Blog(id: i32) -> Element {\n    rsx! {\n        GoBackButton { \"Go back\" }\n        div { \"Blog post {id}\" }\n    }\n}\n\ntype Scroll = Option<PixelsVector2D>;\n\n#[component]\nfn Home() -> Element {\n    let mut element: Signal<Option<Rc<MountedData>>> = use_signal(|| None);\n    let mut scroll = use_context::<Signal<Scroll>>();\n\n    _ = use_resource(move || async move {\n        if let (Some(element), Some(scroll)) = (element.read().as_ref(), *scroll.peek()) {\n            element\n                .scroll(scroll, ScrollBehavior::Instant)\n                .await\n                .unwrap();\n        }\n    });\n\n    rsx! {\n        div {\n            height: \"300px\",\n            overflow_y: \"auto\",\n            border: \"1px solid black\",\n\n            onmounted: move |event| element.set(Some(event.data())),\n\n            onscroll: move |_| async move {\n                if let Some(element) = element.cloned() {\n                    scroll.set(Some(element.get_scroll_offset().await.unwrap()))\n                }\n            },\n\n            for i in 0..100 {\n                div { height: \"20px\",\n\n                    Link { to: Route::Blog { id: i }, \"Blog {i}\" }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "examples/06-routing/simple_router.rs",
    "content": "//! A simple example of a router with a few routes and a nav bar.\n\nuse dioxus::prelude::*;\n\nfn main() {\n    // launch the router, using our `Route` component as the generic type\n    // This will automatically boot the app to \"/\" unless otherwise specified\n    dioxus::launch(|| rsx! { Router::<Route> {} });\n}\n\n/// By default, the Routable derive will use the name of the variant as the route\n/// You can also specify a specific component by adding the Component name to the `#[route]` attribute\n#[rustfmt::skip]\n#[derive(Routable, Clone, PartialEq)]\nenum Route {\n    // Wrap the app in a Nav layout\n    #[layout(Nav)]\n        #[route(\"/\")]\n        Homepage {},\n\n        #[route(\"/blog/:id\")]\n        Blog { id: String },\n}\n\n#[component]\nfn Homepage() -> Element {\n    rsx! { h1 { \"Welcome home\" } }\n}\n\n#[component]\nfn Blog(id: String) -> Element {\n    rsx! {\n        h1 { \"How to make: \" }\n        p { \"{id}\" }\n    }\n}\n\n/// A simple nav bar that links to the homepage and blog pages\n///\n/// The `Route` enum gives up typesafe routes, allowing us to rename routes and serialize them automatically\n#[component]\nfn Nav() -> Element {\n    rsx! {\n        nav {\n            li {\n                Link { to: Route::Homepage {}, \"Go home\" }\n            }\n            li {\n                Link {\n                    to: Route::Blog {\n                        id: \"Brownies\".to_string(),\n                    },\n                    onclick: move |_| { println!(\"Clicked on Brownies\") },\n                    \"Learn Brownies\"\n                }\n            }\n            li {\n                Link {\n                    to: Route::Blog {\n                        id: \"Cookies\".to_string(),\n                    },\n                    \"Learn Cookies\"\n                }\n            }\n        }\n        div { Outlet::<Route> {} }\n    }\n}\n"
  },
  {
    "path": "examples/07-fullstack/auth/Cargo.toml",
    "content": "[package]\nname = \"fullstack-auth-example\"\nversion = \"0.1.0\"\nedition = \"2021\"\npublish = false\n\n\n# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html\n\n[dependencies]\ndioxus-web = { workspace = true, features = [\"hydrate\"], optional = true }\ndioxus = { features = [\"fullstack\"], workspace = true }\naxum = { workspace = true, optional = true, features = [\"macros\"]}\ntokio = { workspace = true, features = [\"full\"], optional = true }\ntower-http = { workspace = true, features = [\"auth\"], optional = true }\nasync-trait = { version = \"0.1.88\", optional = true }\nsqlx = { version = \"0.8.6\", features = [\n    \"macros\",\n    \"migrate\",\n    \"postgres\",\n    \"sqlite\",\n    \"_unstable-all-types\",\n    \"tls-native-tls\",\n    \"runtime-tokio\",\n], optional = true }\nhttp = { workspace = true, optional = true }\ntower = { workspace = true, optional = true }\n\nexecute = \"0.2.13\"\nserde = { workspace = true }\nanyhow = { workspace = true }\n\n[dependencies.axum_session]\nworkspace = true\noptional = true\n\n[dependencies.axum_session_auth]\nworkspace = true\noptional = true\n\n[dependencies.axum_session_sqlx]\nworkspace = true\nfeatures = [\"sqlite\"]\noptional = true\n\n[features]\ndefault = [\"web\", \"server\"]\nserver = [\n    \"dioxus/server\",\n    \"dep:axum\",\n    \"dep:tokio\",\n    \"dep:tower-http\",\n    \"dep:async-trait\",\n    \"dep:sqlx\",\n    \"dep:axum_session\",\n    \"dep:axum_session_auth\",\n    \"dep:axum_session_sqlx\",\n    \"dep:http\",\n    \"dep:tower\",\n]\nweb = [\"dioxus/web\", \"dep:dioxus-web\"]\n"
  },
  {
    "path": "examples/07-fullstack/auth/src/auth.rs",
    "content": "//! The code here is pulled from the `axum-session-auth` crate examples, requiring little to no\n//! modification to work with dioxus fullstack.\n\nuse async_trait::async_trait;\nuse axum_session_auth::*;\nuse axum_session_sqlx::SessionSqlitePool;\nuse serde::{Deserialize, Serialize};\nuse sqlx::sqlite::SqlitePool;\nuse std::collections::HashSet;\n\npub(crate) type Session = axum_session_auth::AuthSession<User, i64, SessionSqlitePool, SqlitePool>;\npub(crate) type AuthLayer =\n    axum_session_auth::AuthSessionLayer<User, i64, SessionSqlitePool, SqlitePool>;\n\n#[derive(Debug, Clone, Serialize, Deserialize)]\npub(crate) struct User {\n    pub id: i32,\n    pub anonymous: bool,\n    pub username: String,\n    pub permissions: HashSet<String>,\n}\n\n#[derive(sqlx::FromRow, Clone)]\npub(crate) struct SqlPermissionTokens {\n    pub token: String,\n}\n\n#[async_trait]\nimpl Authentication<User, i64, SqlitePool> for User {\n    async fn load_user(userid: i64, pool: Option<&SqlitePool>) -> Result<User, anyhow::Error> {\n        let db = pool.unwrap();\n\n        #[derive(sqlx::FromRow, Clone)]\n        struct SqlUser {\n            id: i32,\n            anonymous: bool,\n            username: String,\n        }\n\n        let sqluser = sqlx::query_as::<_, SqlUser>(\"SELECT * FROM users WHERE id = $1\")\n            .bind(userid)\n            .fetch_one(db)\n            .await\n            .unwrap();\n\n        //lets just get all the tokens the user can use, we will only use the full permissions if modifying them.\n        let sql_user_perms = sqlx::query_as::<_, SqlPermissionTokens>(\n            \"SELECT token FROM user_permissions WHERE user_id = $1;\",\n        )\n        .bind(userid)\n        .fetch_all(db)\n        .await\n        .unwrap();\n\n        Ok(User {\n            id: sqluser.id,\n            anonymous: sqluser.anonymous,\n            username: sqluser.username,\n            permissions: sql_user_perms.into_iter().map(|x| x.token).collect(),\n        })\n    }\n\n    fn is_authenticated(&self) -> bool {\n        !self.anonymous\n    }\n\n    fn is_active(&self) -> bool {\n        !self.anonymous\n    }\n\n    fn is_anonymous(&self) -> bool {\n        self.anonymous\n    }\n}\n\n#[async_trait]\nimpl HasPermission<SqlitePool> for User {\n    async fn has(&self, perm: &str, _pool: &Option<&SqlitePool>) -> bool {\n        self.permissions.contains(perm)\n    }\n}\n"
  },
  {
    "path": "examples/07-fullstack/auth/src/main.rs",
    "content": "//! This example showcases how to use the `axum-session-auth` crate with Dioxus fullstack.\n//! We add the `auth::Session` extractor to our server functions to get access to the current user session.\n//!\n//! To initialize the axum router, we use `dioxus::serve` to spawn a custom axum server that creates\n//! our database, session store, and authentication layer.\n//!\n//! The `.serve_dioxus_application` method is used to mount our Dioxus app as a fallback service to\n//! handle HTML rendering and static assets.\n//!\n//! We easily share the \"permissions\" between the server and client by using a `HashSet<String>`\n//! which is serialized to/from JSON automatically by the server function system.\n\nuse std::collections::HashSet;\n\nuse dioxus::prelude::*;\n\n#[cfg(feature = \"server\")]\nmod auth;\n\nfn main() {\n    // On the client, we simply launch the app as normal, taking over the main thread\n    #[cfg(not(feature = \"server\"))]\n    dioxus::launch(app);\n\n    // On the server, we can use `dioxus::serve` to create a server that serves our app.\n    //\n    // The `serve` function takes a closure that returns a `Future` which resolves to an `axum::Router`.\n    //\n    // We return a `Router` such that dioxus sets up logging, hot-reloading, devtools, and wires up the\n    // IP and PORT environment variables to our server.\n    #[cfg(feature = \"server\")]\n    dioxus::serve(|| async {\n        use crate::auth::*;\n        use axum_session::{SessionConfig, SessionLayer, SessionStore};\n        use axum_session_auth::AuthConfig;\n        use axum_session_sqlx::SessionSqlitePool;\n        use sqlx::{sqlite::SqlitePoolOptions, Executor};\n\n        // Create an in-memory SQLite database and set up our tables\n        let db = SqlitePoolOptions::new()\n            .max_connections(20)\n            .connect_with(\"sqlite::memory:\".parse()?)\n            .await?;\n\n        // Create the tables (sessions, users)\n        db.execute(r#\"CREATE TABLE IF NOT EXISTS users ( \"id\" INTEGER PRIMARY KEY, \"anonymous\" BOOLEAN NOT NULL, \"username\" VARCHAR(256) NOT NULL )\"#,)\n            .await?;\n        db.execute(r#\"CREATE TABLE IF NOT EXISTS user_permissions ( \"user_id\" INTEGER NOT NULL, \"token\" VARCHAR(256) NOT NULL)\"#,)\n            .await?;\n\n        // Insert in some test data for two users (one anonymous, one normal)\n        db.execute(r#\"INSERT INTO users (id, anonymous, username) SELECT 1, true, 'Guest' ON CONFLICT(id) DO UPDATE SET anonymous = EXCLUDED.anonymous, username = EXCLUDED.username\"#,)\n            .await?;\n        db.execute(r#\"INSERT INTO users (id, anonymous, username) SELECT 2, false, 'Test' ON CONFLICT(id) DO UPDATE SET anonymous = EXCLUDED.anonymous, username = EXCLUDED.username\"#,)\n            .await?;\n\n        // Make sure our test user has the ability to view categories\n        db.execute(r#\"INSERT INTO user_permissions (user_id, token) SELECT 2, 'Category::View'\"#)\n            .await?;\n\n        // Create an axum router that dioxus will attach the app to\n        Ok(dioxus::server::router(app)\n            .layer(\n                AuthLayer::new(Some(db.clone()))\n                    .with_config(AuthConfig::<i64>::default().with_anonymous_user_id(Some(1))),\n            )\n            .layer(SessionLayer::new(\n                SessionStore::<SessionSqlitePool>::new(\n                    Some(db.into()),\n                    SessionConfig::default().with_table_name(\"test_table\"),\n                )\n                .await?,\n            )))\n    });\n}\n\n/// The UI for our app - is just a few buttons to call our server functions and display the results.\nfn app() -> Element {\n    let mut login = use_action(login);\n    let mut user_name = use_action(get_user_name);\n    let mut permissions = use_action(get_permissions);\n    let mut logout = use_action(logout);\n\n    let fetch_new = move |_| async move {\n        user_name.call().await;\n        permissions.call().await;\n    };\n\n    rsx! {\n        div {\n            button {\n                onclick: move |_| async move {\n                    login.call().await;\n                },\n                \"Login Test User\"\n            }\n            button {\n                onclick: move |_| async move {\n                    logout.call().await;\n                },\n                \"Logout\"\n            }\n            button {\n                onclick: fetch_new,\n                \"Fetch User Info\"\n            }\n\n            pre { \"Logged in: {login.value():?}\" }\n            pre { \"User name: {user_name.value():?}\" }\n            pre { \"Permissions: {permissions.value():?}\" }\n        }\n    }\n}\n\n/// We use the `auth::Session` extractor to get access to the current user session.\n/// This lets us modify the user session, log in/out, and access the current user.\n#[post(\"/api/user/login\", auth: auth::Session)]\npub async fn login() -> Result<()> {\n    auth.login_user(2);\n    Ok(())\n}\n\n/// Just like `login`, but this time we log out the user.\n#[post(\"/api/user/logout\", auth: auth::Session)]\npub async fn logout() -> Result<()> {\n    auth.logout_user();\n    Ok(())\n}\n\n/// We can access the current user via `auth.current_user`.\n/// We can have both anonymous user (id 1) and a logged in user (id 2).\n///\n/// Logged-in users will have more permissions which we can modify.\n#[post(\"/api/user/name\", auth: auth::Session)]\npub async fn get_user_name() -> Result<String> {\n    Ok(auth.current_user.unwrap().username)\n}\n\n/// Get the current user's permissions, guarding the endpoint with the `Auth` validator.\n/// If this returns false, we use the `or_unauthorized` extension to return a 401 error.\n#[get(\"/api/user/permissions\", auth: auth::Session)]\npub async fn get_permissions() -> Result<HashSet<String>> {\n    use crate::auth::User;\n    use axum_session_auth::{Auth, Rights};\n\n    let user = auth.current_user.unwrap();\n\n    Auth::<User, i64, sqlx::SqlitePool>::build([axum::http::Method::GET], false)\n        .requires(Rights::any([\n            Rights::permission(\"Category::View\"),\n            Rights::permission(\"Admin::View\"),\n        ]))\n        .validate(&user, &axum::http::Method::GET, None)\n        .await\n        .or_unauthorized(\"You do not have permission to view categories\")?;\n\n    Ok(user.permissions)\n}\n"
  },
  {
    "path": "examples/07-fullstack/custom_axum_serve.rs",
    "content": "//! This example demonstrates how to use `dioxus::serve` with a custom Axum router.\n//!\n//! By default, `dioxus::launch` takes over the main thread and runs the Dioxus application.\n//! However, if you want to integrate Dioxus into an existing web server or use a custom router,\n//! you can use `dioxus::serve` to create a server that serves your Dioxus application alongside\n//! other routes.\n//!\n//! `dioxus::serve` sets up an async runtime, logging, hot-reloading, crash handling, and more.\n//! You can then use the `.serve_dioxus_application` method on your router to serve the Dioxus app.\n//!\n//! `dioxus::serve` is most useful for customizing the server setup, such as adding middleware,\n//! custom routes, or integrating with existing axum backend code.\n//!\n//! Note that `dioxus::serve` is accepts a Router from `axum`. Dioxus will use the IP and PORT\n//! environment variables to determine where to bind the server. To customize the port, use environment\n//! variables or a `.env` file.\n//!\n//! On other platforms (like desktop or mobile), you'll want to use `dioxus::launch` instead and then\n//! handle async loading of data through hooks like `use_future` or `use_resource` and give the user\n//! a loading state while data is being fetched.\n\nuse dioxus::prelude::*;\n\nfn main() {\n    // On the client we just launch the app as normal.\n    #[cfg(not(feature = \"server\"))]\n    dioxus::launch(app);\n\n    // On the server, we can use `dioxus::serve` and `.serve_dioxus_application` to serve our app with routing.\n    // The `dioxus::server::router` function creates a new axum Router with the necessary routes to serve the Dioxus app.\n    #[cfg(feature = \"server\")]\n    dioxus::serve(|| async move {\n        use dioxus::server::axum::routing::{get, post};\n\n        Ok(dioxus::server::router(app)\n            .route(\"/submit\", post(|| async { \"Form submitted!\" }))\n            .route(\"/about\", get(|| async { \"About us\" }))\n            .route(\"/contact\", get(|| async { \"Contact us\" })))\n    });\n}\n\nfn app() -> Element {\n    rsx! {\n        div { \"Hello from Dioxus!\" }\n    }\n}\n"
  },
  {
    "path": "examples/07-fullstack/custom_error_page.rs",
    "content": "//! To render custom error pages, you can create a layout component that captures errors from routes\n//! with an `ErrorBoundary` and display different content based on the error type.\n//!\n//! While capturing the error, we set the appropriate HTTP status code using `FullstackContext::commit_error_status`.\n//! The router will then use this status code when doing server-side rendering (SSR).\n//!\n//! Any errors not captured by an error boundary will be handled by dioxus-ssr itself, which will render\n//! a generic error page instead.\n\nuse dioxus::prelude::*;\nuse dioxus_fullstack::{FullstackContext, StatusCode};\n\nfn main() {\n    dioxus::launch(|| {\n        rsx! {\n            Router::<Route> {}\n        }\n    });\n}\n\n#[derive(Routable, PartialEq, Clone, Debug)]\nenum Route {\n    #[layout(ErrorLayout)]\n    #[route(\"/\")]\n    Home,\n\n    #[route(\"/blog/:id\")]\n    Blog { id: u32 },\n}\n\n#[component]\nfn Home() -> Element {\n    rsx! {\n        div { \"Welcome to the home page!\" }\n        div { display: \"flex\", flex_direction: \"column\",\n            Link { to: Route::Blog { id: 1 }, \"Go to blog post 1\" }\n            Link { to: Route::Blog { id: 2 }, \"Go to blog post 2\" }\n            Link { to: Route::Blog { id: 3 }, \"Go to blog post 3 (error)\" }\n            Link { to: Route::Blog { id: 4 }, \"Go to blog post 4 (not found)\" }\n        }\n    }\n}\n\n#[component]\nfn Blog(id: u32) -> Element {\n    match id {\n        1 => rsx! { div { \"Blog post 1\" } },\n        2 => rsx! { div { \"Blog post 2\" } },\n        3 => dioxus_core::bail!(\"An error occurred while loading the blog post!\"),\n        _ => HttpError::not_found(\"Blog post not found\")?,\n    }\n}\n\n/// In our `ErrorLayout` component, we wrap the `Outlet` in an `ErrorBoundary`. This lets us attempt\n/// to downcast the error to an `HttpError` and set the appropriate status code.\n///\n/// The `commit_error_status` function will attempt to downcast the error to an `HttpError` and\n/// set the status code accordingly. Note that you can commit any status code you want with `commit_http_status`.\n///\n/// The router will automatically set the HTTP status code when doing SSR.\n#[component]\nfn ErrorLayout() -> Element {\n    rsx! {\n        ErrorBoundary {\n            handle_error: move |err: ErrorContext| {\n                let http_error = FullstackContext::commit_error_status(err.error().unwrap());\n                match http_error.status {\n                    StatusCode::NOT_FOUND => rsx! { div { \"404 - Page not found\" } },\n                    StatusCode::UNAUTHORIZED => rsx! { div { \"401 - Unauthorized\" } },\n                    StatusCode::INTERNAL_SERVER_ERROR => rsx! { div { \"500 - Internal Server Error\" } },\n                    _ => rsx! { div { \"An unknown error occurred\" } },\n                }\n            },\n            Outlet::<Route> {}\n        }\n    }\n}\n"
  },
  {
    "path": "examples/07-fullstack/desktop/Cargo.toml",
    "content": "[package]\nname = \"fullstack-desktop-example\"\nversion = \"0.1.0\"\nedition = \"2021\"\npublish = false\n\n[dependencies]\ndioxus = { workspace = true, features = [\"launch\", \"fullstack\"] }\nserde = { workspace = true }\n\n[features]\ndefault = []\nserver = [\"dioxus/server\"]\ndesktop = [\"dioxus/desktop\"]\n"
  },
  {
    "path": "examples/07-fullstack/desktop/src/main.rs",
    "content": "#![allow(non_snake_case)]\nuse dioxus::prelude::*;\n\nfn main() {\n    // Make sure to set the url of the server where server functions are hosted - they aren't always at localhost\n    #[cfg(not(feature = \"server\"))]\n    dioxus::fullstack::set_server_url(\"http://127.0.0.1:8080\");\n\n    dioxus::launch(app);\n}\n\npub fn app() -> Element {\n    let mut count = use_signal(|| 0);\n    let mut text = use_signal(|| \"...\".to_string());\n\n    rsx! {\n        h1 { \"High-Five counter: {count}\" }\n        button { onclick: move |_| count += 1, \"Up high!\" }\n        button { onclick: move |_| count -= 1, \"Down low!\" }\n        button {\n            onclick: move |_| async move {\n                let data = get_server_data().await?;\n                println!(\"Client received: {}\", data);\n                text.set(data.clone());\n                post_server_data(data).await?;\n                Ok(())\n            },\n            \"Run a server function\"\n        }\n        \"Server said: {text}\"\n    }\n}\n\n#[post(\"/api/data\")]\nasync fn post_server_data(data: String) -> ServerFnResult {\n    println!(\"Server received: {}\", data);\n    Ok(())\n}\n\n#[get(\"/api/data\")]\nasync fn get_server_data() -> ServerFnResult<String> {\n    Ok(\"Hello from the server!\".to_string())\n}\n"
  },
  {
    "path": "examples/07-fullstack/dog_app_self_hosted.rs",
    "content": "//! This example showcases a fullstack variant of the \"dog app\" demo, but with the loader and actions\n//! self-hosted instead of using the Dog API.\n\nuse dioxus::prelude::*;\n\nfn main() {\n    dioxus::launch(app);\n}\n\nfn app() -> Element {\n    // Fetch the list of breeds from the Dog API, using the `?` syntax to suspend or throw errors\n    let breed_list = use_loader(list_breeds)?;\n\n    // Whenever this action is called, it will re-run the future and return the result.\n    let mut breed = use_action(get_random_breed_image);\n\n    rsx! {\n        h1 { \"Doggo selector\" }\n        div { width: \"400px\",\n            for cur_breed in breed_list.read().iter().take(20).cloned() {\n                button { onclick: move |_| { breed.call(cur_breed.clone()); }, \"{cur_breed}\" }\n            }\n        }\n        div {\n            match breed.value() {\n                None => rsx! { div { \"Click the button to fetch a dog!\" } },\n                Some(Err(_e)) => rsx! { div { \"Failed to fetch a dog, please try again.\" } },\n                Some(Ok(res)) => rsx! { img { max_width: \"500px\", max_height: \"500px\", src: \"{res}\" } },\n            }\n        }\n\n    }\n}\n\n#[get(\"/api/breeds/list/all\")]\nasync fn list_breeds() -> Result<Vec<String>> {\n    Ok(vec![\"bulldog\".into(), \"labrador\".into(), \"poodle\".into()])\n}\n\n#[get(\"/api/breed/{breed}/images/random\")]\nasync fn get_random_breed_image(breed: String) -> Result<String> {\n    match breed.as_str() {\n        \"bulldog\" => Ok(\"https://images.dog.ceo/breeds/buhund-norwegian/hakon3.jpg\".into()),\n        \"labrador\" => Ok(\"https://images.dog.ceo/breeds/labrador/n02099712_2501.jpg\".into()),\n        \"poodle\" => Ok(\"https://images.dog.ceo/breeds/poodle-standard/n02113799_5973.jpg\".into()),\n        _ => HttpError::not_found(\"Breed not found\")?,\n    }\n}\n"
  },
  {
    "path": "examples/07-fullstack/full_request_access.rs",
    "content": "//! This example shows how to get access to the full axum request in a handler.\n//!\n//! The extra arguments in the `post` macro are passed to the handler function, but not exposed\n//! to the client. This means we can still call the endpoint from the client, but have full access\n//! to the request on the server.\n\nuse dioxus::prelude::*;\n\nfn main() {\n    dioxus::launch(app);\n}\n\nfn app() -> Element {\n    let mut file_id = use_action(full_request);\n\n    rsx! {\n        div { \"Access to full axum request\" }\n        button { onclick: move |_| file_id.call(), \"Upload file\" }\n    }\n}\n\n/// Example of accessing the full axum request in a handler\n///\n/// The `request: axum_core::extract::Request` argument is placed in the handler function, but not\n/// exposed to the client.\n#[post(\"/api/full_request_access\", request: axum_core::extract::Request)]\nasync fn full_request() -> Result<()> {\n    let headers = request.headers();\n\n    if headers.contains_key(\"x-api-key\") {\n        println!(\"API key found\");\n    } else {\n        println!(\"No API key found\");\n    }\n\n    Ok(())\n}\n"
  },
  {
    "path": "examples/07-fullstack/fullstack_hello_world.rs",
    "content": "//! A simple example using Dioxus Fullstack to call a server action.\n//!\n//! the `get`, `post`, `put`, `delete`, etc macros are used to define server actions that can be\n//! called from the client. The action can take arguments and return a value, and the client\n//! will automatically serialize and deserialize the data.\n\nuse dioxus::prelude::*;\n\nfn main() {\n    dioxus::launch(|| {\n        let mut message = use_action(get_message);\n\n        rsx! {\n            h1 { \"Server says: \"}\n            pre { \"{message:?}\"}\n            button { onclick: move |_| message.call(\"world\".into(), 30), \"Click me!\" }\n        }\n    });\n}\n\n#[get(\"/api/:name/?age\")]\nasync fn get_message(name: String, age: i32) -> Result<String> {\n    Ok(format!(\"Hello {}, you are {} years old!\", name, age))\n}\n"
  },
  {
    "path": "examples/07-fullstack/handling_errors.rs",
    "content": "//! An example of handling errors from server functions.\n//!\n//! This example showcases a few important error handling patterns when using Dioxus Fullstack.\n//!\n//! Run with:\n//!\n//! ```sh\n//! dx serve --web\n//! ```\n//!\n//! What this example shows:\n//! - You can return `anyhow::Result<T>` (i.e. `Result<T>` without an `E`) for\n//!   untyped errors with rich context (converted to HTTP 500 responses by default).\n//! - You can return `Result<T, E>` where `E` is one of:\n//!   - `HttpError` (convenience for returning HTTP status codes)\n//!   - `StatusCode` (return raw status codes)\n//!   - a custom error type that implements `From<ServerFnError>` or\n//!     is `Serialize`/`Deserialize` so it can be sent to the client.\n//! - This file demonstrates external API errors, custom typed errors, explicit\n//!   HTTP errors, and basic success cases. The UI uses `use_action` to call\n//!   server functions and shows loading/result states simply.\n//!\n//! Try running requests against the endpoints directly with `curl` or `postman` to see the actual HTTP responses!\n\nuse dioxus::fullstack::{AsStatusCode, Json, StatusCode};\nuse dioxus::prelude::*;\nuse serde::{Deserialize, Serialize};\n\nfn main() {\n    dioxus::launch(|| {\n        let mut dog_data = use_action(get_dog_data);\n        let mut dog_data_err = use_action(get_dog_data_err);\n        let mut ip_data = use_action(get_ip_data);\n        let mut custom_data = use_action(move || {\n            get_custom_encoding(Json(serde_json::json!({\n                \"example\": \"data\",\n                \"number\": 123,\n                \"array\": [1, 2, 3],\n            })))\n        });\n        let mut error_data = use_action(get_throws_error);\n        let mut typed_error_data = use_action(get_throws_typed_error);\n        let mut throws_ok_data = use_action(get_throws_ok);\n        let mut http_error_data = use_action(throws_http_error);\n        let mut http_error_context_data = use_action(throws_http_error_context);\n\n        rsx! {\n            button { onclick: move |_| { dog_data.call(); }, \"Fetch dog data\" }\n            button { onclick: move |_| { ip_data.call(); }, \"Fetch IP data\" }\n            button { onclick: move |_| { custom_data.call(); }, \"Fetch custom encoded data\" }\n            button { onclick: move |_| { error_data.call(); }, \"Fetch error data\" }\n            button { onclick: move |_| { typed_error_data.call(); }, \"Fetch typed error data\" }\n            button { onclick: move |_| { dog_data_err.call(); }, \"Fetch dog error data\" }\n            button { onclick: move |_| { throws_ok_data.call(); }, \"Fetch throws ok data\" }\n            button { onclick: move |_| { http_error_data.call(); }, \"Fetch HTTP 400\" }\n            button { onclick: move |_| { http_error_context_data.call(); }, \"Fetch HTTP 400 (context)\" }\n            button {\n                onclick: move |_| {\n                    ip_data.reset();\n                    dog_data.reset();\n                    custom_data.reset();\n                    error_data.reset();\n                    typed_error_data.reset();\n                    dog_data_err.reset();\n                    throws_ok_data.reset();\n                    http_error_data.reset();\n                    http_error_context_data.reset();\n                },\n                \"Clear data\"\n            }\n            div { display: \"flex\", flex_direction: \"column\", gap: \"8px\",\n                pre { \"Dog data: {dog_data.value():#?}\" }\n                pre { \"IP data: {ip_data.value():#?}\" }\n                pre { \"Custom encoded data: {custom_data.value():#?}\" }\n                pre { \"Error data: {error_data.value():#?}\" }\n                pre { \"Typed error data: {typed_error_data.value():#?}\" }\n                pre { \"HTTP 400 data: {http_error_data.value():#?}\" }\n                pre { \"HTTP 400 (context) data: {http_error_context_data.value():#?}\" }\n                pre { \"Dog error data: {dog_data_err.value():#?}\" }\n                pre { \"Throws ok data: {throws_ok_data.value():#?}\" }\n            }\n        }\n    });\n}\n\n/// Simple POST endpoint used to show a successful server function that returns `StatusCode`.\n#[post(\"/api/data\")]\nasync fn post_server_data(data: String) -> Result<(), StatusCode> {\n    println!(\"Server received: {}\", data);\n    Ok(())\n}\n\n/// Fetches IP info from an external service. Demonstrates propagation of external errors.\n#[get(\"/api/ip-data\")]\nasync fn get_ip_data() -> Result<serde_json::Value> {\n    Ok(reqwest::get(\"https://httpbin.org/ip\").await?.json().await?)\n}\n\n/// Fetches a random dog image (successful external API example).\n#[get(\"/api/dog-data\")]\nasync fn get_dog_data() -> Result<serde_json::Value> {\n    Ok(reqwest::get(\"https://dog.ceo/api/breeds/image/random\")\n        .await?\n        .json()\n        .await?)\n}\n\n/// Calls the Dog API with an invalid breed to trigger an external API error (e.g. 404).\n#[get(\"/api/dog-data-err\")]\nasync fn get_dog_data_err() -> Result<serde_json::Value> {\n    Ok(\n        reqwest::get(\"https://dog.ceo/api/breed/NOT_A_REAL_DOG/images\")\n            .await?\n            .json()\n            .await?,\n    )\n}\n\n/// Accepts JSON and returns a custom-encoded JSON response.\n#[post(\"/api/custom-encoding\")]\nasync fn get_custom_encoding(takes: Json<serde_json::Value>) -> Result<serde_json::Value> {\n    Ok(serde_json::json!({\n        \"message\": \"This response was encoded with a custom encoder!\",\n        \"success\": true,\n        \"you sent\": takes.0,\n    }))\n}\n\n/// Returns an untyped `anyhow` error with context (results in HTTP 500).\n#[get(\"/api/untyped-error\")]\nasync fn get_throws_error() -> Result<()> {\n    Err(None.context(\"This is an example error using anyhow::Error\")?)\n}\n\n/// Demonstrates returning an explicit HTTP error (400 Bad Request) using `HttpError`.\n#[get(\"/api/throws-http-error\")]\nasync fn throws_http_error() -> Result<()> {\n    HttpError::bad_request(\"Bad request example\")?;\n    Ok(())\n}\n\n/// Convenience example: handles an Option and returns HTTP 400 with a message if None.\n#[get(\"/api/throws-http-error-context\")]\nasync fn throws_http_error_context() -> Result<String> {\n    let res = None.or_bad_request(\"Value was None\")?;\n    Ok(res)\n}\n\n/// A simple server function that always succeeds.\n#[get(\"/api/throws-ok\")]\nasync fn get_throws_ok() -> Result<()> {\n    Ok(())\n}\n\n#[derive(thiserror::Error, Debug, Serialize, Deserialize)]\nenum MyCustomError {\n    #[error(\"bad request\")]\n    BadRequest { custom_name: String },\n\n    #[error(\"not found\")]\n    NotFound,\n\n    #[error(\"internal server error: {0}\")]\n    ServerFnError(#[from] ServerFnError),\n}\n\nimpl AsStatusCode for MyCustomError {\n    fn as_status_code(&self) -> StatusCode {\n        match self {\n            MyCustomError::BadRequest { .. } => StatusCode::BAD_REQUEST,\n            MyCustomError::NotFound => StatusCode::NOT_FOUND,\n            MyCustomError::ServerFnError(e) => e.as_status_code(),\n        }\n    }\n}\n\n/// Returns a custom typed error (serializable) so clients can handle specific cases.\n///\n/// Our custom error must implement `AsStatusCode` so it can properly set the outgoing HTTP status code.\n#[get(\"/api/typed-error\")]\nasync fn get_throws_typed_error() -> Result<(), MyCustomError> {\n    Err(MyCustomError::BadRequest {\n        custom_name: \"Invalid input\".into(),\n    })\n}\n\n/// Simple POST endpoint used to show a successful server function that returns `StatusCode`.\n#[post(\"/api/data\")]\nasync fn get_throws_serverfn_error() -> Result<(), ServerFnError> {\n    Err(ServerFnError::ServerError {\n        message: \"Unauthorized access\".to_string(),\n        code: StatusCode::UNAUTHORIZED.as_u16(),\n        details: None,\n    })\n}\n"
  },
  {
    "path": "examples/07-fullstack/header_map.rs",
    "content": "//! This example shows how you can extract a HeaderMap from requests to read custom headers.\n//!\n//! The extra arguments in the `#[get(...)]` macro are passed to the underlying axum handler,\n//! and only visible on the server. This lets you run normal axum extractors like `HeaderMap`,\n//! `TypedHeader`, `Query`, etc.\n//!\n//! Note that headers returned by the server are not always visible to the client due to CORS.\n//! Headers like `Set-Cookie` are hidden by default, and need to be explicitly allowed\n//! by the server using the `Access-Control-Expose-Headers` header (which dioxus-fullstack does not\n//! currently expose directly).\n\nuse dioxus::prelude::*;\n\nfn main() {\n    dioxus::launch(app);\n}\n\nfn app() -> Element {\n    let mut headers = use_action(get_headers);\n\n    rsx! {\n        h1 { \"Header Map Example\" }\n        button { onclick: move |_| headers.call(), \"Get Headers\" }\n        if let Some(Ok(headers)) = headers.value() {\n            p { \"Response from server:\" }\n            pre { \"{headers}\" }\n        } else {\n            p { \"No headers yet\" }\n        }\n    }\n}\n\n#[get(\"/api/example\", headers: dioxus::fullstack::HeaderMap)]\nasync fn get_headers() -> Result<String> {\n    Ok(format!(\"{:#?}\", headers))\n}\n"
  },
  {
    "path": "examples/07-fullstack/hello-world/Cargo.toml",
    "content": "[package]\nname = \"fullstack-hello-world-example\"\nversion = \"0.1.0\"\nedition = \"2021\"\npublish = false\n\n# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html\n\n[dependencies]\ndioxus = { workspace = true, features = [\"fullstack\"]}\nserde = { workspace = true }\nreqwest = { workspace = true, features = [\"json\"] }\nserde_json = { workspace = true }\nanyhow = { workspace = true }\nthiserror = { workspace = true }\n\n[features]\ndefault = []\nserver = [\"dioxus/server\"]\nweb = [\"dioxus/web\"]\n"
  },
  {
    "path": "examples/07-fullstack/hello-world/assets/hello.css",
    "content": "h1 {\n    font-family: monospace;\n}\n"
  },
  {
    "path": "examples/07-fullstack/hello-world/src/main.rs",
    "content": "//! A simple hello world example for Dioxus fullstack\n//!\n//! Run with:\n//!\n//! ```sh\n//! dx serve --web\n//! ```\n//!\n//! This example demonstrates a simple Dioxus fullstack application with a client-side counter\n//! and a server function that returns a greeting message.\n//!\n//! The `use_action` hook makes it easy to call async work (like server functions) from the client side\n//! and handle loading and error states.\n\nuse dioxus::prelude::*;\nuse dioxus_fullstack::get;\n\nfn main() {\n    dioxus::launch(app);\n}\n\nfn app() -> Element {\n    let mut count = use_signal(|| 0);\n    let mut message = use_action(get_greeting);\n\n    rsx! {\n        div { style: \"padding: 2rem; font-family: Arial, sans-serif;\",\n            h1 { \"Hello, Dioxus Fullstack!\" }\n\n            // Client-side counter - you can use any client functionality in your app!\n            div { style: \"margin: 1rem 0;\",\n                h2 { \"Client Counter: {count}\" }\n                button { onclick: move |_| count += 1, \"Increment\" }\n                button { onclick: move |_| count -= 1, \"Decrement\" }\n            }\n\n            // We can handle the action result and display loading state\n            div { style: \"margin: 1rem 0;\",\n                h2 { \"Server Greeting\" }\n                button { onclick: move |_| message.call(\"World\".to_string(), 30), \"Get Server Greeting\" }\n                if message.pending() {\n                    p { \"Loading...\" }\n                }\n                p { \"{message:#?}\" }\n            }\n        }\n    }\n}\n\n/// A simple server function that returns a greeting\n///\n/// Our server function takes a name as a path and query parameters as inputs and returns a greeting message.\n#[get(\"/api/greeting/{name}/{age}\")]\nasync fn get_greeting(name: String, age: i32) -> Result<String> {\n    Ok(format!(\n        \"Hello from the server, {}! You are {} years old. 🚀\",\n        name, age\n    ))\n}\n"
  },
  {
    "path": "examples/07-fullstack/login_form.rs",
    "content": "//! This example demonstrates how to use types like `Form`, `SetHeader`, and `TypedHeader`\n//! to create a simple login form that sets a cookie in the browser and uses it for authentication\n//! on a protected endpoint.\n//!\n//! For more information on handling forms in general, see the multipart_form example.\n//!\n//! The intent with this example is to show how to use the building blocks like `Form` and `SetHeader`\n//! to roll a simple authentication system.\n\nuse dioxus::fullstack::{Form, SetCookie, SetHeader};\nuse dioxus::prelude::*;\nuse serde::{Deserialize, Serialize};\n\n#[cfg(feature = \"server\")]\nuse {\n    dioxus::fullstack::{Cookie, TypedHeader},\n    std::sync::LazyLock,\n    uuid::Uuid,\n};\n\nfn main() {\n    dioxus::launch(app);\n}\n\nfn app() -> Element {\n    let mut fetch_login = use_action(login);\n    let mut fetch_sensitive = use_action(sensitive);\n\n    rsx! {\n        h1 { \"Login Form Demo\" }\n        button {\n            onclick: move |_| async move {\n                fetch_sensitive.call();\n            },\n            \"Get Sensitive Data\",\n        }\n        pre { \"Response from locked API: {fetch_sensitive.value():?}\"}\n        form {\n            onsubmit: move |evt: FormEvent| async move {\n                // Prevent the browser from navigating away.\n                evt.prevent_default();\n\n                // Extract the form values into our `LoginForm` struct. The `.parsed_values` method\n                // is provided by Dioxus and works with any form element that has `name` attributes.\n                let values: LoginForm = evt.parsed_values().unwrap();\n\n                // Call our server function with the form values wrapped in `Form`. The `SetHeader`\n                // response will set a cookie in the browser if the login is successful.\n                fetch_login.call(Form(values)).await;\n\n                // Now that we're logged in, we can call our sensitive endpoint.\n                fetch_sensitive.call().await;\n            },\n            input { r#type: \"text\", id: \"username\", name: \"username\" }\n            label { \"Username\" }\n            input { r#type: \"password\", id: \"password\", name: \"password\" }\n            label { \"Password\" }\n            button { \"Login\" }\n        }\n\n    }\n}\n\n#[derive(Deserialize, Serialize)]\npub struct LoginForm {\n    username: String,\n    password: String,\n}\n\n/// A static session ID for demonstration purposes. This forces all previous logins to be invalidated\n/// when the server restarts.\n#[cfg(feature = \"server\")]\nstatic THIS_SESSION_ID: LazyLock<Uuid> = LazyLock::new(Uuid::new_v4);\n\n/// In our `login` form, we'll return a `SetCookie` header if the login is successful.\n///\n/// This will set a cookie in the user's browser that can be used for subsequent authenticated requests.\n/// The `SetHeader::new()` method takes anything that can be converted into a `HeaderValue`.\n///\n/// We can set multiple headers by returning a tuple of `SetHeader` types, or passing in a tuple\n/// of headers to `SetHeader::new()`.\n#[post(\"/api/login\")]\nasync fn login(form: Form<LoginForm>) -> Result<SetHeader<SetCookie>> {\n    // Verify the username and password. In a real application, you'd check these against a database.\n    if form.0.username == \"admin\" && form.0.password == \"password\" {\n        return Ok(SetHeader::new(format!(\"auth-demo={};\", &*THIS_SESSION_ID))?);\n    }\n\n    HttpError::unauthorized(\"Invalid username or password\")?\n}\n\n/// We'll use the `TypedHeader` extractor on the server to get the cookie from the request.\n#[get(\"/api/sensitive\", header: TypedHeader<Cookie>)]\nasync fn sensitive() -> Result<String> {\n    // Extract the cookie from the request headers and use `.eq` to verify its value.\n    // The `or_unauthorized` works on boolean values, returning a 401 if the condition is false.\n    header\n        .get(\"auth-demo\")\n        .or_unauthorized(\"Missing auth-demo cookie\")?\n        .eq(THIS_SESSION_ID.to_string().as_str())\n        .or_unauthorized(\"Invalid auth-demo cookie\")?;\n\n    Ok(\"Sensitive data\".to_string())\n}\n"
  },
  {
    "path": "examples/07-fullstack/middleware.rs",
    "content": "//! This example shows how to use middleware in a fullstack Dioxus app.\n//!\n//! Dioxus supports two ways of middleware:\n//! - Applying layers to the top-level axum router\n//! - Apply `#[middleware]` attributes to individual handlers\n\nuse dioxus::prelude::*;\n\n#[cfg(feature = \"server\")]\nuse {std::time::Duration, tower_http::timeout::TimeoutLayer};\n\nfn main() {\n    #[cfg(not(feature = \"server\"))]\n    dioxus::launch(app);\n\n    #[cfg(feature = \"server\")]\n    dioxus::serve(|| async move {\n        use axum::{extract::Request, middleware::Next};\n        use dioxus::server::axum;\n\n        Ok(dioxus::server::router(app)\n            // we can apply a layer to the entire router using axum's `.layer` method\n            .layer(axum::middleware::from_fn(\n                |request: Request, next: Next| async move {\n                    println!(\"Request: {} {}\", request.method(), request.uri().path());\n                    let res = next.run(request).await;\n                    println!(\"Response: {}\", res.status());\n                    res\n                },\n            )))\n    });\n}\n\nfn app() -> Element {\n    let mut per_route = use_action(per_route_middleware);\n\n    rsx! {\n        h1 { \"Fullstack Middleware Example\" }\n        button { onclick: move |_| per_route.call(), \"Fetch Data\" }\n        pre { \"{per_route.value():#?}\" }\n    }\n}\n\n// We can use the `#[middleware]` attribute to apply middleware to individual handlers.\n//\n// Here, we're applying a timeout to the `per_route_middleware` handler, which will return a 504\n// if the handler takes longer than 3 seconds to complete.\n//\n// To add multiple middleware layers, simply stack multiple `#[middleware]` attributes.\n#[get(\"/api/count\")]\n#[middleware(TimeoutLayer::with_status_code(408.try_into().unwrap(), Duration::from_secs(3)))]\nasync fn per_route_middleware() -> Result<String> {\n    tokio::time::sleep(Duration::from_secs(5)).await;\n    Ok(\"Hello, world!\".to_string())\n}\n"
  },
  {
    "path": "examples/07-fullstack/multipart_form.rs",
    "content": "//! This example showcases how to handle multipart form data uploads in Dioxus.\n//!\n//! Dioxus provides the `MultipartFormData` type to allow converting from the websys `FormData`\n//! type directly into a streaming multipart form data handler.\n\nuse dioxus::{fullstack::MultipartFormData, prelude::*};\n\nfn main() {\n    dioxus::launch(app);\n}\n\nfn app() -> Element {\n    // The `MultipartFormData` type can be used to handle multipart form data uploads.\n    // We can convert into it by using `.into()` on the `FormEvent`'s data, or by crafting\n    // a `MultipartFormData` instance manually.\n    let mut upload_as_multipart = use_action(move |event: FormEvent| upload(event.into()));\n\n    rsx! {\n        Stylesheet { href: asset!(\"/examples/assets/file_upload.css\") }\n        img { src: asset!(\"/examples/assets/logo.png\"), width: \"200px\" }\n        div {\n            h3 { \"Upload as Multipart\" }\n            p { \"Use the built-in multipart form handling\" }\n            form {\n                display: \"flex\",\n                flex_direction: \"column\",\n                gap: \"8px\",\n                onsubmit: move |evt| async move {\n                    evt.prevent_default();\n                    upload_as_multipart.call(evt).await;\n                },\n                label { r#for: \"headshot\", \"Photos\" }\n                input { r#type: \"file\", name: \"headshot\", multiple: true, accept: \".png,.jpg,.jpeg\" }\n                label { r#for: \"resume\", \"Resume\" }\n                input { r#type: \"file\", name: \"resume\", multiple: false, accept: \".pdf\" }\n                label { r#for: \"name\", \"Name\" }\n                input { r#type: \"text\", name: \"name\", placeholder: \"Name\" }\n                label { r#for: \"age\", \"Age\" }\n                input { r#type: \"number\", name: \"age\", placeholder: \"Age\" }\n                input { r#type: \"submit\", name: \"submit\", value: \"Submit your resume\" }\n            }\n        }\n    }\n}\n\n/// Upload a form as multipart form data.\n///\n/// MultipartFormData is typed over the form data structure, allowing us to extract\n/// both files and other form fields in a type-safe manner.\n///\n/// On the server, we have access to axum's `Multipart` extractor\n#[post(\"/api/upload-multipart\")]\nasync fn upload(mut form: MultipartFormData) -> Result<()> {\n    while let Ok(Some(field)) = form.next_field().await {\n        let name = field.name().unwrap_or(\"<none>\").to_string();\n        let file_name = field.file_name().unwrap_or(\"<none>\").to_string();\n        let content_type = field.content_type().unwrap_or(\"<none>\").to_string();\n        let size = field.bytes().await.unwrap().len();\n\n        info!(\n            \"Field name: {:?}, filename: {:?}, content_type: {:?}, size: {:?}\",\n            name, file_name, content_type, size\n        );\n    }\n\n    Ok(())\n}\n"
  },
  {
    "path": "examples/07-fullstack/query_params.rs",
    "content": "//! An example showcasing query parameters in Dioxus Fullstack server functions.\n//!\n//! The query parameter syntax mostly follows axum, but with a few extra conveniences.\n//! - can rename parameters in the function signature with `?age=age_in_years` where `age_in_years` is Rust variable name\n//! - can absorb all query params with `?{object}` directly into a struct implementing `Deserialize`\n\nuse dioxus::prelude::*;\n\nfn main() {\n    dioxus::launch(|| {\n        let mut message = use_action(get_message);\n        let mut message_rebind = use_action(get_message_rebind);\n        let mut message_all = use_action(get_message_all);\n\n        rsx! {\n            h1 { \"Server says: \"}\n            div {\n                button { onclick: move |_| message.call(22), \"Single\" }\n                pre { \"{message:?}\"}\n            }\n            div {\n                button { onclick: move |_| message_rebind.call(25), \"Rebind\" }\n                pre { \"{message_rebind:?}\"}\n            }\n            div {\n                button { onclick: move |_| message_all.call(Params { age: 30, name: \"world\".into() }), \"Bind all\" }\n                pre { \"{message_all:?}\"}\n            }\n        }\n    });\n}\n\n#[get(\"/api/message/?age\")]\nasync fn get_message(age: i32) -> Result<String> {\n    Ok(format!(\"You are {} years old!\", age))\n}\n\n#[get(\"/api/rebind/?age=age_in_years\")]\nasync fn get_message_rebind(age_in_years: i32) -> Result<String> {\n    Ok(format!(\"You are {} years old!\", age_in_years))\n}\n\n#[derive(serde::Deserialize, serde::Serialize, Debug)]\nstruct Params {\n    age: i32,\n    name: String,\n}\n\n#[get(\"/api/all/?{query}\")]\nasync fn get_message_all(query: Params) -> Result<String> {\n    Ok(format!(\n        \"Hello {}, you are {} years old!\",\n        query.name, query.age\n    ))\n}\n"
  },
  {
    "path": "examples/07-fullstack/redirect.rs",
    "content": "//! This example shows how to use the axum `Redirect` type to redirect the client to a different URL.\n//!\n//! On the web, a redirect will not be handled directly by JS, but instead the browser will automatically\n//! follow the redirect. This is useful for redirecting to different pages after a form submission.\n//!\n//! Note that redirects returned to the client won't navigate the SPA to a new page automatically.\n//! For managing a session or auth with client side routing, you'll need to handle that in the SPA itself.\n\nuse dioxus::{fullstack::Redirect, prelude::*};\n\nfn main() {\n    dioxus::launch(|| {\n        rsx! {\n            Router::<Route> {}\n        }\n    });\n}\n\n#[derive(Clone, PartialEq, Routable)]\nenum Route {\n    #[route(\"/\")]\n    Home,\n\n    #[route(\"/blog\")]\n    Blog,\n}\n\n#[component]\nfn Home() -> Element {\n    rsx! {\n        h1 { \"Welcome home\" }\n        form {\n            method: \"post\",\n            action: \"/api/old-blog\",\n            button { \"Go to blog\" }\n        }\n    }\n}\n\n#[component]\nfn Blog() -> Element {\n    rsx! {\n        h1 { \"Welcome to the blog!\" }\n    }\n}\n\n#[post(\"/api/old-blog\")]\nasync fn redirect_to_blog() -> Result<Redirect> {\n    Ok(Redirect::to(\"/blog\"))\n}\n"
  },
  {
    "path": "examples/07-fullstack/router/Cargo.toml",
    "content": "[package]\nname = \"fullstack-router-example\"\nversion = \"0.1.0\"\nedition = \"2021\"\npublish = false\n\n# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html\n\n[dependencies]\ndioxus = { workspace = true, features = [\"fullstack\", \"router\"] }\naxum = { workspace = true, optional = true }\ntokio = { workspace = true, features = [\"full\"], optional = true }\nserde = { workspace = true, features = [\"derive\"] }\n\n[features]\ndefault = []\nserver = [\"axum\", \"dioxus/server\"]\nweb = [\"dioxus/web\"]\n\n"
  },
  {
    "path": "examples/07-fullstack/router/src/main.rs",
    "content": "//! Run with:\n//!\n//! ```sh\n//! dx serve --platform web\n//! ```\n\nuse dioxus::prelude::*;\n\nfn main() {\n    dioxus::LaunchBuilder::new()\n        .with_cfg(server_only!(ServeConfig::builder().incremental(\n            dioxus::server::IncrementalRendererConfig::default()\n                .invalidate_after(std::time::Duration::from_secs(120)),\n        )))\n        .launch(app);\n}\n\nfn app() -> Element {\n    rsx! { Router::<Route> {} }\n}\n\n#[derive(Clone, Routable, Debug, PartialEq, serde::Serialize, serde::Deserialize)]\nenum Route {\n    #[route(\"/\")]\n    Home {},\n\n    #[route(\"/blog/:id/\")]\n    Blog { id: i32 },\n}\n\n#[component]\nfn Blog(id: i32) -> Element {\n    rsx! {\n        Link { to: Route::Home {}, \"Go to counter\" }\n        table {\n            tbody {\n                for _ in 0..id {\n                    tr {\n                        for _ in 0..id {\n                            td { \"hello world!\" }\n                        }\n                    }\n                }\n            }\n        }\n    }\n}\n\n#[component]\nfn Home() -> Element {\n    let mut count = use_signal(|| 0);\n    let mut text = use_signal(|| \"...\".to_string());\n\n    rsx! {\n        Link { to: Route::Blog { id: count() }, \"Go to blog\" }\n        div {\n            h1 { \"High-Five counter: {count}\" }\n            button { onclick: move |_| count += 1, \"Up high!\" }\n            button { onclick: move |_| count -= 1, \"Down low!\" }\n            button {\n                onclick: move |_| async move {\n                    let data = get_server_data().await?;\n                    println!(\"Client received: {}\", data);\n                    text.set(data.clone());\n                    post_server_data(data).await?;\n                    Ok(())\n                },\n                \"Run server function!\"\n            }\n            \"Server said: {text}\"\n        }\n    }\n}\n\n#[post(\"/api/data\")]\nasync fn post_server_data(data: String) -> ServerFnResult {\n    println!(\"Server received: {}\", data);\n\n    Ok(())\n}\n\n#[get(\"/api/data\")]\nasync fn get_server_data() -> ServerFnResult<String> {\n    Ok(\"Hello from the server!\".to_string())\n}\n"
  },
  {
    "path": "examples/07-fullstack/server_functions.rs",
    "content": "//! This example is a simple showcase of Dioxus Server Functions.\n//!\n//! The other examples in this folder showcase advanced features of server functions like custom\n//! data types, error handling, websockets, and more.\n//!\n//! This example is meant to just be a simple starting point to show how server functions work.\n//!\n//! ## Server Functions\n//!\n//! In Dioxus, Server Functions are `axum` backend endpoints that can be called directly from the client\n//! as if you were simply calling a local Rust function. You can do anything with a server function\n//! that an Axum handler can do like extracting path, query, headers, and body parameters.\n//!\n//! ## Server Function Arguments\n//!\n//! Unlike Axum handlers, the arguments of the server functions have some special magic enabled by\n//! the accompanying `#[get]`/`#[post]` attributes. This magic enables you to choose between\n//! arguments that are purely serializable (i.e. `String`, `i32`, `Vec<T>`, etc) as the JSON body of\n//!\n//! the request *or* arguments that implement Axum's `FromRequest` trait. This magic enables simple\n//! RPC functions but also complex extractors for things like auth, sessions, cookies, and more.\n//!\n//! ## Server Function Return Types\n//!\n//! The return type of the server function is also somewhat magical. Unlike Axum handlers, all server\n//! functions must return a `Result` type, giving the client an opportunity to handle errors properly.\n//!\n//! The `Ok` type can be anything that implements `Serialize + DeserializeOwned` so it can be sent\n//! to the client as JSON, or it can be anything that implements `IntoResponse` just like an Axum handler.\n//!\n//! ## Error Types\n//!\n//! The `Err` type of the server function return type is also somewhat special. The `Err` type can be:\n//! - `anyhow::Error` (the `dioxus_core::Err` type alias) for untyped errors with rich context. Note\n//!   that these errors will always downcast to `ServerFnError` on the client, losing the original\n//!   error stack and type.\n//! - `ServerFnError` for typed errors with a status code and optional message.\n//! - `StatusCode` for returning raw HTTP status codes.\n//! - `HttpError` for returning HTTP status codes with custom messages.\n//! - Any custom errors that implement `From<ServerFnError>` and are `Serialize`/`Deserialize`\n//!\n//! The only way to set the HTTP status code of the response is to use one of the above error types,\n//! or to implement a custom `IntoResponse` type that sets the status code manually.\n//!\n//! The `anyhow::Error` type is the best choice for rapid development, but is somewhat limited when\n//! handling specific error cases on the client since all errors are downcast to `ServerFnError`.\n//!\n//! ## Calling Server Functions from the Client\n//!\n//! Server functions can be called from the client by simply importing the function and calling it\n//! like a normal Rust function. Unlike regular axum handlers, Dioxus server functions have a few\n//! non-obvious restrictions.\n//!\n//! Most importantly, the arguments to the server function must implement either `Deserialize` *or*\n//! `IntoRequest`. The `IntoRequest` trait is a Dioxus abstraction that represents the \"inverse\" of the\n//! Axum `FromRequest` trait. Anything that is sent to the server from the client must be both extractable\n//! with `FromRequest` on the server *and* constructible with `IntoRequest` on the client.\n//!\n//! Types like `WebsocketOptions` implement `IntoRequest` and pass along things like upgrade headers\n//! to the server so that the server can properly upgrade the connection.\n//!\n//! When receiving data from the server, the return type must implement `Deserialize` *or* `FromResponse`.\n//! The `FromResponse` trait is the inverse of Axum's `IntoResponse` trait, and is implemented\n//! for types like `Websocket` where the raw HTTP response is needed to complete the construction\n//! of the type.\n//!\n//! ## Server-only Extractors\n//!\n//! Because the arguments of the server function define the structure of the public API, some extractors\n//! might not make sense to expose directly, nor would they be possible to construct on the client.\n//! For example, on the web, you typically don't work directly with cookies since the browser handles\n//! them for you. In these cases, the client would omit the `Cookie` header entirely, and we would need\n//! \"hoist\" our extractor into a \"server-only extractor\".\n//!\n//! Server-only extractors are function arguments placed after the path in the `#[get]`/`#[post]` attribute.\n//! These arguments are extracted on the server, but not passed in from the client. This lets the\n//! server function remain callable from the client, while still allowing full access to axum's\n//! extractors.\n//!\n//! ```\n//! #[post(\"/api/authenticate\", auth: AuthCookie)]\n//! async fn authenticate() -> Result<User> { /* ... */ }\n//! ```\n//!\n//! ## Automatic Registration\n//!\n//! Unlike axum handlers, server functions do not need to be manually registered with a router.\n//! By default, *all* server functions in your app will be automatically registered with the\n//! server when you call `dioxus::launch` or create a router manually with `dioxus::server::router()`.\n//!\n//! However, not all server functions are automatically registered by default. Server functions that\n//! take a `State<T>` extractor cannot be automatically added to the router since the dioxus router\n//! type does not know how to construct the `T` type.\n//!\n//! These server functions will be registered once the `ServerState<T>` layer is added to the app with\n//! `router = router.layer(ServerState::new(your_state))`.\n//!\n//! ## Middleware\n//!\n//! Middleware can be added to server functions using the `#[middleware(MiddlewareType)]` attribute.\n//! Middleware will be applied in the order they are specified, and will be applied before any\n//! server-only extractors.\n//!\n//! To add router-level middleware, you can customize the axum `Router` using layers and extensions\n//! as you would in a normal axum app.\n//!\n//! ## Anonymous Server Functions\n//!\n//! The `#[server]` attribute can be used without a path to create an anonymous server function.\n//! These functions are still exposed as HTTP endpoints, but their names are procedurally generated\n//! from the module path, function name, and a hash of the function signature. This makes it hard to\n//! call these functions with `curl` or `postman`, but save you the trouble of coming up with unique\n//! names for simple functions that are only called from your Dioxus app.\n//!\n//! If you're shipping desktop/mobile apps, we don't recommend using anonymous server functions\n//! since the function names could change between builds and thus make older versions of your app\n//! incompatible with newer versions of your server.\n//!\n//! ## Cross-platform Clients\n//!\n//! Server functions can be called from any platform (web, desktop mobile, etc) and use the best\n//! underlying `fetch` implementation available.\n//!\n//! ## More examples\n//!\n//! With Dioxus Fullstack 0.7, pretty much anything you can do with an Axum handler, you can do with\n//! a server function. More advanced examples can be found in this folder showcasing custom data types,\n//! error handling, websockets, and more.\n\nuse axum_core::response::IntoResponse;\nuse dioxus::prelude::*;\nuse dioxus_fullstack::FromResponse;\nuse dioxus_fullstack::http::StatusCode;\nuse serde::{Deserialize, Serialize};\n\nfn main() {\n    dioxus::launch(app);\n}\n\nfn app() -> Element {\n    let mut echo_action = use_action(echo);\n    let mut chat_action = use_action(chat);\n    let mut dog_data = use_action(get_data);\n    let mut custom_data = use_action(get_custom_data);\n    let mut anonymous_action = use_action(anonymous);\n    let mut custom_anonymous_action = use_action(custom_anonymous);\n    let mut custom_response_action = use_action(get_custom_response);\n\n    rsx! {\n        h1 { \"Server Functions Example\" }\n        div {\n            display: \"flex\",\n            flex_direction: \"column\",\n            gap: \"8px\",\n\n            button { onclick: move |_| echo_action.call(\"Hello from client\".into()), \"Echo: Hello\" }\n            button { onclick: move |_| chat_action.call(42u32, Some(7u32)), \"Chat (user 42, room 7)\" }\n            button { onclick: move |_| dog_data.call(), \"Get dog data\" }\n            button { onclick: move |_| custom_data.call(), \"Get custom data\" }\n            button { onclick: move |_| anonymous_action.call(), \"Call anonymous\" }\n            button { onclick: move |_| custom_anonymous_action.call(), \"Call custom anonymous\" }\n            button { onclick: move |_| custom_response_action.call(), \"Get custom response\" }\n\n            button {\n                onclick: move |_| {\n                    echo_action.reset();\n                    chat_action.reset();\n                    dog_data.reset();\n                    custom_data.reset();\n                    anonymous_action.reset();\n                    custom_anonymous_action.reset();\n                    custom_response_action.reset();\n                },\n                \"Clear results\"\n            }\n\n            pre { \"Echo result: {echo_action.value():#?}\" }\n            pre { \"Chat result: {chat_action.value():#?}\" }\n            pre { \"Dog data: {dog_data.value():#?}\" }\n            pre { \"Custom data: {custom_data.value():#?}\" }\n            pre { \"Anonymous: {anonymous_action.value():#?}\" }\n            pre { \"Custom anonymous: {custom_anonymous_action.value():#?}\" }\n            pre { \"Custom response: {custom_response_action.value():#?}\" }\n        }\n    }\n}\n\n/// A plain server function at a `POST` endpoint that takes a string and returns it.\n/// Here, we use the `Result` return type which is an alias to `Result<T, anyhow::Error>`.\n#[post(\"/api/echo\")]\nasync fn echo(body: String) -> Result<String> {\n    Ok(body)\n}\n\n/// A Server function that takes path and query parameters, as well as a server-only extractor.\n#[post(\"/api/{user_id}/chat?room_id\", headers: dioxus_fullstack::HeaderMap)]\nasync fn chat(user_id: u32, room_id: Option<u32>) -> Result<String> {\n    Ok(format!(\n        \"User ID: {}, Room ID: {} - Headers: {:#?}\",\n        user_id,\n        room_id.map_or(\"None\".to_string(), |id| id.to_string()),\n        headers\n    ))\n}\n\n/// A plain server function at a `GET` endpoint that returns some JSON data. Because `DogData` is\n/// `Serialize` and `Deserialize`, it can be sent to the client as JSON automatically.\n///\n/// You can `curl` this endpoint and it will return a 200 status code with a JSON body:\n///\n/// ```json\n/// {\n///   \"name\": \"Fido\",\n///   \"age\": 4\n/// }\n/// ```\n#[get(\"/api/dog\")]\nasync fn get_data() -> Result<DogData> {\n    Ok(DogData {\n        name: \"Fido\".to_string(),\n        age: 4,\n    })\n}\n\n#[derive(Serialize, Deserialize, Debug)]\nstruct DogData {\n    name: String,\n    age: u8,\n}\n\n/// A server function that returns a custom struct as JSON.\n#[get(\"/api/custom\")]\nasync fn get_custom_data() -> Result<CustomData> {\n    Ok(CustomData {\n        message: \"Hello from the server!\".to_string(),\n    })\n}\n\n#[derive(Debug)]\nstruct CustomData {\n    message: String,\n}\nimpl IntoResponse for CustomData {\n    fn into_response(self) -> axum_core::response::Response {\n        axum_core::response::Response::builder()\n            .status(StatusCode::ACCEPTED)\n            .body(serde_json::to_string(&self.message).unwrap().into())\n            .unwrap()\n    }\n}\n\nimpl FromResponse for CustomData {\n    async fn from_response(res: dioxus_fullstack::ClientResponse) -> Result<Self, ServerFnError> {\n        let message = res.json::<String>().await?;\n        Ok(CustomData { message })\n    }\n}\n\n/// A server function that returns an axum type directly.\n///\n/// When make these endpoints, we need to use the `axum::response::Response` type and then call `into_response`\n/// on the return value to convert it into a response.\n#[get(\"/api/custom_response\")]\nasync fn get_custom_response() -> Result<axum_core::response::Response> {\n    Ok(axum_core::response::Response::builder()\n        .status(StatusCode::CREATED)\n        .body(\"Created!\".to_string())\n        .unwrap()\n        .into_response())\n}\n\n/// An anonymous server function - the url path is generated from the module path and function name.\n///\n/// This will end up as `/api/anonymous_<hash>` where `<hash>` is a hash of the function signature.\n#[server]\nasync fn anonymous() -> Result<String> {\n    Ok(\"Hello from an anonymous server function!\".to_string())\n}\n\n/// An anonymous server function with a custom prefix and a fixed endpoint name.\n///\n/// This is less preferred over the `#[get]`/`#[post]` syntax but is still functional for backwards\n/// compatibility. Previously, only the `#[server]` attribute was available, but as of Dioxus 0.7,\n/// the `#[get]`/`#[post]` attributes are preferred for new code.\n///\n/// You can also use server-only extractors here as well, provided they come after the configuration.\n#[server(prefix = \"/api/custom\", endpoint = \"my_anonymous\", headers: dioxus_fullstack::HeaderMap)]\nasync fn custom_anonymous() -> Result<String> {\n    Ok(format!(\n        \"Hello from a custom anonymous server function! -> {:#?}\",\n        headers\n    ))\n}\n"
  },
  {
    "path": "examples/07-fullstack/server_sent_events.rs",
    "content": "//! This example demonstrates server-sent events (SSE) using Dioxus Fullstack.\n//!\n//! Server-sent events allow the server to push updates to the client over a single HTTP connection.\n//! This is useful for real-time updates, notifications, or any scenario where the server needs to\n//! send data to the client without the client explicitly requesting it.\n//!\n//! SSE is a simpler alternative to WebSockets, not requiring a full-duplex, stateful connection with\n//! the server. Instead, it uses a single long-lived HTTP connection to stream events from the server to the client.\n//!\n//! This means that SSE messages are stringly encoded, and thus binary data must be base64 encoded.\n//! If you need to send binary data, consider using the `Streaming<T>` type instead, which lets\n//! you send raw bytes over a streaming HTTP response with a custom encoding. You'd reach for SSE\n//! when dealing with clients that might not support custom streaming protocols.\n//!\n//! Calling an SSE endpoint is as simple as calling any other server function. The return type of an\n//! SSE endpoint is a `ServerEvents<T>` where `T` is the type of event you want to send to the client.\n//!\n//! On the client, the `ServerEvents<T>` type implements `Stream<Item = Result<T, ServerFnError>>`\n//! so you can use it with async streams to get new events as they arrive.\n//!\n//! `T` must be serializable and deserializable, so anything that implements `Serialize` and `Deserialize`\n//! can be used as an event type. Calls to `.recv()` will wait for the next event to arrive and\n//! deserialize it into the correct type.\n\nuse dioxus::prelude::*;\nuse dioxus_fullstack::ServerEvents;\n\nfn main() {\n    dioxus::launch(app);\n}\n\nfn app() -> Element {\n    let mut events = use_signal(Vec::new);\n\n    use_future(move || async move {\n        // Call the SSE endpoint to get a stream of events\n        let mut stream = listen_for_changes().await?;\n\n        // And then poll it for new events, adding them to our signal\n        while let Some(Ok(event)) = stream.recv().await {\n            events.push(event);\n        }\n\n        dioxus::Ok(())\n    });\n\n    rsx! {\n        h1 { \"Events from server: \" }\n        for msg in events.read().iter().rev() {\n            pre { \"{msg:?}\" }\n        }\n    }\n}\n\n/// We can send anything that's serializable as a server event - strings, numbers, structs, enums, etc.\n#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]\nenum MyServerEvent {\n    Yay { message: String },\n    Nay { error: String },\n}\n\n/// Our SSE endpoint, when called, will return the ServerEvents handle which streams events to the client.\n/// On the client, we can interact with this stream object to get new events as they arrive.\n#[get(\"/api/sse\")]\nasync fn listen_for_changes() -> Result<ServerEvents<MyServerEvent>> {\n    use std::time::Duration;\n\n    Ok(ServerEvents::new(|mut tx| async move {\n        let mut count = 1;\n\n        loop {\n            // Create our serializable message\n            let msg = if count % 5 == 0 {\n                MyServerEvent::Nay {\n                    error: \"An error occurred\".into(),\n                }\n            } else {\n                MyServerEvent::Yay {\n                    message: format!(\"Hello number {count}\"),\n                }\n            };\n\n            // Send the message to the client. If it errors, the client has disconnected\n            if tx.send(msg).await.is_err() {\n                // client disconnected, do some cleanup\n                break;\n            }\n\n            count += 1;\n\n            // Poll some data source here, subscribe to changes, maybe call an LLM?\n            tokio::time::sleep(Duration::from_secs(1)).await;\n        }\n    }))\n}\n"
  },
  {
    "path": "examples/07-fullstack/server_state.rs",
    "content": "//! This example shows how to use global state to maintain state between server functions.\n\nuse std::rc::Rc;\n\nuse axum_core::extract::{FromRef, FromRequest};\nuse dioxus::{\n    fullstack::{FullstackContext, extract::State},\n    prelude::*,\n};\nuse reqwest::header::HeaderMap;\n\n#[cfg(feature = \"server\")]\nuse {\n    dioxus::fullstack::Lazy,\n    dioxus::fullstack::axum,\n    futures::lock::Mutex,\n    sqlx::{Executor, Row},\n    std::sync::LazyLock,\n};\n\n/*\nOption 1:\n\nFor simple, synchronous, thread-safe data, we can use statics with atomic types or mutexes.\nThe `LazyLock` type from the standard library is a great choice for simple, synchronous data\n*/\n#[cfg(feature = \"server\")]\nstatic MESSAGES: LazyLock<Mutex<Vec<String>>> = LazyLock::new(|| Mutex::new(Vec::new()));\n\n#[post(\"/api/messages\")]\nasync fn add_message() -> Result<()> {\n    MESSAGES.lock().await.push(\"New message\".to_string());\n    Ok(())\n}\n\n#[get(\"/api/messages\")]\nasync fn read_messages() -> Result<Vec<String>> {\n    Ok(MESSAGES.lock().await.clone())\n}\n\n/*\nOption 2:\n\nFor complex async data, we can use the `Lazy` type from Dioxus Fullstack. The `Lazy` type provides\nan interface like `once_cell::Lazy` but supports async initialization. When reading the value from\na `Lazy<T>`, the value will be initialized synchronously, blocking the current task until the value is ready.\n\nAlternatively, you can create a `Lazy<T>` with `Lazy::lazy` and then initialize it later with\n`Lazy::initialize`.\n*/\n#[cfg(feature = \"server\")]\nstatic DATABASE: Lazy<sqlx::SqlitePool> = Lazy::new(|| async move {\n    use sqlx::sqlite::SqlitePoolOptions;\n    dioxus::Ok(\n        SqlitePoolOptions::new()\n            .max_connections(5)\n            .connect_with(\"sqlite::memory:\".parse().unwrap())\n            .await?,\n    )\n});\n\n/// When using the `Lazy<T>` type, it implements `Deref<Target = T>`, so you can use it like a normal reference.\n#[get(\"/api/users\")]\nasync fn get_users() -> Result<Vec<String>> {\n    let users = DATABASE\n        .fetch_all(sqlx::query(\"SELECT name FROM users\"))\n        .await?\n        .iter()\n        .map(|row| row.get::<String, _>(\"name\"))\n        .collect::<Vec<_>>();\n\n    Ok(users)\n}\n\n/*\nOption 3:\n\nFor data that needs to be provided per-request, we can use axum's `Extension` type to provide\ndata to our app. This is useful for things like request-scoped data or data that needs to be\ninitialized per-requestz\n*/\n#[cfg(feature = \"server\")]\ntype BroadcastExtension = axum::Extension<tokio::sync::broadcast::Sender<String>>;\n\n#[post(\"/api/broadcast\", ext: BroadcastExtension)]\nasync fn broadcast_message() -> Result<()> {\n    let rt = Rc::new(\"asdasd\".to_string());\n    ext.send(\"New broadcast message\".to_string())?;\n    tokio::time::sleep(std::time::Duration::from_millis(100)).await;\n    println!(\"rt: {}\", rt);\n\n    Ok(())\n}\n\n/*\nOption 4:\n\nYou can use Axum's `State` extractor to provide custom application state to your server functions.\n\nAll ServerFunctions pull in `FullstackContext`, so you need to implement `FromRef<FullstackContext>` for your\ncustom state type. To add your state to your app, you can use `.register_server_functions()` on a router\nfor a given state type, which will automatically add your state into the `FullstackContext` used by your server functions.\n\nThere are two details to note here:\n\n- You need to implement `FromRef<FullstackContext>` for your custom state type.\n- Custom extractors need to implement `FromRequest<S>` where `S` is the state type that implements `FromRef<FullstackContext>`.\n*/\n#[derive(Clone)]\nstruct MyAppState {\n    abc: i32,\n}\n\nimpl FromRef<FullstackContext> for MyAppState {\n    fn from_ref(state: &FullstackContext) -> Self {\n        state.extension::<MyAppState>().unwrap()\n    }\n}\n\nstruct CustomExtractor {\n    abc: i32,\n    headermap: HeaderMap,\n}\n\nimpl<S> FromRequest<S> for CustomExtractor\nwhere\n    MyAppState: FromRef<S>,\n    S: Send + Sync,\n{\n    type Rejection = ();\n\n    async fn from_request(\n        _req: axum::extract::Request,\n        state: &S,\n    ) -> std::result::Result<Self, Self::Rejection> {\n        let state = MyAppState::from_ref(state);\n        Ok(CustomExtractor {\n            abc: state.abc,\n            headermap: HeaderMap::new(),\n        })\n    }\n}\n\n#[post(\"/api/stateful\", state: State<MyAppState>, ex: CustomExtractor)]\nasync fn app_state() -> Result<()> {\n    println!(\"abc: {}\", state.abc);\n    println!(\"state abc: {:?}\", ex.abc);\n    println!(\"headermap: {:?}\", ex.headermap);\n    Ok(())\n}\n\nfn main() {\n    #[cfg(not(feature = \"server\"))]\n    dioxus::launch(app);\n\n    // When using `Lazy` items, or axum `Extension`s, we need to initialize them in `dioxus::serve`\n    // before launching our app.\n    #[cfg(feature = \"server\")]\n    dioxus::serve(|| async move {\n        use dioxus::server::axum::Extension;\n\n        // For axum `Extension`s, we can use the `layer` method to add them to our router.\n        let router = dioxus::server::router(app)\n            .layer(Extension(tokio::sync::broadcast::channel::<String>(16).0));\n\n        // To use our custom app state with `State<MyAppState>`, we need to register it\n        // as an extension since our `FromRef<FullstackContext>` implementation relies on it.\n        let router = router.layer(Extension(MyAppState { abc: 42 }));\n\n        Ok(router)\n    });\n}\n\nfn app() -> Element {\n    let mut users = use_action(get_users);\n    let mut messages = use_action(read_messages);\n    let mut broadcast = use_action(broadcast_message);\n    let mut add = use_action(add_message);\n\n    rsx! {\n        div {\n            button { onclick: move |_| users.call(), \"Get Users\" }\n            pre { \"{users.value():?}\" }\n            button { onclick: move |_| messages.call(), \"Get Messages\" }\n            pre { \"{messages.value():?}\" }\n            button { onclick: move |_| broadcast.call(), \"Broadcast Message\" }\n            pre { \"{broadcast.value():?}\" }\n            button { onclick: move |_| add.call(), \"Add Message\" }\n            pre { \"{add.value():?}\" }\n        }\n    }\n}\n"
  },
  {
    "path": "examples/07-fullstack/ssr-only/Cargo.toml",
    "content": "[package]\nname = \"ssr-only\"\nedition = \"2024\"\nversion.workspace = true\npublish = false\n\n[dependencies]\ndioxus = { workspace = true, features = [\"fullstack\", \"router\"] }\n\n[features]\ndefault = [\"server\"]\nserver = [\"dioxus/server\"]\n"
  },
  {
    "path": "examples/07-fullstack/ssr-only/src/main.rs",
    "content": "//! This example showcases how to use Fullstack in a server-side rendering only context.\n//!\n//! This means we have no client-side bundle at all, and *everything* is rendered on the server.\n//! You can still use signals, resources, etc, but they won't be reactive on the client.\n//!\n//! This is useful for static site generation, or if you want to use Dioxus Fullstack as a server-side\n//! framework without the `rsx! {}` markup.\n//!\n//! To run this example, simply run `cargo run --package ssr-only` and navigate to `http://localhost:8080`.\n\nuse dioxus::prelude::*;\n\nfn main() {\n    dioxus::launch(|| rsx! { Router::<Route> { } });\n}\n\n#[derive(Routable, Clone, Debug, PartialEq)]\nenum Route {\n    #[route(\"/\")]\n    Home,\n\n    #[route(\"/post/:id\")]\n    Post { id: u32 },\n}\n\n#[component]\nfn Home() -> Element {\n    rsx! {\n        h1 { \"home\"  }\n        ul {\n            li { a { href: \"/post/1\", \"Post 1\" } }\n            li { a { href: \"/post/2\", \"Post 2\" } }\n            li { a { href: \"/post/3\", \"Post 3 (404)\" } }\n        }\n    }\n}\n\n#[component]\nfn Post(id: ReadSignal<u32>) -> Element {\n    // You can return `HttpError` to return a specific HTTP status code and message.\n    // `404 Not Found` will cause the server to return a 404 status code.\n    //\n    // `use_loader` will suspend the server-side rendering until the future resolves.\n    let post_data = use_loader(move || get_post(id()))?;\n\n    rsx! {\n        h1 { \"Post {id}\" }\n        p { \"{post_data}\" }\n    }\n}\n\n#[get(\"/api/post/{id}\")]\nasync fn get_post(id: u32) -> Result<String, HttpError> {\n    match id {\n        1 => Ok(\"first post\".to_string()),\n        2 => Ok(\"second post\".to_string()),\n        _ => HttpError::not_found(\"Post not found\")?,\n    }\n}\n"
  },
  {
    "path": "examples/07-fullstack/streaming.rs",
    "content": "//! This example shows how to use the `Streaming<T, E>` type to send streaming responses from the\n//! server to the client (and the client to the server!).\n//!\n//! The `Streaming<T, E>` type automatically coordinates sending and receiving streaming data over HTTP.\n//! The `T` type parameter is the type of data being sent, and the `E` type parameter is the encoding\n//! used to serialize and deserialize the data.\n//!\n//! Dioxus Fullstack provides several built-in encodings:\n//! - JsonEncoding: the default, uses JSON for serialization\n//! - CborEncoding: uses CBOR for binary serialization\n//! - PostcardEncoding: uses Postcard for binary serialization\n//! - MsgPackEncoding: uses MessagePack for binary serialization\n//! - RkyvEncoding: uses Rkyv for zero-copy binary serialization\n//!\n//! The default encoding is `JsonEncoding`, which works well for most use cases and can be used by\n//! most clients. If you need a more efficient binary encoding, consider using one of the\n//! binary encodings.\n\nuse bytes::Bytes;\nuse dioxus::{\n    fullstack::{JsonEncoding, Streaming, TextStream},\n    prelude::*,\n};\n\nfn main() {\n    dioxus::launch(app)\n}\n\nfn app() -> Element {\n    let mut text_responses = use_signal(String::new);\n    let mut json_responses = use_signal(Vec::new);\n\n    let mut start_text_stream = use_action(move || async move {\n        text_responses.clear();\n        let mut stream = text_stream(Some(100)).await?;\n\n        while let Some(Ok(text)) = stream.next().await {\n            text_responses.push_str(&text);\n            text_responses.push('\\n');\n        }\n\n        dioxus::Ok(())\n    });\n\n    let mut start_json_stream = use_action(move || async move {\n        json_responses.clear();\n        let mut stream = json_stream().await?;\n\n        while let Some(Ok(dog)) = stream.next().await {\n            json_responses.push(dog);\n        }\n\n        dioxus::Ok(())\n    });\n\n    rsx! {\n        div {\n            button { onclick: move |_| start_text_stream.call(), \"Start text stream\" }\n            button { onclick: move |_| start_text_stream.cancel(), \"Stop text stream\" }\n            pre { \"{text_responses}\" }\n        }\n        div {\n            button { onclick: move |_| start_json_stream.call(), \"Start JSON stream\" }\n            button { onclick: move |_| start_json_stream.cancel(), \"Stop JSON stream\" }\n            for dog in json_responses.read().iter() {\n                pre { \"{dog:?}\" }\n            }\n        }\n    }\n}\n\n/// The `TextStream` type is an alias for `Streaming<String>` with a text/plain encoding.\n///\n/// The `TextStream::new()` method takes anything that implements `Stream<Item = String>`, so\n/// we can use a channel to send strings from a background task.\n#[get(\"/api/test_stream?start\")]\nasync fn text_stream(start: Option<i32>) -> Result<TextStream> {\n    let (tx, rx) = futures::channel::mpsc::unbounded();\n\n    tokio::spawn(async move {\n        let mut count = start.unwrap_or(0);\n        loop {\n            let message = format!(\"Hello, world! {}\", count);\n            if tx.unbounded_send(message).is_err() {\n                break;\n            }\n\n            count += 1;\n            tokio::time::sleep(tokio::time::Duration::from_millis(500)).await;\n        }\n    });\n\n    Ok(Streaming::new(rx))\n}\n\n#[derive(serde::Serialize, serde::Deserialize, Debug)]\nstruct Dog {\n    name: String,\n    age: u8,\n}\n\n/// A custom `Streaming<T, E>` endpoint that streams JSON-encoded `Dog` structs to the client.\n///\n/// Dioxus provides the `JsonEncoding` type which can be used to encode and decode JSON data.\n#[get(\"/api/json_stream\")]\nasync fn json_stream() -> Result<Streaming<Dog, JsonEncoding>> {\n    let (tx, rx) = futures::channel::mpsc::unbounded();\n\n    tokio::spawn(async move {\n        let mut count = 0;\n        loop {\n            let dog = Dog {\n                name: format!(\"Dog {}\", count),\n                age: (count % 10) as u8,\n            };\n            if tx.unbounded_send(dog).is_err() {\n                // If the channel is closed, stop sending chunks\n                break;\n            }\n            count += 1;\n            tokio::time::sleep(tokio::time::Duration::from_millis(500)).await;\n        }\n    });\n\n    Ok(Streaming::new(rx))\n}\n\n/// An example of streaming raw bytes to the client using `Streaming<Bytes>`.\n/// This is useful for sending binary data, such as images, files, or zero-copy data.\n#[get(\"/api/byte_stream\")]\nasync fn byte_stream() -> Result<Streaming<Bytes>> {\n    let (tx, rx) = futures::channel::mpsc::unbounded();\n\n    tokio::spawn(async move {\n        let mut count = 0;\n        loop {\n            let bytes = vec![count; 10];\n            if tx.unbounded_send(bytes.into()).is_err() {\n                break;\n            }\n            count = (count + 1) % 255;\n            tokio::time::sleep(tokio::time::Duration::from_millis(500)).await;\n        }\n    });\n\n    Ok(Streaming::new(rx))\n}\n"
  },
  {
    "path": "examples/07-fullstack/streaming_file_upload.rs",
    "content": "//! This example showcases how to upload files from the client to the server.\n//!\n//! We can use the `FileStream` type to handle file uploads in a streaming fashion.\n//! This allows us to handle large files without loading them entirely into memory.\n//!\n//! `FileStream` and `FileDownload` are built on multi-part form data and streams, which we\n//! also showcase here.\n\nuse dioxus::{\n    fullstack::{ByteStream, FileStream},\n    prelude::*,\n};\nuse dioxus_html::{FileData, HasFileData};\nuse futures::StreamExt;\n\nfn main() {\n    dioxus::launch(app);\n}\n\nfn app() -> Element {\n    // Dioxus provides the `FileStream` type for efficiently uploading files in a streaming fashion.\n    // This approach automatically automatically sets relevant metadata such as headers like\n    // Content-Type, Content-Length, and Content-Disposition.\n    //\n    // The `FileStream` type can be created from a `FileData` instance using `.into()`.\n    // This approach is better suited for public-facing APIs where standard headers are expected.\n    //\n    // `FileStream` uses the platform's native file streaming capabilities when available,\n    // making it more efficient than manually streaming bytes.\n    let mut upload_as_file_upload = use_action(move |files: Vec<FileData>| async move {\n        for file in files {\n            upload_file_as_filestream(file.into()).await?;\n        }\n        dioxus::Ok(())\n    });\n\n    // We can upload files by directly using the `ByteStream` type. With this approach, we need to\n    // specify the file name and size as query parameters since its an opaque stream.\n    //\n    // The `FileData` type has a `byte_stream` method which returns a `Pin<Box<dyn Stream<Item = Bytes> + Send>>`\n    // that we can turn into a `ByteStream` with `.into()`.\n    //\n    // In WASM, this will buffer the entire file in memory, so it's not the most efficient way to upload files.\n    // This approach is best suited for data created by the user in the browser.\n    let mut upload_files_as_bytestream = use_action(move |files: Vec<FileData>| async move {\n        info!(\"Uploading {} files\", files.len());\n        for file in files {\n            upload_as_bytestream(file.name(), file.size(), file.byte_stream().into()).await?;\n        }\n        dioxus::Ok(())\n    });\n\n    let mut download_file = use_action(move || async move {\n        let mut file = download_as_filestream().await?;\n        let mut bytes = vec![];\n\n        info!(\"Downloaded file: {:?}\", file);\n\n        while let Some(Ok(chunk)) = file.next().await {\n            bytes.extend_from_slice(&chunk);\n        }\n\n        dioxus::Ok(String::from_utf8_lossy(&bytes).to_string())\n    });\n\n    rsx! {\n        Stylesheet { href: asset!(\"/examples/assets/file_upload.css\") }\n        div {\n            max_width: \"600px\",\n            margin: \"auto\",\n            h1 { \"File upload example\" }\n            div {\n                h3 { \"Upload as FileUpload\" }\n                div {\n                    class: \"drop-zone\",\n                    ondragover: move |evt| evt.prevent_default(),\n                    ondrop: move |evt| async move {\n                        evt.prevent_default();\n                        upload_as_file_upload.call(evt.files()).await;\n                    },\n                    \"Drop files here\"\n                }\n                pre { \"{upload_as_file_upload.value():?}\" }\n            }\n\n            div {\n                h3 { \"Upload as ByteStream\" }\n                div {\n                    class: \"drop-zone\",\n                    ondragover: move |evt| evt.prevent_default(),\n                    ondrop: move |evt| async move {\n                        evt.prevent_default();\n                        upload_files_as_bytestream.call(evt.files()).await;\n                    },\n                    \"Drop files here\"\n                }\n            }\n\n            div {\n                h3 { \"Download a file from the server\" }\n                button { onclick: move |_| download_file.call(), \"Download file\" }\n                if let Some(Ok(content)) = &download_file.value() {\n                    pre { \"{content}\" }\n                } else if let Some(Err(e)) = &download_file.value() {\n                    pre { \"Error downloading file: {e}\" }\n                }\n            }\n        }\n    }\n}\n\n/// Upload a file using the `FileStream` type which automatically sets relevant metadata\n/// as headers like Content-Type, Content-Length, and Content-Disposition.\n#[post(\"/api/upload_as_file_stream\")]\nasync fn upload_file_as_filestream(mut upload: FileStream) -> Result<u32> {\n    use futures::StreamExt;\n    use std::env::temp_dir;\n    use tokio::io::AsyncWriteExt;\n\n    info!(\"Received file upload: {:?}\", upload);\n\n    // Create a temporary file to write the uploaded data to.\n    let upload_file = std::path::absolute(temp_dir().join(upload.file_name()))?;\n\n    // Reject paths that are outside the temp directory for security reasons.\n    if !upload_file.starts_with(temp_dir()) {\n        HttpError::bad_request(\"Invalid file path\")?;\n    }\n\n    info!(\n        \"Uploading bytes of {:?} file to {:?}\",\n        upload.size(),\n        upload_file\n    );\n\n    // Open the file for writing.\n    tokio::fs::create_dir_all(upload_file.parent().unwrap()).await?;\n    let mut file = tokio::fs::File::create(&upload_file).await?;\n    let expected = upload.size();\n\n    // Stream the data from the request body to the file.\n    let mut uploaded: u64 = 0;\n    let mut errored = false;\n    while let Some(chunk) = upload.next().await {\n        match chunk {\n            Ok(bytes) => {\n                uploaded += bytes.len() as u64;\n                if file.write_all(&bytes).await.is_err() {\n                    errored = true;\n                    break;\n                }\n\n                // 1GB max file size or attempting to upload more than expected.\n                if uploaded > expected.unwrap_or(1024 * 1024 * 1024) {\n                    errored = true;\n                    break;\n                }\n            }\n            Err(_) => {\n                errored = true;\n                break;\n            }\n        }\n    }\n\n    // Clean up the file if there was an error during upload.\n    if errored {\n        _ = file.sync_data().await;\n        let _ = tokio::fs::remove_file(&upload_file).await;\n        HttpError::internal_server_error(\"Failed to upload file\")?;\n    }\n\n    Ok(uploaded as u32)\n}\n\n/// Upload a file as a raw byte stream. This requires us to specify the file name and size\n/// as query parameters since the `ByteStream` type is an opaque stream without metadata.\n///\n/// We could also use custom headers to pass metadata if we wanted to avoid query parameters.\n#[post(\"/api/upload_as_bytestream?name&size\")]\nasync fn upload_as_bytestream(name: String, size: u64, mut stream: ByteStream) -> Result<()> {\n    let mut collected = 0;\n    while let Some(chunk) = stream.next().await {\n        let chunk = chunk?;\n        collected += chunk.len() as u64;\n\n        info!(\"Received {} bytes for file {}\", chunk.len(), name);\n\n        if collected > size {\n            HttpError::bad_request(\"Received more data than expected\")?;\n        }\n    }\n\n    Ok(())\n}\n\n/// Download a file from the server as a `FileStream`. This automatically sets relevant\n/// headers like Content-Type, Content-Length, and Content-Disposition.\n///\n/// This endpoint is nice because 3rd-party clients can visit it directly and download the file!\n/// Try visiting this endpoint directly in your browser.\n#[get(\"/api/download_as_filestream\")]\nasync fn download_as_filestream() -> Result<FileStream> {\n    Ok(FileStream::from_path(file!()).await?)\n}\n"
  },
  {
    "path": "examples/07-fullstack/through_reqwest.rs",
    "content": "//! This example demonstrates that dioxus server functions can be called directly as a Rust\n//! function or via an HTTP request using reqwest.\n//!\n//! Dioxus server functions generated a REST endpoint that can be called using any HTTP client.\n//! By default, they also support different serialization formats like JSON and CBOR. Try changing\n//! your `accept` header to see the different formats.\n\nuse dioxus::prelude::*;\n\nfn main() {\n    dioxus::launch(app);\n}\n\nfn app() -> Element {\n    let mut user_from_server_fn = use_action(get_user);\n\n    let mut user_from_reqwest = use_action(move |id: i32| async move {\n        let port = dioxus::cli_config::server_port().unwrap_or(8080);\n        reqwest::get(&format!(\"http://localhost:{}/api/user/{}\", port, id))\n            .await?\n            .json::<User>()\n            .await\n    });\n\n    rsx! {\n        button { onclick: move |_| user_from_server_fn.call(123), \"Fetch Data\" }\n        button { onclick: move |_| user_from_reqwest.call(456), \"Fetch From Endpoint\" }\n        div { display: \"flex\", flex_direction: \"column\",\n            pre { \"User from server: {user_from_server_fn.value():?}\", }\n            pre { \"User from server: {user_from_reqwest.value():?}\", }\n        }\n    }\n}\n\n#[derive(serde::Serialize, serde::Deserialize, Debug)]\nstruct User {\n    id: String,\n    name: String,\n}\n\n#[get(\"/api/user/{id}\")]\nasync fn get_user(id: i32) -> Result<User> {\n    Ok(User {\n        id: id.to_string(),\n        name: \"John Doe\".into(),\n    })\n}\n"
  },
  {
    "path": "examples/07-fullstack/websocket.rs",
    "content": "//! This example showcases the built-in websocket functionality in Dioxus Fullstack.\n//!\n//! We can create a new websocket endpoint that takes the WebSocketOptions as a body and returns\n//! a `Websocket` instance that the client uses to communicate with the server.\n//!\n//! The `Websocket` type is generic over the message types and the encoding used to serialize the messages.\n//!\n//! By default, we use `JsonEncoding`, but in this example, we use `CborEncoding` to demonstrate that\n//! binary encodings also work.\n//!\n//! The `use_websocket` hook wraps the `Websocket` instance and provides a reactive interface to the\n//! state of the connection, as well as methods to send and receive messages.\n//!\n//! Because the websocket is generic over the message types, calls to `.recv()` and `.send()` are\n//! strongly typed, making it easy to send and receive messages without having to manually\n//! serialize and deserialize them.\n\nuse dioxus::{fullstack::CborEncoding, prelude::*};\nuse dioxus_fullstack::{WebSocketOptions, Websocket, use_websocket};\nuse serde::{Deserialize, Serialize};\n\nfn main() {\n    dioxus::launch(app);\n}\n\nfn app() -> Element {\n    let mut messages = use_signal(std::vec::Vec::new);\n\n    // This signal is read inside the use_websocket closure, making it a reactive dependency.\n    // Whenever it changes, the websocket will automatically re-connect.\n    let mut name = use_signal(|| \"John Doe\".to_string());\n\n    let mut socket =\n        use_websocket(move || uppercase_ws(name.cloned(), 30, WebSocketOptions::new()));\n\n    use_future(move || async move {\n        loop {\n            // Wait for the socket to connect\n            _ = socket.connect().await;\n\n            // Loop poll with recv. Throws an error when the connection closes, making it possible\n            // to run code before the socket re-connects when the name input changes\n            while let Ok(msg) = socket.recv().await {\n                messages.push(msg);\n            }\n        }\n    });\n\n    rsx! {\n        h1 { \"WebSocket Example\" }\n        p { \"Type a message and see it echoed back in uppercase!\" }\n        p { \"Connection status: {socket.status():?}\" }\n        p { \"Change your name to trigger a websocket re-connect\" }\n        input {\n            placeholder: \"Your name\",\n            value: \"{name}\",\n            oninput: move |e| name.set(e.value()),\n        }\n        input {\n            placeholder: \"Type a message\",\n            oninput: move |e| async move { _ = socket.send(ClientEvent::TextInput(e.value())).await; },\n        }\n        button { onclick: move |_| messages.clear(), \"Clear messages\" }\n        for message in messages.read().iter().rev() {\n            pre { \"{message:?}\" }\n        }\n    }\n}\n\n#[derive(Serialize, Deserialize, Debug)]\nenum ClientEvent {\n    TextInput(String),\n}\n\n#[derive(Serialize, Deserialize, Debug)]\nenum ServerEvent {\n    Uppercase(String),\n}\n\n#[get(\"/api/uppercase_ws?name&age\")]\nasync fn uppercase_ws(\n    name: String,\n    age: i32,\n    options: WebSocketOptions,\n) -> Result<Websocket<ClientEvent, ServerEvent, CborEncoding>> {\n    Ok(options.on_upgrade(move |mut socket| async move {\n        // send back a greeting message\n        _ = socket\n            .send(ServerEvent::Uppercase(format!(\n                \"First message from server: Hello, {}! You are {} years old.\",\n                name, age\n            )))\n            .await;\n\n        // Loop and echo back uppercase messages\n        while let Ok(ClientEvent::TextInput(next)) = socket.recv().await {\n            _ = socket.send(ServerEvent::Uppercase(next)).await;\n        }\n    }))\n}\n"
  },
  {
    "path": "examples/08-apis/control_focus.rs",
    "content": "//! Managing focus\n//!\n//! This example shows how to manage focus in a Dioxus application. We implement a \"roulette\" that focuses on each input\n//! in the grid every few milliseconds until the user interacts with the inputs.\n\nuse std::rc::Rc;\n\nuse async_std::task::sleep;\nuse dioxus::prelude::*;\n\nconst STYLE: Asset = asset!(\"/examples/assets/roulette.css\");\n\nfn main() {\n    dioxus::launch(app);\n}\n\nfn app() -> Element {\n    // Element data is stored as Rc<MountedData> so we can clone it and pass it around\n    let mut elements = use_signal(Vec::<Rc<MountedData>>::new);\n    let mut running = use_signal(|| true);\n\n    use_future(move || async move {\n        let mut focused = 0;\n\n        loop {\n            sleep(std::time::Duration::from_millis(50)).await;\n\n            if !running() {\n                continue;\n            }\n\n            if let Some(element) = elements.with(|f| f.get(focused).cloned()) {\n                _ = element.set_focus(true).await;\n            } else {\n                focused = 0;\n            }\n\n            focused += 1;\n        }\n    });\n\n    rsx! {\n        Stylesheet { href: STYLE }\n        h1 { \"Input Roulette\" }\n        button { onclick: move |_| running.toggle(), \"Toggle roulette\" }\n        div { id: \"roulette-grid\",\n            // Restart the roulette if the user presses escape\n            onkeydown: move |event| {\n                if event.code().to_string() == \"Escape\" {\n                    running.set(true);\n                }\n            },\n\n            // Draw the grid of inputs\n            for i in 0..100 {\n                input {\n                    r#type: \"number\",\n                    value: \"{i}\",\n                    onmounted: move |cx| elements.push(cx.data()),\n                    oninput: move |_| running.set(false),\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "examples/08-apis/custom_html.rs",
    "content": "//! This example shows how to use a custom index.html and custom <HEAD> extensions\n//! to add things like stylesheets, scripts, and third-party JS libraries.\n\nuse dioxus::prelude::*;\n\nfn main() {\n    dioxus::LaunchBuilder::new()\n        .with_cfg(\n            dioxus::desktop::Config::new().with_custom_index(\n                r#\"\n<!DOCTYPE html>\n<html>\n  <head>\n    <title>Dioxus app</title>\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <style>body { background-color: olive; }</style>\n  </head>\n  <body>\n    <h1>External HTML</h1>\n    <div id=\"main\"></div>\n  </body>\n</html>\n        \"#\n                .into(),\n            ),\n        )\n        .launch(app);\n}\n\nfn app() -> Element {\n    rsx! {\n        h1 { \"Custom HTML!\" }\n    }\n}\n"
  },
  {
    "path": "examples/08-apis/custom_menu.rs",
    "content": "//! This example shows how to use a custom menu bar with Dioxus desktop.\n//! This example is not supported on the mobile or web renderers.\n\nuse dioxus::desktop::{muda::*, use_muda_event_handler};\nuse dioxus::prelude::*;\n\nfn main() {\n    // Create a menu bar that only contains the edit menu\n    let menu = Menu::new();\n    let edit_menu = Submenu::new(\"Edit\", true);\n\n    edit_menu\n        .append_items(&[\n            &PredefinedMenuItem::undo(None),\n            &PredefinedMenuItem::redo(None),\n            &PredefinedMenuItem::separator(),\n            &PredefinedMenuItem::cut(None),\n            &PredefinedMenuItem::copy(None),\n            &PredefinedMenuItem::paste(None),\n            &PredefinedMenuItem::select_all(None),\n            &MenuItem::with_id(\"switch-text\", \"Switch text\", true, None),\n        ])\n        .unwrap();\n\n    menu.append(&edit_menu).unwrap();\n\n    // Create a desktop config that overrides the default menu with the custom menu\n    let config = dioxus::desktop::Config::new().with_menu(menu);\n\n    // Launch the app with the custom menu\n    dioxus::LaunchBuilder::new().with_cfg(config).launch(app)\n}\n\nfn app() -> Element {\n    let mut text = use_signal(String::new);\n    // You can use the `use_muda_event_handler` hook to run code when a menu event is triggered.\n    use_muda_event_handler(move |muda_event| {\n        if muda_event.id() == \"switch-text\" {\n            text.set(\"Switched to text\".to_string());\n        }\n    });\n\n    rsx! {\n        div {\n            h1 { \"Custom Menu\" }\n            p { \"Text: {text}\" }\n        }\n    }\n}\n"
  },
  {
    "path": "examples/08-apis/drag_and_drop.rs",
    "content": "//! This example shows how to implement a simple drag-and-drop kanban board using Dioxus.\n//! You can drag items between different categories and edit their contents.\n//!\n//! This example uses the `.data_transfer()` API to handle drag-and-drop events. When an item is dragged,\n//! its ID is stored in the data transfer object. When the item is dropped into a new category, its ID is retrieved\n//! from the data transfer object and used to update the item's category.\n//!\n//! Note that in a real-world application, you'll want more sophisticated drop handling, such as visual\n//! feedback during dragging, and better drop-zone detection to allow dropping *between* items.\n\nuse dioxus::prelude::*;\n\nfn main() {\n    dioxus::launch(app);\n}\n\nstruct Item {\n    id: usize,\n    name: String,\n    category: String,\n    contents: String,\n}\n\nfn app() -> Element {\n    let mut items = use_signal(initial_kanban_data);\n\n    rsx! {\n        div {\n            display: \"flex\",\n            gap: \"20px\",\n            flex_direction: \"row\",\n            for category in [\"A\", \"B\", \"C\"] {\n                div {\n                    class: \"category\",\n                    display: \"flex\",\n                    flex_direction: \"column\",\n                    gap: \"10px\",\n                    padding: \"10px\",\n                    flex_grow: \"1\",\n                    border: \"2px solid black\",\n                    min_height: \"300px\",\n                    background_color: \"#f0f0f0\",\n                    ondragover: |e| e.prevent_default(),\n                    ondrop: move |e| {\n                        if let Some(item_id) = e.data_transfer().get_data(\"text/plain\").and_then(|data| data.parse::<usize>().ok()) {\n                            if let Some(pos) = items.iter().position(|item| item.id == item_id) {\n                                items.write()[pos].category = category.to_string();\n                            }\n                        }\n                    },\n                    h2 { \"Category: {category}\" }\n                    for (index, item) in items.iter().enumerate().filter(|item| item.1.category == category) {\n                        div {\n                            key: \"{item.id}\",\n                            width: \"200px\",\n                            height: \"50px\",\n                            border: \"1px solid black\",\n                            padding: \"10px\",\n                            class: \"item\",\n                            draggable: \"true\",\n                            background: \"white\",\n                            cursor: \"grab\",\n                            ondragstart: move |e| {\n                                let id = items.read()[index].id.to_string();\n                                e.data_transfer().set_data(\"text/plain\", &id).unwrap();\n                            },\n                            pre { webkit_user_select: \"none\", \"{item.name}\" }\n                            input {\n                                r#type: \"text\",\n                                value: \"{item.contents}\",\n                                oninput: move |e| {\n                                    items.write()[index].contents = e.value();\n                                }\n                            }\n                        }\n                    }\n                }\n            }\n        }\n    }\n}\n\nfn initial_kanban_data() -> Vec<Item> {\n    vec![\n        Item {\n            id: 1,\n            name: \"Item 1\".into(),\n            category: \"A\".into(),\n            contents: \"This is item 1\".into(),\n        },\n        Item {\n            id: 2,\n            name: \"Item 2\".into(),\n            category: \"A\".into(),\n            contents: \"This is item 2\".into(),\n        },\n        Item {\n            id: 3,\n            name: \"Item 3\".into(),\n            category: \"A\".into(),\n            contents: \"This is item 3\".into(),\n        },\n        Item {\n            id: 4,\n            name: \"Item 4\".into(),\n            category: \"B\".into(),\n            contents: \"This is item 4\".into(),\n        },\n        Item {\n            id: 5,\n            name: \"Item 5\".into(),\n            category: \"B\".into(),\n            contents: \"This is item 5\".into(),\n        },\n        Item {\n            id: 6,\n            name: \"Item 6\".into(),\n            category: \"C\".into(),\n            contents: \"This is item 6\".into(),\n        },\n    ]\n}\n"
  },
  {
    "path": "examples/08-apis/eval.rs",
    "content": "//! This example shows how to use the `eval` function to run JavaScript code in the webview.\n//!\n//! Eval will only work with renderers that support javascript - so currently only the web and desktop/mobile renderers\n//! that use a webview. Native renderers will throw \"unsupported\" errors when calling `eval`.\n\nuse async_std::task::sleep;\nuse dioxus::prelude::*;\n\nfn main() {\n    dioxus::launch(app);\n}\n\nfn app() -> Element {\n    // Create a future that will resolve once the javascript has been successfully executed.\n    let future = use_resource(move || async move {\n        // Wait a little bit just to give the appearance of a loading screen\n        sleep(std::time::Duration::from_secs(1)).await;\n\n        // The `eval` is available in the prelude - and simply takes a block of JS.\n        // Dioxus' eval is interesting since it allows sending messages to and from the JS code using the `await dioxus.recv()`\n        // builtin function. This allows you to create a two-way communication channel between Rust and JS.\n        let mut eval = document::eval(\n            r#\"\n                dioxus.send(\"Hi from JS!\");\n                let msg = await dioxus.recv();\n                console.log(msg);\n                return \"hi from JS!\";\n            \"#,\n        );\n\n        // Send a message to the JS code.\n        eval.send(\"Hi from Rust!\").unwrap();\n\n        // Our line on the JS side will log the message and then return \"hello world\".\n        let res: String = eval.recv().await.unwrap();\n\n        // This will print \"Hi from JS!\" and \"Hi from Rust!\".\n        println!(\"{:?}\", eval.await);\n\n        res\n    });\n\n    match future.value().as_ref() {\n        Some(v) => rsx!( p { \"{v}\" } ),\n        _ => rsx!( p { \"waiting..\" } ),\n    }\n}\n"
  },
  {
    "path": "examples/08-apis/file_upload.rs",
    "content": "//! This example shows how to use the `file` methods on FormEvent and DragEvent to handle file uploads and drops.\n//!\n//! Dioxus intercepts these events and provides a Rusty interface to the file data. Since we want this interface to\n//! be crossplatform,\n\nuse dioxus::html::HasFileData;\nuse dioxus::prelude::*;\nuse dioxus_html::FileData;\n\nconst STYLE: Asset = asset!(\"/examples/assets/file_upload.css\");\n\nfn main() {\n    dioxus::launch(app);\n}\n\nstruct UploadedFile {\n    name: String,\n    contents: String,\n}\n\nfn app() -> Element {\n    let mut enable_directory_upload = use_signal(|| false);\n    let mut files_uploaded = use_signal(|| Vec::new() as Vec<UploadedFile>);\n    let mut hovered = use_signal(|| false);\n\n    let upload_files = move |files: Vec<FileData>| async move {\n        for file in files {\n            let filename = file.name();\n            if let Ok(contents) = file.read_string().await {\n                files_uploaded.push(UploadedFile {\n                    name: filename,\n                    contents,\n                });\n            } else {\n                files_uploaded.push(UploadedFile {\n                    name: filename,\n                    contents: \"Failed to read file\".into(),\n                });\n            }\n        }\n    };\n\n    rsx! {\n        Stylesheet { href: STYLE }\n\n        h1 { \"File Upload Example\" }\n        p { \"Drop a .txt, .rs, or .js file here to read it\" }\n        button { onclick: move |_| files_uploaded.clear(), \"Clear files\" }\n\n        div {\n            label { r#for: \"directory-upload\", \"Enable directory upload\" }\n            input {\n                r#type: \"checkbox\",\n                id: \"directory-upload\",\n                checked: enable_directory_upload,\n                oninput: move |evt| enable_directory_upload.set(evt.checked()),\n            }\n        }\n\n        div {\n            label { r#for: \"textreader\", \"Upload text/rust files and read them\" }\n            input {\n                r#type: \"file\",\n                accept: \".txt,.rs,.js\",\n                multiple: true,\n                name: \"textreader\",\n                directory: enable_directory_upload,\n                onchange: move |evt| async move {\n                    upload_files(evt.files()).await\n                },\n            }\n        }\n\n        div {\n            id: \"drop-zone\",\n            background_color: if hovered() { \"lightblue\" } else { \"lightgray\" },\n            ondragover: move |evt| {\n                evt.prevent_default();\n                hovered.set(true)\n            },\n            ondragleave: move |_| hovered.set(false),\n            ondrop: move |evt| async move {\n                evt.prevent_default();\n                hovered.set(false);\n                upload_files(evt.files()).await;\n            },\n            \"Drop files here\"\n        }\n\n        ul {\n            for file in files_uploaded.read().iter().rev() {\n                li {\n                    span { \"{file.name}\" }\n                    pre  { \"{file.contents}\"  }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "examples/08-apis/form.rs",
    "content": "//! Forms\n//!\n//! Dioxus forms deviate slightly from html, automatically returning all named inputs\n//! in the \"values\" field.\n\nuse dioxus::prelude::*;\n\nfn main() {\n    dioxus::launch(app);\n}\n\nfn app() -> Element {\n    let mut values = use_signal(Vec::new);\n    let mut submitted_values = use_signal(Vec::new);\n\n    rsx! {\n        div { style: \"display: flex\",\n            div { style: \"width: 50%\",\n                h1 { \"Form\" }\n\n                if !submitted_values.read().is_empty() {\n                    h2 { \"Submitted! ✅\" }\n                }\n\n                // The form element is used to create an HTML form for user input\n                // You can attach regular attributes to it\n                form {\n                    id: \"cool-form\",\n                    style: \"display: flex; flex-direction: column;\",\n\n                    // You can attach a handler to the entire form\n                    oninput: move |ev| {\n                        println!(\"Input event: {:#?}\", ev);\n                        values.set(ev.values());\n                    },\n\n                    // On desktop/liveview, the form will not navigate the page - the expectation is that you handle\n                    // The form event.\n                    // However, if your form doesn't have a submit handler, it might navigate the page depending on the webview.\n                    // We suggest always attaching a submit handler to the form.\n                    onsubmit: move |ev| {\n                        println!(\"Submit event: {:#?}\", ev);\n                        submitted_values.set(ev.values());\n                    },\n\n                    // Regular text inputs with handlers\n                    label { r#for: \"username\", \"Username\" }\n                    input {\n                        r#type: \"text\",\n                        name: \"username\",\n                        oninput: move |ev| {\n                            println!(\"setting username\");\n                            values.set(ev.values());\n                        }\n                    }\n\n                    // And then the various inputs that might exist\n                    // Note for a value to be returned in .values(), it must be named!\n\n                    label { r#for: \"full-name\", \"Full Name\" }\n                    input { r#type: \"text\", name: \"full-name\" }\n                    input { r#type: \"text\", name: \"full-name\" }\n\n                    label { r#for: \"email\", \"Email (matching <name>@example.com)\" }\n                    input { r#type: \"email\", size: \"30\", id: \"email\", name: \"email\" }\n\n                    label { r#for: \"password\", \"Password\" }\n                    input { r#type: \"password\", name: \"password\" }\n\n                    label { r#for: \"color\", \"Color\" }\n                    input { r#type: \"radio\", checked: true, name: \"color\", value: \"red\" }\n                    input { r#type: \"radio\", name: \"color\", value: \"blue\" }\n                    input { r#type: \"radio\", name: \"color\", value: \"green\" }\n\n                    // Select multiple comes in as a comma separated list of selected values\n                    // You should split them on the comma to get the values manually\n                    label { r#for: \"country\", \"Country\" }\n                    select {\n                        name: \"country\",\n                        multiple: true,\n                        oninput: move |ev| {\n                            println!(\"Input event: {:#?}\", ev);\n                            println!(\"Values: {:#?}\", ev.value().split(',').collect::<Vec<_>>());\n                        },\n                        option { value: \"usa\",  \"USA\" }\n                        option { value: \"canada\",  \"Canada\" }\n                        option { value: \"mexico\",  \"Mexico\" }\n                    }\n\n                    // Safari can be quirky with color inputs on mac.\n                    // We recommend always providing a text input for color as a fallback.\n                    label { r#for: \"color\", \"Color\" }\n                    input { r#type: \"color\", value: \"#000002\", name: \"head\", id: \"head\" }\n\n                    // Dates!\n                    input {\n                        min: \"2018-01-01\",\n                        value: \"2018-07-22\",\n                        r#type: \"date\",\n                        name: \"trip-start\",\n                        max: \"2025-12-31\",\n                        id: \"start\"\n                    }\n\n                    // CHekcboxes\n                    label { r#for: \"cbox\", \"Color\" }\n                    div {\n                        label { r#for: \"cbox-red\", \"red\" }\n                        input { r#type: \"checkbox\", checked: true, name: \"cbox\", value: \"red\", id: \"cbox-red\" }\n                    }\n                    div {\n                        label { r#for: \"cbox-blue\", \"blue\" }\n                        input { r#type: \"checkbox\", name: \"cbox\", value: \"blue\", id: \"cbox-blue\" }\n                    }\n                    div {\n                        label { r#for: \"cbox-green\", \"green\" }\n                        input { r#type: \"checkbox\", name: \"cbox\", value: \"green\", id: \"cbox-green\" }\n                    }\n                    div {\n                        label { r#for: \"cbox-yellow\", \"yellow\" }\n                        input { r#type: \"checkbox\", name: \"cbox\", value: \"yellow\", id: \"cbox-yellow\" }\n                    }\n\n                    // File input\n                    label { r#for: \"headshot\", \"Headshot\" }\n                    input { r#type: \"file\", name: \"headshot\", id: \"headshot\", multiple: true, accept: \".png,.jpg,.jpeg\" }\n\n                    // Buttons will submit your form by default.\n                    button { r#type: \"submit\", value: \"Submit\", \"Submit the form\" }\n                }\n            }\n            div { style: \"width: 50%\",\n                h1 { \"Oninput Values\" }\n                pre { \"{values:#?}\" }\n            }\n        }\n        button {\n            onclick: move |_| {\n                println!(\"Values: {:#?}\", values.read());\n            },\n            \"Log values\"\n        }\n    }\n}\n"
  },
  {
    "path": "examples/08-apis/logging.rs",
    "content": "//! Dioxus ships out-of-the-box with tracing hooks that integrate with the Dioxus-CLI.\n//!\n//! The built-in tracing-subscriber automatically sets up a wasm panic hook and wires up output\n//! to be consumed in a machine-readable format when running under `dx`.\n//!\n//! You can disable the built-in tracing-subscriber or customize the log level yourself.\n//!\n//! By default:\n//! - in `dev` mode, the default log output is `debug`\n//! - in `release` mode, the default log output is `info`\n//!\n//! To use the dioxus logger in your app, simply call any of the tracing functions (info!(), warn!(), error!())\n\nuse dioxus::logger::tracing::{Level, debug, error, info, warn};\nuse dioxus::prelude::*;\n\nfn main() {\n    // `dioxus::logger::init` is optional and called automatically by `dioxus::launch`.\n    // In development mode, the `Debug` tracing level is set, and in release only the `Info` level is set.\n    // You can call it yourself manually in the cases you:\n    //   - want to customize behavior\n    //   - aren't using `dioxus::launch` (i.e. custom fullstack setups) but want the integration.\n    // The Tracing crate is the logging interface that the dioxus-logger uses.\n    dioxus::logger::init(Level::INFO).expect(\"Failed to initialize logger\");\n    dioxus::launch(app);\n}\n\nfn app() -> Element {\n    rsx! {\n        div {\n            h1 { \"Logger demo\" }\n            button {\n                onclick: move |_| warn!(\"Here's a warning!\"),\n                \"Warn!\"\n            }\n            button {\n                onclick: move |_| error!(\"Here's an error!\"),\n                \"Error!\"\n            }\n            button {\n                onclick: move |_| {\n                    debug!(\"Here's a debug\");\n                    warn!(\"The log level is set to info so there should not be a debug message\")\n                },\n                \"Debug!\"\n            }\n            button {\n                onclick: move |_| info!(\"Here's an info!\"),\n                \"Info!\"\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "examples/08-apis/multiwindow.rs",
    "content": "//! Multiwindow example\n//!\n//! This example shows how to implement a simple multiwindow application using dioxus.\n//! This works by spawning a new window when the user clicks a button. We have to build a new virtualdom which has its\n//! own context, root elements, etc.\n\nuse dioxus::prelude::*;\n\nfn main() {\n    dioxus::launch(app);\n}\n\nfn app() -> Element {\n    let onclick = move |_| {\n        dioxus::desktop::window().new_window(VirtualDom::new(popup), Default::default());\n    };\n\n    rsx! {\n        button { onclick, \"New Window\" }\n    }\n}\n\nfn popup() -> Element {\n    let mut count = use_signal(|| 0);\n    rsx! {\n        div {\n            h1 { \"Popup Window\" }\n            p { \"Count: {count}\" }\n            button { onclick: move |_| count += 1, \"Increment\" }\n        }\n    }\n}\n"
  },
  {
    "path": "examples/08-apis/multiwindow_with_tray_icon.rs",
    "content": "//! Multiwindow with tray icon example\n//!\n//! This example shows how to implement a simple multiwindow application and tray icon using dioxus.\n//! This works by spawning a new window when the user clicks a button. We have to build a new virtualdom which has its\n//! own context, root elements, etc.\n//!\n//! This is useful for apps that incorporate settings panels or persistent windows like Raycast.\n\nuse dioxus::desktop::{\n    WindowCloseBehaviour,\n    trayicon::{default_tray_icon, init_tray_icon},\n    window,\n};\nuse dioxus::prelude::*;\n\nfn main() {\n    dioxus::launch(app);\n}\n\nfn app() -> Element {\n    use_hook(|| {\n        // Set the close behavior for the main window\n        // This will hide the window instead of closing it when the user clicks the close button\n        window().set_close_behavior(WindowCloseBehaviour::WindowHides);\n\n        // Initialize the tray icon with a default icon and no menu\n        // This will provide the tray into context for the application\n        init_tray_icon(default_tray_icon(), None)\n    });\n\n    rsx! {\n        button {\n            onclick: move |_| {\n                window().new_window(VirtualDom::new(popup), Default::default());\n            },\n            \"New Window\"\n        }\n    }\n}\n\nfn popup() -> Element {\n    rsx! {\n        div { \"This is a popup window!\" }\n    }\n}\n"
  },
  {
    "path": "examples/08-apis/on_resize.rs",
    "content": "//! Run a callback\n//!\n//! Whenever an Element is finally mounted to the Dom, its data is available to be read.\n//! These fields can typically only be read asynchronously, since various renderers need to release the main thread to\n//! perform layout and painting.\n\nuse dioxus::prelude::*;\nuse dioxus_elements::geometry::euclid::Size2D;\n\nfn main() {\n    dioxus::launch(app);\n}\n\nfn app() -> Element {\n    let mut dimensions = use_signal(Size2D::zero);\n\n    rsx!(\n        Stylesheet { href: asset!(\"/examples/assets/read_size.css\") }\n        div {\n            width: \"50%\",\n            height: \"50%\",\n            background_color: \"red\",\n            onresize: move |evt| dimensions.set(evt.data().get_content_box_size().unwrap()),\n            \"This element is {dimensions():?}\"\n        }\n    )\n}\n"
  },
  {
    "path": "examples/08-apis/on_visible.rs",
    "content": "//! Port of the https://codepen.io/ryanfinni/pen/VwZeGxN example\n\nuse dioxus::prelude::*;\n\nfn main() {\n    dioxus::launch(app);\n}\n\nfn app() -> Element {\n    let mut animated_classes = use_signal(|| [\"animated-text\", \"\"]);\n\n    rsx! {\n        Stylesheet { href: asset!(\"/examples/assets/visible.css\") }\n        div {\n            class: \"container\",\n\n            p {\n                \"Scroll to the bottom of the page. The text will transition in when it becomes visible in the viewport.\"\n            }\n\n            p {\n                \"First, let's create a new project for our hacker news app. We can use the CLI to create a new\n                project. You can select a platform of your choice or view the getting started guide for more information\n                on each option. If you aren't sure what platform to try out, we recommend getting started with web or\n                desktop:\"\n            }\n\n            p {\n                \"The template contains some boilerplate to help you get started. For this guide, we will be rebuilding some of the code\n                from scratch for learning purposes. You can clear the src/main.rs file. We will be adding new code in the next\n                sections.\"\n            }\n\n            p {\n                \"Next, let's setup our dependencies. We need to set up a few dependencies to work with the hacker news API: \"\n            }\n\n            p {\n                \"First, let's create a new project for our hacker news app. We can use the CLI to create a new\n                project. You can select a platform of your choice or view the getting started guide for more information\n                on each option. If you aren't sure what platform to try out, we recommend getting started with web or\n                desktop:\"\n            }\n\n            p {\n                \"The template contains some boilerplate to help you get started. For this guide, we will be rebuilding some of the code\n                from scratch for learning purposes. You can clear the src/main.rs file. We will be adding new code in the next\n                sections.\"\n            }\n\n            p {\n                \"Next, let's setup our dependencies. We need to set up a few dependencies to work with the hacker news API: \"\n            }\n\n            p {\n                \"First, let's create a new project for our hacker news app. We can use the CLI to create a new\n                project. You can select a platform of your choice or view the getting started guide for more information\n                on each option. If you aren't sure what platform to try out, we recommend getting started with web or\n                desktop:\"\n            }\n\n            p {\n                \"The template contains some boilerplate to help you get started. For this guide, we will be rebuilding some of the code\n                from scratch for learning purposes. You can clear the src/main.rs file. We will be adding new code in the next\n                sections.\"\n            }\n\n            p {\n                \"Next, let's setup our dependencies. We need to set up a few dependencies to work with the hacker news API: \"\n            }\n\n            h2 {\n                class: animated_classes().join(\" \"),\n                onvisible: move |evt| {\n                    let data = evt.data();\n                    if let Ok(is_intersecting) = data.is_intersecting() {\n                        animated_classes.write()[1] = if is_intersecting { \"visible\" } else { \"\" };\n                    }\n                },\n\n                \"Animated Text\"\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "examples/08-apis/overlay.rs",
    "content": "//! This example demonstrates how to create an overlay window with dioxus.\n//!\n//! Basically, we just create a new window with a transparent background and no decorations, size it to the screen, and\n//! then we can draw whatever we want on it. In this case, we're drawing a simple overlay with a draggable header.\n//!\n//! We also add a global shortcut to toggle the overlay on and off, so you could build a raycast-type app with this.\n\nuse dioxus::desktop::{\n    HotKeyState, LogicalSize, WindowBuilder, tao::dpi::PhysicalPosition, use_global_shortcut,\n};\nuse dioxus::prelude::*;\n\nfn main() {\n    dioxus::LaunchBuilder::desktop()\n        .with_cfg(make_config())\n        .launch(app);\n}\n\nfn app() -> Element {\n    let mut show_overlay = use_signal(|| true);\n\n    _ = use_global_shortcut(\"cmd+g\", move |state| {\n        if state == HotKeyState::Pressed {\n            show_overlay.toggle();\n        }\n    });\n\n    rsx! {\n        Stylesheet { href: asset!(\"/examples/assets/overlay.css\") }\n        if show_overlay() {\n            div {\n                width: \"100%\",\n                height: \"100%\",\n                background_color: \"red\",\n                border: \"1px solid black\",\n\n                div {\n                    width: \"100%\",\n                    height: \"10px\",\n                    background_color: \"black\",\n                    onmousedown: move |_| dioxus::desktop::window().drag(),\n                }\n\n                \"This is an overlay!\"\n            }\n        }\n    }\n}\n\nfn make_config() -> dioxus::desktop::Config {\n    dioxus::desktop::Config::default().with_window(make_window())\n}\n\nfn make_window() -> WindowBuilder {\n    WindowBuilder::new()\n        .with_transparent(true)\n        .with_decorations(false)\n        .with_resizable(false)\n        .with_always_on_top(true)\n        .with_position(PhysicalPosition::new(0, 0))\n        .with_max_inner_size(LogicalSize::new(100000, 50))\n}\n"
  },
  {
    "path": "examples/08-apis/read_size.rs",
    "content": "//! Read the size of elements using the MountedData struct.\n//!\n//! Whenever an Element is finally mounted to the Dom, its data is available to be read.\n//! These fields can typically only be read asynchronously, since various renderers need to release the main thread to\n//! perform layout and painting.\n\nuse std::rc::Rc;\n\nuse dioxus::{html::geometry::euclid::Rect, prelude::*};\n\nfn main() {\n    dioxus::launch(app);\n}\n\nfn app() -> Element {\n    let mut div_element = use_signal(|| None as Option<Rc<MountedData>>);\n    let mut dimensions = use_signal(Rect::zero);\n\n    let read_dims = move |_| async move {\n        let read = div_element.read();\n        let client_rect = read.as_ref().map(|el| el.get_client_rect());\n\n        if let Some(client_rect) = client_rect {\n            if let Ok(rect) = client_rect.await {\n                dimensions.set(rect);\n            }\n        }\n    };\n\n    rsx! {\n        Stylesheet { href: asset!(\"/examples/assets/read_size.css\") }\n        div {\n            width: \"50%\",\n            height: \"50%\",\n            background_color: \"red\",\n            onmounted: move |cx| div_element.set(Some(cx.data())),\n            \"This element is {dimensions():?}\"\n        }\n\n        button { onclick: read_dims, \"Read dimensions\" }\n    }\n}\n"
  },
  {
    "path": "examples/08-apis/scroll_to_offset.rs",
    "content": "//! Scroll elements using their MountedData\n//!\n//! Dioxus exposes a few helpful APIs around elements (mimicking the DOM APIs) to allow you to interact with elements\n//! across the renderers. This includes scrolling, reading dimensions, and more.\n//!\n//! In this example we demonstrate how to scroll to a given y offset of the scrollable parent using the `scroll` method on the `MountedData`\n\nuse dioxus::html::geometry::PixelsVector2D;\nuse dioxus::prelude::*;\n\nfn main() {\n    dioxus::launch(app);\n}\n\nfn app() -> Element {\n    rsx! {\n        ScrollToCoordinates {}\n        ScrollToCoordinates {}\n    }\n}\n\n#[component]\nfn ScrollToCoordinates() -> Element {\n    let mut element = use_signal(|| None);\n\n    rsx! {\n        div { border: \"1px solid black\", position: \"relative\",\n\n            div {\n                height: \"300px\",\n                overflow_y: \"auto\",\n\n                onmounted: move |event| element.set(Some(event.data())),\n\n                for i in 0..100 {\n                    div { height: \"20px\", \"Item {i}\" }\n                }\n            }\n\n            div { position: \"absolute\", top: 0, right: 0,\n                input {\n                    r#type: \"number\",\n                    min: \"0\",\n                    max: \"99\",\n                    oninput: move |event| async move {\n                        if let Some(ul) = element.cloned() {\n                            let data = event.data();\n                            if let Ok(value) = data.parsed::<f64>() {\n                                ul.scroll(PixelsVector2D::new(0.0, 20.0 * value), ScrollBehavior::Smooth)\n                                    .await\n                                    .unwrap();\n                            }\n                        }\n                    },\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "examples/08-apis/scroll_to_top.rs",
    "content": "//! Scroll elements using their MountedData\n//!\n//! Dioxus exposes a few helpful APIs around elements (mimicking the DOM APIs) to allow you to interact with elements\n//! across the renderers. This includes scrolling, reading dimensions, and more.\n//!\n//! In this example we demonstrate how to scroll to the top of the page using the `scroll_to` method on the `MountedData`\n\nuse dioxus::prelude::*;\n\nfn main() {\n    dioxus::launch(app);\n}\n\nfn app() -> Element {\n    let mut header_element = use_signal(|| None);\n\n    rsx! {\n        div {\n            h1 {\n                onmounted: move |cx| header_element.set(Some(cx.data())),\n                \"Scroll to top example\"\n            }\n\n            for i in 0..100 {\n                div { \"Item {i}\" }\n            }\n\n            button {\n                onclick: move |_| async move {\n                    if let Some(header) = header_element.cloned() {\n                        header.scroll_to(ScrollBehavior::Smooth).await.unwrap();\n                    }\n                },\n                \"Scroll to top\"\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "examples/08-apis/shortcut.rs",
    "content": "//! Add global shortcuts to your app while a component is active\n//!\n//! This demo shows how to add a global shortcut to your app that toggles a signal. You could use this to implement\n//! a raycast-type app, or to add a global shortcut to your app that toggles a component on and off.\n//!\n//! These are *global* shortcuts, so they will work even if your app is not in focus.\n\nuse dioxus::desktop::{HotKeyState, use_global_shortcut};\nuse dioxus::prelude::*;\n\nfn main() {\n    dioxus::LaunchBuilder::desktop().launch(app);\n}\n\nfn app() -> Element {\n    let mut toggled = use_signal(|| false);\n\n    _ = use_global_shortcut(\"ctrl+s\", move |state| {\n        if state == HotKeyState::Pressed {\n            toggled.toggle();\n        }\n    });\n\n    rsx!(\"toggle: {toggled}\")\n}\n"
  },
  {
    "path": "examples/08-apis/ssr.rs",
    "content": "//! Example: SSR\n//!\n//! This example shows how we can render the Dioxus Virtualdom using SSR.\n//! Dioxus' SSR is quite comprehensive and can generate a number of utility markers for things like hydration.\n//!\n//! You can also render without any markers to get a clean HTML output.\n\nuse dioxus::prelude::*;\n\nfn main() {\n    // We can render VirtualDoms\n    let vdom = VirtualDom::prebuilt(app);\n    println!(\"{}\", dioxus_ssr::render(&vdom));\n\n    // Or we can render rsx! calls themselves\n    println!(\n        \"{}\",\n        dioxus_ssr::render_element(rsx! {\n            div {\n                h1 { \"Hello, world!\" }\n            }\n        })\n    );\n\n    // We can configure the SSR rendering to add ids for rehydration\n    println!(\"{}\", dioxus_ssr::pre_render(&vdom));\n\n    // We can render to a buf directly too\n    let mut file = String::new();\n    let mut renderer = dioxus_ssr::Renderer::default();\n    renderer.render_to(&mut file, &vdom).unwrap();\n    println!(\"{file}\");\n}\n\nfn app() -> Element {\n    rsx!(\n        div {\n            h1 { \"Title\" }\n            p { \"Body\" }\n        }\n    )\n}\n"
  },
  {
    "path": "examples/08-apis/title.rs",
    "content": "//! This example shows how to set the title of the page or window with the Title component\n\nuse dioxus::prelude::*;\n\nfn main() {\n    dioxus::launch(app);\n}\n\nfn app() -> Element {\n    let mut count = use_signal(|| 0);\n\n    rsx! {\n        div {\n            // You can set the title of the page with the Title component\n            // In web applications, this sets the title in the head.\n            // On desktop, it sets the window title\n            Title { \"My Application (Count {count})\" }\n            button { onclick: move |_| count += 1, \"Up high!\" }\n            button { onclick: move |_| count -= 1, \"Down low!\" }\n        }\n    }\n}\n"
  },
  {
    "path": "examples/08-apis/video_stream.rs",
    "content": "//! Using `wry`'s http module, we can stream a video file from the local file system.\n//!\n//! You could load in any file type, but this example uses a video file.\n\nuse dioxus::desktop::wry::http;\nuse dioxus::desktop::wry::http::Response;\nuse dioxus::desktop::{AssetRequest, use_asset_handler};\nuse dioxus::prelude::*;\nuse http::{header::*, response::Builder as ResponseBuilder, status::StatusCode};\nuse std::{io::SeekFrom, path::PathBuf};\nuse tokio::io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt};\n\nconst VIDEO_PATH: &str = \"./examples/assets/test_video.mp4\";\n\nfn main() {\n    // For the sake of this example, we will download the video file if it doesn't exist\n    ensure_video_is_loaded();\n\n    dioxus::launch(app);\n}\n\nfn app() -> Element {\n    // Any request to /videos will be handled by this handler\n    use_asset_handler(\"videos\", move |request, responder| {\n        // Using spawn works, but is slower than a dedicated thread\n        tokio::task::spawn(async move {\n            let video_file = PathBuf::from(VIDEO_PATH);\n            let mut file = tokio::fs::File::open(&video_file).await.unwrap();\n\n            match get_stream_response(&mut file, &request).await {\n                Ok(response) => responder.respond(response),\n                Err(err) => eprintln!(\"Error: {}\", err),\n            }\n        });\n    });\n\n    rsx! {\n        div {\n            video {\n                src: \"/videos/test_video.mp4\",\n                autoplay: true,\n                controls: true,\n                width: 640,\n                height: 480\n            }\n        }\n    }\n}\n\n/// This was taken from wry's example\nasync fn get_stream_response(\n    asset: &mut (impl tokio::io::AsyncSeek + tokio::io::AsyncRead + Unpin + Send + Sync),\n    request: &AssetRequest,\n) -> Result<Response<Vec<u8>>, Box<dyn std::error::Error>> {\n    // get stream length\n    let len = {\n        let old_pos = asset.stream_position().await?;\n        let len = asset.seek(SeekFrom::End(0)).await?;\n        asset.seek(SeekFrom::Start(old_pos)).await?;\n        len\n    };\n\n    let mut resp = ResponseBuilder::new().header(CONTENT_TYPE, \"video/mp4\");\n\n    // if the webview sent a range header, we need to send a 206 in return\n    // Actually only macOS and Windows are supported. Linux will ALWAYS return empty headers.\n    let http_response = if let Some(range_header) = request.headers().get(\"range\") {\n        let not_satisfiable = || {\n            ResponseBuilder::new()\n                .status(StatusCode::RANGE_NOT_SATISFIABLE)\n                .header(CONTENT_RANGE, format!(\"bytes */{len}\"))\n                .body(vec![])\n        };\n\n        // parse range header\n        let ranges = if let Ok(ranges) = http_range::HttpRange::parse(range_header.to_str()?, len) {\n            ranges\n                .iter()\n                // map the output back to spec range <start-end>, example: 0-499\n                .map(|r| (r.start, r.start + r.length - 1))\n                .collect::<Vec<_>>()\n        } else {\n            return Ok(not_satisfiable()?);\n        };\n\n        /// The Maximum bytes we send in one range\n        const MAX_LEN: u64 = 1000 * 1024;\n\n        if ranges.len() == 1 {\n            let &(start, mut end) = ranges.first().unwrap();\n\n            // check if a range is not satisfiable\n            //\n            // this should be already taken care of by HttpRange::parse\n            // but checking here again for extra assurance\n            if start >= len || end >= len || end < start {\n                return Ok(not_satisfiable()?);\n            }\n\n            // adjust end byte for MAX_LEN\n            end = start + (end - start).min(len - start).min(MAX_LEN - 1);\n\n            // calculate number of bytes needed to be read\n            let bytes_to_read = end + 1 - start;\n\n            // allocate a buf with a suitable capacity\n            let mut buf = Vec::with_capacity(bytes_to_read as usize);\n            // seek the file to the starting byte\n            asset.seek(SeekFrom::Start(start)).await?;\n            // read the needed bytes\n            asset.take(bytes_to_read).read_to_end(&mut buf).await?;\n\n            resp = resp.header(CONTENT_RANGE, format!(\"bytes {start}-{end}/{len}\"));\n            resp = resp.header(CONTENT_LENGTH, end + 1 - start);\n            resp = resp.status(StatusCode::PARTIAL_CONTENT);\n            resp.body(buf)\n        } else {\n            let mut buf = Vec::new();\n            let ranges = ranges\n                .iter()\n                .filter_map(|&(start, mut end)| {\n                    // filter out unsatisfiable ranges\n                    //\n                    // this should be already taken care of by HttpRange::parse\n                    // but checking here again for extra assurance\n                    if start >= len || end >= len || end < start {\n                        None\n                    } else {\n                        // adjust end byte for MAX_LEN\n                        end = start + (end - start).min(len - start).min(MAX_LEN - 1);\n                        Some((start, end))\n                    }\n                })\n                .collect::<Vec<_>>();\n\n            let boundary = format!(\"{:x}\", rand::random::<u64>());\n            let boundary_sep = format!(\"\\r\\n--{boundary}\\r\\n\");\n            let boundary_closer = format!(\"\\r\\n--{boundary}\\r\\n\");\n\n            resp = resp.header(\n                CONTENT_TYPE,\n                format!(\"multipart/byteranges; boundary={boundary}\"),\n            );\n\n            for (end, start) in ranges {\n                // a new range is being written, write the range boundary\n                buf.write_all(boundary_sep.as_bytes()).await?;\n\n                // write the needed headers `Content-Type` and `Content-Range`\n                buf.write_all(format!(\"{CONTENT_TYPE}: video/mp4\\r\\n\").as_bytes())\n                    .await?;\n                buf.write_all(format!(\"{CONTENT_RANGE}: bytes {start}-{end}/{len}\\r\\n\").as_bytes())\n                    .await?;\n\n                // write the separator to indicate the start of the range body\n                buf.write_all(\"\\r\\n\".as_bytes()).await?;\n\n                // calculate number of bytes needed to be read\n                let bytes_to_read = end + 1 - start;\n\n                let mut local_buf = vec![0_u8; bytes_to_read as usize];\n                asset.seek(SeekFrom::Start(start)).await?;\n                asset.read_exact(&mut local_buf).await?;\n                buf.extend_from_slice(&local_buf);\n            }\n            // all ranges have been written, write the closing boundary\n            buf.write_all(boundary_closer.as_bytes()).await?;\n\n            resp.body(buf)\n        }\n    } else {\n        resp = resp.header(CONTENT_LENGTH, len);\n        let mut buf = Vec::with_capacity(len as usize);\n        asset.read_to_end(&mut buf).await?;\n        resp.body(buf)\n    };\n\n    http_response.map_err(Into::into)\n}\n\nfn ensure_video_is_loaded() {\n    let video_file = PathBuf::from(VIDEO_PATH);\n    if !video_file.exists() {\n        tokio::runtime::Runtime::new()\n            .unwrap()\n            .block_on(async move {\n                println!(\"Downloading video file...\");\n                let video_url =\n                    \"http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4\";\n                let mut response = reqwest::get(video_url).await.unwrap();\n                let mut file = tokio::fs::File::create(&video_file).await.unwrap();\n                while let Some(chunk) = response.chunk().await.unwrap() {\n                    file.write_all(&chunk).await.unwrap();\n                }\n            });\n    }\n}\n"
  },
  {
    "path": "examples/08-apis/wgpu_child_window.rs",
    "content": "//! Demonstrate how to use dioxus as a child window for use in alternative renderers like wgpu.\n//!\n//! The code here is borrowed from wry's example:\n//! https://github.com/tauri-apps/wry/blob/dev/examples/wgpu.rs\n//!\n//! To use this feature set `with_as_child_window()` on your desktop config which will then let you\n\nuse dioxus::prelude::*;\nuse dioxus::{\n    desktop::tao::{event::Event as WryEvent, window::Window},\n    desktop::{Config, tao::window::WindowBuilder, use_wry_event_handler, window},\n};\nuse std::sync::Arc;\n\nfn main() {\n    let config = Config::new()\n        .with_window(WindowBuilder::new().with_transparent(true))\n        .with_on_window(|window, dom| {\n            let resources = Arc::new(pollster::block_on(async {\n                let resource = GraphicsContextAsyncBuilder {\n                    desktop: window,\n                    resources_builder: |ctx| Box::pin(GraphicsResources::new(ctx.clone())),\n                }\n                .build()\n                .await;\n\n                resource.with_resources(|resources| resources.render());\n\n                resource\n            }));\n\n            dom.provide_root_context(resources);\n        })\n        .with_as_child_window();\n\n    dioxus::LaunchBuilder::desktop()\n        .with_cfg(config)\n        .launch(app);\n}\n\nfn app() -> Element {\n    let graphics_resources = consume_context::<Arc<GraphicsContext>>();\n\n    // on first render request a redraw\n    use_effect(|| {\n        window().window.request_redraw();\n    });\n\n    use_wry_event_handler(move |event, _| {\n        use dioxus::desktop::tao::event::WindowEvent;\n\n        if let WryEvent::WindowEvent {\n            event: WindowEvent::Resized(new_size),\n            ..\n        } = event\n        {\n            graphics_resources.with_resources(|srcs| {\n                let mut cfg = srcs.config.clone();\n                cfg.width = new_size.width;\n                cfg.height = new_size.height;\n                srcs.surface.configure(&srcs.device, &cfg);\n            });\n\n            window().window.request_redraw();\n        }\n    });\n\n    rsx! {\n        div {\n            color: \"blue\",\n            width: \"100vw\",\n            height: \"100vh\",\n            display: \"flex\",\n            justify_content: \"center\",\n            align_items: \"center\",\n            font_size: \"20px\",\n            div { \"text overlaid on a wgpu surface!\" }\n        }\n    }\n}\n\n/// This borrows from the `window` which is contained within an `Arc` so we need to wrap it in a self-borrowing struct\n/// to be able to borrow the window for the wgpu::Surface\n#[ouroboros::self_referencing]\nstruct GraphicsContext {\n    desktop: Arc<Window>,\n    #[borrows(desktop)]\n    #[not_covariant]\n    resources: GraphicsResources<'this>,\n}\n\nstruct GraphicsResources<'a> {\n    surface: wgpu::Surface<'a>,\n    device: wgpu::Device,\n    pipeline: wgpu::RenderPipeline,\n    queue: wgpu::Queue,\n    config: wgpu::SurfaceConfiguration,\n}\n\nimpl<'a> GraphicsResources<'a> {\n    async fn new(window: Arc<Window>) -> Self {\n        let size = window.inner_size();\n\n        let instance = wgpu::Instance::default();\n\n        let surface: wgpu::Surface<'a> = instance.create_surface(window).unwrap();\n        let adapter = instance\n            .request_adapter(&wgpu::RequestAdapterOptions {\n                power_preference: wgpu::PowerPreference::default(),\n                force_fallback_adapter: false,\n                // Request an adapter which can render to our surface\n                compatible_surface: Some(&surface),\n            })\n            .await\n            .expect(\"Failed to find an appropriate adapter\");\n\n        // Create the logical device and command queue\n        let (device, queue) = adapter\n            .request_device(&wgpu::DeviceDescriptor {\n                label: None,\n                required_features: wgpu::Features::empty(),\n                // Make sure we use the texture resolution limits from the adapter, so we can support images the size of the swapchain.\n                required_limits: wgpu::Limits::downlevel_webgl2_defaults()\n                    .using_resolution(adapter.limits()),\n                memory_hints: wgpu::MemoryHints::default(),\n                ..Default::default()\n            })\n            .await\n            .expect(\"Failed to create device\");\n\n        // Load the shaders from disk\n        let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {\n            label: None,\n            source: wgpu::ShaderSource::Wgsl(\n                r#\"\n@vertex\nfn vs_main(@builtin(vertex_index) in_vertex_index: u32) -> @builtin(position) vec4<f32> {\n    let x = f32(i32(in_vertex_index) - 1);\n    let y = f32(i32(in_vertex_index & 1u) * 2 - 1);\n    return vec4<f32>(x, y, 0.0, 1.0);\n}\n\n@fragment\nfn fs_main() -> @location(0) vec4<f32> {\n    return vec4<f32>(1.0, 0.0, 0.0, 1.0);\n}\n\"#\n                .into(),\n            ),\n        });\n\n        let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {\n            label: None,\n            bind_group_layouts: &[],\n            push_constant_ranges: &[],\n        });\n\n        let swapchain_capabilities = surface.get_capabilities(&adapter);\n        let swapchain_format = swapchain_capabilities.formats[0];\n\n        let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {\n            label: None,\n            layout: Some(&pipeline_layout),\n            vertex: wgpu::VertexState {\n                module: &shader,\n                entry_point: Some(\"vs_main\"),\n                buffers: &[],\n                compilation_options: wgpu::PipelineCompilationOptions::default(),\n            },\n            fragment: Some(wgpu::FragmentState {\n                module: &shader,\n                entry_point: Some(\"fs_main\"),\n                targets: &[Some(swapchain_format.into())],\n                compilation_options: wgpu::PipelineCompilationOptions::default(),\n            }),\n            primitive: wgpu::PrimitiveState::default(),\n            depth_stencil: None,\n            multisample: wgpu::MultisampleState::default(),\n            multiview: None,\n            cache: None,\n        });\n\n        let config = wgpu::SurfaceConfiguration {\n            usage: wgpu::TextureUsages::RENDER_ATTACHMENT,\n            format: swapchain_format,\n            width: size.width,\n            height: size.height,\n            present_mode: wgpu::PresentMode::Fifo,\n            desired_maximum_frame_latency: 2,\n            alpha_mode: wgpu::CompositeAlphaMode::PostMultiplied,\n            view_formats: vec![],\n        };\n\n        surface.configure(&device, &config);\n\n        GraphicsResources {\n            surface,\n            device,\n            pipeline,\n            queue,\n            config,\n        }\n    }\n\n    fn render(&self) {\n        let GraphicsResources {\n            surface,\n            device,\n            pipeline,\n            queue,\n            ..\n        } = self;\n\n        let frame = surface\n            .get_current_texture()\n            .expect(\"Failed to acquire next swap chain texture\");\n        let view = frame\n            .texture\n            .create_view(&wgpu::TextureViewDescriptor::default());\n\n        let mut encoder =\n            device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });\n\n        {\n            let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {\n                label: None,\n                color_attachments: &[Some(wgpu::RenderPassColorAttachment {\n                    view: &view,\n                    resolve_target: None,\n                    ops: wgpu::Operations {\n                        load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),\n                        store: wgpu::StoreOp::Store,\n                    },\n                    depth_slice: None,\n                })],\n                depth_stencil_attachment: None,\n                timestamp_writes: None,\n                occlusion_query_set: None,\n            });\n            rpass.set_pipeline(pipeline);\n            rpass.draw(0..3, 0..1);\n        }\n\n        queue.submit(Some(encoder.finish()));\n        frame.present();\n    }\n}\n"
  },
  {
    "path": "examples/08-apis/window_event.rs",
    "content": "//! This example demonstrates how to handle window events and change window properties.\n//!\n//! We're able to do things like:\n//! - implement window dragging\n//! - toggle fullscreen\n//! - toggle always on top\n//! - toggle window decorations\n//! - change the window title\n//!\n//! The entire featuresuite of wry and tao is available to you\n\nuse dioxus::desktop::{Config, WindowBuilder, window};\nuse dioxus::prelude::*;\n\nfn main() {\n    dioxus::LaunchBuilder::desktop()\n        .with_cfg(\n            Config::new().with_window(\n                WindowBuilder::new()\n                    .with_title(\"Borderless Window\")\n                    .with_decorations(false),\n            ),\n        )\n        .launch(app)\n}\n\nfn app() -> Element {\n    rsx!(\n        document::Link { href: \"https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css\", rel: \"stylesheet\" }\n        Header {}\n        div { class: \"container mx-auto\",\n            div { class: \"grid grid-cols-5\",\n                SetOnTop {}\n                SetDecorations {}\n                SetTitle {}\n            }\n        }\n    )\n}\n\n#[component]\nfn Header() -> Element {\n    let mut fullscreen = use_signal(|| false);\n\n    rsx! {\n        header { class: \"text-gray-400 bg-gray-900 body-font\", onmousedown: move |_| window().drag(),\n            div { class: \"container mx-auto flex flex-wrap p-5 flex-col md:flex-row items-center\",\n                a { class: \"flex title-font font-medium items-center text-white mb-4 md:mb-0\",\n                    span { class: \"ml-3 text-xl\", \"Dioxus\" }\n                }\n                nav { class: \"md:ml-auto flex flex-wrap items-center text-base justify-center\" }\n\n                // Set the window to minimized\n                button {\n                    class: \"inline-flex items-center bg-gray-800 border-0 py-1 px-3 focus:outline-none hover:bg-gray-700 rounded text-base mt-4 md:mt-0\",\n                    onmousedown: |evt| evt.stop_propagation(),\n                    onclick: move |_| window().set_minimized(true),\n                    \"Minimize\"\n                }\n\n                // Toggle fullscreen\n                button {\n                    class: \"inline-flex items-center bg-gray-800 border-0 py-1 px-3 focus:outline-none hover:bg-gray-700 rounded text-base mt-4 md:mt-0\",\n                    onmousedown: |evt| evt.stop_propagation(),\n                    onclick: move |_| {\n                        window().set_fullscreen(!fullscreen());\n                        window().set_resizable(fullscreen());\n                        fullscreen.toggle();\n                    },\n                    \"Fullscreen\"\n                }\n\n                // Close the window\n                // If the window is the last window open, the app will close, if you configured the close behavior to do so\n                button {\n                    class: \"inline-flex items-center bg-gray-800 border-0 py-1 px-3 focus:outline-none hover:bg-gray-700 rounded text-base mt-4 md:mt-0\",\n                    onmousedown: |evt| evt.stop_propagation(),\n                    onclick: move |_| window().close(),\n                    \"Close\"\n                }\n            }\n        }\n    }\n}\n\n#[component]\nfn SetOnTop() -> Element {\n    let mut always_on_top = use_signal(|| false);\n\n    rsx! {\n        div {\n            button {\n                class: \"inline-flex items-center text-white bg-green-500 border-0 py-1 px-3 hover:bg-green-700 rounded\",\n                onmousedown: |evt| evt.stop_propagation(),\n                onclick: move |_| {\n                    window().set_always_on_top(!always_on_top());\n                    always_on_top.toggle();\n                },\n                \"Always On Top\"\n            }\n        }\n    }\n}\n\n#[component]\nfn SetDecorations() -> Element {\n    let mut decorations = use_signal(|| false);\n\n    rsx! {\n        div {\n            button {\n                class: \"inline-flex items-center text-white bg-blue-500 border-0 py-1 px-3 hover:bg-green-700 rounded\",\n                onmousedown: |evt| evt.stop_propagation(),\n                onclick: move |_| {\n                    window().set_decorations(!decorations());\n                    decorations.toggle();\n                },\n                \"Set Decorations\"\n            }\n        }\n    }\n}\n\n#[component]\nfn SetTitle() -> Element {\n    rsx! {\n        div {\n            button {\n                class: \"inline-flex items-center text-white bg-blue-500 border-0 py-1 px-3 hover:bg-green-700 rounded\",\n                onmousedown: |evt| evt.stop_propagation(),\n                onclick: move |_| window().set_title(\"Dioxus Application\"),\n                \"Change Title\"\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "examples/08-apis/window_focus.rs",
    "content": "//! Listen for window focus events using a wry event handler\n//!\n//! This example shows how to use the use_wry_event_handler hook to listen for window focus events.\n//! We can intercept any Wry event, but in this case we're only interested in the WindowEvent::Focused event.\n//!\n//! This lets you do things like backgrounding tasks, pausing animations, or changing the UI when the window is focused or not.\n\nuse dioxus::desktop::tao::event::Event as WryEvent;\nuse dioxus::desktop::tao::event::WindowEvent;\nuse dioxus::desktop::use_wry_event_handler;\nuse dioxus::prelude::*;\n\nfn main() {\n    dioxus::launch(app);\n}\n\nfn app() -> Element {\n    let mut focused = use_signal(|| true);\n\n    use_wry_event_handler(move |event, _| {\n        if let WryEvent::WindowEvent {\n            event: WindowEvent::Focused(new_focused),\n            ..\n        } = event\n        {\n            focused.set(*new_focused)\n        }\n    });\n\n    rsx! {\n        div { width: \"100%\", height: \"100%\", display: \"flex\", flex_direction: \"column\", align_items: \"center\",\n            if focused() {\n                \"This window is focused!\"\n            } else {\n                \"This window is not focused!\"\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "examples/08-apis/window_popup.rs",
    "content": "//! This example shows how to create a popup window and send data back to the parent window.\n//! Currently Dioxus doesn't support nested renderers, hence the need to create popups as separate windows.\n\nuse dioxus::prelude::*;\nuse std::rc::Rc;\n\nfn main() {\n    dioxus::LaunchBuilder::desktop().launch(app);\n}\n\nfn app() -> Element {\n    let mut emails_sent = use_signal(|| Vec::new() as Vec<String>);\n\n    // Wait for responses to the compose channel, and then push them to the emails_sent signal.\n    let handle = use_coroutine(move |mut rx: UnboundedReceiver<String>| async move {\n        use futures_util::StreamExt;\n        while let Some(message) = rx.next().await {\n            emails_sent.push(message);\n        }\n    });\n\n    let open_compose_window = move |_evt: MouseEvent| {\n        let tx = handle.tx();\n        dioxus::desktop::window().new_window(\n            VirtualDom::new_with_props(popup, Rc::new(move |s| tx.unbounded_send(s).unwrap())),\n            Default::default(),\n        );\n    };\n\n    rsx! {\n        h1 { \"This is your email\" }\n        button { onclick: open_compose_window, \"Click to compose a new email\" }\n        ul {\n            for message in emails_sent.read().iter() {\n                li {\n                    h3 { \"email\" }\n                    span { \"{message}\" }\n                }\n            }\n        }\n    }\n}\n\nfn popup(send: Rc<dyn Fn(String)>) -> Element {\n    let mut user_input = use_signal(String::new);\n    let window = dioxus::desktop::use_window();\n\n    let close_window = move |_| {\n        println!(\"Attempting to close Window B\");\n        window.close();\n    };\n\n    rsx! {\n        div {\n            h1 { \"Compose a new email\" }\n            button {\n                onclick: close_window,\n                \"Close Window B (button)\"\n            }\n            button {\n                onclick: move |_| {\n                    send(user_input.cloned());\n                    dioxus::desktop::window().close();\n                },\n                \"Send\"\n            }\n            input { oninput: move |e| user_input.set(e.value()), value: \"{user_input}\" }\n        }\n    }\n}\n"
  },
  {
    "path": "examples/08-apis/window_zoom.rs",
    "content": "//! Adjust the zoom of a desktop app\n//!\n//! This example shows how to adjust the zoom of a desktop app using the webview.zoom method.\n\nuse dioxus::prelude::*;\n\nfn main() {\n    dioxus::LaunchBuilder::desktop().launch(app);\n}\n\nfn app() -> Element {\n    let mut level = use_signal(|| 1.0);\n\n    rsx! {\n        h1 { \"Zoom level: {level}\" }\n        p { \"Change the zoom level of the webview by typing a number in the input below.\" }\n        input {\n            r#type: \"number\",\n            value: \"{level}\",\n            oninput: move |e| {\n                if let Ok(new_zoom) = e.value().parse::<f64>() {\n                    level.set(new_zoom);\n                    _ = dioxus::desktop::window().webview.zoom(new_zoom);\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "examples/09-reference/all_events.rs",
    "content": "//! This example shows how to listen to all events on a div and log them to the console.\n//!\n//! The primary demonstration here is the properties on the events themselves, hoping to give you some inspiration\n//! on adding interactivity to your own application.\n\nuse dioxus::prelude::*;\nuse std::{collections::VecDeque, fmt::Debug, rc::Rc};\n\nconst STYLE: Asset = asset!(\"/examples/assets/events.css\");\n\nfn main() {\n    dioxus::launch(app);\n}\n\nfn app() -> Element {\n    // Using a VecDeque so its cheap to pop old events off the front\n    let mut events = use_signal(VecDeque::new);\n\n    // All events and their data implement Debug, so we can re-cast them as Rc<dyn Debug> instead of their specific type\n    let mut log_event = move |event: Rc<dyn Debug>| {\n        // Only store the last 20 events\n        if events.read().len() >= 20 {\n            events.write().pop_front();\n        }\n        events.write().push_back(event);\n    };\n\n    let random_text = \"This is some random repeating text. \".repeat(1000);\n\n    rsx! {\n        Stylesheet { href: STYLE }\n        div { id: \"container\",\n            // focusing is necessary to catch keyboard events\n            div { id: \"receiver\", tabindex: 0,\n                onmousemove: move |event| log_event(event.data()),\n                onclick: move |event| log_event(event.data()),\n                ondoubleclick: move |event| log_event(event.data()),\n                onmousedown: move |event| log_event(event.data()),\n                onmouseup: move |event| log_event(event.data()),\n\n                onwheel: move |event| log_event(event.data()),\n\n                onkeydown: move |event| log_event(event.data()),\n                onkeyup: move |event| log_event(event.data()),\n                onkeypress: move |event| log_event(event.data()),\n\n                onfocusin: move |event| log_event(event.data()),\n                onfocusout: move |event| log_event(event.data()),\n\n                \"Hover, click, type or scroll to see the info down below\"\n            }\n            div {\n                style: \"padding: 50px;\",\n                div {\n                    style: \"text-align: center; padding: 20px; font-family: sans-serif; overflow: auto; height: 400px;\",\n                    onscroll: move |event: Event<ScrollData>| {\n                        log_event(event.data());\n                    },\n                    div { style: \"margin: 20px; padding: 15px; border: 1px solid #ccc; border-radius: 5px;\",\n                        p { \"{random_text}\" }\n                    }\n                }\n            }\n            div { id: \"log\",\n                for event in events.read().iter() {\n                    div { \"{event:?}\" }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "examples/09-reference/generic_component.rs",
    "content": "//! This example demonstrates how to create a generic component in Dioxus.\n//!\n//! Generic components can be useful when you want to create a component that renders differently depending on the type\n//! of data it receives. In this particular example, we're just using a type that implements `Display` and `PartialEq`,\n\nuse dioxus::prelude::*;\nuse std::fmt::Display;\n\nfn main() {\n    dioxus::launch(app);\n}\n\nfn app() -> Element {\n    rsx! {\n        generic_child { data: 0 }\n    }\n}\n\n#[derive(PartialEq, Props, Clone)]\nstruct GenericChildProps<T: Display + PartialEq + Clone + 'static> {\n    data: T,\n}\n\nfn generic_child<T: Display + PartialEq + Clone>(props: GenericChildProps<T>) -> Element {\n    rsx! {\n        div { \"{props.data}\" }\n    }\n}\n"
  },
  {
    "path": "examples/09-reference/optional_props.rs",
    "content": "//! Optional props\n//!\n//! This example demonstrates how to use optional props in your components. The `Button` component has several props,\n//! and we use a variety of attributes to set them.\n\nuse dioxus::prelude::*;\n\nfn main() {\n    dioxus::launch(app);\n}\n\nfn app() -> Element {\n    rsx! {\n        // We can set some of the props, and the rest will be filled with their default values\n        // By default `c` can take a `None` value, but `d` is required to wrap a `Some` value\n        Button {\n            a: \"asd\".to_string(),\n            // b can be omitted, and it will be filled with its default value\n            c: \"asd\".to_string(),\n            d: Some(\"asd\".to_string()),\n            e: Some(\"asd\".to_string()),\n        }\n\n        Button {\n            a: \"asd\".to_string(),\n            b: \"asd\".to_string(),\n\n            // We can omit the `Some` on `c` since Dioxus automatically transforms Option<T> into optional\n            c: \"asd\".to_string(),\n            d: Some(\"asd\".to_string()),\n            e: \"asd\".to_string(),\n        }\n\n        // `b` and `e` are omitted\n        Button {\n            a: \"asd\".to_string(),\n            c: \"asd\".to_string(),\n            d: Some(\"asd\".to_string()),\n        }\n    }\n}\n\n#[derive(Props, PartialEq, Clone)]\nstruct ButtonProps {\n    a: String,\n\n    #[props(default)]\n    b: String,\n\n    c: Option<String>,\n\n    #[props(!optional)]\n    d: Option<String>,\n\n    #[props(optional)]\n    e: SthElse<String>,\n}\n\ntype SthElse<T> = Option<T>;\n\n#[allow(non_snake_case)]\nfn Button(props: ButtonProps) -> Element {\n    rsx! {\n        button {\n            \"{props.a} | \"\n            \"{props.b:?} | \"\n            \"{props.c:?} | \"\n            \"{props.d:?} | \"\n            \"{props.e:?}\"\n        }\n    }\n}\n"
  },
  {
    "path": "examples/09-reference/rsx_usage.rs",
    "content": "//! A tour of the rsx! macro\n//! ------------------------\n//!\n//! This example serves as an informal quick reference of all the things that the rsx! macro can do.\n//!\n//! A full in-depth reference guide is available at: https://www.notion.so/rsx-macro-basics-ef6e367dec124f4784e736d91b0d0b19\n//!\n//! ### Elements\n//! - Create any element from its tag\n//! - Accept compile-safe attributes for each tag\n//! - Display documentation for elements\n//! - Arguments instead of String\n//! - Text\n//! - Inline Styles\n//!\n//! ## General Concepts\n//! - Iterators\n//! - Keys\n//! - Match statements\n//! - Conditional Rendering\n//!\n//! ### Events\n//! - Handle events with the \"onXYZ\" syntax\n//! - Closures can capture their environment with the 'static lifetime\n//!\n//!\n//! ### Components\n//! - Components can be made by specifying the name\n//! - Components can be referenced by path\n//! - Components may have optional parameters\n//! - Components may have their properties specified by spread syntax\n//! - Components may accept child nodes\n//! - Components that accept \"onXYZ\" get those closures bump allocated\n//!\n//! ### Fragments\n//! - Allow fragments using the built-in `Fragment` component\n//! - Accept a list of vnodes as children for a Fragment component\n//! - Allow keyed fragments in iterators\n//! - Allow top-level fragments\n\nfn main() {\n    dioxus::launch(app)\n}\n\nuse core::{fmt, str::FromStr};\nuse std::fmt::Display;\n\nuse baller::Baller;\nuse dioxus::prelude::*;\n\nfn app() -> Element {\n    let formatting = \"formatting!\";\n    let formatting_tuple = (\"a\", \"b\");\n    let lazy_fmt = format_args!(\"lazily formatted text\");\n    let asd = 123;\n\n    rsx! {\n        div {\n            // Elements\n            div {}\n            h1 {\"Some text\"}\n            h1 {\"Some text with {formatting}\"}\n            h1 {\"Formatting basic expressions {formatting_tuple.0} and {formatting_tuple.1}\"}\n            h1 {\"Formatting without interpolation \" {formatting_tuple.0} \"and\" {formatting_tuple.1} }\n            h2 {\n                \"Multiple\"\n                \"Text\"\n                \"Blocks\"\n                \"Use comments as separators in html\"\n            }\n            div {\n                h1 {\"multiple\"}\n                h2 {\"nested\"}\n                h3 {\"elements\"}\n            }\n            div {\n                class: \"my special div\",\n                h1 {\"Headers and attributes!\"}\n            }\n            div {\n                h1 {\"Style attributes!\"}\n                p {\n                    \"hello\"\n                    b {\n                        \"world\"\n                    }\n                    i {\n                        \"foo\"\n                    }\n                    span {\n                        style: \"color: red;font-style:italic\",\n                        \"red\"\n                    }\n                    span {\n                        color: \"blue\",\n                        font_weight: \"bold\",\n                        \"attr_blue\"\n                    }\n                }\n            }\n            div {\n                // pass simple rust expressions in\n                class: \"{lazy_fmt}\",\n                id: format_args!(\"attributes can be passed lazily with std::fmt::Arguments\"),\n                class: \"asd\",\n                class: \"{asd}\",\n                // if statements can be used to conditionally render attributes\n                class: if formatting.contains(\"form\") { \"{asd}\" },\n                // longer if chains also work\n                class: if formatting.contains(\"form\") { \"{asd}\" } else if formatting.contains(\"my other form\") { \"{asd}\" },\n                class: if formatting.contains(\"form\") { \"{asd}\" } else if formatting.contains(\"my other form\") { \"{asd}\" } else { \"{asd}\" },\n                div {\n                    class: format_args!(\"Arguments can be passed in through curly braces for complex {asd}\")\n                }\n            }\n\n            // dangerous_inner_html for both html and svg\n            div { dangerous_inner_html: \"<p>hello dangerous inner html</p>\" }\n            svg { dangerous_inner_html: \"<circle r='50' cx='50' cy='50' />\" }\n\n            // Built-in idents can be used\n            use {}\n            link {\n                as: \"asd\"\n            }\n\n            // Expressions can be used in element position too:\n            {rsx!(p { \"More templating!\" })}\n\n            // Iterators\n            {(0..10).map(|i| rsx!(li { \"{i}\" }))}\n\n            // Iterators within expressions\n            {\n                let data = std::collections::HashMap::<&'static str, &'static str>::new();\n                // Iterators *should* have keys when you can provide them.\n                // Keys make your app run faster. Make sure your keys are stable, unique, and predictable.\n                // Using an \"ID\" associated with your data is a good idea.\n                data.into_iter().map(|(k, v)| rsx!(li { key: \"{k}\", \"{v}\" }))\n            }\n\n            // Matching\n            match true {\n                true => rsx!( h1 {\"Top text\"}),\n                false => rsx!( h1 {\"Bottom text\"})\n            }\n\n            // Conditional rendering\n            // Dioxus conditional rendering is based around None/Some. We have no special syntax for conditionals.\n            // You can convert a bool condition to rsx! with .then and .or\n            {true.then(|| rsx!(div {}))}\n\n            // Alternatively, you can use the \"if\" syntax - but both branches must be resolve to Element\n            if false {\n                h1 {\"Top text\"}\n            } else {\n                h1 {\"Bottom text\"}\n            }\n\n            // Using optionals for diverging branches\n            // Note that since this is wrapped in curlies, it's interpreted as an expression\n            {if true {\n                Some(rsx!(h1 {\"Top text\"}))\n            } else {\n                None\n            }}\n\n            // returning \"None\" without a diverging branch is a bit noisy... but rare in practice\n            {None as Option<()>}\n\n            // can also just use empty fragments\n            Fragment {}\n\n            // Fragments let you insert groups of nodes without a parent.\n            // This lets you make components that insert elements as siblings without a container.\n            div {\"A\"}\n            Fragment {\n                div {\"B\"}\n                div {\"C\"}\n                Fragment {\n                    \"D\"\n                    Fragment {\n                        \"E\"\n                        \"F\"\n                    }\n                }\n            }\n\n            // Components\n            // Can accept any paths\n            // Notice how you still get syntax highlighting and IDE support :)\n            Baller {}\n            baller::Baller {}\n            crate::baller::Baller {}\n\n            // Can take properties\n            Taller { a: \"asd\" }\n\n            // Can take optional properties\n            Taller { a: \"asd\" }\n\n            // Can pass in props directly as an expression\n            {\n                let props = TallerProps {a: \"hello\", children: VNode::empty() };\n                rsx!(Taller { ..props })\n            }\n\n            // Spreading can also be overridden manually\n            Taller {\n                a: \"not ballin!\",\n                ..TallerProps { a: \"ballin!\", children: VNode::empty() }\n            }\n\n            // Can take children too!\n            Taller { a: \"asd\", div {\"hello world!\"} }\n\n            // This component's props are defined *inline* with the `component` macro\n            WithInline { text: \"using functionc all syntax\" }\n\n            // Components can be generic too\n            // This component takes i32 type to give you typed input\n            TypedInput::<i32> {}\n\n            // Type inference can be used too\n            TypedInput { initial: 10.0 }\n\n            // generic with the `component` macro\n            Label { text: \"hello generic world!\" }\n            Label { text: 99.9 }\n\n            // Lowercase components work too, as long as they are access using a path\n            baller::lowercase_component {}\n\n            // For in-scope lowercase components, use the `self` keyword\n            self::lowercase_helper {}\n\n            // helper functions\n            // Anything that implements IntoVnode can be dropped directly into Rsx\n            {helper(\"hello world!\")}\n\n            // Strings can be supplied directly\n            {String::from(\"Hello world!\")}\n\n            // So can format_args\n            // todo(jon): this is broken in edition 2024\n            // {format_args!(\"Hello {}!\", \"world\")}\n\n            // Or we can shell out to a helper function\n            {format_dollars(10, 50)}\n        }\n    }\n}\n\nfn format_dollars(dollars: u32, cents: u32) -> String {\n    format!(\"${dollars}.{cents:02}\")\n}\n\nfn helper(text: &str) -> Element {\n    rsx! {\n        p { \"{text}\" }\n    }\n}\n\n// no_case_check disables PascalCase checking if you *really* want a snake_case component.\n// This will likely be deprecated/removed in a future update that will introduce a more polished linting system,\n// something like Clippy.\n#[component(no_case_check)]\nfn lowercase_helper() -> Element {\n    rsx! {\n        \"asd\"\n    }\n}\n\nmod baller {\n    use super::*;\n\n    #[component]\n    /// This component totally balls\n    pub fn Baller() -> Element {\n        rsx! { \"ballin'\" }\n    }\n\n    // no_case_check disables PascalCase checking if you *really* want a snake_case component.\n    // This will likely be deprecated/removed in a future update that will introduce a more polished linting system,\n    // something like Clippy.\n    #[component(no_case_check)]\n    pub fn lowercase_component() -> Element {\n        rsx! { \"look ma, no uppercase\" }\n    }\n}\n\n/// Documentation for this component is visible within the rsx macro\n#[component]\npub fn Taller(\n    /// Fields are documented and accessible in rsx!\n    a: &'static str,\n    children: Element,\n) -> Element {\n    rsx! { {&children} }\n}\n\n#[derive(Props, Clone, PartialEq, Eq)]\npub struct TypedInputProps<T: 'static + Clone + PartialEq> {\n    #[props(optional, default)]\n    initial: Option<T>,\n}\n\n#[allow(non_snake_case)]\npub fn TypedInput<T>(props: TypedInputProps<T>) -> Element\nwhere\n    T: FromStr + fmt::Display + PartialEq + Clone + 'static,\n    <T as FromStr>::Err: std::fmt::Display,\n{\n    if let Some(props) = props.initial {\n        return rsx! { \"{props}\" };\n    }\n\n    VNode::empty()\n}\n\n#[component]\nfn WithInline(text: String) -> Element {\n    rsx! {\n        p { \"{text}\" }\n    }\n}\n\n#[component]\nfn Label<T: Clone + PartialEq + Display + 'static>(text: T) -> Element {\n    rsx! {\n        p { \"{text}\" }\n    }\n}\n"
  },
  {
    "path": "examples/09-reference/shorthand.rs",
    "content": "//! Dioxus supports shorthand syntax for creating elements and components.\n\nuse dioxus::prelude::*;\n\nfn main() {\n    dioxus::launch(app);\n}\n\nfn app() -> Element {\n    let a = 123;\n    let b = 456;\n    let c = 789;\n    let class = \"class\";\n    let id = \"id\";\n\n    // todo: i'd like it for children on elements to be inferred as the children of the element\n    // also should shorthands understand references/dereferences?\n    // ie **a, *a, &a, &mut a, etc\n    let children = rsx! { \"Child\" };\n    let onclick = move |_| println!(\"Clicked!\");\n\n    rsx! {\n        div { class, id, {&children} }\n        Component { a, b, c, children, onclick }\n        Component { a, ..ComponentProps { a: 1, b: 2, c: 3, children: VNode::empty(), onclick: Default::default() } }\n    }\n}\n\n#[component]\nfn Component(\n    a: i32,\n    b: i32,\n    c: i32,\n    children: Element,\n    onclick: EventHandler<MouseEvent>,\n) -> Element {\n    rsx! {\n        div { \"{a}\" }\n        div { \"{b}\" }\n        div { \"{c}\" }\n        div { {children} }\n        div { onclick }\n    }\n}\n"
  },
  {
    "path": "examples/09-reference/simple_list.rs",
    "content": "//! A few ways of mapping elements into rsx! syntax\n//!\n//! Rsx allows anything that's an iterator where the output type implements Into<Element>, so you can use any of the following:\n\nuse dioxus::prelude::*;\n\nfn main() {\n    dioxus::launch(app);\n}\n\nfn app() -> Element {\n    rsx!(\n        div {\n            // Use Map directly to lazily pull elements\n            {(0..10).map(|f| rsx! { \"{f}\" })}\n\n            // Collect into an intermediate collection if necessary, and call into_iter\n            {[\"a\", \"b\", \"c\", \"d\", \"e\", \"f\"]\n                .into_iter()\n                .map(|f| rsx! { \"{f}\" })\n                .collect::<Vec<_>>()\n                .into_iter()}\n\n            // Use optionals\n            {Some(rsx! { \"Some\" })}\n\n            // use a for loop where the body itself is RSX\n            for name in 0..10 {\n                div { \"{name}\" }\n            }\n\n            // Or even use an unterminated conditional\n            if true {\n                \"hello world!\"\n            }\n        }\n    )\n}\n"
  },
  {
    "path": "examples/09-reference/spread.rs",
    "content": "//! This example demonstrates how to use the spread operator to pass attributes to child components.\n//!\n//! This lets components like the `Link` allow the user to extend the attributes of the underlying `a` tag.\n//! These attributes are bundled into a `Vec<Attribute>` which can be spread into the child component using the `..` operator.\n\nuse dioxus::prelude::*;\n\nfn main() {\n    let dom = VirtualDom::prebuilt(app);\n    let html = dioxus_ssr::render(&dom);\n\n    println!(\"{}\", html);\n}\n\nfn app() -> Element {\n    rsx! {\n        SpreadableComponent {\n            width: \"10px\",\n            extra_data: \"hello{1}\",\n            extra_data2: \"hello{2}\",\n            height: \"10px\",\n            left: 1,\n            \"data-custom-attribute\": \"value\",\n        }\n    }\n}\n\n#[derive(Props, PartialEq, Clone)]\nstruct Props {\n    #[props(extends = GlobalAttributes)]\n    attributes: Vec<Attribute>,\n\n    extra_data: String,\n\n    extra_data2: String,\n}\n\n#[component]\nfn SpreadableComponent(props: Props) -> Element {\n    rsx! {\n        audio { ..props.attributes, \"1: {props.extra_data}\\n2: {props.extra_data2}\" }\n    }\n}\n"
  },
  {
    "path": "examples/09-reference/web_component.rs",
    "content": "//! Dioxus allows webcomponents to be created with a simple syntax.\n//!\n//! Read more about webcomponents [here](https://developer.mozilla.org/en-US/docs/Web/Web_Components)\n//!\n//! We typically suggest wrapping webcomponents in a strongly typed interface using a component.\n\nuse dioxus::prelude::*;\n\nfn main() {\n    dioxus::launch(app);\n}\n\nfn app() -> Element {\n    rsx! {\n        div {\n            h1 { \"Web Components\" }\n            CoolWebComponent { my_prop: \"Hello, world!\".to_string() }\n        }\n    }\n}\n\n/// A web-component wrapped with a strongly typed interface using a component\n#[component]\nfn CoolWebComponent(my_prop: String) -> Element {\n    rsx! {\n        // rsx! takes a webcomponent as long as its tag name is separated with dashes\n        web-component {\n            // Since web-components don't have built-in attributes, the attribute names must be passed as a string\n            \"my-prop\": my_prop,\n        }\n    }\n}\n"
  },
  {
    "path": "examples/09-reference/xss_safety.rs",
    "content": "//! XSS Safety\n//!\n//! This example proves that Dioxus is safe from XSS attacks.\n\nuse dioxus::prelude::*;\n\nfn main() {\n    dioxus::launch(app);\n}\n\nfn app() -> Element {\n    let mut contents = use_signal(|| String::from(\"<script>alert(\\\"hello world\\\")</script>\"));\n\n    rsx! {\n        div {\n            h1 {\"Dioxus is XSS-Safe\"}\n            h3 { \"{contents}\" }\n            input {\n                value: \"{contents}\",\n                r#type: \"text\",\n                oninput: move |e| contents.set(e.value()),\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "examples/10-integrations/bevy/Cargo.toml",
    "content": "[package]\nname = \"bevy-example\"\nversion = \"0.0.0\"\nedition = \"2021\"\nlicense = \"MIT\"\npublish = false\n\n[features]\ntracing = [\"dep:tracing-subscriber\", \"dioxus-native/tracing\"]\n\n[dependencies]\nbevy = { workspace = true }\ndioxus-native = { workspace = true, features = [\"prelude\"] }\nwgpu = { workspace = true }\ncolor = \"0.3\"\ntracing-subscriber = { workspace = true, optional = true }\n"
  },
  {
    "path": "examples/10-integrations/bevy/src/bevy_renderer.rs",
    "content": "use crate::bevy_scene_plugin::BevyScenePlugin;\nuse bevy::{\n    camera::{ManualTextureViewHandle, RenderTarget},\n    prelude::*,\n    render::{\n        render_resource::TextureFormat,\n        renderer::{\n            RenderAdapter, RenderAdapterInfo, RenderDevice, RenderInstance, RenderQueue,\n            WgpuWrapper,\n        },\n        settings::{RenderCreation, RenderResources},\n        texture::ManualTextureView,\n        RenderPlugin,\n    },\n};\nuse dioxus_native::{CustomPaintCtx, DeviceHandle, TextureHandle};\nuse std::sync::Arc;\n\n#[derive(Resource, Default)]\npub struct UIData {\n    pub color: [f32; 3],\n}\n\npub struct BevyRenderer {\n    app: App,\n    wgpu_device: wgpu::Device,\n    last_texture_size: (u32, u32),\n    texture_handle: Option<TextureHandle>,\n    manual_texture_view_handle: Option<ManualTextureViewHandle>,\n}\n\nimpl BevyRenderer {\n    pub fn new(device_handle: &DeviceHandle) -> Self {\n        // Create a headless Bevy App.\n        let mut app = App::new();\n        app.add_plugins(\n            DefaultPlugins\n                .set(RenderPlugin {\n                    // Reuse the render resources from the Dioxus native renderer.\n                    render_creation: RenderCreation::Manual(RenderResources(\n                        RenderDevice::new(WgpuWrapper::new(device_handle.device.clone())),\n                        RenderQueue(Arc::new(WgpuWrapper::new(device_handle.queue.clone()))),\n                        RenderAdapterInfo(WgpuWrapper::new(device_handle.adapter.get_info())),\n                        RenderAdapter(Arc::new(WgpuWrapper::new(device_handle.adapter.clone()))),\n                        RenderInstance(Arc::new(WgpuWrapper::new(device_handle.instance.clone()))),\n                    )),\n                    synchronous_pipeline_compilation: true,\n                    ..default()\n                })\n                .set(WindowPlugin {\n                    primary_window: None,\n                    exit_condition: bevy::window::ExitCondition::DontExit,\n                    close_when_requested: false,\n                    ..Default::default()\n                })\n                .disable::<bevy::winit::WinitPlugin>(),\n        );\n\n        // Setup the rendering to texture.\n        app.insert_resource(ManualTextureViews::default());\n\n        // Add data from the UI.\n        app.insert_resource(UIData::default());\n\n        // Add the Bevy scene.\n        app.add_plugins(BevyScenePlugin {});\n\n        // Initialize the app to set up the render world properly.\n        app.finish();\n        app.cleanup();\n\n        Self {\n            app,\n            wgpu_device: device_handle.device.clone(),\n            last_texture_size: (0, 0),\n            texture_handle: None,\n            manual_texture_view_handle: None,\n        }\n    }\n\n    pub fn render(\n        &mut self,\n        ctx: CustomPaintCtx<'_>,\n        color: [f32; 3],\n        width: u32,\n        height: u32,\n        _start_time: &std::time::Instant,\n    ) -> Option<TextureHandle> {\n        // Update the UI data.\n        if let Some(mut ui) = self.app.world_mut().get_resource_mut::<UIData>() {\n            ui.color = color;\n        }\n\n        // Init self.texture_handle if None or if width/height changed.\n        self.init_texture(ctx, width, height);\n        // Run one frame of the Bevy app to render the 3D scene.\n        self.app.update();\n\n        self.texture_handle.clone()\n    }\n\n    fn init_texture(&mut self, mut ctx: CustomPaintCtx<'_>, width: u32, height: u32) {\n        // Reuse self.texture_handle if already initialized to the correct size.\n        let current_size = (width, height);\n        if self.texture_handle.is_some() && self.last_texture_size == current_size {\n            return;\n        }\n\n        let world = self.app.world_mut();\n\n        // Skip if no camera.\n        if world.query::<&Camera>().single(world).is_err() {\n            return;\n        }\n\n        if let Some(mut manual_texture_views) = world.get_resource_mut::<ManualTextureViews>() {\n            // Clean previous texture if any.\n            if self.texture_handle.is_some() {\n                ctx.unregister_texture(self.texture_handle.take().unwrap());\n            }\n            if let Some(old_handle) = self.manual_texture_view_handle {\n                manual_texture_views.remove(&old_handle);\n                self.manual_texture_view_handle = None;\n            }\n\n            // Create the texture for the camera target and the CustomPaintCtx.\n            let format = TextureFormat::Rgba8UnormSrgb;\n            let wgpu_texture = self.wgpu_device.create_texture(&wgpu::TextureDescriptor {\n                label: None,\n                size: wgpu::Extent3d {\n                    width,\n                    height,\n                    depth_or_array_layers: 1,\n                },\n                mip_level_count: 1,\n                sample_count: 1,\n                dimension: wgpu::TextureDimension::D2,\n                format,\n                usage: wgpu::TextureUsages::TEXTURE_BINDING\n                    | wgpu::TextureUsages::RENDER_ATTACHMENT\n                    | wgpu::TextureUsages::COPY_SRC,\n                view_formats: &[],\n            });\n            let wgpu_texture_view =\n                wgpu_texture.create_view(&wgpu::TextureViewDescriptor::default());\n            let manual_texture_view = ManualTextureView {\n                texture_view: wgpu_texture_view.into(),\n                size: bevy::math::UVec2::new(width, height),\n                format,\n            };\n            let manual_texture_view_handle = ManualTextureViewHandle(0);\n            manual_texture_views.insert(manual_texture_view_handle, manual_texture_view);\n\n            if let Ok(mut camera) = world.query::<&mut Camera>().single_mut(world) {\n                camera.target = RenderTarget::TextureView(manual_texture_view_handle);\n\n                self.last_texture_size = current_size;\n                self.manual_texture_view_handle = Some(manual_texture_view_handle);\n                self.texture_handle = Some(ctx.register_texture(wgpu_texture));\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "examples/10-integrations/bevy/src/bevy_scene_plugin.rs",
    "content": "use crate::bevy_renderer::UIData;\nuse bevy::prelude::*;\n\n#[derive(Component)]\npub struct DynamicColoredCube;\n\npub struct BevyScenePlugin {}\n\nimpl Plugin for BevyScenePlugin {\n    fn build(&self, app: &mut App) {\n        app.insert_resource(ClearColor(bevy::color::Color::srgba(0.0, 0.0, 0.0, 0.0)));\n        app.add_systems(Startup, setup);\n        app.add_systems(Update, (animate, update_cube_color));\n    }\n}\n\nfn setup(\n    mut commands: Commands,\n    mut meshes: ResMut<Assets<Mesh>>,\n    mut materials: ResMut<Assets<StandardMaterial>>,\n) {\n    commands.spawn((\n        Mesh3d(meshes.add(Cuboid::new(1.0, 1.0, 1.0))),\n        MeshMaterial3d(materials.add(StandardMaterial {\n            base_color: bevy::color::Color::srgb(1.0, 0.0, 0.0),\n            metallic: 0.0,\n            perceptual_roughness: 0.5,\n            ..default()\n        })),\n        Transform::from_xyz(0.0, 0.0, 0.0),\n        DynamicColoredCube,\n    ));\n\n    commands.spawn((\n        DirectionalLight {\n            color: bevy::color::Color::WHITE,\n            illuminance: 10000.0,\n            shadows_enabled: false,\n            ..default()\n        },\n        Transform::from_xyz(1.0, 1.0, 1.0).looking_at(Vec3::ZERO, Vec3::Y),\n    ));\n\n    commands.insert_resource(AmbientLight {\n        color: bevy::color::Color::WHITE,\n        brightness: 100.0,\n        affects_lightmapped_meshes: true,\n    });\n\n    commands.spawn((\n        Camera3d::default(),\n        Transform::from_xyz(0.0, 0.0, 3.0).looking_at(Vec3::new(0.0, 0.0, 0.0), Vec3::Y),\n        Name::new(\"MainCamera\"),\n    ));\n}\n\nfn animate(time: Res<Time>, mut cube_query: Query<&mut Transform, With<DynamicColoredCube>>) {\n    for mut transform in cube_query.iter_mut() {\n        transform.rotation = Quat::from_rotation_y(time.elapsed_secs());\n        transform.translation.x = (time.elapsed_secs() * 2.0).sin() * 0.5;\n    }\n}\n\nfn update_cube_color(\n    ui: Res<UIData>,\n    cube_query: Query<&MeshMaterial3d<StandardMaterial>, With<DynamicColoredCube>>,\n    mut materials: ResMut<Assets<StandardMaterial>>,\n) {\n    if ui.is_changed() {\n        for mesh_material in cube_query.iter() {\n            if let Some(material) = materials.get_mut(&mesh_material.0) {\n                let [r, g, b] = ui.color;\n                material.base_color = bevy::color::Color::srgb(r, g, b);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "examples/10-integrations/bevy/src/demo_renderer.rs",
    "content": "use crate::bevy_renderer::BevyRenderer;\nuse crate::Color;\nuse dioxus_native::{CustomPaintCtx, CustomPaintSource, DeviceHandle, TextureHandle};\nuse std::sync::mpsc::{channel, Receiver, Sender};\n\npub enum DemoMessage {\n    // Color in RGB format\n    SetColor(Color),\n}\n\nenum DemoRendererState {\n    Active(Box<BevyRenderer>),\n    Suspended,\n}\n\npub struct DemoPaintSource {\n    state: DemoRendererState,\n    start_time: std::time::Instant,\n    tx: Sender<DemoMessage>,\n    rx: Receiver<DemoMessage>,\n    color: Color,\n}\n\nimpl DemoPaintSource {\n    pub fn new() -> Self {\n        let (tx, rx) = channel();\n        Self::with_channel(tx, rx)\n    }\n\n    pub fn with_channel(tx: Sender<DemoMessage>, rx: Receiver<DemoMessage>) -> Self {\n        Self {\n            state: DemoRendererState::Suspended,\n            start_time: std::time::Instant::now(),\n            tx,\n            rx,\n            color: Color::WHITE,\n        }\n    }\n\n    pub fn sender(&self) -> Sender<DemoMessage> {\n        self.tx.clone()\n    }\n\n    fn process_messages(&mut self) {\n        loop {\n            match self.rx.try_recv() {\n                Err(_) => return,\n                Ok(msg) => match msg {\n                    DemoMessage::SetColor(color) => self.color = color,\n                },\n            }\n        }\n    }\n\n    fn render(\n        &mut self,\n        ctx: CustomPaintCtx<'_>,\n        width: u32,\n        height: u32,\n    ) -> Option<TextureHandle> {\n        if width == 0 || height == 0 {\n            return None;\n        }\n        let DemoRendererState::Active(state) = &mut self.state else {\n            return None;\n        };\n\n        state.render(ctx, self.color.components, width, height, &self.start_time)\n    }\n}\n\nimpl CustomPaintSource for DemoPaintSource {\n    fn resume(&mut self, device_handle: &DeviceHandle) {\n        let active_state = BevyRenderer::new(device_handle);\n        self.state = DemoRendererState::Active(Box::new(active_state));\n    }\n\n    fn suspend(&mut self) {\n        self.state = DemoRendererState::Suspended;\n    }\n\n    fn render(\n        &mut self,\n        ctx: CustomPaintCtx<'_>,\n        width: u32,\n        height: u32,\n        _scale: f64,\n    ) -> Option<TextureHandle> {\n        self.process_messages();\n        self.render(ctx, width, height)\n    }\n}\n"
  },
  {
    "path": "examples/10-integrations/bevy/src/main.rs",
    "content": "use std::any::Any;\n\nuse color::{palette::css::WHITE, parse_color};\nuse color::{OpaqueColor, Srgb};\nuse demo_renderer::{DemoMessage, DemoPaintSource};\nuse dioxus_native::prelude::*;\nuse dioxus_native::use_wgpu;\nuse wgpu::Limits;\n\nmod bevy_renderer;\nmod bevy_scene_plugin;\nmod demo_renderer;\n\n// CSS Styles\nstatic STYLES: Asset = asset!(\"/src/styles.css\");\n\ntype Color = OpaqueColor<Srgb>;\n\nfn limits() -> Limits {\n    Limits {\n        max_storage_buffers_per_shader_stage: 12,\n        ..Limits::default()\n    }\n}\n\nfn main() {\n    #[cfg(feature = \"tracing\")]\n    tracing_subscriber::fmt::init();\n\n    let config: Vec<Box<dyn Any>> = vec![Box::new(limits())];\n    dioxus_native::launch_cfg(app, Vec::new(), config);\n}\n\nfn app() -> Element {\n    let mut show_cube = use_signal(|| true);\n\n    let color_str = use_signal(|| String::from(\"red\"));\n    let color = use_memo(move || {\n        parse_color(&color_str())\n            .map(|c| c.to_alpha_color())\n            .unwrap_or(WHITE)\n            .split()\n            .0\n    });\n\n    use_effect(move || println!(\"{:?}\", color().components));\n\n    rsx!(\n        Stylesheet { href: STYLES }\n        div { id:\"overlay\",\n            h2 { \"Control Panel\" },\n            button {\n                onclick: move |_| show_cube.toggle(),\n                if show_cube() {\n                    \"Hide cube\"\n                } else {\n                    \"Show cube\"\n                }\n            }\n            br {}\n            ColorControl { label: \"Color:\", color_str },\n            p { \"This overlay demonstrates that the custom Bevy content can be rendered beneath layers of HTML content\" }\n        }\n        div { id:\"underlay\",\n            h2 { \"Underlay\" },\n            p { \"This underlay demonstrates that the custom Bevy content can be rendered above layers and blended with the content underneath\" }\n        }\n        header {\n            h1 { \"Blitz Bevy Demo\" }\n        }\n        if show_cube() {\n            SpinningCube { color }\n        }\n    )\n}\n\n#[component]\nfn ColorControl(label: &'static str, color_str: WriteSignal<String>) -> Element {\n    rsx!(div {\n        class: \"color-control\",\n        { label },\n        input {\n            value: color_str(),\n            oninput: move |evt| {\n                *color_str.write() = evt.value()\n            }\n        }\n    })\n}\n\n#[component]\nfn SpinningCube(color: Memo<Color>) -> Element {\n    // Create custom paint source and register it with the renderer\n    let paint_source = DemoPaintSource::new();\n    let sender = paint_source.sender();\n    let paint_source_id = use_wgpu(move || paint_source);\n\n    use_effect(move || {\n        sender.send(DemoMessage::SetColor(color())).unwrap();\n    });\n\n    rsx!(\n        div { id:\"canvas-container\",\n            canvas {\n                id: \"demo-canvas\",\n                \"src\": paint_source_id\n            }\n        }\n    )\n}\n"
  },
  {
    "path": "examples/10-integrations/bevy/src/styles.css",
    "content": "* {\n    box-sizing: border-box;\n}\n\nhtml, body, main {\n    height: 100%;\n    font-family: system-ui, sans;\n    margin: 0;\n}\n\nmain {\n    display: grid;\n    grid-template-rows: 100px 1fr;\n    grid-template-columns: 100%;\n    background: #f4e8d2;\n}\n\n#canvas-container {\n    display: grid;\n    opacity: 0.8;\n    grid-row: 2;\n}\n\nheader {\n    padding: 10px 40px;\n    background-color: white;\n    z-index: 100;\n    grid-row: 1;\n}\n\n#overlay {\n    position: absolute;\n    width: 33%;\n    height: 100%;\n    right: 0;\n    z-index: 10;\n    background-color: rgba(0, 0, 0, 0.5);\n    padding-top: 40%;\n    padding-inline: 20px;\n    color: white;\n}\n\n#underlay {\n    position: absolute;\n    width: 33%;\n    height: 100%;\n    z-index: -10;\n    background-color: black;\n    padding-top: 40%;\n    padding-inline: 20px;\n    color: white;\n}\n\n.color-control {\n    display: flex;\n    gap: 12px;\n\n    > input {\n        width: 150px;\n        color: black;\n    }\n}\n"
  },
  {
    "path": "examples/10-integrations/native-headless/Cargo.toml",
    "content": "[package]\nname = \"native-headless\"\nversion = \"0.0.0\"\nedition = \"2021\"\nlicense = \"MIT\"\npublish = false\n\n[features]\ntracing = [\"dep:tracing-subscriber\", \"dioxus-native-dom/tracing\"]\n\n[dependencies]\ndioxus = { workspace = true, features = [\"html\", \"hooks\", \"signals\"] }\ndioxus-native-dom = { workspace = true }\nanyrender_vello = { workspace = true }\nvello = { workspace = true }\nrustc-hash = \"1\"\nfutures-util = { workspace = true }\ntracing-subscriber = { workspace = true, optional = true }\nwgpu = { workspace = true }\nwgpu_context = { workspace = true }\npollster = \"0.4\"\nbytemuck = \"1\"\nblitz-paint = { workspace = true, default-features = true }\nblitz-traits = { workspace = true, default-features = true }\nblitz-dom = { workspace = true, default-features = true }\n"
  },
  {
    "path": "examples/10-integrations/native-headless/src/main.rs",
    "content": "//! A \"sketch\" of how to integrate a Dioxus Native app to into a wider application\n//! by rendering the UI to a texture and driving it with your own event loop\n//!\n//! (this example is not really intended to be run as-is, and requires you to fill\n//! in the missing pieces)\nuse anyrender_vello::VelloScenePainter;\nuse blitz_dom::{Document as _, DocumentConfig};\nuse blitz_paint::paint_scene;\nuse blitz_traits::{\n    events::{BlitzMouseButtonEvent, MouseEventButton, MouseEventButtons, UiEvent},\n    shell::{ColorScheme, Viewport},\n};\nuse dioxus::prelude::*;\nuse dioxus_native_dom::DioxusDocument;\nuse pollster::FutureExt as _;\nuse std::sync::Arc;\nuse std::task::Context;\nuse vello::{\n    peniko::color::AlphaColor, RenderParams, Renderer as VelloRenderer, RendererOptions, Scene,\n};\nuse wgpu::TextureFormat;\nuse wgpu_context::WGPUContext;\n\n// Constant width, height, scale factor and color schemefor example purposes\nconst SCALE_FACTOR: f32 = 1.0;\nconst WIDTH: u32 = 800;\nconst HEIGHT: u32 = 600;\nconst COLOR_SCHEME: ColorScheme = ColorScheme::Light;\n\n// Example Dioxus app.\nfn app() -> Element {\n    rsx! {\n        div { \"Hello, world!\" }\n    }\n}\n\nfn main() {\n    #[cfg(feature = \"tracing\")]\n    tracing_subscriber::fmt::init();\n\n    // =============\n    // INITIAL SETUP\n    // =============\n\n    let waker = create_waker(Box::new(|| {\n        // This should wake up and \"poll\" your event loop\n    }));\n\n    // Create the dioxus virtual dom and the dioxus-native document\n    // It is important to set the width, height, and scale factor on the document as these are used for layout.\n    let vdom = VirtualDom::new(app);\n    let mut dioxus_doc = DioxusDocument::new(\n        vdom,\n        DocumentConfig {\n            viewport: Some(Viewport::new(WIDTH, HEIGHT, SCALE_FACTOR, COLOR_SCHEME)),\n            ..Default::default()\n        },\n    );\n\n    // Setup a WGPU Device and Queue\n    //\n    // There is nothing special about WGPUContext. It is just used to\n    // reduce the amount of boilerplate associated with setting up WGPU\n    let mut wgpu_context = WGPUContext::new();\n    let device_id = wgpu_context\n        .find_or_create_device(None)\n        .block_on()\n        .expect(\"Failed to create WGPU device\");\n    let device_handle = &wgpu_context.device_pool[device_id];\n    let device = device_handle.device.clone();\n    let queue = device_handle.queue.clone();\n\n    // Create Vello renderer\n    // Note: creating a VelloRenderer is expensive, so it should be done once per Device.\n    let mut vello_renderer = VelloRenderer::new(&device, RendererOptions::default()).unwrap();\n\n    // =============\n    // CREATE TEXTURE (RECREATE ON RESIZE)\n    // =============\n\n    // Create texture and texture view\n    let texture = device.create_texture(&wgpu::TextureDescriptor {\n        label: None,\n        size: wgpu::Extent3d {\n            width: WIDTH,\n            height: HEIGHT,\n            depth_or_array_layers: 1,\n        },\n        mip_level_count: 1,\n        sample_count: 1,\n        dimension: wgpu::TextureDimension::D2,\n        usage: wgpu::TextureUsages::STORAGE_BINDING | wgpu::TextureUsages::TEXTURE_BINDING,\n        format: TextureFormat::Rgba8Unorm,\n        view_formats: &[],\n    });\n    let texture_view = texture.create_view(&wgpu::TextureViewDescriptor::default());\n\n    // =============\n    // EACH FRAME\n    // =============\n\n    // Poll the vdom\n    dioxus_doc.poll(Some(Context::from_waker(&waker)));\n\n    // Create a `vello::Scene` to paint into\n    let mut scene = Scene::new();\n\n    // Paint the document using `blitz_paint::paint_scene`\n    //\n    // Note: the `paint_scene` will work with any implementation of `anyrender::PaintScene`\n    // so you could also write your own implementation if you want more control over rendering\n    // (i.e. to render a custom renderer instead of Vello)\n    paint_scene(\n        &mut VelloScenePainter::new(&mut scene),\n        &dioxus_doc,\n        SCALE_FACTOR as f64,\n        WIDTH,\n        HEIGHT,\n    );\n\n    // Render the `vello::Scene` to the Texture using the `VelloRenderer`\n    vello_renderer\n        .render_to_texture(\n            &device,\n            &queue,\n            &scene,\n            &texture_view,\n            &RenderParams {\n                base_color: AlphaColor::TRANSPARENT,\n                width: WIDTH,\n                height: HEIGHT,\n                antialiasing_method: vello::AaConfig::Msaa16,\n            },\n        )\n        .expect(\"failed to render to texture\");\n\n    // `texture` will now contain the rendered Scene\n\n    // =============\n    // EVENT HANDLING\n    // =============\n\n    let event = UiEvent::MouseDown(BlitzMouseButtonEvent {\n        x: 30.0,\n        y: 40.0,\n        button: MouseEventButton::Main,\n        buttons: MouseEventButtons::Primary, // keep track of all pressed buttons\n        mods: Modifiers::empty(),            // ctrl, alt, shift, etc\n    });\n    dioxus_doc.handle_ui_event(event);\n\n    // Trigger a poll via your event loop (or wait for next frame)\n}\n\n/// Create a waker that will call an arbitrary callback\npub fn create_waker(callback: Box<dyn Fn() + 'static + Send + Sync>) -> std::task::Waker {\n    struct DomHandle {\n        callback: Box<dyn Fn() + 'static + Send + Sync>,\n    }\n\n    impl futures_util::task::ArcWake for DomHandle {\n        fn wake_by_ref(arc_self: &Arc<Self>) {\n            (arc_self.callback)()\n        }\n    }\n\n    futures_util::task::waker(Arc::new(DomHandle { callback }))\n}\n"
  },
  {
    "path": "examples/10-integrations/native-headless-in-bevy/Cargo.toml",
    "content": "# Copyright © SixtyFPS GmbH <info@slint.dev>\n# SPDX-License-Identifier: MIT\n\n[package]\nname = \"native-headless-in-bevy\"\nversion = \"0.0.0\"\nedition = \"2021\"\nlicense = \"MIT\"\npublish = false\n\n[features]\ndefault = [\"hot-reload\"]\ntracing = [\"dep:tracing-subscriber\", \"dioxus-native-dom/tracing\"]\nhot-reload = [\"dep:dioxus-devtools\"]\n\n[dependencies]\ndioxus = { workspace = true, features = [\"document\"]}\ndioxus-native-dom = { workspace = true }\ndioxus-asset-resolver = { workspace = true, features = [\"native\"] }\ndioxus-devtools = { workspace = true, optional = true }\nblitz-dom = { workspace = true, default-features = true }\nblitz-paint = { workspace = true, default-features = true }\nblitz-traits = { workspace = true, default-features = true }\ndata-url = { workspace = true, default-features = true }\nanyrender_vello = { workspace = true }\nwgpu = { workspace = true }\ntracing-subscriber = { workspace = true, optional = true }\nbevy = { workspace = true }\nvello = { workspace = true }\nbytes = { workspace = true }\nasync-std = \"1.13\"\ncrossbeam-channel = \"0.5\"\npaste = \"1.0\"\nrustc-hash = \"1\"\n\n"
  },
  {
    "path": "examples/10-integrations/native-headless-in-bevy/README.md",
    "content": "Example: Integrating Dioxus into a Bevy application\n\nThis example demonstrates rendering a Dioxus application onto a texture within a Bevy application.\n\n## Core Concepts\n- Render a headless Dioxus-native app to a texture.\n- Share and display the texture on a quad in the Bevy app.\n- Transmit mouse and keyboard events from Bevy to Dioxus.\n  - Events are captured when hovering over elements with the `catch-events` class, ensuring only Dioxus receives them.\n- Manage application state through channel messages between Dioxus and Bevy.\n\n"
  },
  {
    "path": "examples/10-integrations/native-headless-in-bevy/src/bevy_scene_plugin.rs",
    "content": "use crate::ui::{UIMessage, UiState};\nuse bevy::input::mouse::{MouseButton, MouseMotion};\nuse bevy::prelude::*;\nuse crossbeam_channel::{Receiver, Sender};\n\n#[derive(Component)]\npub struct DynamicCube;\n\n#[derive(Component)]\npub struct OrbitCamera {\n    pub distance: f32,\n    pub yaw: f32,\n    pub pitch: f32,\n    pub sensitivity: f32,\n}\n\nimpl Default for OrbitCamera {\n    fn default() -> Self {\n        Self {\n            distance: 3.0,\n            yaw: 0.0,\n            pitch: 0.0,\n            sensitivity: 0.01,\n        }\n    }\n}\n\n#[derive(Resource, Deref)]\nstruct UIMessageSender(Sender<UIMessage>);\n\n#[derive(Resource, Deref)]\nstruct UIMessageReceiver(Receiver<UIMessage>);\n\n#[derive(Resource)]\nstruct CubeTranslationSpeed(f32);\n\nimpl Default for CubeTranslationSpeed {\n    fn default() -> Self {\n        Self(UiState::DEFAULT_CUBE_TRANSLATION_SPEED)\n    }\n}\n\n#[derive(Resource)]\nstruct CubeRotationSpeed(f32);\n\nimpl Default for CubeRotationSpeed {\n    fn default() -> Self {\n        Self(UiState::DEFAULT_CUBE_ROTATION_SPEED)\n    }\n}\n\npub struct BevyScenePlugin {\n    pub app_sender: Sender<UIMessage>,\n    pub ui_receiver: Receiver<UIMessage>,\n}\n\nimpl Plugin for BevyScenePlugin {\n    fn build(&self, app: &mut App) {\n        app.insert_resource(ClearColor(bevy::color::Color::srgba(0.0, 0.0, 0.0, 0.0)));\n        app.insert_resource(UIMessageSender(self.app_sender.clone()));\n        app.insert_resource(UIMessageReceiver(self.ui_receiver.clone()));\n        app.insert_resource(CubeTranslationSpeed::default());\n        app.insert_resource(CubeRotationSpeed::default());\n        app.add_systems(Startup, setup);\n        app.add_systems(Update, (sync_with_ui, animate, orbit_camera_system));\n    }\n}\n\nfn setup(\n    mut commands: Commands,\n    mut meshes: ResMut<Assets<Mesh>>,\n    mut materials: ResMut<Assets<StandardMaterial>>,\n) {\n    commands.spawn((\n        Mesh3d(meshes.add(Cuboid::new(1.0, 1.0, 1.0))),\n        MeshMaterial3d(materials.add(StandardMaterial {\n            base_color: Color::Srgba(bevy::color::Srgba::from_f32_array(\n                UiState::DEFAULT_CUBE_COLOR,\n            )),\n            metallic: 0.0,\n            perceptual_roughness: 0.5,\n            ..default()\n        })),\n        Transform::from_xyz(0.0, 0.0, 0.0),\n        DynamicCube,\n    ));\n\n    commands.spawn((\n        DirectionalLight {\n            color: bevy::color::Color::WHITE,\n            illuminance: 10000.0,\n            shadows_enabled: false,\n            ..default()\n        },\n        Transform::from_xyz(1.0, 1.0, 1.0).looking_at(Vec3::ZERO, Vec3::Y),\n    ));\n\n    commands.insert_resource(AmbientLight {\n        color: bevy::color::Color::WHITE,\n        brightness: 100.0,\n        affects_lightmapped_meshes: true,\n    });\n\n    commands.spawn((\n        Camera3d::default(),\n        Transform::from_xyz(0.0, 0.0, 3.0).looking_at(Vec3::new(0.0, 0.0, 0.0), Vec3::Y),\n        Name::new(\"MainCamera\"),\n        OrbitCamera::default(),\n    ));\n}\n\nfn sync_with_ui(\n    sender: Res<UIMessageSender>,\n    receiver: Res<UIMessageReceiver>,\n    cube_query: Query<&MeshMaterial3d<StandardMaterial>, With<DynamicCube>>,\n    mut materials: ResMut<Assets<StandardMaterial>>,\n    mut translation_speed: ResMut<CubeTranslationSpeed>,\n    mut rotation_speed: ResMut<CubeRotationSpeed>,\n    time: Res<Time>,\n) {\n    let fps = 1000.0 / time.delta().as_millis() as f32;\n    sender.send(UIMessage::Fps(fps)).unwrap();\n\n    while let Ok(message) = receiver.try_recv() {\n        match message {\n            UIMessage::CubeColor(c) => {\n                for cube_material in cube_query.iter() {\n                    if let Some(material) = materials.get_mut(&cube_material.0) {\n                        material.base_color = Color::Srgba(bevy::color::Srgba::from_f32_array(c));\n                    }\n                }\n            }\n            UIMessage::CubeTranslationSpeed(speed) => {\n                translation_speed.0 = speed;\n            }\n            UIMessage::CubeRotationSpeed(speed) => {\n                rotation_speed.0 = speed;\n            }\n            _ => {}\n        }\n    }\n}\n\nfn animate(\n    time: Res<Time>,\n    mut cube_query: Query<&mut Transform, With<DynamicCube>>,\n    translation_speed: Res<CubeTranslationSpeed>,\n    rotation_speed: Res<CubeRotationSpeed>,\n) {\n    for mut transform in cube_query.iter_mut() {\n        transform.rotation = Quat::from_rotation_y(time.elapsed_secs() * rotation_speed.0);\n        transform.translation.x = (time.elapsed_secs() * translation_speed.0).sin() * 0.5;\n    }\n}\n\nfn orbit_camera_system(\n    mut camera_query: Query<(&mut Transform, &mut OrbitCamera), With<Camera3d>>,\n    mut mouse_motion_events: MessageReader<MouseMotion>,\n    mouse_button_input: Res<ButtonInput<MouseButton>>,\n) {\n    for (mut transform, mut orbit_camera) in camera_query.iter_mut() {\n        // Handle mouse input for camera rotation\n        if mouse_button_input.pressed(MouseButton::Left) {\n            for mouse_motion in mouse_motion_events.read() {\n                orbit_camera.yaw -= mouse_motion.delta.x * orbit_camera.sensitivity;\n                orbit_camera.pitch -= mouse_motion.delta.y * orbit_camera.sensitivity;\n\n                // Clamp pitch to prevent camera flipping\n                orbit_camera.pitch = orbit_camera.pitch.clamp(-1.5, 1.5);\n            }\n        }\n\n        // Calculate camera position based on spherical coordinates\n        let yaw_quat = Quat::from_rotation_y(orbit_camera.yaw);\n        let pitch_quat = Quat::from_rotation_x(orbit_camera.pitch);\n\n        let rotation = yaw_quat * pitch_quat;\n        let position = rotation * Vec3::new(0.0, 0.0, orbit_camera.distance);\n\n        transform.translation = position;\n        transform.look_at(Vec3::ZERO, Vec3::Y);\n    }\n}\n"
  },
  {
    "path": "examples/10-integrations/native-headless-in-bevy/src/dioxus_in_bevy_plugin.rs",
    "content": "use std::rc::Rc;\nuse std::sync::Arc;\nuse std::time::Instant;\n\nuse bevy::asset::RenderAssetUsages;\nuse bevy::prelude::*;\nuse bevy::{\n    input::{\n        keyboard::{Key as BevyKey, KeyCode as BevyKeyCode, KeyboardInput},\n        mouse::{MouseButton, MouseButtonInput},\n        ButtonInput, ButtonState, InputSystems,\n    },\n    render::{\n        render_asset::RenderAssets,\n        render_graph::{self, NodeRunError, RenderGraph, RenderGraphContext, RenderLabel},\n        render_resource::{Extent3d, TextureDimension, TextureFormat},\n        renderer::{RenderContext, RenderDevice, RenderQueue},\n        texture::GpuImage,\n        Extract, RenderApp,\n    },\n    window::{CursorMoved, WindowResized},\n};\n\nuse anyrender_vello::VelloScenePainter;\nuse blitz_dom::{Document as _, DocumentConfig};\nuse blitz_paint::paint_scene;\nuse blitz_traits::events::{\n    BlitzKeyEvent, BlitzMouseButtonEvent, KeyState, MouseEventButton, MouseEventButtons, UiEvent,\n};\nuse blitz_traits::net::{NetCallback, NetProvider};\nuse blitz_traits::shell::{ColorScheme, Viewport};\nuse bytes::Bytes;\nuse crossbeam_channel::{Receiver, Sender};\nuse data_url::DataUrl;\nuse dioxus::prelude::*;\nuse dioxus_devtools::DevserverMsg;\nuse dioxus_native_dom::DioxusDocument;\nuse vello::{\n    peniko::color::AlphaColor, RenderParams, Renderer as VelloRenderer, RendererOptions, Scene,\n};\n\n// Constant scale factor and color scheme for example purposes\nconst SCALE_FACTOR: f32 = 1.0;\nconst COLOR_SCHEME: ColorScheme = ColorScheme::Light;\nconst CATCH_EVENTS_CLASS: &str = \"catch-events\";\n\npub struct DioxusInBevyPlugin<UIProps> {\n    pub ui: fn(props: UIProps) -> Element,\n    pub props: UIProps,\n}\n\n#[derive(Resource)]\nstruct AnimationTime(Instant);\n\nimpl<UIProps: std::marker::Send + std::marker::Sync + std::clone::Clone + 'static> Plugin\n    for DioxusInBevyPlugin<UIProps>\n{\n    fn build(&self, app: &mut App) {\n        let epoch = AnimationTime(Instant::now());\n        let (s, r) = crossbeam_channel::unbounded();\n\n        // Create the dioxus virtual dom and the dioxus-native document\n        // The viewport will be set in setup_ui after we get the window size\n        let vdom = VirtualDom::new_with_props(self.ui, self.props.clone());\n        // FIXME add a NetProvider\n        let mut dioxus_doc = DioxusDocument::new(vdom, DocumentConfig::default());\n\n        // Setup NetProvider\n        let net_provider = BevyNetProvider::shared(s.clone());\n        dioxus_doc.set_net_provider(net_provider);\n\n        // Setup DocumentProxy to process CreateHeadElement messages\n        let proxy = Rc::new(DioxusDocumentProxy::new(s.clone()));\n        dioxus_doc.vdom.in_scope(ScopeId::ROOT, move || {\n            provide_context(proxy as Rc<dyn dioxus::document::Document>);\n        });\n\n        dioxus_doc.initial_build();\n        dioxus_doc.resolve(0.0);\n\n        // Dummy waker\n        struct NullWake;\n        impl std::task::Wake for NullWake {\n            fn wake(self: std::sync::Arc<Self>) {}\n        }\n        let waker = std::task::Waker::from(std::sync::Arc::new(NullWake));\n\n        // Setup devtools listener for hot-reloading\n        dioxus_devtools::connect(move |msg| s.send(DioxusMessage::Devserver(msg)).unwrap());\n        app.insert_resource(DioxusMessages(r));\n\n        app.insert_non_send_resource(dioxus_doc);\n        app.insert_non_send_resource(waker);\n        app.insert_resource(epoch);\n\n        app.add_systems(Startup, setup_ui);\n        app.add_systems(\n            PreUpdate,\n            (\n                handle_window_resize,\n                handle_mouse_events.after(InputSystems),\n                handle_keyboard_events.after(InputSystems),\n            )\n                .chain(),\n        );\n        app.add_systems(Update, update_ui);\n    }\n\n    fn finish(&self, app: &mut App) {\n        // Add the UI rendrer\n        let render_app = app.sub_app(RenderApp);\n        let render_device = render_app.world().resource::<RenderDevice>();\n        let device = render_device.wgpu_device();\n        let vello_renderer = VelloRenderer::new(device, RendererOptions::default()).unwrap();\n        app.insert_non_send_resource(vello_renderer);\n\n        // Setup communication between main world and render world, to send\n        // and receive the texture\n        let (s, r) = crossbeam_channel::unbounded();\n        app.insert_resource(MainWorldReceiver(r));\n        let render_app = app.sub_app_mut(RenderApp);\n        render_app.add_systems(bevy::render::ExtractSchedule, extract_texture_image);\n        render_app.insert_resource(RenderWorldSender(s));\n\n        // Add a render graph node to get the GPU texture\n        let mut graph = render_app.world_mut().resource_mut::<RenderGraph>();\n        graph.add_node(TextureGetterNode, TextureGetterNodeDriver);\n        graph.add_node_edge(bevy::render::graph::CameraDriverLabel, TextureGetterNode);\n    }\n}\n\nstruct RenderTexture {\n    pub texture_view: wgpu::TextureView,\n    pub width: u32,\n    pub height: u32,\n}\n\n#[derive(Resource, Deref)]\nstruct MainWorldReceiver(Receiver<RenderTexture>);\n\n#[derive(Resource, Deref)]\nstruct RenderWorldSender(Sender<RenderTexture>);\n\nfn create_ui_texture(width: u32, height: u32) -> Image {\n    let mut image = Image::new_fill(\n        Extent3d {\n            width,\n            height,\n            depth_or_array_layers: 1,\n        },\n        TextureDimension::D2,\n        &[0u8; 4],\n        TextureFormat::Rgba8Unorm,\n        RenderAssetUsages::RENDER_WORLD,\n    );\n    image.texture_descriptor.usage = wgpu::TextureUsages::COPY_DST\n        | wgpu::TextureUsages::STORAGE_BINDING\n        | wgpu::TextureUsages::TEXTURE_BINDING;\n    image\n}\n\n#[derive(Debug, PartialEq, Eq, Clone, Hash, RenderLabel)]\nstruct TextureGetterNode;\n\n#[derive(Default)]\nstruct TextureGetterNodeDriver;\n\nimpl render_graph::Node for TextureGetterNodeDriver {\n    fn update(&mut self, world: &mut World) {\n        // Get the GPU texture from the texture image, and send it to the main world\n        if let Some(sender) = world.get_resource::<RenderWorldSender>() {\n            if let Some(image) = world\n                .get_resource::<ExtractedTextureImage>()\n                .and_then(|e| e.0.as_ref())\n            {\n                if let Some(gpu_images) = world\n                    .get_resource::<RenderAssets<GpuImage>>()\n                    .and_then(|a| a.get(image))\n                {\n                    let _ = sender.send(RenderTexture {\n                        texture_view: (*gpu_images.texture_view).clone(),\n                        width: gpu_images.size.width,\n                        height: gpu_images.size.height,\n                    });\n                    if let Some(mut extracted_image) =\n                        world.get_resource_mut::<ExtractedTextureImage>()\n                    {\n                        // Reset the image, so it is not sent again, unless it changes\n                        extracted_image.0 = None;\n                    }\n                }\n            }\n        }\n    }\n    fn run(\n        &self,\n        _graph: &mut RenderGraphContext,\n        _render_context: &mut RenderContext,\n        _world: &World,\n    ) -> bevy::prelude::Result<(), NodeRunError> {\n        Ok(())\n    }\n}\n\n#[derive(Resource)]\npub struct TextureImage(Handle<Image>);\n\n#[derive(Resource)]\npub struct ExtractedTextureImage(Option<Handle<Image>>);\n\nfn extract_texture_image(\n    mut commands: Commands,\n    texture_image: Extract<Option<Res<TextureImage>>>,\n    mut last_texture_image: Local<Option<Handle<Image>>>,\n) {\n    if let Some(texture_image) = texture_image.as_ref() {\n        if let Some(last_texture_image) = &*last_texture_image {\n            if last_texture_image == &texture_image.0 {\n                return;\n            }\n        }\n        commands.insert_resource(ExtractedTextureImage(Some(texture_image.0.clone())));\n        *last_texture_image = Some(texture_image.0.clone());\n    }\n}\n\nstruct HeadElement {\n    name: String,\n    attributes: Vec<(String, String)>,\n    contents: Option<String>,\n}\n\nenum DioxusMessage {\n    Devserver(DevserverMsg),\n    CreateHeadElement(HeadElement),\n    ResourceLoad(blitz_dom::net::Resource),\n}\n\n#[derive(Resource, Deref)]\nstruct DioxusMessages(Receiver<DioxusMessage>);\n\n#[derive(Component)]\npub struct DioxusUiQuad;\n\nfn setup_ui(\n    mut commands: Commands,\n    mut meshes: ResMut<Assets<Mesh>>,\n    mut materials: ResMut<Assets<ColorMaterial>>,\n    mut images: ResMut<Assets<Image>>,\n    mut dioxus_doc: NonSendMut<DioxusDocument>,\n    mut animation_epoch: ResMut<AnimationTime>,\n    windows: Query<&Window>,\n) {\n    let window = windows\n        .iter()\n        .next()\n        .expect(\"Should have at least one window\");\n    let width = window.physical_width();\n    let height = window.physical_height();\n\n    debug!(\"Initial window size: {}x{}\", width, height);\n\n    // Set the initial viewport\n    animation_epoch.0 = Instant::now();\n    dioxus_doc.set_viewport(Viewport::new(width, height, SCALE_FACTOR, COLOR_SCHEME));\n    dioxus_doc.resolve(0.0);\n\n    // Create Bevy Image from the texture data\n    let image = create_ui_texture(width, height);\n    let handle = images.add(image);\n\n    // Create a quad to display the texture\n    commands.spawn((\n        Mesh2d(meshes.add(Rectangle::new(1.0, 1.0))),\n        MeshMaterial2d(materials.add(ColorMaterial {\n            texture: Some(handle.clone()),\n            ..default()\n        })),\n        Transform::from_scale(Vec3::new(width as f32, height as f32, 0.0)),\n        DioxusUiQuad,\n    ));\n    commands.spawn((\n        Camera2d,\n        Camera {\n            order: isize::MAX,\n            ..default()\n        },\n    ));\n\n    commands.insert_resource(TextureImage(handle));\n}\n\n#[allow(clippy::too_many_arguments)]\nfn update_ui(\n    mut dioxus_doc: NonSendMut<DioxusDocument>,\n    dioxus_messages: Res<DioxusMessages>,\n    waker: NonSendMut<std::task::Waker>,\n    vello_renderer: Option<NonSendMut<VelloRenderer>>,\n    render_device: Res<RenderDevice>,\n    render_queue: Res<RenderQueue>,\n    receiver: Res<MainWorldReceiver>,\n    animation_epoch: Res<AnimationTime>,\n    mut cached_texture: Local<Option<RenderTexture>>,\n) {\n    while let Ok(msg) = dioxus_messages.0.try_recv() {\n        match msg {\n            DioxusMessage::Devserver(devserver_msg) => match devserver_msg {\n                dioxus_devtools::DevserverMsg::HotReload(hotreload_message) => {\n                    // Apply changes to vdom\n                    dioxus_devtools::apply_changes(&dioxus_doc.vdom, &hotreload_message);\n\n                    // Reload changed assets\n                    for asset_path in &hotreload_message.assets {\n                        if let Some(url) = asset_path.to_str() {\n                            dioxus_doc.reload_resource_by_href(url);\n                        }\n                    }\n                }\n                dioxus_devtools::DevserverMsg::FullReloadStart => {}\n                _ => {}\n            },\n            DioxusMessage::CreateHeadElement(el) => {\n                dioxus_doc.create_head_element(&el.name, &el.attributes, &el.contents);\n                dioxus_doc.poll(Some(std::task::Context::from_waker(&waker)));\n            }\n            DioxusMessage::ResourceLoad(resource) => {\n                dioxus_doc.load_resource(resource);\n            }\n        };\n    }\n\n    while let Ok(texture) = receiver.try_recv() {\n        *cached_texture = Some(texture);\n    }\n\n    if let (Some(texture), Some(mut vello_renderer)) = ((*cached_texture).as_ref(), vello_renderer)\n    {\n        // Poll the vdom\n        dioxus_doc.poll(Some(std::task::Context::from_waker(&waker)));\n\n        // Refresh the document\n        let animation_time = animation_epoch.0.elapsed().as_secs_f64();\n        dioxus_doc.resolve(animation_time);\n\n        // Create a `vello::Scene` to paint into\n        let mut scene = Scene::new();\n\n        // Paint the document\n        paint_scene(\n            &mut VelloScenePainter::new(&mut scene),\n            &dioxus_doc,\n            SCALE_FACTOR as f64,\n            texture.width,\n            texture.height,\n        );\n\n        // Render the `vello::Scene` to the Texture using the `VelloRenderer`\n        vello_renderer\n            .render_to_texture(\n                render_device.wgpu_device(),\n                render_queue.into_inner(),\n                &scene,\n                &texture.texture_view,\n                &RenderParams {\n                    base_color: AlphaColor::TRANSPARENT,\n                    width: texture.width,\n                    height: texture.height,\n                    antialiasing_method: vello::AaConfig::Msaa16,\n                },\n            )\n            .expect(\"failed to render to texture\");\n    }\n}\n\nfn handle_window_resize(\n    mut dioxus_doc: NonSendMut<DioxusDocument>,\n    mut resize_events: MessageReader<WindowResized>,\n    mut commands: Commands,\n    mut images: ResMut<Assets<Image>>,\n    mut materials: ResMut<Assets<ColorMaterial>>,\n    texture_image: Option<Res<TextureImage>>,\n    mut query: Query<(&mut Transform, &mut MeshMaterial2d<ColorMaterial>), With<DioxusUiQuad>>,\n) {\n    for resize_event in resize_events.read() {\n        let width = resize_event.width as u32;\n        let height = resize_event.height as u32;\n\n        debug!(\"Window resized to: {}x{}\", width, height);\n\n        // Update the dioxus viewport\n        dioxus_doc.set_viewport(Viewport::new(width, height, SCALE_FACTOR, COLOR_SCHEME));\n        // dioxus_doc.resolve();\n\n        // Create a new texture with the new size\n        let new_image = create_ui_texture(width, height);\n        let new_handle = images.add(new_image);\n\n        // Update the quad mesh to match the new size\n        if let Ok((mut trans, mut mat)) = query.single_mut() {\n            *trans = Transform::from_scale(Vec3::new(width as f32, height as f32, 0.0));\n            materials.get_mut(&mut mat.0).unwrap().texture = Some(new_handle.clone());\n        }\n\n        // Remove the old texture\n        if let Some(texture_image) = texture_image.as_ref() {\n            images.remove(&texture_image.0);\n        }\n\n        // Insert the new texture resource\n        commands.insert_resource(TextureImage(new_handle));\n    }\n}\n\n#[derive(Resource, Default)]\npub struct MouseState {\n    pub x: f32,\n    pub y: f32,\n    pub buttons: MouseEventButtons,\n    pub mods: Modifiers,\n}\n\nfn does_catch_events(dioxus_doc: &DioxusDocument, node_id: usize) -> bool {\n    if let Some(node) = dioxus_doc.get_node(node_id) {\n        let class = node.attr(blitz_dom::local_name!(\"class\")).unwrap_or(\"\");\n        if class\n            .split_whitespace()\n            .any(|word| word == CATCH_EVENTS_CLASS)\n        {\n            true\n        } else if let Some(parent) = node.parent {\n            does_catch_events(dioxus_doc, parent)\n        } else {\n            false\n        }\n    } else {\n        false\n    }\n}\n\nfn handle_mouse_events(\n    mut dioxus_doc: NonSendMut<DioxusDocument>,\n    mut cursor_moved: MessageReader<CursorMoved>,\n    mut mouse_button_input_events: ResMut<Messages<MouseButtonInput>>,\n    mut mouse_buttons: ResMut<ButtonInput<MouseButton>>,\n    mut last_mouse_state: Local<MouseState>,\n) {\n    if cursor_moved.is_empty() && mouse_button_input_events.is_empty() {\n        return;\n    }\n\n    let mouse_state = &mut last_mouse_state;\n\n    for cursor_event in cursor_moved.read() {\n        mouse_state.x = cursor_event.position.x;\n        mouse_state.y = cursor_event.position.y;\n        dioxus_doc.handle_ui_event(UiEvent::MouseMove(BlitzMouseButtonEvent {\n            x: mouse_state.x,\n            y: mouse_state.y,\n            button: Default::default(),\n            buttons: mouse_state.buttons,\n            mods: mouse_state.mods,\n        }));\n    }\n\n    for event in mouse_button_input_events\n        .get_cursor()\n        .read(&mouse_button_input_events)\n    {\n        let button_blitz = match event.button {\n            MouseButton::Left => MouseEventButton::Main,\n            MouseButton::Right => MouseEventButton::Secondary,\n            MouseButton::Middle => MouseEventButton::Auxiliary,\n            MouseButton::Back => MouseEventButton::Fourth,\n            MouseButton::Forward => MouseEventButton::Fifth,\n            _ => continue,\n        };\n        let buttons_blitz = MouseEventButtons::from(button_blitz);\n        match event.state {\n            ButtonState::Pressed => {\n                mouse_state.buttons |= buttons_blitz;\n                dioxus_doc.handle_ui_event(UiEvent::MouseDown(BlitzMouseButtonEvent {\n                    x: mouse_state.x,\n                    y: mouse_state.y,\n                    button: button_blitz,\n                    buttons: mouse_state.buttons,\n                    mods: mouse_state.mods,\n                }));\n            }\n            ButtonState::Released => {\n                mouse_state.buttons &= !buttons_blitz;\n                dioxus_doc.handle_ui_event(UiEvent::MouseUp(BlitzMouseButtonEvent {\n                    x: mouse_state.x,\n                    y: mouse_state.y,\n                    button: button_blitz,\n                    buttons: mouse_state.buttons,\n                    mods: mouse_state.mods,\n                }));\n            }\n        }\n    }\n\n    let should_catch_events = dioxus_doc\n        .hit(mouse_state.x, mouse_state.y)\n        .map(|hit| does_catch_events(&dioxus_doc, hit.node_id))\n        .unwrap_or(false);\n    if should_catch_events {\n        mouse_button_input_events.clear();\n        mouse_buttons.reset_all();\n    }\n\n    // dioxus_doc.resolve();\n}\n\nfn handle_keyboard_events(\n    mut dioxus_doc: NonSendMut<DioxusDocument>,\n    mut keyboard_input_events: ResMut<Messages<KeyboardInput>>,\n    mut keys: ResMut<ButtonInput<BevyKeyCode>>,\n    mut last_mouse_state: Local<MouseState>,\n) {\n    if keyboard_input_events.is_empty() {\n        return;\n    }\n\n    for event in keyboard_input_events\n        .get_cursor()\n        .read(&keyboard_input_events)\n    {\n        let modifier = match event.logical_key {\n            BevyKey::Alt => Some(Modifiers::ALT),\n            BevyKey::AltGraph => Some(Modifiers::ALT_GRAPH),\n            BevyKey::CapsLock => Some(Modifiers::CAPS_LOCK),\n            BevyKey::Control => Some(Modifiers::CONTROL),\n            BevyKey::Fn => Some(Modifiers::FN),\n            BevyKey::FnLock => Some(Modifiers::FN_LOCK),\n            BevyKey::NumLock => Some(Modifiers::NUM_LOCK),\n            BevyKey::ScrollLock => Some(Modifiers::SCROLL_LOCK),\n            BevyKey::Shift => Some(Modifiers::SHIFT),\n            BevyKey::Symbol => Some(Modifiers::SYMBOL),\n            BevyKey::SymbolLock => Some(Modifiers::SYMBOL_LOCK),\n            BevyKey::Meta => Some(Modifiers::META),\n            BevyKey::Hyper => Some(Modifiers::HYPER),\n            BevyKey::Super => Some(Modifiers::SUPER),\n            _ => None,\n        };\n        if let Some(modifier) = modifier {\n            match event.state {\n                ButtonState::Pressed => last_mouse_state.mods.insert(modifier),\n                ButtonState::Released => last_mouse_state.mods.remove(modifier),\n            };\n        }\n        let key_state = match event.state {\n            ButtonState::Pressed => KeyState::Pressed,\n            ButtonState::Released => KeyState::Released,\n        };\n        let blitz_key_event = BlitzKeyEvent {\n            key: bevy_key_to_blitz_key(&event.logical_key),\n            code: bevy_key_code_to_blitz_code(&event.key_code),\n            modifiers: last_mouse_state.mods,\n            location: Location::Standard,\n            is_auto_repeating: event.repeat,\n            is_composing: false,\n            state: key_state,\n            text: event.text.clone(),\n        };\n\n        match key_state {\n            KeyState::Pressed => {\n                dioxus_doc.handle_ui_event(UiEvent::KeyDown(blitz_key_event));\n            }\n            KeyState::Released => {\n                dioxus_doc.handle_ui_event(UiEvent::KeyUp(blitz_key_event));\n            }\n        }\n    }\n\n    let should_catch_events = dioxus_doc\n        .hit(last_mouse_state.x, last_mouse_state.y)\n        .map(|hit| does_catch_events(&dioxus_doc, hit.node_id))\n        .unwrap_or(false);\n    if should_catch_events {\n        keyboard_input_events.clear();\n        keys.reset_all();\n    }\n\n    // dioxus_doc.resolve();\n}\n\npub struct DioxusDocumentProxy {\n    sender: Sender<DioxusMessage>,\n}\n\nimpl DioxusDocumentProxy {\n    fn new(sender: Sender<DioxusMessage>) -> Self {\n        Self { sender }\n    }\n}\n\nimpl dioxus::document::Document for DioxusDocumentProxy {\n    fn eval(&self, _js: String) -> dioxus::document::Eval {\n        dioxus::document::NoOpDocument.eval(_js)\n    }\n\n    fn create_head_element(\n        &self,\n        name: &str,\n        attributes: &[(&str, String)],\n        contents: Option<String>,\n    ) {\n        self.sender\n            .send(DioxusMessage::CreateHeadElement(HeadElement {\n                name: name.to_string(),\n                attributes: attributes\n                    .iter()\n                    .map(|(name, value)| (name.to_string(), value.clone()))\n                    .collect(),\n                contents,\n            }))\n            .unwrap();\n    }\n\n    fn set_title(&self, title: String) {\n        self.create_head_element(\"title\", &[], Some(title));\n    }\n\n    fn create_meta(&self, props: dioxus::document::MetaProps) {\n        let attributes = props.attributes();\n        self.create_head_element(\"meta\", &attributes, None);\n    }\n\n    fn create_script(&self, props: dioxus::document::ScriptProps) {\n        let attributes = props.attributes();\n        self.create_head_element(\"script\", &attributes, props.script_contents().ok());\n    }\n\n    fn create_style(&self, props: dioxus::document::StyleProps) {\n        let attributes = props.attributes();\n        self.create_head_element(\"style\", &attributes, props.style_contents().ok());\n    }\n\n    fn create_link(&self, props: dioxus::document::LinkProps) {\n        let attributes = props.attributes();\n        self.create_head_element(\"link\", &attributes, None);\n    }\n\n    fn create_head_component(&self) -> bool {\n        true\n    }\n}\n\nstruct BevyNetCallback {\n    sender: Sender<DioxusMessage>,\n}\n\nuse blitz_dom::net::Resource as BlitzResource;\nuse blitz_traits::net::NetHandler;\n\nimpl NetCallback<BlitzResource> for BevyNetCallback {\n    fn call(&self, _doc_id: usize, result: core::result::Result<BlitzResource, Option<String>>) {\n        if let Ok(res) = result {\n            self.sender.send(DioxusMessage::ResourceLoad(res)).unwrap();\n        }\n    }\n}\n\npub struct BevyNetProvider {\n    callback: Arc<dyn NetCallback<BlitzResource> + 'static>,\n}\nimpl BevyNetProvider {\n    fn shared(sender: Sender<DioxusMessage>) -> Arc<dyn NetProvider<BlitzResource>> {\n        Arc::new(Self::new(sender)) as _\n    }\n\n    fn new(sender: Sender<DioxusMessage>) -> Self {\n        Self {\n            callback: Arc::new(BevyNetCallback { sender }) as _,\n        }\n    }\n}\n\nimpl NetProvider<BlitzResource> for BevyNetProvider {\n    fn fetch(\n        &self,\n        doc_id: usize,\n        request: blitz_traits::net::Request,\n        handler: Box<dyn NetHandler<BlitzResource>>,\n    ) {\n        match request.url.scheme() {\n            // Load Dioxus assets\n            \"dioxus\" => match dioxus_asset_resolver::native::serve_asset(request.url.path()) {\n                Ok(res) => handler.bytes(doc_id, res.into_body().into(), self.callback.clone()),\n                Err(_) => {\n                    self.callback.call(\n                        doc_id,\n                        Err(Some(String::from(\"Error loading Dioxus asset\"))),\n                    );\n                }\n            },\n            // Decode data URIs\n            \"data\" => {\n                let Ok(data_url) = DataUrl::process(request.url.as_str()) else {\n                    self.callback\n                        .call(doc_id, Err(Some(String::from(\"Failed to parse data uri\"))));\n                    return;\n                };\n                let Ok(decoded) = data_url.decode_to_vec() else {\n                    self.callback\n                        .call(doc_id, Err(Some(String::from(\"Failed to decode data uri\"))));\n                    return;\n                };\n                let bytes = Bytes::from(decoded.0);\n                handler.bytes(doc_id, bytes, Arc::clone(&self.callback));\n            }\n            // TODO: support http requests\n            _ => {\n                self.callback\n                    .call(doc_id, Err(Some(String::from(\"UnsupportedScheme\"))));\n            }\n        }\n    }\n}\n\nfn bevy_key_to_blitz_key(key: &BevyKey) -> Key {\n    match key {\n        BevyKey::Character(c) => Key::Character(c.to_string()),\n        BevyKey::Unidentified(_) => Key::Unidentified,\n        BevyKey::Dead(_) => Key::Dead,\n        BevyKey::Alt => Key::Alt,\n        BevyKey::AltGraph => Key::AltGraph,\n        BevyKey::CapsLock => Key::CapsLock,\n        BevyKey::Control => Key::Control,\n        BevyKey::Fn => Key::Fn,\n        BevyKey::FnLock => Key::FnLock,\n        BevyKey::NumLock => Key::Meta,\n        BevyKey::ScrollLock => Key::NumLock,\n        BevyKey::Shift => Key::ScrollLock,\n        BevyKey::Symbol => Key::Shift,\n        BevyKey::SymbolLock => Key::Symbol,\n        BevyKey::Meta => Key::SymbolLock,\n        BevyKey::Hyper => Key::Hyper,\n        BevyKey::Super => Key::Super,\n        BevyKey::Enter => Key::Enter,\n        BevyKey::Tab => Key::Tab,\n        BevyKey::Space => Key::Character(\" \".to_string()),\n        BevyKey::ArrowDown => Key::ArrowDown,\n        BevyKey::ArrowLeft => Key::ArrowLeft,\n        BevyKey::ArrowRight => Key::ArrowRight,\n        BevyKey::ArrowUp => Key::ArrowUp,\n        BevyKey::End => Key::End,\n        BevyKey::Home => Key::Home,\n        BevyKey::PageDown => Key::PageDown,\n        BevyKey::PageUp => Key::PageUp,\n        BevyKey::Backspace => Key::Backspace,\n        BevyKey::Clear => Key::Clear,\n        BevyKey::Copy => Key::Copy,\n        BevyKey::CrSel => Key::CrSel,\n        BevyKey::Cut => Key::Cut,\n        BevyKey::Delete => Key::Delete,\n        BevyKey::EraseEof => Key::EraseEof,\n        BevyKey::ExSel => Key::ExSel,\n        BevyKey::Insert => Key::Insert,\n        BevyKey::Paste => Key::Paste,\n        BevyKey::Redo => Key::Redo,\n        BevyKey::Undo => Key::Undo,\n        BevyKey::Accept => Key::Accept,\n        BevyKey::Again => Key::Again,\n        BevyKey::Attn => Key::Attn,\n        BevyKey::Cancel => Key::Cancel,\n        BevyKey::ContextMenu => Key::ContextMenu,\n        BevyKey::Escape => Key::Escape,\n        BevyKey::Execute => Key::Execute,\n        BevyKey::Find => Key::Find,\n        BevyKey::Help => Key::Help,\n        BevyKey::Pause => Key::Pause,\n        BevyKey::Play => Key::Play,\n        BevyKey::Props => Key::Props,\n        BevyKey::Select => Key::Select,\n        BevyKey::ZoomIn => Key::ZoomIn,\n        BevyKey::ZoomOut => Key::ZoomOut,\n        BevyKey::BrightnessDown => Key::BrightnessDown,\n        BevyKey::BrightnessUp => Key::BrightnessUp,\n        BevyKey::Eject => Key::Eject,\n        BevyKey::LogOff => Key::LogOff,\n        BevyKey::Power => Key::Power,\n        BevyKey::PowerOff => Key::PowerOff,\n        BevyKey::PrintScreen => Key::PrintScreen,\n        BevyKey::Hibernate => Key::Hibernate,\n        BevyKey::Standby => Key::Standby,\n        BevyKey::WakeUp => Key::WakeUp,\n        BevyKey::AllCandidates => Key::AllCandidates,\n        BevyKey::Alphanumeric => Key::Alphanumeric,\n        BevyKey::CodeInput => Key::CodeInput,\n        BevyKey::Compose => Key::Compose,\n        BevyKey::Convert => Key::Convert,\n        BevyKey::FinalMode => Key::FinalMode,\n        BevyKey::GroupFirst => Key::GroupFirst,\n        BevyKey::GroupLast => Key::GroupLast,\n        BevyKey::GroupNext => Key::GroupNext,\n        BevyKey::GroupPrevious => Key::GroupPrevious,\n        BevyKey::ModeChange => Key::ModeChange,\n        BevyKey::NextCandidate => Key::NextCandidate,\n        BevyKey::NonConvert => Key::NonConvert,\n        BevyKey::PreviousCandidate => Key::PreviousCandidate,\n        BevyKey::Process => Key::Process,\n        BevyKey::SingleCandidate => Key::SingleCandidate,\n        BevyKey::HangulMode => Key::HangulMode,\n        BevyKey::HanjaMode => Key::HanjaMode,\n        BevyKey::JunjaMode => Key::JunjaMode,\n        BevyKey::Eisu => Key::Eisu,\n        BevyKey::Hankaku => Key::Hankaku,\n        BevyKey::Hiragana => Key::Hiragana,\n        BevyKey::HiraganaKatakana => Key::HiraganaKatakana,\n        BevyKey::KanaMode => Key::KanaMode,\n        BevyKey::KanjiMode => Key::KanjiMode,\n        BevyKey::Katakana => Key::Katakana,\n        BevyKey::Romaji => Key::Romaji,\n        BevyKey::Zenkaku => Key::Zenkaku,\n        BevyKey::ZenkakuHankaku => Key::ZenkakuHankaku,\n        BevyKey::Soft1 => Key::Soft1,\n        BevyKey::Soft2 => Key::Soft2,\n        BevyKey::Soft3 => Key::Soft3,\n        BevyKey::Soft4 => Key::Soft4,\n        BevyKey::ChannelDown => Key::ChannelDown,\n        BevyKey::ChannelUp => Key::ChannelUp,\n        BevyKey::Close => Key::Close,\n        BevyKey::MailForward => Key::MailForward,\n        BevyKey::MailReply => Key::MailReply,\n        BevyKey::MailSend => Key::MailSend,\n        BevyKey::MediaClose => Key::MediaClose,\n        BevyKey::MediaFastForward => Key::MediaFastForward,\n        BevyKey::MediaPause => Key::MediaPause,\n        BevyKey::MediaPlay => Key::MediaPlay,\n        BevyKey::MediaPlayPause => Key::MediaPlayPause,\n        BevyKey::MediaRecord => Key::MediaRecord,\n        BevyKey::MediaRewind => Key::MediaRewind,\n        BevyKey::MediaStop => Key::MediaStop,\n        BevyKey::MediaTrackNext => Key::MediaTrackNext,\n        BevyKey::MediaTrackPrevious => Key::MediaTrackPrevious,\n        BevyKey::New => Key::New,\n        BevyKey::Open => Key::Open,\n        BevyKey::Print => Key::Print,\n        BevyKey::Save => Key::Save,\n        BevyKey::SpellCheck => Key::SpellCheck,\n        BevyKey::Key11 => Key::Key11,\n        BevyKey::Key12 => Key::Key12,\n        BevyKey::AudioBalanceLeft => Key::AudioBalanceLeft,\n        BevyKey::AudioBalanceRight => Key::AudioBalanceRight,\n        BevyKey::AudioBassBoostDown => Key::AudioBassBoostDown,\n        BevyKey::AudioBassBoostToggle => Key::AudioBassBoostToggle,\n        BevyKey::AudioBassBoostUp => Key::AudioBassBoostUp,\n        BevyKey::AudioFaderFront => Key::AudioFaderFront,\n        BevyKey::AudioFaderRear => Key::AudioFaderRear,\n        BevyKey::AudioSurroundModeNext => Key::AudioSurroundModeNext,\n        BevyKey::AudioTrebleDown => Key::AudioTrebleDown,\n        BevyKey::AudioTrebleUp => Key::AudioTrebleUp,\n        BevyKey::AudioVolumeDown => Key::AudioVolumeDown,\n        BevyKey::AudioVolumeUp => Key::AudioVolumeUp,\n        BevyKey::AudioVolumeMute => Key::AudioVolumeMute,\n        BevyKey::MicrophoneToggle => Key::MicrophoneToggle,\n        BevyKey::MicrophoneVolumeDown => Key::MicrophoneVolumeDown,\n        BevyKey::MicrophoneVolumeUp => Key::MicrophoneVolumeUp,\n        BevyKey::MicrophoneVolumeMute => Key::MicrophoneVolumeMute,\n        BevyKey::SpeechCorrectionList => Key::SpeechCorrectionList,\n        BevyKey::SpeechInputToggle => Key::SpeechInputToggle,\n        BevyKey::LaunchApplication1 => Key::LaunchApplication1,\n        BevyKey::LaunchApplication2 => Key::LaunchApplication2,\n        BevyKey::LaunchCalendar => Key::LaunchCalendar,\n        BevyKey::LaunchContacts => Key::LaunchContacts,\n        BevyKey::LaunchMail => Key::LaunchMail,\n        BevyKey::LaunchMediaPlayer => Key::LaunchMediaPlayer,\n        BevyKey::LaunchMusicPlayer => Key::LaunchMusicPlayer,\n        BevyKey::LaunchPhone => Key::LaunchPhone,\n        BevyKey::LaunchScreenSaver => Key::LaunchScreenSaver,\n        BevyKey::LaunchSpreadsheet => Key::LaunchSpreadsheet,\n        BevyKey::LaunchWebBrowser => Key::LaunchWebBrowser,\n        BevyKey::LaunchWebCam => Key::LaunchWebCam,\n        BevyKey::LaunchWordProcessor => Key::LaunchWordProcessor,\n        BevyKey::BrowserBack => Key::BrowserBack,\n        BevyKey::BrowserFavorites => Key::BrowserFavorites,\n        BevyKey::BrowserForward => Key::BrowserForward,\n        BevyKey::BrowserHome => Key::BrowserHome,\n        BevyKey::BrowserRefresh => Key::BrowserRefresh,\n        BevyKey::BrowserSearch => Key::BrowserSearch,\n        BevyKey::BrowserStop => Key::BrowserStop,\n        BevyKey::AppSwitch => Key::AppSwitch,\n        BevyKey::Call => Key::Call,\n        BevyKey::Camera => Key::Camera,\n        BevyKey::CameraFocus => Key::CameraFocus,\n        BevyKey::EndCall => Key::EndCall,\n        BevyKey::GoBack => Key::GoBack,\n        BevyKey::GoHome => Key::GoHome,\n        BevyKey::HeadsetHook => Key::HeadsetHook,\n        BevyKey::LastNumberRedial => Key::LastNumberRedial,\n        BevyKey::Notification => Key::Notification,\n        BevyKey::MannerMode => Key::MannerMode,\n        BevyKey::VoiceDial => Key::VoiceDial,\n        BevyKey::TV => Key::TV,\n        BevyKey::TV3DMode => Key::TV3DMode,\n        BevyKey::TVAntennaCable => Key::TVAntennaCable,\n        BevyKey::TVAudioDescription => Key::TVAudioDescription,\n        BevyKey::TVAudioDescriptionMixDown => Key::TVAudioDescriptionMixDown,\n        BevyKey::TVAudioDescriptionMixUp => Key::TVAudioDescriptionMixUp,\n        BevyKey::TVContentsMenu => Key::TVContentsMenu,\n        BevyKey::TVDataService => Key::TVDataService,\n        BevyKey::TVInput => Key::TVInput,\n        BevyKey::TVInputComponent1 => Key::TVInputComponent1,\n        BevyKey::TVInputComponent2 => Key::TVInputComponent2,\n        BevyKey::TVInputComposite1 => Key::TVInputComposite1,\n        BevyKey::TVInputComposite2 => Key::TVInputComposite2,\n        BevyKey::TVInputHDMI1 => Key::TVInputHDMI1,\n        BevyKey::TVInputHDMI2 => Key::TVInputHDMI2,\n        BevyKey::TVInputHDMI3 => Key::TVInputHDMI3,\n        BevyKey::TVInputHDMI4 => Key::TVInputHDMI4,\n        BevyKey::TVInputVGA1 => Key::TVInputVGA1,\n        BevyKey::TVMediaContext => Key::TVMediaContext,\n        BevyKey::TVNetwork => Key::TVNetwork,\n        BevyKey::TVNumberEntry => Key::TVNumberEntry,\n        BevyKey::TVPower => Key::TVPower,\n        BevyKey::TVRadioService => Key::TVRadioService,\n        BevyKey::TVSatellite => Key::TVSatellite,\n        BevyKey::TVSatelliteBS => Key::TVSatelliteBS,\n        BevyKey::TVSatelliteCS => Key::TVSatelliteCS,\n        BevyKey::TVSatelliteToggle => Key::TVSatelliteToggle,\n        BevyKey::TVTerrestrialAnalog => Key::TVTerrestrialAnalog,\n        BevyKey::TVTerrestrialDigital => Key::TVTerrestrialDigital,\n        BevyKey::TVTimer => Key::TVTimer,\n        BevyKey::AVRInput => Key::AVRInput,\n        BevyKey::AVRPower => Key::AVRPower,\n        BevyKey::ColorF0Red => Key::ColorF0Red,\n        BevyKey::ColorF1Green => Key::ColorF1Green,\n        BevyKey::ColorF2Yellow => Key::ColorF2Yellow,\n        BevyKey::ColorF3Blue => Key::ColorF3Blue,\n        BevyKey::ColorF4Grey => Key::ColorF4Grey,\n        BevyKey::ColorF5Brown => Key::ColorF5Brown,\n        BevyKey::ClosedCaptionToggle => Key::ClosedCaptionToggle,\n        BevyKey::Dimmer => Key::Dimmer,\n        BevyKey::DisplaySwap => Key::DisplaySwap,\n        BevyKey::DVR => Key::DVR,\n        BevyKey::Exit => Key::Exit,\n        BevyKey::FavoriteClear0 => Key::FavoriteClear0,\n        BevyKey::FavoriteClear1 => Key::FavoriteClear1,\n        BevyKey::FavoriteClear2 => Key::FavoriteClear2,\n        BevyKey::FavoriteClear3 => Key::FavoriteClear3,\n        BevyKey::FavoriteRecall0 => Key::FavoriteRecall0,\n        BevyKey::FavoriteRecall1 => Key::FavoriteRecall1,\n        BevyKey::FavoriteRecall2 => Key::FavoriteRecall2,\n        BevyKey::FavoriteRecall3 => Key::FavoriteRecall3,\n        BevyKey::FavoriteStore0 => Key::FavoriteStore0,\n        BevyKey::FavoriteStore1 => Key::FavoriteStore1,\n        BevyKey::FavoriteStore2 => Key::FavoriteStore2,\n        BevyKey::FavoriteStore3 => Key::FavoriteStore3,\n        BevyKey::Guide => Key::Guide,\n        BevyKey::GuideNextDay => Key::GuideNextDay,\n        BevyKey::GuidePreviousDay => Key::GuidePreviousDay,\n        BevyKey::Info => Key::Info,\n        BevyKey::InstantReplay => Key::InstantReplay,\n        BevyKey::Link => Key::Link,\n        BevyKey::ListProgram => Key::ListProgram,\n        BevyKey::LiveContent => Key::LiveContent,\n        BevyKey::Lock => Key::Lock,\n        BevyKey::MediaApps => Key::MediaApps,\n        BevyKey::MediaAudioTrack => Key::MediaAudioTrack,\n        BevyKey::MediaLast => Key::MediaLast,\n        BevyKey::MediaSkipBackward => Key::MediaSkipBackward,\n        BevyKey::MediaSkipForward => Key::MediaSkipForward,\n        BevyKey::MediaStepBackward => Key::MediaStepBackward,\n        BevyKey::MediaStepForward => Key::MediaStepForward,\n        BevyKey::MediaTopMenu => Key::MediaTopMenu,\n        BevyKey::NavigateIn => Key::NavigateIn,\n        BevyKey::NavigateNext => Key::NavigateNext,\n        BevyKey::NavigateOut => Key::NavigateOut,\n        BevyKey::NavigatePrevious => Key::NavigatePrevious,\n        BevyKey::NextFavoriteChannel => Key::NextFavoriteChannel,\n        BevyKey::NextUserProfile => Key::NextUserProfile,\n        BevyKey::OnDemand => Key::OnDemand,\n        BevyKey::Pairing => Key::Pairing,\n        BevyKey::PinPDown => Key::PinPDown,\n        BevyKey::PinPMove => Key::PinPMove,\n        BevyKey::PinPToggle => Key::PinPToggle,\n        BevyKey::PinPUp => Key::PinPUp,\n        BevyKey::PlaySpeedDown => Key::PlaySpeedDown,\n        BevyKey::PlaySpeedReset => Key::PlaySpeedReset,\n        BevyKey::PlaySpeedUp => Key::PlaySpeedUp,\n        BevyKey::RandomToggle => Key::RandomToggle,\n        BevyKey::RcLowBattery => Key::RcLowBattery,\n        BevyKey::RecordSpeedNext => Key::RecordSpeedNext,\n        BevyKey::RfBypass => Key::RfBypass,\n        BevyKey::ScanChannelsToggle => Key::ScanChannelsToggle,\n        BevyKey::ScreenModeNext => Key::ScreenModeNext,\n        BevyKey::Settings => Key::Settings,\n        BevyKey::SplitScreenToggle => Key::SplitScreenToggle,\n        BevyKey::STBInput => Key::STBInput,\n        BevyKey::STBPower => Key::STBPower,\n        BevyKey::Subtitle => Key::Subtitle,\n        BevyKey::Teletext => Key::Teletext,\n        BevyKey::VideoModeNext => Key::VideoModeNext,\n        BevyKey::Wink => Key::Wink,\n        BevyKey::ZoomToggle => Key::ZoomToggle,\n        BevyKey::F1 => Key::F1,\n        BevyKey::F2 => Key::F2,\n        BevyKey::F3 => Key::F3,\n        BevyKey::F4 => Key::F4,\n        BevyKey::F5 => Key::F5,\n        BevyKey::F6 => Key::F6,\n        BevyKey::F7 => Key::F7,\n        BevyKey::F8 => Key::F8,\n        BevyKey::F9 => Key::F9,\n        BevyKey::F10 => Key::F10,\n        BevyKey::F11 => Key::F11,\n        BevyKey::F12 => Key::F12,\n        BevyKey::F13 => Key::F13,\n        BevyKey::F14 => Key::F14,\n        BevyKey::F15 => Key::F15,\n        BevyKey::F16 => Key::F16,\n        BevyKey::F17 => Key::F17,\n        BevyKey::F18 => Key::F18,\n        BevyKey::F19 => Key::F19,\n        BevyKey::F20 => Key::F20,\n        BevyKey::F21 => Key::F21,\n        BevyKey::F22 => Key::F22,\n        BevyKey::F23 => Key::F23,\n        BevyKey::F24 => Key::F24,\n        BevyKey::F25 => Key::F25,\n        BevyKey::F26 => Key::F26,\n        BevyKey::F27 => Key::F27,\n        BevyKey::F28 => Key::F28,\n        BevyKey::F29 => Key::F29,\n        BevyKey::F30 => Key::F30,\n        BevyKey::F31 => Key::F31,\n        BevyKey::F32 => Key::F32,\n        BevyKey::F33 => Key::F33,\n        BevyKey::F34 => Key::F34,\n        BevyKey::F35 => Key::F35,\n        _ => Key::Unidentified,\n    }\n}\n\nfn bevy_key_code_to_blitz_code(key_code: &BevyKeyCode) -> Code {\n    match key_code {\n        BevyKeyCode::Unidentified(_) => Code::Unidentified,\n        BevyKeyCode::Backquote => Code::Backquote,\n        BevyKeyCode::Backslash => Code::Backslash,\n        BevyKeyCode::BracketLeft => Code::BracketLeft,\n        BevyKeyCode::BracketRight => Code::BracketRight,\n        BevyKeyCode::Comma => Code::Comma,\n        BevyKeyCode::Digit0 => Code::Digit0,\n        BevyKeyCode::Digit1 => Code::Digit1,\n        BevyKeyCode::Digit2 => Code::Digit2,\n        BevyKeyCode::Digit3 => Code::Digit3,\n        BevyKeyCode::Digit4 => Code::Digit4,\n        BevyKeyCode::Digit5 => Code::Digit5,\n        BevyKeyCode::Digit6 => Code::Digit6,\n        BevyKeyCode::Digit7 => Code::Digit7,\n        BevyKeyCode::Digit8 => Code::Digit8,\n        BevyKeyCode::Digit9 => Code::Digit9,\n        BevyKeyCode::Equal => Code::Equal,\n        BevyKeyCode::IntlBackslash => Code::IntlBackslash,\n        BevyKeyCode::IntlRo => Code::IntlRo,\n        BevyKeyCode::IntlYen => Code::IntlYen,\n        BevyKeyCode::KeyA => Code::KeyA,\n        BevyKeyCode::KeyB => Code::KeyB,\n        BevyKeyCode::KeyC => Code::KeyC,\n        BevyKeyCode::KeyD => Code::KeyD,\n        BevyKeyCode::KeyE => Code::KeyE,\n        BevyKeyCode::KeyF => Code::KeyF,\n        BevyKeyCode::KeyG => Code::KeyG,\n        BevyKeyCode::KeyH => Code::KeyH,\n        BevyKeyCode::KeyI => Code::KeyI,\n        BevyKeyCode::KeyJ => Code::KeyJ,\n        BevyKeyCode::KeyK => Code::KeyK,\n        BevyKeyCode::KeyL => Code::KeyL,\n        BevyKeyCode::KeyM => Code::KeyM,\n        BevyKeyCode::KeyN => Code::KeyN,\n        BevyKeyCode::KeyO => Code::KeyO,\n        BevyKeyCode::KeyP => Code::KeyP,\n        BevyKeyCode::KeyQ => Code::KeyQ,\n        BevyKeyCode::KeyR => Code::KeyR,\n        BevyKeyCode::KeyS => Code::KeyS,\n        BevyKeyCode::KeyT => Code::KeyT,\n        BevyKeyCode::KeyU => Code::KeyU,\n        BevyKeyCode::KeyV => Code::KeyV,\n        BevyKeyCode::KeyW => Code::KeyW,\n        BevyKeyCode::KeyX => Code::KeyX,\n        BevyKeyCode::KeyY => Code::KeyY,\n        BevyKeyCode::KeyZ => Code::KeyZ,\n        BevyKeyCode::Minus => Code::Minus,\n        BevyKeyCode::Period => Code::Period,\n        BevyKeyCode::Quote => Code::Quote,\n        BevyKeyCode::Semicolon => Code::Semicolon,\n        BevyKeyCode::Slash => Code::Slash,\n        BevyKeyCode::AltLeft => Code::AltLeft,\n        BevyKeyCode::AltRight => Code::AltRight,\n        BevyKeyCode::Backspace => Code::Backspace,\n        BevyKeyCode::CapsLock => Code::CapsLock,\n        BevyKeyCode::ContextMenu => Code::ContextMenu,\n        BevyKeyCode::ControlLeft => Code::ControlLeft,\n        BevyKeyCode::ControlRight => Code::ControlRight,\n        BevyKeyCode::Enter => Code::Enter,\n        BevyKeyCode::SuperLeft => Code::MetaLeft,\n        BevyKeyCode::SuperRight => Code::MetaRight,\n        BevyKeyCode::ShiftLeft => Code::ShiftLeft,\n        BevyKeyCode::ShiftRight => Code::ShiftRight,\n        BevyKeyCode::Space => Code::Space,\n        BevyKeyCode::Tab => Code::Tab,\n        BevyKeyCode::Convert => Code::Convert,\n        BevyKeyCode::KanaMode => Code::KanaMode,\n        BevyKeyCode::Lang1 => Code::Lang1,\n        BevyKeyCode::Lang2 => Code::Lang2,\n        BevyKeyCode::Lang3 => Code::Lang3,\n        BevyKeyCode::Lang4 => Code::Lang4,\n        BevyKeyCode::Lang5 => Code::Lang5,\n        BevyKeyCode::NonConvert => Code::NonConvert,\n        BevyKeyCode::Delete => Code::Delete,\n        BevyKeyCode::End => Code::End,\n        BevyKeyCode::Help => Code::Help,\n        BevyKeyCode::Home => Code::Home,\n        BevyKeyCode::Insert => Code::Insert,\n        BevyKeyCode::PageDown => Code::PageDown,\n        BevyKeyCode::PageUp => Code::PageUp,\n        BevyKeyCode::ArrowDown => Code::ArrowDown,\n        BevyKeyCode::ArrowLeft => Code::ArrowLeft,\n        BevyKeyCode::ArrowRight => Code::ArrowRight,\n        BevyKeyCode::ArrowUp => Code::ArrowUp,\n        BevyKeyCode::NumLock => Code::NumLock,\n        BevyKeyCode::Numpad0 => Code::Numpad0,\n        BevyKeyCode::Numpad1 => Code::Numpad1,\n        BevyKeyCode::Numpad2 => Code::Numpad2,\n        BevyKeyCode::Numpad3 => Code::Numpad3,\n        BevyKeyCode::Numpad4 => Code::Numpad4,\n        BevyKeyCode::Numpad5 => Code::Numpad5,\n        BevyKeyCode::Numpad6 => Code::Numpad6,\n        BevyKeyCode::Numpad7 => Code::Numpad7,\n        BevyKeyCode::Numpad8 => Code::Numpad8,\n        BevyKeyCode::Numpad9 => Code::Numpad9,\n        BevyKeyCode::NumpadAdd => Code::NumpadAdd,\n        BevyKeyCode::NumpadBackspace => Code::NumpadBackspace,\n        BevyKeyCode::NumpadClear => Code::NumpadClear,\n        BevyKeyCode::NumpadClearEntry => Code::NumpadClearEntry,\n        BevyKeyCode::NumpadComma => Code::NumpadComma,\n        BevyKeyCode::NumpadDecimal => Code::NumpadDecimal,\n        BevyKeyCode::NumpadDivide => Code::NumpadDivide,\n        BevyKeyCode::NumpadEnter => Code::NumpadEnter,\n        BevyKeyCode::NumpadEqual => Code::NumpadEqual,\n        BevyKeyCode::NumpadHash => Code::NumpadHash,\n        BevyKeyCode::NumpadMemoryAdd => Code::NumpadMemoryAdd,\n        BevyKeyCode::NumpadMemoryClear => Code::NumpadMemoryClear,\n        BevyKeyCode::NumpadMemoryRecall => Code::NumpadMemoryRecall,\n        BevyKeyCode::NumpadMemoryStore => Code::NumpadMemoryStore,\n        BevyKeyCode::NumpadMemorySubtract => Code::NumpadMemorySubtract,\n        BevyKeyCode::NumpadMultiply => Code::NumpadMultiply,\n        BevyKeyCode::NumpadParenLeft => Code::NumpadParenLeft,\n        BevyKeyCode::NumpadParenRight => Code::NumpadParenRight,\n        BevyKeyCode::NumpadStar => Code::NumpadStar,\n        BevyKeyCode::NumpadSubtract => Code::NumpadSubtract,\n        BevyKeyCode::Escape => Code::Escape,\n        BevyKeyCode::Fn => Code::Fn,\n        BevyKeyCode::FnLock => Code::FnLock,\n        BevyKeyCode::PrintScreen => Code::PrintScreen,\n        BevyKeyCode::ScrollLock => Code::ScrollLock,\n        BevyKeyCode::Pause => Code::Pause,\n        BevyKeyCode::BrowserBack => Code::BrowserBack,\n        BevyKeyCode::BrowserFavorites => Code::BrowserFavorites,\n        BevyKeyCode::BrowserForward => Code::BrowserForward,\n        BevyKeyCode::BrowserHome => Code::BrowserHome,\n        BevyKeyCode::BrowserRefresh => Code::BrowserRefresh,\n        BevyKeyCode::BrowserSearch => Code::BrowserSearch,\n        BevyKeyCode::BrowserStop => Code::BrowserStop,\n        BevyKeyCode::Eject => Code::Eject,\n        BevyKeyCode::LaunchApp1 => Code::LaunchApp1,\n        BevyKeyCode::LaunchApp2 => Code::LaunchApp2,\n        BevyKeyCode::LaunchMail => Code::LaunchMail,\n        BevyKeyCode::MediaPlayPause => Code::MediaPlayPause,\n        BevyKeyCode::MediaSelect => Code::MediaSelect,\n        BevyKeyCode::MediaStop => Code::MediaStop,\n        BevyKeyCode::MediaTrackNext => Code::MediaTrackNext,\n        BevyKeyCode::MediaTrackPrevious => Code::MediaTrackPrevious,\n        BevyKeyCode::Power => Code::Power,\n        BevyKeyCode::Sleep => Code::Sleep,\n        BevyKeyCode::AudioVolumeDown => Code::AudioVolumeDown,\n        BevyKeyCode::AudioVolumeMute => Code::AudioVolumeMute,\n        BevyKeyCode::AudioVolumeUp => Code::AudioVolumeUp,\n        BevyKeyCode::WakeUp => Code::WakeUp,\n        BevyKeyCode::Meta => Code::Hyper,\n        BevyKeyCode::Hyper => Code::Super,\n        BevyKeyCode::Turbo => Code::Turbo,\n        BevyKeyCode::Abort => Code::Abort,\n        BevyKeyCode::Resume => Code::Resume,\n        BevyKeyCode::Suspend => Code::Suspend,\n        BevyKeyCode::Again => Code::Again,\n        BevyKeyCode::Copy => Code::Copy,\n        BevyKeyCode::Cut => Code::Cut,\n        BevyKeyCode::Find => Code::Find,\n        BevyKeyCode::Open => Code::Open,\n        BevyKeyCode::Paste => Code::Paste,\n        BevyKeyCode::Props => Code::Props,\n        BevyKeyCode::Select => Code::Select,\n        BevyKeyCode::Undo => Code::Undo,\n        BevyKeyCode::Hiragana => Code::Hiragana,\n        BevyKeyCode::Katakana => Code::Katakana,\n        BevyKeyCode::F1 => Code::F1,\n        BevyKeyCode::F2 => Code::F2,\n        BevyKeyCode::F3 => Code::F3,\n        BevyKeyCode::F4 => Code::F4,\n        BevyKeyCode::F5 => Code::F5,\n        BevyKeyCode::F6 => Code::F6,\n        BevyKeyCode::F7 => Code::F7,\n        BevyKeyCode::F8 => Code::F8,\n        BevyKeyCode::F9 => Code::F9,\n        BevyKeyCode::F10 => Code::F10,\n        BevyKeyCode::F11 => Code::F11,\n        BevyKeyCode::F12 => Code::F12,\n        BevyKeyCode::F13 => Code::F13,\n        BevyKeyCode::F14 => Code::F14,\n        BevyKeyCode::F15 => Code::F15,\n        BevyKeyCode::F16 => Code::F16,\n        BevyKeyCode::F17 => Code::F17,\n        BevyKeyCode::F18 => Code::F18,\n        BevyKeyCode::F19 => Code::F19,\n        BevyKeyCode::F20 => Code::F20,\n        BevyKeyCode::F21 => Code::F21,\n        BevyKeyCode::F22 => Code::F22,\n        BevyKeyCode::F23 => Code::F23,\n        BevyKeyCode::F24 => Code::F24,\n        BevyKeyCode::F25 => Code::F25,\n        BevyKeyCode::F26 => Code::F26,\n        BevyKeyCode::F27 => Code::F27,\n        BevyKeyCode::F28 => Code::F28,\n        BevyKeyCode::F29 => Code::F29,\n        BevyKeyCode::F30 => Code::F30,\n        BevyKeyCode::F31 => Code::F31,\n        BevyKeyCode::F32 => Code::F32,\n        BevyKeyCode::F33 => Code::F33,\n        BevyKeyCode::F34 => Code::F34,\n        BevyKeyCode::F35 => Code::F35,\n    }\n}\n"
  },
  {
    "path": "examples/10-integrations/native-headless-in-bevy/src/main.rs",
    "content": "mod bevy_scene_plugin;\nmod dioxus_in_bevy_plugin;\nmod ui;\n\nuse crate::bevy_scene_plugin::BevyScenePlugin;\nuse crate::dioxus_in_bevy_plugin::DioxusInBevyPlugin;\nuse crate::ui::{ui, UIProps};\nuse bevy::prelude::*;\n\nfn main() {\n    #[cfg(feature = \"tracing\")]\n    tracing_subscriber::fmt::init();\n\n    let (ui_sender, ui_receiver) = crossbeam_channel::unbounded();\n    let (app_sender, app_receiver) = crossbeam_channel::unbounded();\n    let props = UIProps {\n        ui_sender,\n        app_receiver,\n    };\n\n    App::new()\n        .add_plugins(DefaultPlugins)\n        .add_plugins(DioxusInBevyPlugin::<UIProps> { ui, props })\n        .add_plugins(BevyScenePlugin {\n            app_sender,\n            ui_receiver,\n        })\n        .run();\n}\n"
  },
  {
    "path": "examples/10-integrations/native-headless-in-bevy/src/ui.css",
    "content": "html, body, #main {\n    color: white;\n    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;\n    height: 100%;\n    width: 100%;\n    overflow: hidden;\n    margin: 0;\n}\n\n#panel {\n    background-color: #C0C0C033;\n    display: flex;\n    flex-direction: column;\n    height: 100%;\n    width: 30%;\n    justify-content: start;\n}\n\n#title {\n    color: #FFFFFF;\n    text-align: center;\n    padding-top: 10px;\n    border-bottom: 1px solid #a8a8a8;\n    display: flex;\n    flex-direction: row;\n    justify-content: space-evenly;\n    align-items: center;\n}\n\n#buttons {\n    display: flex;\n    flex-direction: row;\n    justify-content: center;\n    gap: 20px;\n    padding-top: 20px;\n    padding-bottom: 20px;\n}\n\n.color-button {\n    width: 50px;\n    height: 50px;\n    border-radius: 3px;\n}\n\n.color-button:hover {\n    box-shadow: 0px 0px 1px #c1c1c1;\n}\n\n.color-button:active {\n    box-shadow: 0px 0px 5px #c1c1c1;\n}\n\n#button-red {\n    background-color: red;\n}\n\n#button-green {\n    background-color: green;\n}\n\n#button-blue {\n    background-color: blue;\n}\n\n.spin-box {\n    width: 50px;\n    height: 50px;\n    animation: spin 4s infinite linear;\n}\n\n@keyframes spin {\n    from { transform:rotate(0deg); }\n    to { transform:rotate(360deg); }\n}\n\n#translation-speed-control, #rotation-speed-control {\n    display: flex;\n    flex-direction: row;\n    align-items: start;\n    padding: 20px;\n    gap: 10px;\n}\n\ninput[type=\"number\"] {\n    width: 3em;\n    height: 1.5em;\n    line-height: 1.5em;\n    background: #444;\n    border: 2px solid #666;\n    border-radius: 4px;\n    color: #ffffff;\n    font-size: 16px;\n    font-weight: bold;\n    text-align: center;\n    padding: 0 10px;\n    outline: none;\n    margin-left: auto;\n}\n\n#footer {\n    margin-top: auto;\n    margin-left: 10px;\n}\n\n"
  },
  {
    "path": "examples/10-integrations/native-headless-in-bevy/src/ui.rs",
    "content": "use async_std::task::sleep;\nuse crossbeam_channel::{Receiver, Sender};\nuse dioxus::prelude::*;\nuse paste::paste;\n\nmacro_rules! define_ui_state {\n    (\n        $($field:ident : $type:ty = $default:expr),* $(,)?\n    ) => { paste! {\n        #[allow(dead_code)]\n        #[derive(Clone, Copy)]\n        pub struct UiState {\n            $($field: Signal<$type>,)*\n        }\n\n        #[allow(dead_code)]\n        impl UiState {\n            fn default() -> Self {\n                Self {\n                    $($field: Signal::new($default),)*\n                }\n            }\n\n            $(pub const [<DEFAULT_ $field:upper>]: $type = $default;)*\n        }\n\n        #[allow(dead_code)]\n        pub enum UIMessage {\n            $([<$field:camel>]($type),)*\n        }\n    }};\n}\n\ndefine_ui_state! {\n    cube_color: [f32; 4] = [0.0, 0.0, 1.0, 1.0],\n    cube_translation_speed: f32 = 2.0,\n    cube_rotation_speed: f32 = 1.0,\n    fps: f32 = 0.0,\n}\n\n#[derive(Clone)]\npub struct UIProps {\n    pub ui_sender: Sender<UIMessage>,\n    pub app_receiver: Receiver<UIMessage>,\n}\n\npub fn ui(props: UIProps) -> Element {\n    let mut state = use_context_provider(UiState::default);\n\n    use_effect({\n        let ui_sender = props.ui_sender.clone();\n        move || {\n            println!(\"Color changed to {:?}\", state.cube_color);\n            ui_sender\n                .send(UIMessage::CubeColor((state.cube_color)()))\n                .unwrap();\n        }\n    });\n\n    use_effect({\n        let ui_sender = props.ui_sender.clone();\n        move || {\n            println!(\"Rotation speed changed to {:?}\", state.cube_rotation_speed);\n            ui_sender\n                .send(UIMessage::CubeRotationSpeed((state.cube_rotation_speed)()))\n                .unwrap();\n        }\n    });\n\n    use_effect({\n        let ui_sender = props.ui_sender.clone();\n        move || {\n            println!(\n                \"Translation speed changed to {:?}\",\n                state.cube_translation_speed\n            );\n            ui_sender\n                .send(UIMessage::CubeTranslationSpeed((state\n                    .cube_translation_speed)(\n                )))\n                .unwrap();\n        }\n    });\n\n    use_future(move || {\n        let app_receiver = props.app_receiver.clone();\n        async move {\n            loop {\n                // Update UI every 1s in this demo.\n                sleep(std::time::Duration::from_millis(1000)).await;\n\n                let mut fps = Option::<f32>::None;\n\n                while let Ok(message) = app_receiver.try_recv() {\n                    if let UIMessage::Fps(v) = message {\n                        fps = Some(v)\n                    }\n                }\n\n                if let Some(fps) = fps {\n                    state.fps.set(fps);\n                }\n            }\n        }\n    });\n\n    let color = *state.cube_color.read();\n    let [r, g, b, a] = color.map(|c| (c * 255.0) as u8);\n\n    println!(\"rgba({r}, {g}, {b}, {a})\");\n\n    rsx! {\n        document::Stylesheet { href: asset!(\"/src/ui.css\") }\n        div {\n            id: \"panel\",\n            class: \"catch-events\",\n            div {\n                id: \"title\",\n                h1 { \"Dioxus In Bevy Example\" }\n            }\n            div {\n                id: \"buttons\",\n                button {\n                    id: \"button-red\",\n                    class: \"color-button\",\n                    onclick: move |_| state.cube_color.set([1.0, 0.0, 0.0, 1.0]),\n                }\n                button {\n                    id: \"button-green\",\n                    class: \"color-button\",\n                    onclick: move |_| state.cube_color.set([0.0, 1.0, 0.0, 1.0]),\n                }\n                button {\n                    id: \"button-blue\",\n                    class: \"color-button\",\n                    onclick: move |_| state.cube_color.set([0.0, 0.0, 1.0, 1.0]),\n                }\n            }\n            div {\n                id: \"translation-speed-control\",\n                label { \"Translation Speed:\" }\n                input {\n                    r#type: \"number\",\n                    min: \"0.0\",\n                    max: \"10.0\",\n                    step: \"0.1\",\n                    value: \"{state.cube_translation_speed}\",\n                    oninput: move |event| {\n                        if let Ok(speed) = event.value().parse::<f32>() {\n                            state.cube_translation_speed.set(speed);\n                        }\n                    }\n                }\n            }\n            div {\n                id: \"rotation-speed-control\",\n                label { \"Rotation Speed:\" }\n                input {\n                    r#type: \"number\",\n                    min: \"0.0\",\n                    max: \"10.0\",\n                    step: \"0.1\",\n                    value: \"{state.cube_rotation_speed}\",\n                    oninput: move |event| {\n                        if let Ok(speed) = event.value().parse::<f32>() {\n                            state.cube_rotation_speed.set(speed);\n                        }\n                    }\n                }\n            }\n            div {\n                flex: \"0 0 150px\",\n                display: \"grid\",\n                align_items: \"center\",\n                justify_items: \"center\",\n                div {\n                    class: \"spin-box\",\n                    background: \"rgba({r}, {g}, {b}, {a}\",\n                }\n            }\n            div {\n                id: \"footer\",\n                p { \"Bevy framerate: {state.fps}\" }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "examples/10-integrations/pwa/Cargo.toml",
    "content": "[package]\nname = \"dioxus-pwa-example\"\nversion = \"0.1.0\"\nauthors = [\"Antonio Curavalea <one.kyonblack@gmail.com>\"]\nedition = \"2021\"\npublish = false\n\n# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html\n\n[dependencies]\ndioxus = { workspace = true, features = [\"web\"] }\n"
  },
  {
    "path": "examples/10-integrations/pwa/Dioxus.toml",
    "content": "[application]\n\n# App (Project) Name\nname = \"dioxus-pwa-example\"\n\n# Dioxus App Default Platform\n# desktop, web, mobile, ssr\ndefault_platform = \"web\"\n\n# `build` & `serve` dist path\nout_dir = \"dist\"\n\n# resource (public) file folder\nasset_dir = \"public\"\n\n[web.app]\n\n# HTML title tag content\ntitle = \"dioxus | ⛺\"\n\n[web.watcher]\n\n# when watcher trigger, regenerate the `index.html`\nreload_html = true\n\n# which files or dirs will be watcher monitoring\nwatch_path = [\"src\", \"public\"]\n"
  },
  {
    "path": "examples/10-integrations/pwa/LICENSE",
    "content": "MIT License\n\nCopyright (c) 2022 Dioxus\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "examples/10-integrations/pwa/README.md",
    "content": "# Dioxus PWA example\n\nThis is a basic example of a progressive web app (PWA) using Dioxus and Dioxus CLI.\nCurrently PWA functionality requires the use of a service worker and manifest file, so this isn't 100% Rust yet.\n\nIt is also very much usable as a template for your projects, if you're aiming to create a PWA.\n\n## Try the example\n\nMake sure you have Dioxus CLI installed (if you're unsure, run `cargo install dioxus-cli --locked`).\n\nYou can run `dx serve` in this directory to start the web server locally, or run\n`dx build --release` to build the project so you can deploy it on a separate web-server.\n\n## Project Structure\n\n```\n├── Cargo.toml\n├── Dioxus.toml\n├── index.html // Custom HTML is needed for this, to load the SW and manifest.\n├── LICENSE\n├── public\n│   ├── favicon.ico\n│   ├── logo_192.png\n│   ├── logo_512.png\n│   ├── manifest.json // The manifest file - edit this as you need to.\n│   └── sw.js // The service worker - you must edit this for actual projects.\n├── README.md\n└── src\n    └── main.rs\n```\n\n## Resources\n\nIf you're just getting started with PWAs, here are some useful resources:\n\n- [PWABuilder docs](https://docs.pwabuilder.com/#/)\n- [MDN article on PWAs](https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps)\n\nFor service worker scripting (in JavaScript):\n\n- [Service worker guide from PWABuilder](https://docs.pwabuilder.com/#/home/sw-intro)\n- [Service worker examples, also from PWABuilder](https://github.com/pwa-builder/pwabuilder-serviceworkers)\n\nIf you want to stay as close to 100% Rust as possible, you can try using [wasi-worker](https://github.com/dunnock/wasi-worker) to replace the JS service worker file. The JSON manifest will still be required though.\n"
  },
  {
    "path": "examples/10-integrations/pwa/index.html",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <title>{app_title}</title>\n    <script>\n      if (\"serviceWorker\" in navigator) {\n        navigator.serviceWorker.register(\"/{base_path}/assets/sw.js\");\n      }\n    </script>\n    <link rel=\"manifest\" href=\"/assets/manifest.json\" />\n    <meta content=\"text/html;charset=utf-8\" http-equiv=\"Content-Type\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <meta charset=\"UTF-8\" />\n  </head>\n  <body>\n    <div id=\"main\"></div>\n  </body>\n</html>\n"
  },
  {
    "path": "examples/10-integrations/pwa/public/manifest.json",
    "content": "{\n  \"name\": \"Dioxus\",\n  \"icons\": [\n    {\n      \"src\": \"logo_192.png\",\n      \"type\": \"image/png\",\n      \"sizes\": \"192x192\"\n    },\n    {\n      \"src\": \"logo_512.png\",\n      \"type\": \"image/png\",\n      \"sizes\": \"512x512\",\n      \"purpose\": \"any\"\n    },\n    {\n      \"src\": \"logo_512.png\",\n      \"type\": \"image/png\",\n      \"sizes\": \"any\",\n      \"purpose\": \"any\"\n    }\n  ],\n  \"start_url\": \"/\",\n  \"id\": \"/\",\n  \"display\": \"standalone\",\n  \"display_override\": [\"window-control-overlay\", \"standalone\"],\n  \"scope\": \"/\",\n  \"theme_color\": \"#000000\",\n  \"background_color\": \"#ffffff\",\n  \"short_name\": \"Dioxus\",\n  \"description\": \"Dioxus is a portable, performant, and ergonomic framework for building cross-platform user interfaces in Rust.\",\n  \"dir\": \"ltr\",\n  \"lang\": \"en\",\n  \"orientation\": \"portrait\"\n}\n"
  },
  {
    "path": "examples/10-integrations/pwa/public/sw.js",
    "content": "\"use strict\";\n\n//console.log('WORKER: executing.');\n\n/* A version number is useful when updating the worker logic,\n   allowing you to remove outdated cache entries during the update.\n*/\nvar version = 'v1.0.0::';\n\n/* These resources will be downloaded and cached by the service worker\n   during the installation process. If any resource fails to be downloaded,\n   then the service worker won't be installed either.\n*/\nvar offlineFundamentals = [\n  // add here the files you want to cache\n  'favicon.ico'\n];\n\n/* The install event fires when the service worker is first installed.\n   You can use this event to prepare the service worker to be able to serve\n   files while visitors are offline.\n*/\nself.addEventListener(\"install\", function (event) {\n  //console.log('WORKER: install event in progress.');\n  /* Using event.waitUntil(p) blocks the installation process on the provided\n     promise. If the promise is rejected, the service worker won't be installed.\n  */\n  event.waitUntil(\n    /* The caches built-in is a promise-based API that helps you cache responses,\n       as well as finding and deleting them.\n    */\n    caches\n      /* You can open a cache by name, and this method returns a promise. We use\n         a versioned cache name here so that we can remove old cache entries in\n         one fell swoop later, when phasing out an older service worker.\n      */\n      .open(version + 'fundamentals')\n      .then(function (cache) {\n        /* After the cache is opened, we can fill it with the offline fundamentals.\n           The method below will add all resources in `offlineFundamentals` to the\n           cache, after making requests for them.\n        */\n        return cache.addAll(offlineFundamentals);\n      })\n      .then(function () {\n        //console.log('WORKER: install completed');\n      })\n  );\n});\n\n/* The fetch event fires whenever a page controlled by this service worker requests\n   a resource. This isn't limited to `fetch` or even XMLHttpRequest. Instead, it\n   comprehends even the request for the HTML page on first load, as well as JS and\n   CSS resources, fonts, any images, etc.\n*/\nself.addEventListener(\"fetch\", function (event) {\n  //console.log('WORKER: fetch event in progress.');\n\n  /* We should only cache GET requests, and deal with the rest of method in the\n     client-side, by handling failed POST,PUT,PATCH,etc. requests.\n  */\n  if (event.request.method !== 'GET') {\n    /* If we don't block the event as shown below, then the request will go to\n       the network as usual.\n    */\n    //console.log('WORKER: fetch event ignored.', event.request.method, event.request.url);\n    return;\n  }\n  /* Similar to event.waitUntil in that it blocks the fetch event on a promise.\n     Fulfillment result will be used as the response, and rejection will end in a\n     HTTP response indicating failure.\n  */\n  event.respondWith(\n    caches\n      /* This method returns a promise that resolves to a cache entry matching\n         the request. Once the promise is settled, we can then provide a response\n         to the fetch request.\n      */\n      .match(event.request)\n      .then(function (cached) {\n        /* Even if the response is in our cache, we go to the network as well.\n           This pattern is known for producing \"eventually fresh\" responses,\n           where we return cached responses immediately, and meanwhile pull\n           a network response and store that in the cache.\n\n           Read more:\n           https://ponyfoo.com/articles/progressive-networking-serviceworker\n        */\n        var networked = fetch(event.request)\n          // We handle the network request with success and failure scenarios.\n          .then(fetchedFromNetwork, unableToResolve)\n          // We should catch errors on the fetchedFromNetwork handler as well.\n          .catch(unableToResolve);\n\n        /* We return the cached response immediately if there is one, and fall\n           back to waiting on the network as usual.\n        */\n        //console.log('WORKER: fetch event', cached ? '(cached)' : '(network)', event.request.url);\n        return cached || networked;\n\n        function fetchedFromNetwork(response) {\n          /* We copy the response before replying to the network request.\n             This is the response that will be stored on the ServiceWorker cache.\n          */\n          var cacheCopy = response.clone();\n\n          //console.log('WORKER: fetch response from network.', event.request.url);\n\n          caches\n            // We open a cache to store the response for this request.\n            .open(version + 'pages')\n            .then(function add(cache) {\n              /* We store the response for this request. It'll later become\n                 available to caches.match(event.request) calls, when looking\n                 for cached responses.\n              */\n              cache.put(event.request, cacheCopy);\n            })\n            .then(function () {\n              //console.log('WORKER: fetch response stored in cache.', event.request.url);\n            });\n\n          // Return the response so that the promise is settled in fulfillment.\n          return response;\n        }\n\n        /* When this method is called, it means we were unable to produce a response\n           from either the cache or the network. This is our opportunity to produce\n           a meaningful response even when all else fails. It's the last chance, so\n           you probably want to display a \"Service Unavailable\" view or a generic\n           error response.\n        */\n        function unableToResolve() {\n          /* There's a couple of things we can do here.\n             - Test the Accept header and then return one of the `offlineFundamentals`\n               e.g: `return caches.match('/some/cached/image.png')`\n             - You should also consider the origin. It's easier to decide what\n               \"unavailable\" means for requests against your origins than for requests\n               against a third party, such as an ad provider.\n             - Generate a Response programmatically, as shown below, and return that.\n          */\n\n          //console.log('WORKER: fetch request failed in both cache and network.');\n\n          /* Here we're creating a response programmatically. The first parameter is the\n             response body, and the second one defines the options for the response.\n          */\n          return new Response('<h1>Service Unavailable</h1>', {\n            status: 503,\n            statusText: 'Service Unavailable',\n            headers: new Headers({\n              'Content-Type': 'text/html'\n            })\n          });\n        }\n      })\n  );\n});\n\n/* The activate event fires after a service worker has been successfully installed.\n   It is most useful when phasing out an older version of a service worker, as at\n   this point you know that the new worker was installed correctly. In this example,\n   we delete old caches that don't match the version in the worker we just finished\n   installing.\n*/\nself.addEventListener(\"activate\", function (event) {\n  /* Just like with the install event, event.waitUntil blocks activate on a promise.\n     Activation will fail unless the promise is fulfilled.\n  */\n  //console.log('WORKER: activate event in progress.');\n\n  event.waitUntil(\n    caches\n      /* This method returns a promise which will resolve to an array of available\n         cache keys.\n      */\n      .keys()\n      .then(function (keys) {\n        // We return a promise that settles when all outdated caches are deleted.\n        return Promise.all(\n          keys\n            .filter(function (key) {\n              // Filter by keys that don't start with the latest version prefix.\n              return !key.startsWith(version);\n            })\n            .map(function (key) {\n              /* Return a promise that's fulfilled\n                 when each outdated cache is deleted.\n              */\n              return caches.delete(key);\n            })\n        );\n      })\n      .then(function () {\n        //console.log('WORKER: activate completed.');\n      })\n  );\n});\n"
  },
  {
    "path": "examples/10-integrations/pwa/src/main.rs",
    "content": "use dioxus::prelude::*;\n\nfn main() {\n    dioxus::launch(app);\n}\n\nfn app() -> Element {\n    rsx! (\n        div { style: \"text-align: center;\",\n            h1 { \"🌗 Dioxus 🚀\" }\n            h3 { \"Frontend that scales.\" }\n            p { \"Build web, desktop, and mobile apps with Dioxus\" }\n        }\n    )\n}\n"
  },
  {
    "path": "examples/10-integrations/tailwind/.gitignore",
    "content": "dist\n"
  },
  {
    "path": "examples/10-integrations/tailwind/Cargo.toml",
    "content": "[package]\nname = \"dioxus-tailwind\"\nversion = \"0.0.0\"\nauthors = []\nedition = \"2021\"\ndescription = \"A tailwindcss example using Dioxus\"\nlicense = \"MIT OR Apache-2.0\"\nrepository = \"https://github.com/DioxusLabs/dioxus/\"\nhomepage = \"https://dioxuslabs.com\"\ndocumentation = \"https://dioxuslabs.com\"\npublish = false\n\n[dependencies]\nmanganis = { workspace = true }\ndioxus = { workspace = true }\n\n[features]\ndefault = [\"desktop\"]\nweb = [\"dioxus/web\"]\ndesktop = [\"dioxus/desktop\"]\n"
  },
  {
    "path": "examples/10-integrations/tailwind/README.md",
    "content": "Example: Basic Tailwind usage\n\nThis example shows how an app might be styled with TailwindCSS.\n\n## Running\n\nOur [Tailwind](https://dioxuslabs.com/learn/0.7/guides/utilities/tailwind) guide explains how to setup and run Dioxus-Tailwind projects.\n\nNote that in Dioxus 0.7, the Tailwind watcher is initialized automatically if a `tailwind.css` file is find in your app's root.\n"
  },
  {
    "path": "examples/10-integrations/tailwind/assets/tailwind.css",
    "content": "/*! tailwindcss v4.1.0 | MIT License | https://tailwindcss.com */\n@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))) {\n  @layer base {\n    *, ::before, ::after, ::backdrop {\n      --tw-border-style: solid;\n      --tw-leading: initial;\n      --tw-font-weight: initial;\n    }\n  }\n}\n@layer theme, base, components, utilities;\n@layer theme {\n  :root, :host {\n    --font-sans: ui-sans-serif, system-ui, sans-serif, \"Apple Color Emoji\",\n      \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\";\n    --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\",\n      \"Courier New\", monospace;\n    --color-indigo-500: oklch(58.5% 0.233 277.117);\n    --color-indigo-600: oklch(51.1% 0.262 276.966);\n    --color-gray-400: oklch(70.7% 0.022 261.325);\n    --color-gray-700: oklch(37.3% 0.034 259.733);\n    --color-gray-800: oklch(27.8% 0.033 256.848);\n    --color-gray-900: oklch(21% 0.034 264.665);\n    --color-white: #fff;\n    --spacing: 0.25rem;\n    --container-lg: 32rem;\n    --text-base: 1rem;\n    --text-base--line-height: calc(1.5 / 1);\n    --text-lg: 1.125rem;\n    --text-lg--line-height: calc(1.75 / 1.125);\n    --text-xl: 1.25rem;\n    --text-xl--line-height: calc(1.75 / 1.25);\n    --text-3xl: 1.875rem;\n    --text-3xl--line-height: calc(2.25 / 1.875);\n    --text-4xl: 2.25rem;\n    --text-4xl--line-height: calc(2.5 / 2.25);\n    --font-weight-medium: 500;\n    --leading-relaxed: 1.625;\n    --radius-sm: 0.25rem;\n    --default-font-family: var(--font-sans);\n    --default-mono-font-family: var(--font-mono);\n  }\n}\n@layer base {\n  *, ::after, ::before, ::backdrop, ::file-selector-button {\n    box-sizing: border-box;\n    margin: 0;\n    padding: 0;\n    border: 0 solid;\n  }\n  html, :host {\n    line-height: 1.5;\n    -webkit-text-size-adjust: 100%;\n    tab-size: 4;\n    font-family: var(--default-font-family, ui-sans-serif, system-ui, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\");\n    font-feature-settings: var(--default-font-feature-settings, normal);\n    font-variation-settings: var(--default-font-variation-settings, normal);\n    -webkit-tap-highlight-color: transparent;\n  }\n  hr {\n    height: 0;\n    color: inherit;\n    border-top-width: 1px;\n  }\n  abbr:where([title]) {\n    -webkit-text-decoration: underline dotted;\n    text-decoration: underline dotted;\n  }\n  h1, h2, h3, h4, h5, h6 {\n    font-size: inherit;\n    font-weight: inherit;\n  }\n  a {\n    color: inherit;\n    -webkit-text-decoration: inherit;\n    text-decoration: inherit;\n  }\n  b, strong {\n    font-weight: bolder;\n  }\n  code, kbd, samp, pre {\n    font-family: var(--default-mono-font-family, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace);\n    font-feature-settings: var(--default-mono-font-feature-settings, normal);\n    font-variation-settings: var(--default-mono-font-variation-settings, normal);\n    font-size: 1em;\n  }\n  small {\n    font-size: 80%;\n  }\n  sub, sup {\n    font-size: 75%;\n    line-height: 0;\n    position: relative;\n    vertical-align: baseline;\n  }\n  sub {\n    bottom: -0.25em;\n  }\n  sup {\n    top: -0.5em;\n  }\n  table {\n    text-indent: 0;\n    border-color: inherit;\n    border-collapse: collapse;\n  }\n  :-moz-focusring {\n    outline: auto;\n  }\n  progress {\n    vertical-align: baseline;\n  }\n  summary {\n    display: list-item;\n  }\n  ol, ul, menu {\n    list-style: none;\n  }\n  img, svg, video, canvas, audio, iframe, embed, object {\n    display: block;\n    vertical-align: middle;\n  }\n  img, video {\n    max-width: 100%;\n    height: auto;\n  }\n  button, input, select, optgroup, textarea, ::file-selector-button {\n    font: inherit;\n    font-feature-settings: inherit;\n    font-variation-settings: inherit;\n    letter-spacing: inherit;\n    color: inherit;\n    border-radius: 0;\n    background-color: transparent;\n    opacity: 1;\n  }\n  :where(select:is([multiple], [size])) optgroup {\n    font-weight: bolder;\n  }\n  :where(select:is([multiple], [size])) optgroup option {\n    padding-inline-start: 20px;\n  }\n  ::file-selector-button {\n    margin-inline-end: 4px;\n  }\n  ::placeholder {\n    opacity: 1;\n  }\n  @supports (not (-webkit-appearance: -apple-pay-button))  or (contain-intrinsic-size: 1px) {\n    ::placeholder {\n      color: color-mix(in oklab, currentColor 50%, transparent);\n    }\n  }\n  textarea {\n    resize: vertical;\n  }\n  ::-webkit-search-decoration {\n    -webkit-appearance: none;\n  }\n  ::-webkit-date-and-time-value {\n    min-height: 1lh;\n    text-align: inherit;\n  }\n  ::-webkit-datetime-edit {\n    display: inline-flex;\n  }\n  ::-webkit-datetime-edit-fields-wrapper {\n    padding: 0;\n  }\n  ::-webkit-datetime-edit, ::-webkit-datetime-edit-year-field, ::-webkit-datetime-edit-month-field, ::-webkit-datetime-edit-day-field, ::-webkit-datetime-edit-hour-field, ::-webkit-datetime-edit-minute-field, ::-webkit-datetime-edit-second-field, ::-webkit-datetime-edit-millisecond-field, ::-webkit-datetime-edit-meridiem-field {\n    padding-block: 0;\n  }\n  :-moz-ui-invalid {\n    box-shadow: none;\n  }\n  button, input:where([type=\"button\"], [type=\"reset\"], [type=\"submit\"]), ::file-selector-button {\n    appearance: button;\n  }\n  ::-webkit-inner-spin-button, ::-webkit-outer-spin-button {\n    height: auto;\n  }\n  [hidden]:where(:not([hidden=\"until-found\"])) {\n    display: none !important;\n  }\n}\n@layer utilities {\n  .container {\n    width: 100%;\n    @media (width >= 40rem) {\n      max-width: 40rem;\n    }\n    @media (width >= 48rem) {\n      max-width: 48rem;\n    }\n    @media (width >= 64rem) {\n      max-width: 64rem;\n    }\n    @media (width >= 80rem) {\n      max-width: 80rem;\n    }\n    @media (width >= 96rem) {\n      max-width: 96rem;\n    }\n  }\n  .mx-auto {\n    margin-inline: auto;\n  }\n  .mt-4 {\n    margin-top: calc(var(--spacing) * 4);\n  }\n  .mr-5 {\n    margin-right: calc(var(--spacing) * 5);\n  }\n  .mb-4 {\n    margin-bottom: calc(var(--spacing) * 4);\n  }\n  .mb-8 {\n    margin-bottom: calc(var(--spacing) * 8);\n  }\n  .mb-16 {\n    margin-bottom: calc(var(--spacing) * 16);\n  }\n  .ml-1 {\n    margin-left: calc(var(--spacing) * 1);\n  }\n  .ml-3 {\n    margin-left: calc(var(--spacing) * 3);\n  }\n  .ml-4 {\n    margin-left: calc(var(--spacing) * 4);\n  }\n  .flex {\n    display: flex;\n  }\n  .hidden {\n    display: none;\n  }\n  .inline-flex {\n    display: inline-flex;\n  }\n  .h-4 {\n    height: calc(var(--spacing) * 4);\n  }\n  .h-10 {\n    height: calc(var(--spacing) * 10);\n  }\n  .w-4 {\n    width: calc(var(--spacing) * 4);\n  }\n  .w-5\\/6 {\n    width: calc(5/6 * 100%);\n  }\n  .w-10 {\n    width: calc(var(--spacing) * 10);\n  }\n  .flex-col {\n    flex-direction: column;\n  }\n  .flex-wrap {\n    flex-wrap: wrap;\n  }\n  .items-center {\n    align-items: center;\n  }\n  .justify-center {\n    justify-content: center;\n  }\n  .rounded-full {\n    border-radius: calc(infinity * 1px);\n  }\n  .rounded-sm {\n    border-radius: var(--radius-sm);\n  }\n  .border-0 {\n    border-style: var(--tw-border-style);\n    border-width: 0px;\n  }\n  .bg-gray-800 {\n    background-color: var(--color-gray-800);\n  }\n  .bg-gray-900 {\n    background-color: var(--color-gray-900);\n  }\n  .bg-indigo-500 {\n    background-color: var(--color-indigo-500);\n  }\n  .object-cover {\n    object-fit: cover;\n  }\n  .object-center {\n    object-position: center;\n  }\n  .p-2 {\n    padding: calc(var(--spacing) * 2);\n  }\n  .p-5 {\n    padding: calc(var(--spacing) * 5);\n  }\n  .px-3 {\n    padding-inline: calc(var(--spacing) * 3);\n  }\n  .px-5 {\n    padding-inline: calc(var(--spacing) * 5);\n  }\n  .px-6 {\n    padding-inline: calc(var(--spacing) * 6);\n  }\n  .py-1 {\n    padding-block: calc(var(--spacing) * 1);\n  }\n  .py-2 {\n    padding-block: calc(var(--spacing) * 2);\n  }\n  .py-24 {\n    padding-block: calc(var(--spacing) * 24);\n  }\n  .text-center {\n    text-align: center;\n  }\n  .text-3xl {\n    font-size: var(--text-3xl);\n    line-height: var(--tw-leading, var(--text-3xl--line-height));\n  }\n  .text-base {\n    font-size: var(--text-base);\n    line-height: var(--tw-leading, var(--text-base--line-height));\n  }\n  .text-lg {\n    font-size: var(--text-lg);\n    line-height: var(--tw-leading, var(--text-lg--line-height));\n  }\n  .text-xl {\n    font-size: var(--text-xl);\n    line-height: var(--tw-leading, var(--text-xl--line-height));\n  }\n  .leading-relaxed {\n    --tw-leading: var(--leading-relaxed);\n    line-height: var(--leading-relaxed);\n  }\n  .font-medium {\n    --tw-font-weight: var(--font-weight-medium);\n    font-weight: var(--font-weight-medium);\n  }\n  .text-gray-400 {\n    color: var(--color-gray-400);\n  }\n  .text-white {\n    color: var(--color-white);\n  }\n  .hover\\:bg-gray-700 {\n    &:hover {\n      @media (hover: hover) {\n        background-color: var(--color-gray-700);\n      }\n    }\n  }\n  .hover\\:bg-indigo-600 {\n    &:hover {\n      @media (hover: hover) {\n        background-color: var(--color-indigo-600);\n      }\n    }\n  }\n  .hover\\:text-white {\n    &:hover {\n      @media (hover: hover) {\n        color: var(--color-white);\n      }\n    }\n  }\n  .focus\\:outline-hidden {\n    &:focus {\n      --tw-outline-style: none;\n      outline-style: none;\n      @media (forced-colors: active) {\n        outline: 2px solid transparent;\n        outline-offset: 2px;\n      }\n    }\n  }\n  .sm\\:text-4xl {\n    @media (width >= 40rem) {\n      font-size: var(--text-4xl);\n      line-height: var(--tw-leading, var(--text-4xl--line-height));\n    }\n  }\n  .md\\:mt-0 {\n    @media (width >= 48rem) {\n      margin-top: calc(var(--spacing) * 0);\n    }\n  }\n  .md\\:mb-0 {\n    @media (width >= 48rem) {\n      margin-bottom: calc(var(--spacing) * 0);\n    }\n  }\n  .md\\:ml-auto {\n    @media (width >= 48rem) {\n      margin-left: auto;\n    }\n  }\n  .md\\:w-1\\/2 {\n    @media (width >= 48rem) {\n      width: calc(1/2 * 100%);\n    }\n  }\n  .md\\:flex-row {\n    @media (width >= 48rem) {\n      flex-direction: row;\n    }\n  }\n  .md\\:items-start {\n    @media (width >= 48rem) {\n      align-items: flex-start;\n    }\n  }\n  .md\\:pr-16 {\n    @media (width >= 48rem) {\n      padding-right: calc(var(--spacing) * 16);\n    }\n  }\n  .md\\:text-left {\n    @media (width >= 48rem) {\n      text-align: left;\n    }\n  }\n  .lg\\:inline-block {\n    @media (width >= 64rem) {\n      display: inline-block;\n    }\n  }\n  .lg\\:w-full {\n    @media (width >= 64rem) {\n      width: 100%;\n    }\n  }\n  .lg\\:max-w-lg {\n    @media (width >= 64rem) {\n      max-width: var(--container-lg);\n    }\n  }\n  .lg\\:grow {\n    @media (width >= 64rem) {\n      flex-grow: 1;\n    }\n  }\n  .lg\\:pr-24 {\n    @media (width >= 64rem) {\n      padding-right: calc(var(--spacing) * 24);\n    }\n  }\n}\n@property --tw-border-style {\n  syntax: \"*\";\n  inherits: false;\n  initial-value: solid;\n}\n@property --tw-leading {\n  syntax: \"*\";\n  inherits: false;\n}\n@property --tw-font-weight {\n  syntax: \"*\";\n  inherits: false;\n}\n"
  },
  {
    "path": "examples/10-integrations/tailwind/src/main.rs",
    "content": "use dioxus::prelude::*;\n\nfn main() {\n    dioxus::launch(app);\n}\n\nfn app() -> Element {\n    let grey_background = true;\n\n    rsx! {\n        Stylesheet { href: asset!(\"/assets/tailwind.css\") }\n        div {\n            header {\n                class: \"text-gray-400 body-font\",\n                // you can use optional attributes to optionally apply a tailwind class\n                class: if grey_background { \"bg-gray-900\" },\n                div { class: \"container mx-auto flex flex-wrap p-5 flex-col md:flex-row items-center\",\n                    a { class: \"flex title-font font-medium items-center text-white mb-4 md:mb-0\",\n                        StacksIcon {}\n                        span { class: \"ml-3 text-xl\", \"Hello Dioxus!\" }\n                    }\n                    nav { class: \"md:ml-auto flex flex-wrap items-center text-base justify-center\",\n                        a { class: \"mr-5 hover:text-white\", \"First Link\" }\n                        a { class: \"mr-5 hover:text-white\", \"Second Link\" }\n                        a { class: \"mr-5 hover:text-white\", \"Third Link\" }\n                        a { class: \"mr-5 hover:text-white\", \"Fourth Link\" }\n                    }\n                    button { class: \"inline-flex items-center bg-gray-800 border-0 py-1 px-3 focus:outline-hidden hover:bg-gray-700 rounded-sm text-base mt-4 md:mt-0\",\n                        \"Button\"\n                        RightArrowIcon {}\n                    }\n                }\n            }\n\n            section { class: \"text-gray-400 bg-gray-900 body-font\",\n                div { class: \"container mx-auto flex px-5 py-24 md:flex-row flex-col items-center\",\n                    div { class: \"lg:grow md:w-1/2 lg:pr-24 md:pr-16 flex flex-col md:items-start md:text-left mb-16 md:mb-0 items-center text-center\",\n                        h1 { class: \"title-font sm:text-4xl text-3xl mb-4 font-medium text-white\",\n                            br { class: \"hidden lg:inline-block\" }\n                            \"Dioxus Sneak Peek\"\n                        }\n                        p { class: \"mb-8 leading-relaxed\",\n\n                            \"Dioxus is a new UI framework that makes it easy and simple to write cross-platform apps using web\n                            technologies! It is functional, fast, and portable. Dioxus can run on the web, on the desktop, and\n                            on mobile and embedded platforms.\"\n                        }\n                        div { class: \"flex justify-center\",\n                            button { class: \"inline-flex text-white bg-indigo-500 border-0 py-2 px-6 focus:outline-hidden hover:bg-indigo-600 rounded-sm text-lg\",\n                                \"Learn more\"\n                            }\n                            button { class: \"ml-4 inline-flex text-gray-400 bg-gray-800 border-0 py-2 px-6 focus:outline-hidden hover:bg-gray-700 hover:text-white rounded-sm text-lg\",\n                                \"Build an app\"\n                            }\n                        }\n                    }\n                    div { class: \"lg:max-w-lg lg:w-full md:w-1/2 w-5/6\",\n                        img {\n                            class: \"object-cover object-center rounded-sm\",\n                            src: \"https://i.imgur.com/oK6BLtw.png\",\n                            referrerpolicy: \"no-referrer\",\n                            alt: \"hero\",\n                        }\n                    }\n                }\n            }\n        }\n    }\n}\n\n#[component]\npub fn StacksIcon() -> Element {\n    rsx! {\n        svg {\n            fill: \"none\",\n            stroke: \"currentColor\",\n            stroke_linecap: \"round\",\n            stroke_linejoin: \"round\",\n            stroke_width: \"2\",\n            class: \"w-10 h-10 text-white p-2 bg-indigo-500 rounded-full\",\n            view_box: \"0 0 24 24\",\n            path { d: \"M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5\" }\n        }\n    }\n}\n\n#[component]\npub fn RightArrowIcon() -> Element {\n    rsx! {\n        svg {\n            fill: \"none\",\n            stroke: \"currentColor\",\n            stroke_linecap: \"round\",\n            stroke_linejoin: \"round\",\n            stroke_width: \"2\",\n            class: \"w-4 h-4 ml-1\",\n            view_box: \"0 0 24 24\",\n            path { d: \"M5 12h14M12 5l7 7-7 7\" }\n        }\n    }\n}\n"
  },
  {
    "path": "examples/10-integrations/tailwind/tailwind.css",
    "content": "@import \"tailwindcss\";\n@source \"./src/**/*.{rs,html,css}\";\n"
  },
  {
    "path": "examples/10-integrations/wgpu-texture/Cargo.toml",
    "content": "[package]\nname = \"wgpu-texture\"\nversion = \"0.0.0\"\nedition = \"2021\"\nlicense = \"MIT\"\npublish = false\n\n[features]\ndefault = [\"desktop\"]\ndesktop = [\"dioxus/desktop\"]\nnative = [\"dioxus/native\"]\ntracing = [\"dep:tracing-subscriber\", \"dioxus-native/tracing\"]\n\n[dependencies]\ndioxus-native = { workspace = true }\ndioxus = { workspace = true }\nwgpu = { workspace = true }\nwinit = \"0.30\"\nbytemuck = \"1\"\ncolor = \"0.3\"\ntracing-subscriber = { workspace = true, optional = true }\n"
  },
  {
    "path": "examples/10-integrations/wgpu-texture/src/demo_renderer.rs",
    "content": "// Copyright © SixtyFPS GmbH <info@slint.dev>\n// SPDX-License-Identifier: MIT\nuse crate::Color;\nuse dioxus_native::{CustomPaintCtx, CustomPaintSource, DeviceHandle, TextureHandle};\nuse std::sync::mpsc::{channel, Receiver, Sender};\nuse std::time::Instant;\nuse wgpu::{\n    CommandEncoderDescriptor, Device, Extent3d, FragmentState, LoadOp, MultisampleState,\n    Operations, PipelineLayoutDescriptor, PrimitiveState, PushConstantRange, Queue,\n    RenderPassColorAttachment, RenderPassDescriptor, RenderPipeline, RenderPipelineDescriptor,\n    ShaderModuleDescriptor, ShaderSource, ShaderStages, StoreOp, Texture, TextureDescriptor,\n    TextureDimension, TextureFormat, TextureUsages, TextureViewDescriptor, VertexState,\n};\n\npub struct DemoPaintSource {\n    state: DemoRendererState,\n    start_time: std::time::Instant,\n    tx: Sender<DemoMessage>,\n    rx: Receiver<DemoMessage>,\n    color: Color,\n}\n\nimpl CustomPaintSource for DemoPaintSource {\n    fn resume(&mut self, device_handle: &DeviceHandle) {\n        // Extract device and queue from device_handle\n        let device = &device_handle.device;\n        let queue = &device_handle.queue;\n        let active_state = ActiveDemoRenderer::new(device, queue);\n        self.state = DemoRendererState::Active(Box::new(active_state));\n    }\n\n    fn suspend(&mut self) {\n        self.state = DemoRendererState::Suspended;\n    }\n\n    fn render(\n        &mut self,\n        ctx: CustomPaintCtx<'_>,\n        width: u32,\n        height: u32,\n        _scale: f64,\n    ) -> Option<TextureHandle> {\n        self.process_messages();\n        self.render(ctx, width, height)\n    }\n}\n\npub enum DemoMessage {\n    // Color in RGB format\n    SetColor(Color),\n}\n\nenum DemoRendererState {\n    Active(Box<ActiveDemoRenderer>),\n    Suspended,\n}\n\n#[derive(Clone)]\nstruct TextureAndHandle {\n    texture: Texture,\n    handle: TextureHandle,\n}\n\nstruct ActiveDemoRenderer {\n    device: Device,\n    queue: Queue,\n    pipeline: RenderPipeline,\n    displayed_texture: Option<TextureAndHandle>,\n    next_texture: Option<TextureAndHandle>,\n}\n\nimpl DemoPaintSource {\n    pub fn new() -> Self {\n        let (tx, rx) = channel();\n        Self::with_channel(tx, rx)\n    }\n\n    pub fn with_channel(tx: Sender<DemoMessage>, rx: Receiver<DemoMessage>) -> Self {\n        Self {\n            state: DemoRendererState::Suspended,\n            start_time: std::time::Instant::now(),\n            tx,\n            rx,\n            color: Color::WHITE,\n        }\n    }\n\n    pub fn sender(&self) -> Sender<DemoMessage> {\n        self.tx.clone()\n    }\n\n    fn process_messages(&mut self) {\n        loop {\n            match self.rx.try_recv() {\n                Err(_) => return,\n                Ok(msg) => match msg {\n                    DemoMessage::SetColor(color) => self.color = color,\n                },\n            }\n        }\n    }\n\n    fn render(\n        &mut self,\n        ctx: CustomPaintCtx<'_>,\n        width: u32,\n        height: u32,\n    ) -> Option<TextureHandle> {\n        if width == 0 || height == 0 {\n            return None;\n        }\n        let DemoRendererState::Active(state) = &mut self.state else {\n            return None;\n        };\n\n        state.render(ctx, self.color.components, width, height, &self.start_time)\n    }\n}\n\nimpl ActiveDemoRenderer {\n    pub(crate) fn new(device: &Device, queue: &Queue) -> Self {\n        let shader = device.create_shader_module(ShaderModuleDescriptor {\n            label: None,\n            source: ShaderSource::Wgsl(std::borrow::Cow::Borrowed(include_str!(\"shader.wgsl\"))),\n        });\n\n        let pipeline_layout = device.create_pipeline_layout(&PipelineLayoutDescriptor {\n            label: None,\n            bind_group_layouts: &[],\n            push_constant_ranges: &[PushConstantRange {\n                stages: ShaderStages::FRAGMENT,\n                range: 0..16, // full size in bytes, aligned\n            }],\n        });\n\n        let pipeline = device.create_render_pipeline(&RenderPipelineDescriptor {\n            label: None,\n            layout: Some(&pipeline_layout),\n            vertex: VertexState {\n                module: &shader,\n                entry_point: Some(\"vs_main\"),\n                buffers: &[],\n                compilation_options: Default::default(),\n            },\n            fragment: Some(FragmentState {\n                module: &shader,\n                entry_point: Some(\"fs_main\"),\n                compilation_options: Default::default(),\n                targets: &[Some(TextureFormat::Rgba8Unorm.into())],\n            }),\n            primitive: PrimitiveState::default(),\n            depth_stencil: None,\n            multisample: MultisampleState::default(),\n            multiview: None,\n            cache: None,\n        });\n\n        Self {\n            device: device.clone(),\n            queue: queue.clone(),\n            pipeline,\n            displayed_texture: None,\n            next_texture: None,\n        }\n    }\n\n    pub(crate) fn render(\n        &mut self,\n        mut ctx: CustomPaintCtx<'_>,\n        light: [f32; 3],\n        width: u32,\n        height: u32,\n        start_time: &Instant,\n    ) -> Option<TextureHandle> {\n        // If \"next texture\" size doesn't match specified size then unregister and drop texture\n        if let Some(next) = &self.next_texture {\n            if next.texture.width() != width || next.texture.height() != height {\n                ctx.unregister_texture(self.next_texture.take().unwrap().handle);\n            }\n        }\n\n        // If there is no \"next texture\" then create one and register it.\n        let texture_and_handle = match &self.next_texture {\n            Some(next) => next,\n            None => {\n                let texture = create_texture(&self.device, width, height);\n                let handle = ctx.register_texture(texture.clone());\n                self.next_texture = Some(TextureAndHandle { texture, handle });\n                self.next_texture.as_ref().unwrap()\n            }\n        };\n\n        let next_texture = &texture_and_handle.texture;\n        let next_texture_handle = texture_and_handle.handle.clone();\n\n        let elapsed: f32 = start_time.elapsed().as_millis() as f32 / 500.;\n        let [light_red, light_green, light_blue] = light;\n        let push_constants = PushConstants {\n            light_color_and_time: [light_red, light_green, light_blue, elapsed],\n        };\n\n        let mut encoder = self\n            .device\n            .create_command_encoder(&CommandEncoderDescriptor { label: None });\n        {\n            let mut rpass = encoder.begin_render_pass(&RenderPassDescriptor {\n                label: None,\n                color_attachments: &[Some(RenderPassColorAttachment {\n                    view: &next_texture.create_view(&TextureViewDescriptor::default()),\n                    resolve_target: None,\n                    ops: Operations {\n                        load: LoadOp::Clear(wgpu::Color::GREEN),\n                        store: StoreOp::Store,\n                    },\n                    depth_slice: None,\n                })],\n                depth_stencil_attachment: None,\n                timestamp_writes: None,\n                occlusion_query_set: None,\n            });\n            rpass.set_pipeline(&self.pipeline);\n            rpass.set_push_constants(\n                ShaderStages::FRAGMENT, // Stage (your constants are for fragment shader)\n                0,                      // Offset in bytes (start at 0)\n                bytemuck::bytes_of(&push_constants),\n            );\n            rpass.draw(0..3, 0..1);\n        }\n\n        self.queue.submit(Some(encoder.finish()));\n\n        std::mem::swap(&mut self.next_texture, &mut self.displayed_texture);\n        Some(next_texture_handle)\n    }\n}\n\n#[repr(C)]\n#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]\nstruct PushConstants {\n    light_color_and_time: [f32; 4],\n}\n\nfn create_texture(device: &Device, width: u32, height: u32) -> Texture {\n    device.create_texture(&TextureDescriptor {\n        label: None,\n        size: Extent3d {\n            width,\n            height,\n            depth_or_array_layers: 1,\n        },\n        mip_level_count: 1,\n        sample_count: 1,\n        dimension: TextureDimension::D2,\n        format: TextureFormat::Rgba8Unorm,\n        usage: TextureUsages::RENDER_ATTACHMENT\n            | TextureUsages::TEXTURE_BINDING\n            | TextureUsages::COPY_SRC,\n        view_formats: &[],\n    })\n}\n"
  },
  {
    "path": "examples/10-integrations/wgpu-texture/src/main.rs",
    "content": "use color::{palette::css::WHITE, parse_color};\nuse color::{OpaqueColor, Srgb};\nuse demo_renderer::{DemoMessage, DemoPaintSource};\nuse dioxus::prelude::*;\nuse dioxus_native::use_wgpu;\nuse std::any::Any;\nuse wgpu::{Features, Limits};\nuse winit::dpi::LogicalSize;\nuse winit::window::WindowAttributes;\n\nmod demo_renderer;\n\n// CSS Styles\nstatic STYLES: Asset = asset!(\"/src/styles.css\");\n\n// WGPU settings required by this example\nconst FEATURES: Features = Features::PUSH_CONSTANTS;\nfn limits() -> Limits {\n    Limits {\n        max_push_constant_size: 16,\n        ..Limits::default()\n    }\n}\nfn window_attributes() -> WindowAttributes {\n    // You can also use a `<title>` element to set the window title\n    // but this demonstrates the use of `WindowAttributes`\n    WindowAttributes::default()\n        .with_title(\"WGPU Example\")\n        .with_inner_size(LogicalSize::new(800, 600))\n}\n\ntype Color = OpaqueColor<Srgb>;\n\nfn main() {\n    #[cfg(feature = \"tracing\")]\n    tracing_subscriber::fmt::init();\n\n    let config: Vec<Box<dyn Any>> = vec![\n        Box::new(FEATURES),\n        Box::new(limits()),\n        Box::new(window_attributes()),\n    ];\n    dioxus_native::launch_cfg(app, Vec::new(), config);\n}\n\nfn app() -> Element {\n    let mut show_cube = use_signal(|| true);\n\n    let color_str = use_signal(|| String::from(\"red\"));\n    let color = use_memo(move || {\n        parse_color(&color_str())\n            .map(|c| c.to_alpha_color())\n            .unwrap_or(WHITE)\n            .split()\n            .0\n    });\n\n    use_effect(move || println!(\"{:?}\", color().components));\n\n    rsx!(\n        Stylesheet { href: STYLES }\n        div { id:\"overlay\",\n            h2 { \"Control Panel\" },\n            button {\n                onclick: move |_| show_cube.toggle(),\n                if show_cube() {\n                    \"Hide cube\"\n                } else {\n                    \"Show cube\"\n                }\n            }\n            br {}\n            ColorControl { label: \"Color:\", color_str },\n            p { \"This overlay demonstrates that the custom WGPU content can be rendered beneath layers of HTML content\" }\n        }\n        div { id:\"underlay\",\n            h2 { \"Underlay\" },\n            p { \"This underlay demonstrates that the custom WGPU content can be rendered above layers and blended with the content underneath\" }\n        }\n        header {\n            h1 { \"Blitz WGPU Demo\" }\n        }\n        if show_cube() {\n            SpinningCube { color }\n        }\n    )\n}\n\n#[component]\nfn ColorControl(label: &'static str, color_str: WriteSignal<String>) -> Element {\n    rsx!(div {\n        class: \"color-control\",\n        { label },\n        input {\n            value: color_str(),\n            oninput: move |evt| {\n                *color_str.write() = evt.value()\n            }\n        }\n    })\n}\n\n#[component]\nfn SpinningCube(color: Memo<Color>) -> Element {\n    // Create custom paint source and register it with the renderer\n    let paint_source = DemoPaintSource::new();\n    let sender = paint_source.sender();\n    let paint_source_id = use_wgpu(move || paint_source);\n\n    use_effect(move || {\n        sender.send(DemoMessage::SetColor(color())).unwrap();\n    });\n\n    rsx!(\n        div { id:\"canvas-container\",\n            canvas {\n                id: \"demo-canvas\",\n                \"src\": paint_source_id\n            }\n        }\n    )\n}\n"
  },
  {
    "path": "examples/10-integrations/wgpu-texture/src/shader.wgsl",
    "content": "// Copyright © SixtyFPS GmbH <info@slint.dev>\n// SPDX-License-Identifier: MIT\n\nstruct VertexOutput {\n    @builtin(position) position: vec4<f32>,\n    @location(0) frag_position: vec2<f32>,\n};\n\n@vertex\nfn vs_main(\n    @builtin(vertex_index) vertex_index: u32\n) -> VertexOutput {\n    var output: VertexOutput;\n\n    var positions = array<vec2<f32>, 3>(\n        vec2<f32>(-1.0,  3.0),\n        vec2<f32>(-1.0, -1.0),\n        vec2<f32>( 3.0, -1.0)\n    );\n\n    let pos = positions[vertex_index];\n    output.position = vec4<f32>(pos.x, -pos.y, 0.0, 1.0);\n    output.frag_position = pos;\n    return output;\n}\n\nstruct PushConstants {\n    light_color_and_time: vec4<f32>,\n};\n\nvar<push_constant> pc: PushConstants;\n\nfn sdRoundBox(p: vec3<f32>, b: vec3<f32>, r: f32) -> f32 {\n    let q = abs(p) - b;\n    return length(max(q, vec3<f32>(0.0))) + min(max(q.x, max(q.y, q.z)), 0.0) - r;\n}\n\nfn rotateY(r: vec3<f32>, angle: f32) -> vec3<f32> {\n    let c = cos(angle);\n    let s = sin(angle);\n    let rotation_matrix = mat3x3<f32>(\n        vec3<f32>( c, 0.0,  s),\n        vec3<f32>(0.0, 1.0, 0.0),\n        vec3<f32>(-s, 0.0,  c)\n    );\n    return rotation_matrix * r;\n}\n\nfn rotateZ(r: vec3<f32>, angle: f32) -> vec3<f32> {\n    let c = cos(angle);\n    let s = sin(angle);\n    let rotation_matrix = mat3x3<f32>(\n        vec3<f32>( c, -s, 0.0),\n        vec3<f32>( s,  c, 0.0),\n        vec3<f32>(0.0, 0.0, 1.0)\n    );\n    return rotation_matrix * r;\n}\n\n// Distance from the scene\nfn scene(r: vec3<f32>) -> f32 {\n    let iTime = pc.light_color_and_time.w;\n    let pos = rotateZ(rotateY(r + vec3<f32>(-1.0, -1.0, 4.0), iTime), iTime);\n    let cube = vec3<f32>(0.5, 0.5, 0.5);\n    let edge = 0.1;\n    return sdRoundBox(pos, cube, edge);\n}\n\n// https://iquilezles.org/articles/normalsSDF\nfn normal(pos: vec3<f32>) -> vec3<f32> {\n    let e = vec2<f32>(1.0, -1.0) * 0.5773;\n    let eps = 0.0005;\n    return normalize(\n        e.xyy * scene(pos + e.xyy * eps) +\n        e.yyx * scene(pos + e.yyx * eps) +\n        e.yxy * scene(pos + e.yxy * eps) +\n        e.xxx * scene(pos + e.xxx * eps)\n    );\n}\n\nfn render(fragCoord: vec2<f32>, light_color: vec3<f32>) -> vec4<f32> {\n    var color = vec4<f32>(0.0, 0.0, 0.0, 1.0);\n\n    var camera = vec3<f32>(1.0, 2.0, 1.0);\n    var p = vec3<f32>(fragCoord.x, fragCoord.y + 1.0, -1.0);\n    var dir = normalize(p - camera);\n\n    var i = 0;\n    loop {\n        if (i >= 90) { break; }\n        let dist = scene(p);\n        if (dist < 0.0001) { break; }\n        p = p + dir * dist;\n        i = i + 1;\n    }\n\n    let surf_normal = normal(p);\n    let light_position = vec3<f32>(2.0, 4.0, -0.5);\n    var light = 7.0 + 2.0 * dot(surf_normal, light_position);\n    light = light / (0.2 * pow(length(light_position - p), 3.5));\n\n    let alpha = select(0.0, 1.0, i < 90);\n    return vec4<f32>(light * light_color, alpha) * 2.0;\n}\n\n@fragment\nfn fs_main(@location(0) frag_position: vec2<f32>) -> @location(0) vec4<f32> {\n    let selected_light_color = pc.light_color_and_time.xyz;\n    let r = vec2<f32>(0.5 * frag_position.x + 1.0, 0.5 - 0.5 * frag_position.y);\n    return render(r, selected_light_color);\n}"
  },
  {
    "path": "examples/10-integrations/wgpu-texture/src/styles.css",
    "content": "* {\n    box-sizing: border-box;\n}\n\nhtml, body, main {\n    height: 100%;\n    font-family: system-ui, sans;\n    margin: 0;\n}\n\nmain {\n    display: grid;\n    grid-template-rows: 100px 1fr;\n    grid-template-columns: 100%;\n    background: #f4e8d2;\n}\n\n#canvas-container {\n    display: grid;\n    opacity: 0.8;\n    grid-row: 2;\n}\n\nheader {\n    padding: 10px 40px;\n    background-color: white;\n    z-index: 100;\n    grid-row: 1;\n}\n\n#overlay {\n    position: absolute;\n    width: 33%;\n    height: 100%;\n    right: 0;\n    z-index: 10;\n    background-color: rgba(0, 0, 0, 0.5);\n    padding-top: 40%;\n    padding-inline: 20px;\n    color: white;\n}\n\n#underlay {\n    position: absolute;\n    width: 33%;\n    height: 100%;\n    z-index: -10;\n    background-color: black;\n    padding-top: 40%;\n    padding-inline: 20px;\n    color: white;\n}\n\n.color-control {\n    display: flex;\n    gap: 12px;\n\n    > input {\n        width: 150px;\n        color: black;\n    }\n}"
  },
  {
    "path": "examples/assets/calculator.css",
    "content": "html {\n  box-sizing: border-box;\n}\n\n*,\n*:before,\n*:after {\n  box-sizing: inherit;\n}\n\nbody {\n  margin: 0;\n  font: 100 14px 'Roboto';\n  font-family: Arial;\n  overflow: hidden;\n}\n\nbutton {\n  display: block;\n  background: none;\n  border: none;\n  padding: 0;\n  font-family: inherit;\n  user-select: none;\n  cursor: pointer;\n  outline: none;\n\n  -webkit-tap-highlight-color: rgba(0, 0, 0, 0);\n}\n\nbutton:active {\n  box-shadow: inset 0px 0px 80px 0px rgba(0, 0, 0, 0.25);\n}\n\n#wrapper {\n  /* height: 100vh; */\n  height: max-content;\n\n  display: flex;\n  align-items: center;\n  justify-content: center;\n}\n\n#app {\n  width: 320px;\n  height: 520px;\n  position: relative;\n}\n\n.calculator {\n  width: 100%;\n  height: 100%;\n  background: black;\n\n  display: flex;\n  flex-direction: column;\n}\n\n#wrapper .calculator {\n  /* box-shadow: 0px 0px 20px 0px #aaa; */\n}\n\n.calculator-display {\n  color: white;\n  background: #1c191c;\n  line-height: 130px;\n  /* font-size: 6em; */\n  font-size: 16px;\n  font-size: 4vw;\n\n\n  max-height: 160px;\n  padding: 0 30px;\n  /* height: 80px; */\n\n  flex: 1;\n}\n\n.auto-scaling-text {\n  display: inline-block;\n}\n\n.calculator-display .auto-scaling-text {\n  /* padding: 0 30px; */\n  /* position: absolute; */\n  /* right: 0; */\n  /* transform-origin: right; */\n}\n\n.calculator-keypad {\n  height: 400px;\n\n  display: flex;\n}\n\n.calculator .input-keys {\n  width: 240px;\n}\n\n.calculator .function-keys {\n  display: flex;\n}\n\n.calculator .digit-keys {\n  background: #e0e0e7;\n\n  display: flex;\n  flex-direction: row;\n  flex-wrap: wrap-reverse;\n}\n\n.calculator-key {\n  width: 80px;\n  height: 80px;\n  border-top: 1px solid #777;\n  border-right: 1px solid #666;\n  text-align: center;\n  line-height: 80px;\n}\n\n.calculator .function-keys .calculator-key {\n  font-size: 2em;\n}\n\n.calculator .function-keys .key-multiply {\n  line-height: 50px;\n}\n\n.calculator .digit-keys .calculator-key {\n  font-size: 2.25em;\n}\n\n.calculator .digit-keys .key-0 {\n  width: 160px;\n  text-align: left;\n  padding-left: 32px;\n}\n\n.calculator .digit-keys .key-dot {\n  padding-top: 1em;\n  font-size: 0.75em;\n}\n\n.calculator .operator-keys .calculator-key {\n  color: white;\n  border-right: 0;\n  font-size: 3em;\n}\n\n.calculator .function-keys {\n  background: linear-gradient(to bottom, rgba(202, 202, 204, 1) 0%, rgba(196, 194, 204, 1) 100%);\n}\n\n.calculator .operator-keys {\n  background: linear-gradient(to bottom, rgba(252, 156, 23, 1) 0%, rgba(247, 126, 27, 1) 100%);\n}\n"
  },
  {
    "path": "examples/assets/clock.css",
    "content": "html body {\n    margin: 0;\n    padding: 0;\n    height: 100vh;\n    font-family: 'Courier New', Courier, monospace;\n}\n\n#app {\n    display: flex;\n    justify-content: center;\n    align-items: center;\n    flex-direction: column;\n    height: 100vh;\n    background-color: plum;\n    font-size: 6em;\n    color: aliceblue;\n}\n\n#title {\n    font-size: 0.5em;\n    color: black;\n    margin-bottom: 0.5em;\n}\n"
  },
  {
    "path": "examples/assets/context_api.css",
    "content": ".main-container {\n  font-family: system-ui, sans-serif;\n  max-width: 600px;\n  margin: 2rem auto;\n  padding: 2rem;\n}\n\n.light-theme {\n  background: #ffffff;\n  color: #1a1a1a;\n  border: 1px solid #e0e0e0;\n}\n\n.dark-theme {\n  background: #1a1a1a;\n  color: #ffffff;\n  border: 1px solid #333;\n}\n\n.controls {\n  display: flex;\n  gap: 1rem;\n  margin: 2rem 0;\n}\n\n.btn {\n  padding: 0.5rem 1rem;\n  border: none;\n  border-radius: 4px;\n  cursor: pointer;\n  transition: opacity 0.2s;\n}\n\n.btn:disabled {\n  opacity: 0.7;\n  cursor: not-allowed;\n}\n\n.display {\n  padding: 2rem;\n  border-radius: 8px;\n  margin-top: 2rem;\n}\n"
  },
  {
    "path": "examples/assets/counter.css",
    "content": "html body {\n    font-family: Arial, sans-serif;\n    margin: 20px;\n    padding: 0;\n    display: flex;\n    justify-content: center;\n    font-size: 2rem;\n    height: 100vh;\n    background-color: #f0f0f0;\n}\n\n#controls {\n    display: flex;\n    justify-content: center;\n    align-items: center;\n    margin-top: 20px;\n}\n\nbutton {\n    padding: 5px 10px;\n    margin: 0 5px;\n}\n\ninput {\n    width: 50px;\n}\n"
  },
  {
    "path": "examples/assets/crm.css",
    "content": "body {\n    background-color: #f4f4f4;\n    font-family: 'Open Sans', sans-serif;\n    font-size: 14px;\n    line-height: 1.42857143;\n    color: #333;\n    margin: 20px;\n    padding: 20px;\n    display: flex;\n    flex-direction: column;\n    align-items: center;\n}\n\n.red {\n    background-color: rgb(202, 60, 60) !important;\n}\n"
  },
  {
    "path": "examples/assets/css_module1.css",
    "content": ".container {\n    background-color: lightblue;\n    padding: 20px;\n    border-radius: 8px;\n}\n\n/* The `:global` selector can be used to define global styles that are not scoped */\n:global(.global-class) {\n    color: red;\n    font-weight: bold;\n}"
  },
  {
    "path": "examples/assets/css_module2.css",
    "content": ".test {\n    font-size: 24px;\n    color: darkblue;\n}\n\n.highlight {\n    background-color: yellow;\n    padding: 5px;\n}"
  },
  {
    "path": "examples/assets/custom_assets.css",
    "content": "body {\n    background-color: #f0f0f0;\n    font-family: Arial, sans-serif;\n    margin: 0;\n    padding: 0;\n    display: flex;\n    justify-content: center;\n    align-items: center;\n    text-align: center;\n    height: 100vh;\n    width: 100vw;\n}\n"
  },
  {
    "path": "examples/assets/events.css",
    "content": "#container {\n    display: flex;\n    flex-direction: column;\n    align-items: center;\n}\n\n#receiver {\n    background: deepskyblue;\n    height: 30vh;\n    width: 80vw;\n    color: white;\n    padding: 20px;\n    margin: 20px;\n    text-align: center;\n}\n\n#log {\n    background: lightgray;\n    padding: 20px;\n    margin: 20px;\n    overflow-y: scroll;\n    align-items: start;\n    text-align: left;\n}\n"
  },
  {
    "path": "examples/assets/file_upload.css",
    "content": "body {\n    font-family: Arial, sans-serif;\n    margin: 10px;\n    padding: 10px;\n    background-color: #f4f4f4;\n    justify-content: center;\n    align-items: center;\n    min-height: 100vh;\n}\n\n.drop-zone {\n    border: 2px dashed #ccc;\n    border-radius: 3px;\n    padding: 20px;\n    text-align: center;\n    cursor: pointer;\n    margin: 20px;\n    background-color: rgba(225, 124, 225, 0);\n}\n"
  },
  {
    "path": "examples/assets/flat_router.css",
    "content": "body {\n    font-family: Arial, sans-serif;\n    margin: 20px;\n    padding: 20px;\n    background-color: #f4f4f4;\n    height: 100vh;\n}\n\nnav {\n    display: flex;\n    justify-content: space-around;\n}\n\n.nav-btn {\n    text-decoration: none;\n    color: black;\n}\n\na {\n    padding: 10px;\n    border: none;\n    border-radius: 5px;\n    cursor: pointer;\n}\n\n/* button hover effect */\na:hover {\n    background-color: #dd6a6a;\n}\n\n#content {\n    border: 2px dashed #ccc;\n    padding-top: 20px;\n    display: flex;\n    justify-content: center;\n    align-items: center;\n    height: 100%;\n    flex-direction: column;\n    gap: 20px;\n}\n"
  },
  {
    "path": "examples/assets/links.css",
    "content": "#external-links {\n    display: flex;\n    flex-direction: column;\n}\n\n#nav {\n    display: flex;\n    justify-content: space-between;\n    align-items: center;\n    padding: 1rem;\n    background-color: #f4f4f4;\n}\n"
  },
  {
    "path": "examples/assets/overlay.css",
    "content": "html, body {\n    height: 100px;\n    margin: 0;\n    overscroll-behavior-y: none;\n    overscroll-behavior-x: none;\n    overflow: hidden;\n}\n#main, #bodywrap {\n    height: 100%;\n    margin: 0;\n    overscroll-behavior-x: none;\n    overscroll-behavior-y: none;\n}"
  },
  {
    "path": "examples/assets/radio.css",
    "content": "body {\n    display: flex;\n    flex-direction: column;\n    align-items: center;\n    justify-content: center;\n    text-align: center;\n}\n\nbutton {\n    margin: 10px;\n    padding: 10px;\n    border: none;\n    border-radius: 5px;\n    background-color: #f0f0f0;\n    cursor: pointer;\n}\n\n#pause {\n    background-color: #ff0000;\n}\n\n#play {\n    background-color: #00ff00;\n}\n\n\n.bounce {\n    animation: boomBox 0.5s infinite;\n}\n\n@keyframes boomBox {\n    0% {\n        transform: scale(1.0);\n    }\n\n    50% {\n        transform: scale(2);\n    }\n\n    100% {\n        transform: scale(1.0);\n    }\n}\n"
  },
  {
    "path": "examples/assets/read_size.css",
    "content": "html, body {\n    height: 100%;\n    width: 100%;\n    margin: 0;\n}\n#main {\n    height: 100%;\n    width: 100%;\n}"
  },
  {
    "path": "examples/assets/roulette.css",
    "content": "#main {\n    display: flex;\n    flex-direction: column;\n    justify-content: center;\n    align-items: center;\n}\n\n#roulette-grid {\n    margin: 10px;\n    display: grid;\n    grid-template-columns: repeat(9, 1fr);\n    grid-gap: 10px;\n    margin: 0 auto;\n    padding: 20px;\n}\n\n#roulette-grid>input {\n    color: white;\n    font-size: 20px;\n    width: 50px;\n}\n\n#roulette-grid>input:nth-child(odd) {\n    background-color: red;\n}\n\n#roulette-grid>input:nth-child(even) {\n    background-color: black;\n}\n"
  },
  {
    "path": "examples/assets/router.css",
    "content": "#navbar {\n    display: flex;\n    justify-content: flex-start;\n    gap: 1rem;\n    align-items: center;\n}\n\n#blog-list {\n    display: flex;\n    flex-direction: column;\n    align-items: start;\n    gap: 1rem;\n}\n"
  },
  {
    "path": "examples/assets/todomvc.css",
    "content": "html,\nbody, pre {\n    margin: 0;\n    padding: 0;\n}\n\nbutton {\n    margin: 0;\n    padding: 0;\n    border: 0;\n    background: none;\n    font-size: 100%;\n    vertical-align: baseline;\n    font-family: inherit;\n    font-weight: inherit;\n    color: inherit;\n    -webkit-appearance: none;\n    appearance: none;\n    -webkit-font-smoothing: antialiased;\n    -moz-osx-font-smoothing: grayscale;\n}\n\nbody {\n    font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif;\n    line-height: 1.4em;\n    background: #f5f5f5;\n    color: #4d4d4d;\n    min-width: 230px;\n    max-width: 550px;\n    margin: 0 auto;\n    -webkit-font-smoothing: antialiased;\n    -moz-osx-font-smoothing: grayscale;\n    font-weight: 300;\n}\n\n:focus {\n    outline: 0;\n}\n\n.hidden {\n    display: none;\n}\n\n.todoapp {\n    background: #fff;\n    margin: 130px 0 40px 0;\n    position: relative;\n    box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1);\n}\n\n.todoapp input::-webkit-input-placeholder {\n    font-style: italic;\n    font-weight: 300;\n    color: #e6e6e6;\n}\n\n.todoapp input::-moz-placeholder {\n    font-style: italic;\n    font-weight: 300;\n    color: #e6e6e6;\n}\n\n.todoapp input::input-placeholder {\n    font-style: italic;\n    font-weight: 300;\n    color: #e6e6e6;\n}\n\n.todoapp h1 {\n    position: absolute;\n    top: -155px;\n    width: 100%;\n    font-size: 100px;\n    font-weight: 100;\n    text-align: center;\n    color: rgba(175, 47, 47, 0.15);\n    -webkit-text-rendering: optimizeLegibility;\n    -moz-text-rendering: optimizeLegibility;\n    text-rendering: optimizeLegibility;\n}\n\n.new-todo,\n.edit {\n    position: relative;\n    margin: 0;\n    width: 100%;\n    font-size: 24px;\n    font-family: inherit;\n    font-weight: inherit;\n    line-height: 1.4em;\n    border: 0;\n    color: inherit;\n    padding: 6px;\n    border: 1px solid #999;\n    box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2);\n    box-sizing: border-box;\n    -webkit-font-smoothing: antialiased;\n    -moz-osx-font-smoothing: grayscale;\n}\n\n.new-todo {\n    padding: 16px 16px 16px 60px;\n    border: none;\n    background: rgba(0, 0, 0, 0.003);\n    box-shadow: inset 0 -2px 1px rgba(0, 0, 0, 0.03);\n}\n\n.main {\n    position: relative;\n    z-index: 2;\n    border-top: 1px solid #e6e6e6;\n}\n\n.toggle-all {\n    text-align: center;\n    border: none;\n    /* Mobile Safari */\n    opacity: 0;\n    position: absolute;\n}\n\n.toggle-all+label {\n    width: 60px;\n    height: 34px;\n    font-size: 0;\n    position: absolute;\n    top: -52px;\n    left: -13px;\n    -webkit-transform: rotate(90deg);\n    transform: rotate(90deg);\n}\n\n.toggle-all+label:before {\n    content: '❯';\n    font-size: 22px;\n    color: #e6e6e6;\n    padding: 10px 27px 10px 27px;\n}\n\n.toggle-all:checked+label:before {\n    color: #737373;\n}\n\n.todo-list {\n    margin: 0;\n    padding: 0;\n    list-style: none;\n}\n\n.todo-list li {\n    position: relative;\n    font-size: 24px;\n    border-bottom: 1px solid #ededed;\n}\n\n.todo-list li:last-child {\n    border-bottom: none;\n}\n\n.todo-list li.editing {\n    border-bottom: none;\n    padding: 0;\n}\n\n.todo-list li.editing .edit {\n    display: block;\n    width: 506px;\n    padding: 12px 16px;\n    margin: 0 0 0 43px;\n}\n\n.todo-list li.editing .view {\n    display: none;\n}\n\n.todo-list li .toggle {\n    text-align: center;\n    width: 40px;\n    /* auto, since non-WebKit browsers doesn't support input styling */\n    height: auto;\n    position: absolute;\n    top: 0;\n    bottom: 0;\n    margin: auto 0;\n    border: none;\n    /* Mobile Safari */\n    -webkit-appearance: none;\n    appearance: none;\n}\n\n.todo-list li .toggle {\n    opacity: 0;\n}\n\n.todo-list li .toggle+label {\n    /*\n\t\tFirefox requires `#` to be escaped - https://bugzilla.mozilla.org/show_bug.cgi?id=922433\n\t\tIE and Edge requires *everything* to be escaped to render, so we do that instead of just the `#` - https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/7157459/\n\t*/\n    background-image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23ededed%22%20stroke-width%3D%223%22/%3E%3C/svg%3E');\n    background-repeat: no-repeat;\n    background-position: center left;\n}\n\n.todo-list li .toggle:checked+label {\n    background-image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23bddad5%22%20stroke-width%3D%223%22/%3E%3Cpath%20fill%3D%22%235dc2af%22%20d%3D%22M72%2025L42%2071%2027%2056l-4%204%2020%2020%2034-52z%22/%3E%3C/svg%3E');\n}\n\n.todo-list li label {\n    word-break: break-all;\n    padding: 15px 15px 15px 60px;\n    display: block;\n    line-height: 1.2;\n    transition: color 0.4s;\n}\n\n.todo-list li.completed label {\n    color: #d9d9d9;\n    text-decoration: line-through;\n}\n\n.todo-list li .destroy {\n    display: none;\n    position: absolute;\n    top: 0;\n    right: 10px;\n    bottom: 0;\n    width: 40px;\n    height: 40px;\n    margin: auto 0;\n    font-size: 30px;\n    color: #cc9a9a;\n    margin-bottom: 11px;\n    transition: color 0.2s ease-out;\n}\n\n.todo-list li .destroy:hover {\n    color: #af5b5e;\n}\n\n.todo-list li .destroy:after {\n    content: '×';\n}\n\n.todo-list li:hover .destroy {\n    display: block;\n}\n\n.todo-list li .edit {\n    display: none;\n}\n\n.todo-list li.editing:last-child {\n    margin-bottom: -1px;\n}\n\n.footer {\n    display: flex;\n    justify-content: space-between;\n    align-items: center;\n    color: #777;\n    padding: 10px 15px;\n    height: 20px;\n    text-align: center;\n    border-top: 1px solid #e6e6e6;\n}\n\n.footer:before {\n    content: '';\n    position: absolute;\n    right: 0;\n    bottom: 0;\n    left: 0;\n    height: 50px;\n    overflow: hidden;\n    box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2), 0 8px 0 -3px #f6f6f6, 0 9px 1px -3px rgba(0, 0, 0, 0.2), 0 16px 0 -6px #f6f6f6, 0 17px 2px -6px rgba(0, 0, 0, 0.2);\n}\n\n.todo-count {\n    float: left;\n    text-align: left;\n    white-space-collapse: preserve;\n}\n\n.todo-count strong {\n    font-weight: 300;\n}\n\n.filters {\n    margin: 0;\n    padding: 0;\n    list-style: none;\n    position: absolute;\n    right: 0;\n    left: 0;\n}\n\n.filters li {\n    display: inline;\n}\n\n.filters li a {\n    display: inline-block;\n    color: inherit;\n    margin: 3px;\n    padding: 3px 7px;\n    text-decoration: none;\n    border: 1px solid transparent;\n    border-radius: 3px;\n}\n\n.filters li a:hover {\n    border-color: rgba(175, 47, 47, 0.1);\n}\n\n.filters li a.selected {\n    border-color: rgba(175, 47, 47, 0.2);\n}\n\n.clear-completed,\nhtml .clear-completed:active {\n    float: right;\n    position: relative;\n    line-height: 20px;\n    text-decoration: none;\n    cursor: pointer;\n}\n\n.clear-completed:hover {\n    text-decoration: underline;\n}\n\n.info {\n    margin: 65px auto 0;\n    color: #bfbfbf;\n    font-size: 10px;\n    text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);\n    text-align: center;\n}\n\n.info p {\n    line-height: 1;\n}\n\n.info a {\n    color: inherit;\n    text-decoration: none;\n    font-weight: 400;\n}\n\n.info a:hover {\n    text-decoration: underline;\n}\n\n\n/*\n\tHack to remove background from Mobile Safari.\n\tCan't use it globally since it destroys checkboxes in Firefox\n*/\n\n@media screen and (-webkit-min-device-pixel-ratio:0) {\n    .toggle-all,\n    .todo-list li .toggle {\n        background: none;\n    }\n    .todo-list li .toggle {\n        height: 40px;\n    }\n}\n\n@media (max-width: 430px) {\n    .footer {\n        height: 50px;\n    }\n    .filters {\n        bottom: 10px;\n    }\n}\n"
  },
  {
    "path": "examples/assets/visible.css",
    "content": "* {\n  box-sizing: border-box;\n}\n\n.container {\n  height: 500px;\n  width: 500px;\n  margin: 100px auto;\n}\n\np {\n  line-height: 30px;\n\n}\n\n.animated-text {\n  font-size: 30px;\n  text-align: center;\n  opacity: 0;\n  transform: translateY(100px);\n  transition: opacity 1s ease, transform 1s ease;\n}\n\n.animated-text.visible {\n  opacity: 1;\n  transform: translateX(0);\n}\n"
  },
  {
    "path": "examples/assets/weatherapp.css",
    "content": "/*! tailwindcss v4.1.5 | MIT License | https://tailwindcss.com */\n@layer properties;\n@layer theme, base, components, utilities;\n\n@layer theme {\n\n    :root,\n    :host {\n        --font-sans: ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol',\n            'Noto Color Emoji';\n        --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New',\n            monospace;\n        --color-orange-300: oklch(83.7% 0.128 66.29);\n        --color-orange-400: oklch(75% 0.183 55.934);\n        --color-yellow-50: oklch(98.7% 0.026 102.212);\n        --color-yellow-600: oklch(68.1% 0.162 75.834);\n        --color-green-500: oklch(72.3% 0.219 149.579);\n        --color-blue-300: oklch(80.9% 0.105 251.813);\n        --color-indigo-500: oklch(58.5% 0.233 277.117);\n        --color-indigo-600: oklch(51.1% 0.262 276.966);\n        --color-purple-50: oklch(97.7% 0.014 308.299);\n        --color-purple-500: oklch(62.7% 0.265 303.9);\n        --color-gray-50: oklch(98.5% 0.002 247.839);\n        --color-gray-100: oklch(96.7% 0.003 264.542);\n        --color-gray-200: oklch(92.8% 0.006 264.531);\n        --color-gray-400: oklch(70.7% 0.022 261.325);\n        --color-gray-500: oklch(55.1% 0.027 264.364);\n        --color-gray-600: oklch(44.6% 0.03 256.802);\n        --color-gray-700: oklch(37.3% 0.034 259.733);\n        --color-gray-800: oklch(27.8% 0.033 256.848);\n        --color-gray-900: oklch(21% 0.034 264.665);\n        --color-white: #fff;\n        --spacing: 0.25rem;\n        --container-sm: 24rem;\n        --container-md: 28rem;\n        --container-xl: 36rem;\n        --container-2xl: 42rem;\n        --text-xs: 0.75rem;\n        --text-xs--line-height: calc(1 / 0.75);\n        --text-xl: 1.25rem;\n        --text-xl--line-height: calc(1.75 / 1.25);\n        --text-2xl: 1.5rem;\n        --text-2xl--line-height: calc(2 / 1.5);\n        --text-3xl: 1.875rem;\n        --text-3xl--line-height: calc(2.25 / 1.875);\n        --text-5xl: 3rem;\n        --text-5xl--line-height: 1;\n        --text-6xl: 3.75rem;\n        --text-6xl--line-height: 1;\n        --font-weight-medium: 500;\n        --font-weight-semibold: 600;\n        --font-weight-bold: 700;\n        --radius-md: 0.375rem;\n        --radius-lg: 0.5rem;\n        --default-transition-duration: 150ms;\n        --default-transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);\n        --default-font-family: var(--font-sans);\n        --default-mono-font-family: var(--font-mono);\n    }\n}\n\n@layer base {\n\n    *,\n    ::after,\n    ::before,\n    ::backdrop,\n    ::file-selector-button {\n        box-sizing: border-box;\n        margin: 0;\n        padding: 0;\n        border: 0 solid;\n    }\n\n    html,\n    :host {\n        line-height: 1.5;\n        -webkit-text-size-adjust: 100%;\n        tab-size: 4;\n        font-family: var(--default-font-family, ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji');\n        font-feature-settings: var(--default-font-feature-settings, normal);\n        font-variation-settings: var(--default-font-variation-settings, normal);\n        -webkit-tap-highlight-color: transparent;\n    }\n\n    hr {\n        height: 0;\n        color: inherit;\n        border-top-width: 1px;\n    }\n\n    abbr:where([title]) {\n        -webkit-text-decoration: underline dotted;\n        text-decoration: underline dotted;\n    }\n\n    h1,\n    h2,\n    h3,\n    h4,\n    h5,\n    h6 {\n        font-size: inherit;\n        font-weight: inherit;\n    }\n\n    a {\n        color: inherit;\n        -webkit-text-decoration: inherit;\n        text-decoration: inherit;\n    }\n\n    b,\n    strong {\n        font-weight: bolder;\n    }\n\n    code,\n    kbd,\n    samp,\n    pre {\n        font-family: var(--default-mono-font-family, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace);\n        font-feature-settings: var(--default-mono-font-feature-settings, normal);\n        font-variation-settings: var(--default-mono-font-variation-settings, normal);\n        font-size: 1em;\n    }\n\n    small {\n        font-size: 80%;\n    }\n\n    sub,\n    sup {\n        font-size: 75%;\n        line-height: 0;\n        position: relative;\n        vertical-align: baseline;\n    }\n\n    sub {\n        bottom: -0.25em;\n    }\n\n    sup {\n        top: -0.5em;\n    }\n\n    table {\n        text-indent: 0;\n        border-color: inherit;\n        border-collapse: collapse;\n    }\n\n    :-moz-focusring {\n        outline: auto;\n    }\n\n    progress {\n        vertical-align: baseline;\n    }\n\n    summary {\n        display: list-item;\n    }\n\n    ol,\n    ul,\n    menu {\n        list-style: none;\n    }\n\n    img,\n    svg,\n    video,\n    canvas,\n    audio,\n    iframe,\n    embed,\n    object {\n        display: block;\n        vertical-align: middle;\n    }\n\n    img,\n    video {\n        max-width: 100%;\n        height: auto;\n    }\n\n    button,\n    input,\n    select,\n    optgroup,\n    textarea,\n    ::file-selector-button {\n        font: inherit;\n        font-feature-settings: inherit;\n        font-variation-settings: inherit;\n        letter-spacing: inherit;\n        color: inherit;\n        border-radius: 0;\n        background-color: transparent;\n        opacity: 1;\n    }\n\n    :where(select:is([multiple], [size])) optgroup {\n        font-weight: bolder;\n    }\n\n    :where(select:is([multiple], [size])) optgroup option {\n        padding-inline-start: 20px;\n    }\n\n    ::file-selector-button {\n        margin-inline-end: 4px;\n    }\n\n    ::placeholder {\n        opacity: 1;\n    }\n\n    @supports (not (-webkit-appearance: -apple-pay-button)) or (contain-intrinsic-size: 1px) {\n        ::placeholder {\n            color: currentcolor;\n\n            @supports (color: color-mix(in lab, red, red)) {\n                color: color-mix(in oklab, currentcolor 50%, transparent);\n            }\n        }\n    }\n\n    textarea {\n        resize: vertical;\n    }\n\n    ::-webkit-search-decoration {\n        -webkit-appearance: none;\n    }\n\n    ::-webkit-date-and-time-value {\n        min-height: 1lh;\n        text-align: inherit;\n    }\n\n    ::-webkit-datetime-edit {\n        display: inline-flex;\n    }\n\n    ::-webkit-datetime-edit-fields-wrapper {\n        padding: 0;\n    }\n\n    ::-webkit-datetime-edit,\n    ::-webkit-datetime-edit-year-field,\n    ::-webkit-datetime-edit-month-field,\n    ::-webkit-datetime-edit-day-field,\n    ::-webkit-datetime-edit-hour-field,\n    ::-webkit-datetime-edit-minute-field,\n    ::-webkit-datetime-edit-second-field,\n    ::-webkit-datetime-edit-millisecond-field,\n    ::-webkit-datetime-edit-meridiem-field {\n        padding-block: 0;\n    }\n\n    :-moz-ui-invalid {\n        box-shadow: none;\n    }\n\n    button,\n    input:where([type='button'], [type='reset'], [type='submit']),\n    ::file-selector-button {\n        appearance: button;\n    }\n\n    ::-webkit-inner-spin-button,\n    ::-webkit-outer-spin-button {\n        height: auto;\n    }\n\n    [hidden]:where(:not([hidden='until-found'])) {\n        display: none !important;\n    }\n}\n\n@layer utilities {\n    .visible {\n        visibility: visible;\n    }\n\n    .absolute {\n        position: absolute;\n    }\n\n    .fixed {\n        position: fixed;\n    }\n\n    .relative {\n        position: relative;\n    }\n\n    .static {\n        position: static;\n    }\n\n    .inset-0 {\n        inset: calc(var(--spacing) * 0);\n    }\n\n    .top-0 {\n        top: calc(var(--spacing) * 0);\n    }\n\n    .top-1\\/2 {\n        top: calc(1/2 * 100%);\n    }\n\n    .top-2 {\n        top: calc(var(--spacing) * 2);\n    }\n\n    .top-3\\.5 {\n        top: calc(var(--spacing) * 3.5);\n    }\n\n    .right-0 {\n        right: calc(var(--spacing) * 0);\n    }\n\n    .bottom-0 {\n        bottom: calc(var(--spacing) * 0);\n    }\n\n    .left-0 {\n        left: calc(var(--spacing) * 0);\n    }\n\n    .left-2 {\n        left: calc(var(--spacing) * 2);\n    }\n\n    .left-2\\.5 {\n        left: calc(var(--spacing) * 2.5);\n    }\n\n    .isolate {\n        isolation: isolate;\n    }\n\n    .z-50 {\n        z-index: 50;\n    }\n\n    .container {\n        width: 100%;\n\n        @media (width >=40rem) {\n            max-width: 40rem;\n        }\n\n        @media (width >=48rem) {\n            max-width: 48rem;\n        }\n\n        @media (width >=64rem) {\n            max-width: 64rem;\n        }\n\n        @media (width >=80rem) {\n            max-width: 80rem;\n        }\n\n        @media (width >=96rem) {\n            max-width: 96rem;\n        }\n    }\n\n    .m-0 {\n        margin: calc(var(--spacing) * 0);\n    }\n\n    .m-2 {\n        margin: calc(var(--spacing) * 2);\n    }\n\n    .-mx-4 {\n        margin-inline: calc(var(--spacing) * -4);\n    }\n\n    .mx-2 {\n        margin-inline: calc(var(--spacing) * 2);\n    }\n\n    .mx-4 {\n        margin-inline: calc(var(--spacing) * 4);\n    }\n\n    .mx-8 {\n        margin-inline: calc(var(--spacing) * 8);\n    }\n\n    .mx-auto {\n        margin-inline: auto;\n    }\n\n    .my-1 {\n        margin-block: calc(var(--spacing) * 1);\n    }\n\n    .mt-2 {\n        margin-top: calc(var(--spacing) * 2);\n    }\n\n    .mt-4 {\n        margin-top: calc(var(--spacing) * 4);\n    }\n\n    .mr-1 {\n        margin-right: calc(var(--spacing) * 1);\n    }\n\n    .mr-2 {\n        margin-right: calc(var(--spacing) * 2);\n    }\n\n    .mr-3 {\n        margin-right: calc(var(--spacing) * 3);\n    }\n\n    .mr-6 {\n        margin-right: calc(var(--spacing) * 6);\n    }\n\n    .mr-8 {\n        margin-right: calc(var(--spacing) * 8);\n    }\n\n    .mr-10 {\n        margin-right: calc(var(--spacing) * 10);\n    }\n\n    .mr-12 {\n        margin-right: calc(var(--spacing) * 12);\n    }\n\n    .mr-14 {\n        margin-right: calc(var(--spacing) * 14);\n    }\n\n    .mr-16 {\n        margin-right: calc(var(--spacing) * 16);\n    }\n\n    .mr-auto {\n        margin-right: auto;\n    }\n\n    .mb-0 {\n        margin-bottom: calc(var(--spacing) * 0);\n    }\n\n    .mb-4 {\n        margin-bottom: calc(var(--spacing) * 4);\n    }\n\n    .mb-6 {\n        margin-bottom: calc(var(--spacing) * 6);\n    }\n\n    .mb-8 {\n        margin-bottom: calc(var(--spacing) * 8);\n    }\n\n    .mb-10 {\n        margin-bottom: calc(var(--spacing) * 10);\n    }\n\n    .mb-12 {\n        margin-bottom: calc(var(--spacing) * 12);\n    }\n\n    .mb-14 {\n        margin-bottom: calc(var(--spacing) * 14);\n    }\n\n    .mb-16 {\n        margin-bottom: calc(var(--spacing) * 16);\n    }\n\n    .mb-24 {\n        margin-bottom: calc(var(--spacing) * 24);\n    }\n\n    .ml-8 {\n        margin-left: calc(var(--spacing) * 8);\n    }\n\n    .block {\n        display: block;\n    }\n\n    .contents {\n        display: contents;\n    }\n\n    .flex {\n        display: flex;\n    }\n\n    .hidden {\n        display: none;\n    }\n\n    .inline-block {\n        display: inline-block;\n    }\n\n    .inline-flex {\n        display: inline-flex;\n    }\n\n    .table {\n        display: table;\n    }\n\n    .h-2 {\n        height: calc(var(--spacing) * 2);\n    }\n\n    .h-4 {\n        height: calc(var(--spacing) * 4);\n    }\n\n    .h-6 {\n        height: calc(var(--spacing) * 6);\n    }\n\n    .h-8 {\n        height: calc(var(--spacing) * 8);\n    }\n\n    .h-9 {\n        height: calc(var(--spacing) * 9);\n    }\n\n    .h-40 {\n        height: calc(var(--spacing) * 40);\n    }\n\n    .h-full {\n        height: 100%;\n    }\n\n    .h-px {\n        height: 1px;\n    }\n\n    .h-screen {\n        height: 100vh;\n    }\n\n    .max-h-72 {\n        max-height: calc(var(--spacing) * 72);\n    }\n\n    .w-1\\/2 {\n        width: calc(1/2 * 100%);\n    }\n\n    .w-1\\/4 {\n        width: calc(1/4 * 100%);\n    }\n\n    .w-1\\/6 {\n        width: calc(1/6 * 100%);\n    }\n\n    .w-2 {\n        width: calc(var(--spacing) * 2);\n    }\n\n    .w-4 {\n        width: calc(var(--spacing) * 4);\n    }\n\n    .w-5\\/6 {\n        width: calc(5/6 * 100%);\n    }\n\n    .w-6 {\n        width: calc(var(--spacing) * 6);\n    }\n\n    .w-8 {\n        width: calc(var(--spacing) * 8);\n    }\n\n    .w-12 {\n        width: calc(var(--spacing) * 12);\n    }\n\n    .w-16 {\n        width: calc(var(--spacing) * 16);\n    }\n\n    .w-30 {\n        width: calc(var(--spacing) * 30);\n    }\n\n    .w-full {\n        width: 100%;\n    }\n\n    .max-w-2xl {\n        max-width: var(--container-2xl);\n    }\n\n    .max-w-md {\n        max-width: var(--container-md);\n    }\n\n    .max-w-sm {\n        max-width: var(--container-sm);\n    }\n\n    .max-w-xl {\n        max-width: var(--container-xl);\n    }\n\n    .min-w-0 {\n        min-width: calc(var(--spacing) * 0);\n    }\n\n    .shrink-0 {\n        flex-shrink: 0;\n    }\n\n    .table-auto {\n        table-layout: auto;\n    }\n\n    .translate-1\\/2 {\n        --tw-translate-x: calc(1/2 * 100%);\n        --tw-translate-y: calc(1/2 * 100%);\n        translate: var(--tw-translate-x) var(--tw-translate-y);\n    }\n\n    .transform {\n        transform: var(--tw-rotate-x, ) var(--tw-rotate-y, ) var(--tw-rotate-z, ) var(--tw-skew-x, ) var(--tw-skew-y, );\n    }\n\n    .cursor-pointer {\n        cursor: pointer;\n    }\n\n    .flex-col {\n        flex-direction: column;\n    }\n\n    .flex-row {\n        flex-direction: row;\n    }\n\n    .flex-wrap {\n        flex-wrap: wrap;\n    }\n\n    .place-items-center {\n        place-items: center;\n    }\n\n    .items-center {\n        align-items: center;\n    }\n\n    .items-start {\n        align-items: flex-start;\n    }\n\n    .justify-between {\n        justify-content: space-between;\n    }\n\n    .justify-center {\n        justify-content: center;\n    }\n\n    .self-center {\n        align-self: center;\n    }\n\n    .overflow-auto {\n        overflow: auto;\n    }\n\n    .overflow-hidden {\n        overflow: hidden;\n    }\n\n    .overflow-x-auto {\n        overflow-x: auto;\n    }\n\n    .overflow-y-auto {\n        overflow-y: auto;\n    }\n\n    .rounded {\n        border-radius: 0.25rem;\n    }\n\n    .rounded-full {\n        border-radius: calc(infinity * 1px);\n    }\n\n    .rounded-lg {\n        border-radius: var(--radius-lg);\n    }\n\n    .rounded-md {\n        border-radius: var(--radius-md);\n    }\n\n    .border {\n        border-style: var(--tw-border-style);\n        border-width: 1px;\n    }\n\n    .border-0 {\n        border-style: var(--tw-border-style);\n        border-width: 0px;\n    }\n\n    .border-r {\n        border-right-style: var(--tw-border-style);\n        border-right-width: 1px;\n    }\n\n    .border-b {\n        border-bottom-style: var(--tw-border-style);\n        border-bottom-width: 1px;\n    }\n\n    .border-b-2 {\n        border-bottom-style: var(--tw-border-style);\n        border-bottom-width: 2px;\n    }\n\n    .border-l {\n        border-left-style: var(--tw-border-style);\n        border-left-width: 1px;\n    }\n\n    .border-gray-100 {\n        border-color: var(--color-gray-100);\n    }\n\n    .border-gray-200 {\n        border-color: var(--color-gray-200);\n    }\n\n    .border-transparent {\n        border-color: transparent;\n    }\n\n    .bg-gray-50 {\n        background-color: var(--color-gray-50);\n    }\n\n    .bg-gray-100 {\n        background-color: var(--color-gray-100);\n    }\n\n    .bg-gray-200 {\n        background-color: var(--color-gray-200);\n    }\n\n    .bg-gray-800 {\n        background-color: var(--color-gray-800);\n    }\n\n    .bg-gray-900 {\n        background-color: var(--color-gray-900);\n    }\n\n    .bg-green-500 {\n        background-color: var(--color-green-500);\n    }\n\n    .bg-indigo-500 {\n        background-color: var(--color-indigo-500);\n    }\n\n    .bg-orange-300 {\n        background-color: var(--color-orange-300);\n    }\n\n    .bg-purple-50 {\n        background-color: var(--color-purple-50);\n    }\n\n    .bg-white {\n        background-color: var(--color-white);\n    }\n\n    .stroke-current {\n        stroke: currentcolor;\n    }\n\n    .object-cover {\n        object-fit: cover;\n    }\n\n    .object-scale-down {\n        object-fit: scale-down;\n    }\n\n    .p-2 {\n        padding: calc(var(--spacing) * 2);\n    }\n\n    .p-4 {\n        padding: calc(var(--spacing) * 4);\n    }\n\n    .p-10 {\n        padding: calc(var(--spacing) * 10);\n    }\n\n    .px-2 {\n        padding-inline: calc(var(--spacing) * 2);\n    }\n\n    .px-4 {\n        padding-inline: calc(var(--spacing) * 4);\n    }\n\n    .px-6 {\n        padding-inline: calc(var(--spacing) * 6);\n    }\n\n    .px-8 {\n        padding-inline: calc(var(--spacing) * 8);\n    }\n\n    .px-10 {\n        padding-inline: calc(var(--spacing) * 10);\n    }\n\n    .px-12 {\n        padding-inline: calc(var(--spacing) * 12);\n    }\n\n    .py-1 {\n        padding-block: calc(var(--spacing) * 1);\n    }\n\n    .py-2 {\n        padding-block: calc(var(--spacing) * 2);\n    }\n\n    .py-3 {\n        padding-block: calc(var(--spacing) * 3);\n    }\n\n    .py-4 {\n        padding-block: calc(var(--spacing) * 4);\n    }\n\n    .py-5 {\n        padding-block: calc(var(--spacing) * 5);\n    }\n\n    .py-6 {\n        padding-block: calc(var(--spacing) * 6);\n    }\n\n    .py-8 {\n        padding-block: calc(var(--spacing) * 8);\n    }\n\n    .py-20 {\n        padding-block: calc(var(--spacing) * 20);\n    }\n\n    .pt-4 {\n        padding-top: calc(var(--spacing) * 4);\n    }\n\n    .pr-2 {\n        padding-right: calc(var(--spacing) * 2);\n    }\n\n    .pr-10 {\n        padding-right: calc(var(--spacing) * 10);\n    }\n\n    .pb-3 {\n        padding-bottom: calc(var(--spacing) * 3);\n    }\n\n    .pb-10 {\n        padding-bottom: calc(var(--spacing) * 10);\n    }\n\n    .pl-4 {\n        padding-left: calc(var(--spacing) * 4);\n    }\n\n    .pl-6 {\n        padding-left: calc(var(--spacing) * 6);\n    }\n\n    .pl-8 {\n        padding-left: calc(var(--spacing) * 8);\n    }\n\n    .text-center {\n        text-align: center;\n    }\n\n    .text-left {\n        text-align: left;\n    }\n\n    .text-2xl {\n        font-size: var(--text-2xl);\n        line-height: var(--tw-leading, var(--text-2xl--line-height));\n    }\n\n    .text-3xl {\n        font-size: var(--text-3xl);\n        line-height: var(--tw-leading, var(--text-3xl--line-height));\n    }\n\n    .text-5xl {\n        font-size: var(--text-5xl);\n        line-height: var(--tw-leading, var(--text-5xl--line-height));\n    }\n\n    .text-xl {\n        font-size: var(--text-xl);\n        line-height: var(--tw-leading, var(--text-xl--line-height));\n    }\n\n    .text-xs {\n        font-size: var(--text-xs);\n        line-height: var(--tw-leading, var(--text-xs--line-height));\n    }\n\n    .font-bold {\n        --tw-font-weight: var(--font-weight-bold);\n        font-weight: var(--font-weight-bold);\n    }\n\n    .font-medium {\n        --tw-font-weight: var(--font-weight-medium);\n        font-weight: var(--font-weight-medium);\n    }\n\n    .font-semibold {\n        --tw-font-weight: var(--font-weight-semibold);\n        font-weight: var(--font-weight-semibold);\n    }\n\n    .break-words {\n        overflow-wrap: break-word;\n    }\n\n    .text-ellipsis {\n        text-overflow: ellipsis;\n    }\n\n    .text-blue-300 {\n        color: var(--color-blue-300);\n    }\n\n    .text-gray-400 {\n        color: var(--color-gray-400);\n    }\n\n    .text-gray-500 {\n        color: var(--color-gray-500);\n    }\n\n    .text-gray-600 {\n        color: var(--color-gray-600);\n    }\n\n    .text-purple-500 {\n        color: var(--color-purple-500);\n    }\n\n    .text-white {\n        color: var(--color-white);\n    }\n\n    .uppercase {\n        text-transform: uppercase;\n    }\n\n    .placeholder-gray-400 {\n        &::placeholder {\n            color: var(--color-gray-400);\n        }\n    }\n\n    .opacity-25 {\n        opacity: 25%;\n    }\n\n    .shadow {\n        --tw-shadow: 0 1px 3px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 1px 2px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1));\n        box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);\n    }\n\n    .shadow-2xl {\n        --tw-shadow: 0 25px 50px -12px var(--tw-shadow-color, rgb(0 0 0 / 0.25));\n        box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);\n    }\n\n    .shadow-lg {\n        --tw-shadow: 0 10px 15px -3px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 4px 6px -4px var(--tw-shadow-color, rgb(0 0 0 / 0.1));\n        box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);\n    }\n\n    .shadow-sm {\n        --tw-shadow: 0 1px 3px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 1px 2px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1));\n        box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);\n    }\n\n    .ring-1 {\n        --tw-ring-shadow: var(--tw-ring-inset, ) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);\n        box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);\n    }\n\n    .filter {\n        filter: var(--tw-blur, ) var(--tw-brightness, ) var(--tw-contrast, ) var(--tw-grayscale, ) var(--tw-hue-rotate, ) var(--tw-invert, ) var(--tw-saturate, ) var(--tw-sepia, ) var(--tw-drop-shadow, );\n    }\n\n    .transition {\n        transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to, opacity, box-shadow, transform, translate, scale, rotate, filter, -webkit-backdrop-filter, backdrop-filter, display, visibility, content-visibility, overlay, pointer-events;\n        transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));\n        transition-duration: var(--tw-duration, var(--default-transition-duration));\n    }\n\n    .transition-all {\n        transition-property: all;\n        transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));\n        transition-duration: var(--tw-duration, var(--default-transition-duration));\n    }\n\n    .duration-200 {\n        --tw-duration: 200ms;\n        transition-duration: 200ms;\n    }\n\n    .hover\\:bg-indigo-600 {\n        &:hover {\n            @media (hover: hover) {\n                background-color: var(--color-indigo-600);\n            }\n        }\n    }\n\n    .hover\\:bg-orange-400 {\n        &:hover {\n            @media (hover: hover) {\n                background-color: var(--color-orange-400);\n            }\n        }\n    }\n\n    .hover\\:bg-yellow-50 {\n        &:hover {\n            @media (hover: hover) {\n                background-color: var(--color-yellow-50);\n            }\n        }\n    }\n\n    .hover\\:text-gray-600 {\n        &:hover {\n            @media (hover: hover) {\n                color: var(--color-gray-600);\n            }\n        }\n    }\n\n    .hover\\:text-gray-700 {\n        &:hover {\n            @media (hover: hover) {\n                color: var(--color-gray-700);\n            }\n        }\n    }\n\n    .hover\\:text-gray-900 {\n        &:hover {\n            @media (hover: hover) {\n                color: var(--color-gray-900);\n            }\n        }\n    }\n\n    .hover\\:shadow-2xl {\n        &:hover {\n            @media (hover: hover) {\n                --tw-shadow: 0 25px 50px -12px var(--tw-shadow-color, rgb(0 0 0 / 0.25));\n                box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);\n            }\n        }\n    }\n\n    .hover\\:ring-4 {\n        &:hover {\n            @media (hover: hover) {\n                --tw-ring-shadow: var(--tw-ring-inset, ) 0 0 0 calc(4px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);\n                box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);\n            }\n        }\n    }\n\n    .focus\\:border-blue-300 {\n        &:focus {\n            border-color: var(--color-blue-300);\n        }\n    }\n\n    .focus\\:border-transparent {\n        &:focus {\n            border-color: transparent;\n        }\n    }\n\n    .focus\\:bg-white {\n        &:focus {\n            background-color: var(--color-white);\n        }\n    }\n\n    .focus\\:ring-2 {\n        &:focus {\n            --tw-ring-shadow: var(--tw-ring-inset, ) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);\n            box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);\n        }\n    }\n\n    .focus\\:ring-blue-300 {\n        &:focus {\n            --tw-ring-color: var(--color-blue-300);\n        }\n    }\n\n    .focus\\:ring-transparent {\n        &:focus {\n            --tw-ring-color: transparent;\n        }\n    }\n\n    .focus\\:ring-yellow-600 {\n        &:focus {\n            --tw-ring-color: var(--color-yellow-600);\n        }\n    }\n\n    .focus\\:outline-hidden {\n        &:focus {\n            --tw-outline-style: none;\n            outline-style: none;\n\n            @media (forced-colors: active) {\n                outline: 2px solid transparent;\n                outline-offset: 2px;\n            }\n        }\n    }\n\n    .focus\\:outline-none {\n        &:focus {\n            --tw-outline-style: none;\n            outline-style: none;\n        }\n    }\n\n    .md\\:mb-0 {\n        @media (width >=48rem) {\n            margin-bottom: calc(var(--spacing) * 0);\n        }\n    }\n\n    .md\\:w-1\\/2 {\n        @media (width >=48rem) {\n            width: calc(1/2 * 100%);\n        }\n    }\n\n    .md\\:w-auto {\n        @media (width >=48rem) {\n            width: auto;\n        }\n    }\n\n    .md\\:text-right {\n        @media (width >=48rem) {\n            text-align: right;\n        }\n    }\n\n    .md\\:text-6xl {\n        @media (width >=48rem) {\n            font-size: var(--text-6xl);\n            line-height: var(--tw-leading, var(--text-6xl--line-height));\n        }\n    }\n\n    .lg\\:pl-20 {\n        @media (width >=64rem) {\n            padding-left: calc(var(--spacing) * 20);\n        }\n    }\n\n    .xl\\:mx-auto {\n        @media (width >=80rem) {\n            margin-inline: auto;\n        }\n    }\n\n    .xl\\:mb-0 {\n        @media (width >=80rem) {\n            margin-bottom: calc(var(--spacing) * 0);\n        }\n    }\n\n    .xl\\:block {\n        @media (width >=80rem) {\n            display: block;\n        }\n    }\n\n    .xl\\:flex {\n        @media (width >=80rem) {\n            display: flex;\n        }\n    }\n\n    .xl\\:hidden {\n        @media (width >=80rem) {\n            display: none;\n        }\n    }\n\n    .xl\\:inline-block {\n        @media (width >=80rem) {\n            display: inline-block;\n        }\n    }\n\n    .xl\\:w-2\\/3 {\n        @media (width >=80rem) {\n            width: calc(2/3 * 100%);\n        }\n    }\n\n    .dark\\:bg-gray-600 {\n        @media (prefers-color-scheme: dark) {\n            background-color: var(--color-gray-600);\n        }\n    }\n}\n\n@property --tw-translate-x {\n    syntax: \"*\";\n    inherits: false;\n    initial-value: 0;\n}\n\n@property --tw-translate-y {\n    syntax: \"*\";\n    inherits: false;\n    initial-value: 0;\n}\n\n@property --tw-translate-z {\n    syntax: \"*\";\n    inherits: false;\n    initial-value: 0;\n}\n\n@property --tw-rotate-x {\n    syntax: \"*\";\n    inherits: false;\n}\n\n@property --tw-rotate-y {\n    syntax: \"*\";\n    inherits: false;\n}\n\n@property --tw-rotate-z {\n    syntax: \"*\";\n    inherits: false;\n}\n\n@property --tw-skew-x {\n    syntax: \"*\";\n    inherits: false;\n}\n\n@property --tw-skew-y {\n    syntax: \"*\";\n    inherits: false;\n}\n\n@property --tw-border-style {\n    syntax: \"*\";\n    inherits: false;\n    initial-value: solid;\n}\n\n@property --tw-font-weight {\n    syntax: \"*\";\n    inherits: false;\n}\n\n@property --tw-shadow {\n    syntax: \"*\";\n    inherits: false;\n    initial-value: 0 0 #0000;\n}\n\n@property --tw-shadow-color {\n    syntax: \"*\";\n    inherits: false;\n}\n\n@property --tw-shadow-alpha {\n    syntax: \"<percentage>\";\n    inherits: false;\n    initial-value: 100%;\n}\n\n@property --tw-inset-shadow {\n    syntax: \"*\";\n    inherits: false;\n    initial-value: 0 0 #0000;\n}\n\n@property --tw-inset-shadow-color {\n    syntax: \"*\";\n    inherits: false;\n}\n\n@property --tw-inset-shadow-alpha {\n    syntax: \"<percentage>\";\n    inherits: false;\n    initial-value: 100%;\n}\n\n@property --tw-ring-color {\n    syntax: \"*\";\n    inherits: false;\n}\n\n@property --tw-ring-shadow {\n    syntax: \"*\";\n    inherits: false;\n    initial-value: 0 0 #0000;\n}\n\n@property --tw-inset-ring-color {\n    syntax: \"*\";\n    inherits: false;\n}\n\n@property --tw-inset-ring-shadow {\n    syntax: \"*\";\n    inherits: false;\n    initial-value: 0 0 #0000;\n}\n\n@property --tw-ring-inset {\n    syntax: \"*\";\n    inherits: false;\n}\n\n@property --tw-ring-offset-width {\n    syntax: \"<length>\";\n    inherits: false;\n    initial-value: 0px;\n}\n\n@property --tw-ring-offset-color {\n    syntax: \"*\";\n    inherits: false;\n    initial-value: #fff;\n}\n\n@property --tw-ring-offset-shadow {\n    syntax: \"*\";\n    inherits: false;\n    initial-value: 0 0 #0000;\n}\n\n@property --tw-blur {\n    syntax: \"*\";\n    inherits: false;\n}\n\n@property --tw-brightness {\n    syntax: \"*\";\n    inherits: false;\n}\n\n@property --tw-contrast {\n    syntax: \"*\";\n    inherits: false;\n}\n\n@property --tw-grayscale {\n    syntax: \"*\";\n    inherits: false;\n}\n\n@property --tw-hue-rotate {\n    syntax: \"*\";\n    inherits: false;\n}\n\n@property --tw-invert {\n    syntax: \"*\";\n    inherits: false;\n}\n\n@property --tw-opacity {\n    syntax: \"*\";\n    inherits: false;\n}\n\n@property --tw-saturate {\n    syntax: \"*\";\n    inherits: false;\n}\n\n@property --tw-sepia {\n    syntax: \"*\";\n    inherits: false;\n}\n\n@property --tw-drop-shadow {\n    syntax: \"*\";\n    inherits: false;\n}\n\n@property --tw-drop-shadow-color {\n    syntax: \"*\";\n    inherits: false;\n}\n\n@property --tw-drop-shadow-alpha {\n    syntax: \"<percentage>\";\n    inherits: false;\n    initial-value: 100%;\n}\n\n@property --tw-drop-shadow-size {\n    syntax: \"*\";\n    inherits: false;\n}\n\n@property --tw-duration {\n    syntax: \"*\";\n    inherits: false;\n}\n\n@layer properties {\n    @supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))) {\n\n        *,\n        ::before,\n        ::after,\n        ::backdrop {\n            --tw-translate-x: 0;\n            --tw-translate-y: 0;\n            --tw-translate-z: 0;\n            --tw-rotate-x: initial;\n            --tw-rotate-y: initial;\n            --tw-rotate-z: initial;\n            --tw-skew-x: initial;\n            --tw-skew-y: initial;\n            --tw-border-style: solid;\n            --tw-font-weight: initial;\n            --tw-shadow: 0 0 #0000;\n            --tw-shadow-color: initial;\n            --tw-shadow-alpha: 100%;\n            --tw-inset-shadow: 0 0 #0000;\n            --tw-inset-shadow-color: initial;\n            --tw-inset-shadow-alpha: 100%;\n            --tw-ring-color: initial;\n            --tw-ring-shadow: 0 0 #0000;\n            --tw-inset-ring-color: initial;\n            --tw-inset-ring-shadow: 0 0 #0000;\n            --tw-ring-inset: initial;\n            --tw-ring-offset-width: 0px;\n            --tw-ring-offset-color: #fff;\n            --tw-ring-offset-shadow: 0 0 #0000;\n            --tw-blur: initial;\n            --tw-brightness: initial;\n            --tw-contrast: initial;\n            --tw-grayscale: initial;\n            --tw-hue-rotate: initial;\n            --tw-invert: initial;\n            --tw-opacity: initial;\n            --tw-saturate: initial;\n            --tw-sepia: initial;\n            --tw-drop-shadow: initial;\n            --tw-drop-shadow-color: initial;\n            --tw-drop-shadow-alpha: 100%;\n            --tw-drop-shadow-size: initial;\n            --tw-duration: initial;\n        }\n    }\n}\n"
  },
  {
    "path": "examples/scripts/scrape_examples.rs",
    "content": "use std::path::PathBuf;\n\nstruct Example {\n    name: String,\n    path: PathBuf,\n}\n\nfn main() {\n    let dir = PathBuf::from(\"/Users/jonathankelley/Development/dioxus/examples\");\n    let out_file = PathBuf::from(\"/Users/jonathankelley/Development/dioxus/target/decl.toml\");\n\n    let mut out_items = vec![];\n\n    // Iterate through the sub-directories of the examples directory\n    for dir in dir.read_dir().unwrap().flatten() {\n        // For each sub-directory, walk it too, collecting .rs files\n        let Ok(dir) = dir.path().read_dir() else {\n            continue;\n        };\n\n        for dir in dir.flatten() {\n            let path = dir.path();\n            if path.extension().and_then(|s| s.to_str()) == Some(\"rs\") {\n                let file_stem = path.file_stem().and_then(|s| s.to_str()).unwrap();\n                let workspace_path = path\n                    .strip_prefix(\"/Users/jonathankelley/Development/dioxus\")\n                    .unwrap();\n                out_items.push(Example {\n                    name: file_stem.to_string(),\n                    path: workspace_path.to_path_buf(),\n                });\n            }\n        }\n    }\n\n    let mut out_toml = String::new();\n    out_items.sort_by(|a, b| a.path.cmp(&b.path));\n\n    for item in out_items {\n        out_toml.push_str(&format!(\n            \"[[example]]\\nname = \\\"{}\\\"\\npath = \\\"{}\\\"\\ndoc-scrape-examples = true\\n\\n\",\n            item.name,\n            item.path.display()\n        ));\n    }\n\n    std::fs::write(out_file, out_toml).unwrap();\n}\n"
  },
  {
    "path": "flake.nix",
    "content": "{\n  inputs = {\n    nixpkgs.url = \"github:nixos/nixpkgs/nixpkgs-unstable\";\n    flake-parts.url = \"github:hercules-ci/flake-parts\";\n    systems.url = \"github:nix-systems/default\";\n\n    rust-overlay.url = \"github:oxalica/rust-overlay\";\n    # crane.url = \"github:ipetkov/crane\";\n    # crane.inputs.nixpkgs.follows = \"nixpkgs\";\n  };\n\n  outputs =\n    inputs:\n    inputs.flake-parts.lib.mkFlake { inherit inputs; } {\n      systems = import inputs.systems;\n\n      perSystem =\n        {\n          config,\n          self',\n          pkgs,\n          lib,\n          system,\n          ...\n        }:\n        let\n          rustToolchain = pkgs.rust-bin.stable.latest.default.override {\n            extensions = [\n              \"rust-src\"\n              \"rust-analyzer\"\n              \"clippy\"\n            ];\n          };\n          rustBuildInputs = [\n            pkgs.openssl\n            pkgs.libiconv\n            pkgs.pkg-config\n          ]\n          ++ lib.optionals pkgs.stdenv.isLinux [\n            pkgs.glib\n            pkgs.gtk3\n            pkgs.libsoup_3\n            pkgs.webkitgtk_4_1\n            pkgs.xdotool\n          ]\n          ++ lib.optionals pkgs.stdenv.isDarwin [\n            pkgs.apple-sdk\n            pkgs.libiconv\n          ];\n\n          cargoToml = builtins.fromTOML (builtins.readFile ./Cargo.toml);\n\n          rustPackage =\n            package:\n            {\n              binary ? package,\n              features ? [ ],\n            }:\n            (pkgs.makeRustPlatform {\n              cargo = rustToolchain;\n              rustc = rustToolchain;\n            }).buildRustPackage\n              {\n                pname = package;\n                version = cargoToml.package.version;\n                src = pkgs.lib.cleanSource ./.;\n                cargoLock.lockFile = ./Cargo.lock;\n                buildInputs = rustBuildInputs;\n                nativeBuildInputs = [\n                  rustToolchain\n                  pkgs.pkg-config\n                ];\n                buildPhase = ''\n                  mkdir -p .cargo\n                  cp ${./Cargo.lock} Cargo.lock\n                  cargo build --release --package ${package} ${\n                    lib.concatStringsSep \" \" (map (f: \"--features ${f}\") features)\n                  }\n                '';\n                installPhase = ''\n                  mkdir -p $out/bin\n                  ls -alR target/release\n                  cp target/release/${binary} $out/bin/\n                '';\n                doCheck = false; # Disable tests to avoid building deps for them\n              };\n\n          # This is useful when building crates as packages\n          # Note that it does require a `Cargo.lock` which this repo does not have\n          # craneLib = (inputs.crane.mkLib pkgs).overrideToolchain rustToolchain;\n        in\n        {\n          _module.args.pkgs = import inputs.nixpkgs {\n            inherit system;\n            overlays = [\n              inputs.rust-overlay.overlays.default\n            ];\n          };\n\n          packages.dioxus-cli = (\n            rustPackage \"dioxus-cli\" {\n              binary = \"dx\";\n              features = [ \"no-downloads\" ];\n            }\n          );\n\n          devShells.default = pkgs.mkShell {\n            name = \"dioxus-dev\";\n            buildInputs = rustBuildInputs;\n            nativeBuildInputs = [\n              # Add shell dependencies here\n              rustToolchain\n            ];\n            shellHook = ''\n              # For rust-analyzer 'hover' tooltips to work.\n              export RUST_SRC_PATH=\"${rustToolchain}/lib/rustlib/src/rust/library\";\n            '';\n          };\n        };\n    };\n}\n"
  },
  {
    "path": "lychee.toml",
    "content": "# This is the configuration file for lychee, a link validator.\nexclude = ['file:///', 'https://github.com/DioxusLabs/dioxus/commit']\nexclude_path = ['target']\nno_progress = false\ncache = true\nmax_cache_age = \"10d\"\naccept = [\"200..=204\", \"429\"]\n"
  },
  {
    "path": "notes/CONTRIBUTING.md",
    "content": "# Contributing\n\nOn Linux, install the following packages:\n\n```bash\nsudo apt install libgdk3.0-cil libatk1.0-dev libcairo2-dev libpango1.0-dev libgdk-pixbuf2.0-dev libsoup-3.0-dev libjavascriptcoregtk-4.1-dev libwebkit2gtk-4.1-dev\n```\n\nThen run:\n\n```bash\ncargo test --workspace --tests\n```\n"
  },
  {
    "path": "notes/FAQ.md",
    "content": "\n### Aren't VDOMs just pure overhead? Why not something like Solid or Svelte?\nRemember: Dioxus is a library - not a compiler like Svelte. Plus, the inner VirtualDOM allows Dioxus to easily port into different runtimes, support SSR, and run remotely in the cloud. VDOMs tend to more ergonomic to work with and feel roughly like natural Rust code. The overhead of Dioxus is **extraordinarily** minimal... sure, there may be some overhead but on an order of magnitude lower than the time required to actually update the page.\n\n\n### Isn't the overhead for interacting with the DOM from Wasm too much?\nThe overhead layer between Wasm and JS APIs is extremely poorly understood. Rust web benchmarks typically suffer from differences in how Rust and JS cache strings. In Dioxus, we solve most of these issues and our JS Framework Benchmark actually beats the Wasm Bindgen benchmark in many cases. Compared to a \"pure vanilla JS\" solution, Dioxus adds less than 5% of overhead and takes advantage of batched DOM manipulation.\n\n### Aren't Wasm binaries too huge to deploy in production?\nWasm binary sizes are another poorly understood characteristic of Rust web apps. 50kb of Wasm and 50kb of JS are _not_ made equally. In JS, the code must be downloaded _first_ and _then_ JIT-ted. Just-in-time compiling 50kb of JavaScript takes a while which is why 50kb of JavaScript sounds like a lot! However, with Wasm, the code is downloaded and JIT-ted _simultaneously_ through the magic of streaming compilation. By the time the 50kb of Rust is finished downloading, it is already ready to go. Again, Dioxus beats out many benchmarks with time-to-interactivity.\n\nFor reference, Dioxus `hello-world` clocks in at around 70kb gzip or 60kb brotli, and Dioxus supports SSR.\n\n### Why hooks? Why not MVC, classes, traits, messages, etc?\nThere are plenty Rust Elm-like frameworks in the world - we were not interested in making another! Instead, we borrowed hooks from React. JS and Rust share many structural similarities, so if you're comfortable with React, then you'll be plenty comfortable with Dioxus.\n\n### Why a custom DSL? Why not just pure function calls?\nThe `RSX` DSL is _barely_ a DSL. Rustaceans will find the DSL very similar to simply assembling nested structs, but without the syntactical overhead of \"Default\" everywhere or having to jump through hoops with the builder pattern. Between RSX, HTML, the Raw Factory API, and the NodeBuilder syntax, there's plenty of options to choose from.\n\n### What are the build times like? Why on earth would I choose Rust instead of JS/TS/Elm?\ndx builds as roughly as fast as a complex WebPack-TypeScript site. Compile times will be slower than an equivalent TypeScript site, but not unbearably slow. The Wasm compiler backend for Rust is very fast. Iterating on small components is basically instant and larger apps takes a few seconds. In practice, the compiler guarantees of Rust balance out the rebuild times.\n\n### What about Yew/Seed/Sycamore/Dominator/Dodrio/Percy?\n- Yew and Seed use an Elm-like pattern and don't support SSR or any alternate rendering platforms\n- Sycamore and Dominator are more like SolidJS/Svelte, requiring no VDOM but has less naturally-Rusty state management\n- Percy isn't quite mature yet\n- Dodrio is the spiritual predecessor of Dioxus, but is currently an archived research project without the batteries of Dioxus\n\n### How do the mobile and desktop renderers work? Is it Electron?\nCurrently, Dioxus uses your device's native WebView library to draw the page. None of your app code is actually running in the WebView thread, so you can access system resources instead of having to go through something like NodeJS. This means your app will use Safari on macOS/iOS, Edge (Chromium) on Windows, and whatever is the default Web Browser for Linux and Android. Because your code is compiled and running natively, performance is not a problem. You will have to use the various \"Escape Hatches\" to use browser-native APIs (like WebGL) and work around visual differences in how Safari and Chrome render the page.\n\nIn the future, we are interested in using Webrenderer to provide a fully native renderer without having to go through the system WebView library. In practice, Dioxus mobile and desktop are great for CRUD-style apps, but the ergonomic cross-platform APIs (GPS, Camera, etc) are not there yet.\n"
  },
  {
    "path": "notes/RELEASING.md",
    "content": "# Releasing Dioxus\n\nDioxus is big and requires a lot of hand-holding to release. This guide is accurate as of Dec 17, 2024. As we improve the release process, we'll update this guide.\n\n## Checklist\n\nWhat we need to release:\n- [ ] The dioxus crates in the workspace\n- [ ] The docsite and its nightly docs\n- [ ] The VSCode Extension\n- [ ] Any supporting crates (sdk, include_mdbook, etc)\n\n### Prepping for the release\n\nThis involves making sure the metadata of the crates is correct before we publish.\n\n1. [ ] Draft a release post for GitHub (but don't publish it yet)\n2. [ ] Make sure every crate has a *complete* Cargo.toml\n3. [ ] Make sure shared dependencies are hoisted to the workspace\n4. [ ] Pin dependencies that are known to break semver\n5. [ ] Make sure no dependencies are `git` or `path` dependencies - all dependencies should be versioned\n6. [ ] Make sure all workspace members are listed in the members array of the workspace\n7. [ ] Make sure all workspace members are listed in the workspace dependencies list\n8. [ ] Make sure all \"author\" fields are correct\n9. [ ] Make sure no assets or READMEs are pointed to outside their respective crates\n10. [ ] Run `cargo doc --workspace --no-deps --all-features --document-private-items` to make sure docs can compile\n11. [ ] Look through the docs to ensure nothing is glaringly missing or broken\n12. [ ] Ensure the crate actually builds the features listed in its docs.rs platform list (we've had [issues where dioxus doesn't build on wasm with --all-features](https://github.com/DioxusLabs/dioxus/issues/3381))\n13. [ ] Make sure all the workspace crates are actually owned by either Jonathan Kelley or the Dioxus Labs publish org (`cargo owner --add github:dioxuslabs:publish <crate>`)\n14. [ ] If there's *any* crates used by dioxus but not in dioxuslabs/dioxus make sure they're owned by the Dioxus Labs github org in case we need to fix them later\n15. [ ] Run through all the examples and make sure they compile and actually *work*. `cargo run --example <example>` and `dx run <example>` should both work.\n16. [ ] Install the current CLI version and make sure it works (`cargo install --path packages/cli`). Ideally `cargo update` as well since `crates.io` doesn't use a locked install.\n17. [ ] Update the dioxus crate versions to be the intended release version. We set all the versions *manually* instead of relying on `cargo workspaces`. This involves going to `Cargo.toml` and updating the `[workspace.package]` version and each crate's version to the new version in the [workspace.dependencies] section.\n18. [ ] Go to the [template repo](http://github.com/dioxusLabs/dioxus-template) and make sure a branch exists for the new major version. IE a v0.5, v0.6, v0.7, etc branch. If it doesn't exist, create it. This *needs* to exist for `dx new` to work. It's likely that this already exists since we tend to try `dx new` frequently before releasing.\n19. [ ] If performing a major release, make sure all the links in `dioxus` are updated to point to the new version. This involves basically CTRL-F'ing for `/0.6/` and replacing it with `/0.7/` etc.\n20. [ ] It's likely that docsite links might not be updated just yet. If nightly docs are released, there shouldn't need to be any changes. It's fine to bypass the link checker for now, but you should be ready to fix links once the docsite is ready. Any links that are broken are \"frozen in time\" and will need to be fixed. We can't change links in published crates, so if the link never exists, it's just broken forever.\n21. [ ] Inform \"VIP\" community projects that a final RC is out (i.e. projects like [Freya](http://freyaui.dev)) so their authors can test new versions.\n\n### Releasing the workspace:\n\n1. [ ] Make sure all latent commits have been merged into the `main` branch and pushed to github.\n2. [ ] Ensure you've published a pre-release of the same code (only necessary for major releases... patch releases are generally fine to skip a prerelease)\n3. [ ] Make sure the version you're releasing is correct (see above)\n4. [ ] Make sure you're on the `main` branch (cargo workspaces publish requires you to be on the main branch)\n5. [ ] Make sure you have [`cargo-workspaces` installed](https://crates.io/crates/cargo-workspaces). There are other tools but this one is the one we've used in the past. It has some small bugs but is generally reliable. This tool is important because it coordinates the release order of the crates since they depend on each other. Eventually cargo itself will have this functionality, I believe. Unfortunately, there's no way to \"dry-run\" a workspace publish since crates rely on each other and won't succeed if their dependencies aren't published.\n6. [ ] Run the release: `cargo workspaces publish --publish-as-is --allow-dirty --no-git-push --no-individual-tags`. This will publish the crates to crates.io. It might take a while. Only `jkelleyrtp` currently has sufficient rate-limits to publish all the crates at once. If any crate fails, you might need to fix the problems manually and then run the command again. If an error occurs, you might also need to reset the most recent git commit and wipe the tag. `git reset --hard HEAD~1` and `git tag -d <tag>`. Be careful with these commands, especially if you're on the `main` branch.\n7. [ ] Once the release is up, commit the most recent changes to the `main` branch and push it.\n8. [ ] Also push the tag to the `main` branch, e.g. `git push origin v0.6.0`\n9. [ ] Verify crates.io is showing the new version\n10. [ ] Verify `docs.rs` builds the new docs for each crate. IE go to `https://docs.rs/crate/dioxus/latest` and ensure there's no errors. We've had issues before with docs.rs [not building properly](https://docs.rs/crate/dioxus/0.6.0).\n11. [ ] Verify you can create a new project with the new version and it works. IE `dx new app`. Do a dry-run of building a new app to make sure it works and no obvious errors are present.\n12. [ ] Release the GitHub release using the tag we pushed earlier.\n13. [ ] Execute the [`Publish CLI` github action](https://github.com/DioxusLabs/dioxus/actions/workflows/publish.yml) using a manual trigger. Fill in the small form with the appropriate information. This should be the version you just released IE `v0.6.1`. The corresponding github release post must exist for the binstall to be published! You need to be part of the dioxuslabs/publish org to trigger this action.\n14. [ ] If you're about to start working on a \"dev\" version of Dioxus, create a new branch for the last version that we backport fixes to. IE the dioxus repo has a v0.4, v0.5, v0.6, etc branch. We generally only create this branch when we're ready to start merging breaking PRs.\n\n### Releasing the docsite\n\n1. [ ] Stabilize the current docs version on the docsite. This will be manual, unfortunately, and involves changing URLs in the navbar and the sidebar. See [this PR for an example for v0.6](https://github.com/DioxusLabs/docsite/pull/342).\n2. [ ] Make sure to update the \"current version\" and any stability bools in the version switcher.\n3. [ ] Update the `deploy.yml` to point to the new binstall version\n4. [ ] Remove any latent `git` or `path` dependencies and `crates.io` patches.\n5. [ ] todo: discourage any old versions of dioxus from ending up in robots.txt - ideally for better AI support\n6. [ ] Move any non-working examples to the relevant `examples/untested` folder.\n7. [ ] Update the current `dioxus` version in Cargo.toml (use the docsite as a canary)\n8. [ ] run `dx serve` and make sure it works\n9. [ ] Commit to main and ensure the build and checks CI passes\n10. [ ] Ensure that `ssg` is properly generating *all* the pages. Currently it's flakey and occasionally fails.\n11. [ ] Ensure google analytics is working. Check the console and make sure we haven't \"gone silent.\"\n12. [ ] Double-check that we're generating OpenGraph images. Twitter/Discord/Reddit/etc will use these images.\n\n### Releasing the vscode extension\n\nThis is very manual. In theory it can be automated but I've struggled with the azure portal in the past.\n\n1. [ ] Update the version in `package.json` and `package-lock.json`\n2. [ ] Make sure `wasm-bindgen` is installed to current version (`cargo binstall wasm-bindgen-cli`)\n3. [ ] Run `npm install` to make sure you install all the dependencies\n4. [ ] run `npm run vsix` to create the vsix file\n5. [ ] Either use `vsce publish` or manually upload the vsix file to the marketplace\n\n### Releasing ecosystem crates\n\nThere are a number of crates that are part of the Dioxus ecosystem that we usually want ready before publishing to reddit/twitter/youtube/etc.\n\n1. [ ] [dioxus-sdk](https://github.com/DioxusLabs/sdk): folks use this and we like to release it with the rest of the ecosystem\n2. [ ] [blitz](https://github.com/DioxusLabs/blitz): integrates with dioxus-native and thus dioxus-native needs to be version matched\n3. [ ] [taffy](https://github.com/DioxusLabs/taffy): usually exists on its own version system and is used by major projects like Bevy and Zed\n4. [ ] [icons](https://github.com/dioxus-community/dioxus-free-icons): a community crate that is usually updated by marc\n5. [ ] [use-mounted](https://crates.io/crates/dioxus-use-mounted): a community crate that is usually updated by marc\n6. [ ] [resize-observer](https://crates.io/crates/dioxus-resize-observer): a community crate that is usually updated by marc\n7. [ ] [charts](https://crates.io/crates/dioxus-charts): a community crate that is usually updated by marc\n8. [ ] [lazy](https://crates.io/crates/dioxus-lazy): a community crate that is usually updated by marc\n9. [ ] [free-icons](https://crates.io/crates/dioxus-free-icons): a community crate that is usually updated by marc\n10. [ ] [query](https://crates.io/crates/dioxus-query): a community crate that is usually updated by marc\n11. [ ] [radio](https://crates.io/crates/dioxus-radio): a community crate that is usually updated by marc\n12. [ ] [i18n](https://crates.io/crates/dioxus-i18n): a community crate that is usually updated by marc\n13. [ ] [clipboard](https://crates.io/crates/dioxus-clipboard): a community crate that is usually updated by marc\n14. [ ] [use-computed](https://crates.io/crates/dioxus-use-computed): a community crate that is usually updated by marc\n15. [ ] there might be more...\n\n\n## Marketing\n\nVerify everything works and is ready to go.\n- [ ] crates.io is up to date\n- [ ] docs.rs is up to date and has no failing builds for major crates (dioxus, dioxus-desktop, etc)\n- [ ] github release post is out\n- [ ] The relevant GitHub tag is created\n- [ ] Any fixes are backported to the relevant branches.\n- [ ] binstalls are published to the github release post.\n- [ ] `dx new app` plus `dx serve` works out of the box. hot-reload works for rsx and assets.\n- [ ] No `wasm-bindgen` errors popup, especially with version bumps.\n- [ ] YouTube video is uploaded\n- [ ] Ensure open-graph images are generated. Ideally we add custom open-graph images per-release.\n\nActually marketing:\n- [ ] Publish to reddit (usually early morning monday)\n- [ ] Publish to twitter\n- [ ] Publish to discord in the release-notes channel\n- [ ] Publish the youtube video\n- [ ] (sometimes) publish to hackernews\n\n## Notes, todos, etc\n\n- We manually bump versions of the crates in the workspace. I don't really trust it to bump automatically, but in theory it is able to do it automatically using the git history. A challenge is crates relying on versions of each other - like core (0.6.2) relying on html (0.6.1)  or something like that. This means our releases release the entire crate with no changes, but it does keep every crate in sync with each other.\n- We need to expand the rate limit on crates.io for `github:dioxuslabs:publish` to be able to publish all the crates at once.\n- We should make stabilizing the docs easier. It's quite manual now.\n- We should automate the release of the vscode extension.\n- We move examples to the `examples/untested` folder when we release a new version. Ideally we have a workspace with all versions of dioxus and can include the examples in the relevant frames.\n- SSG is flakey and occasionally fails. This needs to be fixed.\n- We should promote \"community\" crates to enjoy auto-updates via codemods whenever a new version is released.\n- We should be running `docs` CI using DOCS_RS env var to ensure docs are built the same way as docs.rs\n"
  },
  {
    "path": "notes/SECURITY.md",
    "content": "# Security Policy\n\n## Reporting a Vulnerability\n\nIf you discover a security vulnerability in the Dioxus project, please report it **privately and responsibly** by emailing [security@dioxuslabs.com](mailto:security@dioxuslabs.com). **Do not report security issues publicly on GitHub or through issue trackers**. We take all security reports seriously and will respond promptly.\n\n## Coordinated Vulnerability Response\n\nWhen a security issue is reported, the Dioxus team prioritizes its resolution and coordinates a fix. We may work with affected users, upstream maintainers, and the original reporter to ensure a responsible and timely remediation. We use [GitHub Security Advisories](https://docs.github.com/en/code-security/security-advisories/working-with-repository-security-advisories/about-repository-security-advisories) for secure communication and coordinated disclosure.\n\nIf you're a downstream user or maintainer and believe you're affected, you can request to join the coordination process. Please email us at [security@dioxuslabs.com](mailto:security@dioxuslabs.com) with your:\n\n- Contact email\n- GitHub username(s)\n- Relevant project or ecosystem information\n\nParticipation is granted at the discretion of the Dioxus team.\n\n## Security Advisory Disclosures\n\nWe are committed to being transparent about security issues that affect Dioxus. Once a fix is in place, we announce advisories through:\n\n- [GitHub Release Notes](https://github.com/DioxusLabs/dioxus/releases).\n- The [RustSec Advisory Database](https://github.com/RustSec/advisory-db) (used by tools like `cargo-audit`).\n\nUsers are encouraged to stay up to date with releases and monitor advisories relevant to their projects.\n"
  },
  {
    "path": "notes/architecture/00-OVERVIEW.md",
    "content": "# Dioxus Architecture Overview\n\nThis directory contains comprehensive architecture documentation for the Dioxus framework, designed to help future Claude agents (and human contributors) understand the codebase deeply.\n\n## Document Index\n\n| File | Description |\n|------|-------------|\n| `01-CORE.md` | VirtualDOM, rendering pipeline, component system, diffing algorithm |\n| `02-CLI.md` | Build system, dev server, bundling, terminal UI, platform support |\n| `03-RSX.md` | RSX macro parsing, AST types, code generation, autofmt |\n| `04-SIGNALS.md` | Signals, generational-box, hooks, stores, state management |\n| `05-FULLSTACK.md` | Server functions, SSR, hydration, client-server communication |\n| `06-RENDERERS.md` | Web, desktop, native, liveview renderer implementations |\n| `07-HOTRELOAD.md` | Subsecond hot-patching, RSX hot-reload, devtools protocol |\n| `08-ASSETS.md` | Manganis asset system, const serialization, build-time processing |\n| `09-ROUTER.md` | Routing system, Routable trait, navigation, history |\n| `10-WASM-SPLIT.md` | WASM code splitting, lazy loading, chunk management |\n\n## Crate Dependency Overview\n\n```\ndioxus (umbrella crate)\n├── dioxus-core (VirtualDOM, runtime, scheduler)\n│   └── generational-box (Copy semantics for references)\n├── dioxus-rsx (macro parsing)\n│   └── dioxus-autofmt (code formatting)\n├── dioxus-signals (reactive state)\n│   ├── generational-box\n│   └── dioxus-stores (nested reactivity)\n├── dioxus-hooks (use_signal, use_effect, etc.)\n├── dioxus-html (HTML elements, events)\n├── dioxus-router (client-side routing)\n│\n├── Renderers:\n│   ├── dioxus-web (WASM/browser)\n│   ├── dioxus-desktop (wry/tao webview)\n│   ├── dioxus-native (blitz/vello GPU)\n│   ├── dioxus-liveview (WebSocket streaming)\n│   └── dioxus-ssr (server-side rendering)\n│\n├── Fullstack:\n│   ├── dioxus-fullstack (client/server coordination)\n│   ├── dioxus-fullstack-core (transport, types)\n│   ├── dioxus-fullstack-macro (#[server] macro)\n│   └── dioxus-server (Axum integration)\n│\n├── CLI (dx command):\n│   ├── dioxus-cli (main binary)\n│   ├── dioxus-cli-config (Dioxus.toml parsing)\n│   └── dioxus-cli-opt (optimization passes)\n│\n├── Hot Reload:\n│   ├── subsecond (hot-patching runtime)\n│   ├── dioxus-rsx-hotreload (template diffing)\n│   └── dioxus-devtools (WebSocket communication)\n│\n├── Assets:\n│   ├── manganis (asset!() macro)\n│   ├── manganis-core (asset types)\n│   ├── manganis-macro (proc-macro)\n│   └── const-serialize (compile-time CBOR)\n│\n└── Code Splitting:\n    ├── wasm-splitter (CLI tool)\n    ├── wasm-split-macro (#[wasm_split])\n    └── wasm-split-cli (binary processing)\n```\n\n## Key Architectural Patterns\n\n### 1. Copy Semantics for Reactive State\nDioxus uses `generational-box` to provide `Copy` semantics for references. This enables signals and other reactive primitives to be freely passed around without explicit cloning.\n\n### 2. WriteMutations Trait\nAll renderers implement `WriteMutations` which defines how VirtualDOM changes translate to real DOM operations. This abstraction enables the same component code to run on web, desktop, mobile, and server.\n\n### 3. Template-Based Rendering\nRSX macros generate static `Template` definitions at compile time. Only dynamic parts are diffed at runtime, making updates extremely efficient.\n\n### 4. Hot-Reload Without Full Rebuild\nTwo complementary systems:\n- **RSX Hot-Reload**: Changes to template literals are diffed and sent via WebSocket\n- **Subsecond Hot-Patching**: Full Rust function replacement via jump table indirection\n\n### 5. Compile-Time Asset Processing\nThe `asset!()` macro embeds asset metadata in binary link sections. The CLI extracts this at build time, processes assets, and patches the binary with final URLs.\n\n### 6. Server Functions as RPC\nThe `#[server]` macro generates dual code paths - client serialization and server handler - enabling seamless RPC-style communication.\n\n## Important Design Decisions\n\n1. **Single VirtualDOM**: Even multi-window desktop apps share one VirtualDOM for simpler state management\n2. **Scope-Based Cleanup**: All state (signals, tasks, contexts) is tied to component scope lifetime\n3. **Platform Agnostic Events**: Events are converted through `HtmlEventConverter` trait for cross-platform support\n4. **Binary Protocols**: Desktop and liveview use Sledgehammer binary format for compact mutation encoding\n5. **Generational GC**: References are invalidated by generation counters, preventing use-after-free without runtime overhead per access\n\n## Future Development Areas\n\nThese docs are designed to help with planned features:\n- **Custom Bundlers**: Replacing tauri-bundler with native implementation\n- **CLI Tunnels**: Remote device hot-reloading via SSH/SCP\n- **Workspace Hot-Patching**: Patching library crates, not just the tip crate\n- **Universal Android Builds**: Single APK for all architectures\n- **Fullstack Extensions**: First-party database, auth, IAAC, secrets\n"
  },
  {
    "path": "notes/architecture/01-CORE.md",
    "content": "# Dioxus Core - VirtualDOM Architecture\n\nThe `dioxus-core` crate is the heart of Dioxus, implementing the virtual DOM, component lifecycle, rendering pipeline, and reactive runtime.\n\n## VirtualDom Structure\n\nThe `VirtualDom` struct orchestrates the entire reactive component tree:\n\n```\nVirtualDom\n├── scopes: Slab<ScopeState>        // Arena allocator for all component scopes\n├── dirty_scopes: BTreeSet<ScopeOrder>  // Scopes marked for re-render (height-ordered)\n├── runtime: Rc<Runtime>            // Shared async runtime\n├── resolved_scopes: Vec<ScopeId>   // Scopes resolved during suspense\n└── rx: UnboundedReceiver<SchedulerMsg>  // Event channel from scheduler\n```\n\n### Key Methods\n\n- `VirtualDom::new(app)` - Create new DOM with root component\n- `rebuild()` / `rebuild_in_place()` - Full tree reconstruction\n- `render_immediate()` - Render all dirty scopes without suspense blocking\n- `wait_for_work()` - Async poll for futures and scheduler events\n- `wait_for_suspense()` - Block until all suspended futures complete\n- `mark_dirty(scope_id)` - Schedule scope for re-render\n- `with_root_context<T>()` - Inject dependency into root scope\n\n## Rendering Pipeline\n\n### Initial Render Flow\n1. `rebuild()` calls `run_scope(ScopeId::ROOT)`\n2. Result wraps in `LastRenderedNode` and calls `create_scope()`\n3. `create_scope()` recursively creates all child scopes and DOM elements\n4. Mutations written to `WriteMutations` sink\n\n### Update Render Flow\n1. `wait_for_work()` polls scheduler and pending futures\n2. `process_events()` consumes `SchedulerMsg::Immediate(scope_id)` messages\n3. `render_immediate()` pops dirty scopes in height order (top-to-bottom)\n4. For each scope: `run_and_diff_scope()` executes component, then `diff_scope()`\n5. Diff generates mutations via `WriteMutations` trait\n\n## Component System\n\n### Component Definition\n- `Component<P>` type alias: `fn(P) -> Element`\n- `ComponentFunction<P, M>` trait for component functions\n  - `fn_ptr(&self) -> usize` - Raw function pointer for identity\n  - `rebuild(&self, props: P) -> Element` - Execute component\n\n### Props System\n- `Properties` trait for all props:\n  - `type Builder` - For DSL prop construction\n  - `builder()` - Create props builder\n  - `memoize(&mut self, other: &Self) -> bool` - Check if props changed\n  - `into_vcomponent()` - Convert to VComponent\n\n### Type Erasure\n`VProps<F,P,M>` wraps strongly-typed props, erased to `BoxedAnyProps` via `AnyProps` trait:\n- `render(&self) -> Element`\n- `memoize(&mut self, other: &dyn Any) -> bool`\n- `props()` / `props_mut()` - Access as `dyn Any`\n- `duplicate()` - Clone into new box\n\n## Scope System\n\n### ScopeId\nSmall unique identifier (usize index into Slab):\n- `ScopeId::ROOT` (0) - Root wrapper scope\n- `ScopeId::ROOT_SUSPENSE_BOUNDARY` (1) - Default suspense\n- `ScopeId::ROOT_ERROR_BOUNDARY` (2) - Default error boundary\n- `ScopeId::APP` (3) - User's root component\n\n### ScopeState (Public API)\n```\nScopeState\n├── context_id: ScopeId\n├── last_rendered_node: Option<LastRenderedNode>\n├── props: BoxedAnyProps\n└── reactive_context: ReactiveContext\n```\n\n### Scope (Internal State)\n```\nScope\n├── name: &'static str          // Component name for debugging\n├── id: ScopeId\n├── parent_id: Option<ScopeId>\n├── height: u32                 // Distance from root\n├── hooks: RefCell<Vec<Box<dyn Any>>>  // Hook state storage\n├── hook_index: Cell<usize>     // Current hook being accessed\n├── shared_contexts: RefCell<Vec<Box<dyn Any>>>\n├── spawned_tasks: RefCell<FxHashSet<Task>>\n├── before_render / after_render  // Callbacks\n├── status: RefCell<ScopeStatus>  // Mounted/Unmounted\n└── suspense_boundary: SuspenseLocation\n```\n\n### Context Propagation\n- `provide_context<T: Clone + 'static>(context: T)` - Store in scope\n- `consume_context<T>()` - Retrieve from this scope or any parent\n- Lookup walks parent chain via `parent_id`\n\n## Event System\n\n### Event Structure\n```rust\npub struct Event<T: ?Sized> {\n    pub data: Rc<T>,\n    pub(crate) metadata: Rc<RefCell<EventMetadata>>,\n}\n\npub struct EventMetadata {\n    pub propagates: bool,\n    pub prevent_default: bool,\n}\n```\n\n### Event Flow\n1. `Runtime::handle_event()` looks up element by `ElementId`\n2. Gets parent `ElementRef` from slab\n3. If bubbles: `handle_bubbling_event()` walks parent chain\n4. Collects listeners in path order, calls in reverse (parents first)\n5. Breaks on `stop_propagation()`\n\n## Diffing Algorithm\n\n### Entry Point\n`diff_scope()` gets old/new VNodes and calls `old.diff_node(new, dom, mutations)`\n\n### VNode Diffing\n- **Template Check**: Different templates → replace entire subtree\n- **Identity Check**: Same pointer → skip\n- **Attribute Diffing**: `diff_attributes()` for dynamic attrs\n- **Dynamic Node Diffing**: Recursively diff each dynamic node\n\n### Dynamic Node Cases\n- Text → Text: `diff_vtext()` updates content\n- Placeholder → Placeholder: No-op\n- Fragment → Fragment: Recursively diff children\n- Component → Component: Check render fn, then memoize props\n- Type Change: Remove old, create new\n\n### Keyed List Diffing\n1. **Prefix Pass**: Match keys from start\n2. **Suffix Pass**: Match keys from end\n3. **Middle Pass**: Handle insertions/deletions/moves\n4. Uses `FxHashMap` for old→new key matching\n\n## Mutations System\n\n### WriteMutations Trait\nInterface between VirtualDOM and real DOM:\n- `append_children(id, count)` - Add N nodes to element\n- `assign_node_id(path, id)` - Mark element at template path\n- `create_placeholder(id)` - Create marker node\n- `create_text_node(value, id)` - Create text node\n- `load_template(template, index, id)` - Clone from template cache\n- `replace_node_with(id, count)` - Replace element\n- `set_attribute(name, ns, value, id)` - Update attribute\n- `create_event_listener(name, id)` - Register listener\n- `remove_node(id)` - Delete element\n\n### Mutation Enum (for testing/serialization)\n```rust\npub enum Mutation {\n    AppendChildren { id, m },\n    CreatePlaceholder { id },\n    CreateTextNode { value, id },\n    LoadTemplate { index, id },\n    ReplaceWith { id, m },\n    SetAttribute { name, ns, value, id },\n    // ... etc\n}\n```\n\n## Scheduler\n\n### Work Priority\n1. **Dirty Scopes** (Highest): Re-render in height order\n2. **Tasks**: Poll spawned futures\n3. **Effects** (Lowest): Run after DOM changes\n\n### ScopeOrder\nComposite key: `(height: u32, id: ScopeId)` in `BTreeSet` ensures parent scopes run before children.\n\n### Key Methods\n- `queue_scope(order)` - Add to dirty_scopes\n- `queue_task(task, order)` - Add to dirty_tasks\n- `pop_work()` - Get next dirty scope or task\n- `pop_effect()` - Get next pending effect\n\n## Runtime\n\nManages async/scope/task coordination:\n```\nRuntime\n├── scope_states: RefCell<Vec<Option<Scope>>>\n├── scope_stack: RefCell<Vec<ScopeId>>\n├── suspense_stack: RefCell<Vec<SuspenseLocation>>\n├── tasks: RefCell<SlotMap<DefaultKey, Rc<LocalTask>>>\n├── current_task: Cell<Option<Task>>\n├── dirty_tasks: RefCell<BTreeSet<DirtyTasks>>\n├── pending_effects: RefCell<BTreeSet<Effect>>\n├── rendering: Cell<bool>\n├── sender: UnboundedSender<SchedulerMsg>\n├── elements: RefCell<Slab<Option<ElementRef>>>\n└── mounts: RefCell<Slab<VNodeMount>>\n```\n\n## Tasks & Async\n\n### Task Structure\n- `id: TaskId` (slotmap key)\n- `!Send + !Sync` marker\n\n### Task Methods\n- `Task::new(future)` - Spawn new task\n- `cancel()` - Remove task\n- `pause()` / `resume()` - Control polling\n- `wake()` - Wake sleeping task\n\n### LocalTask (Internal)\n- Wraps `Pin<Box<dyn Future<Output = ()>>>`\n- Custom waker that sends `SchedulerMsg::TaskNotified`\n- Task dropped when owning scope drops\n\n## Effects\n\nEffects run AFTER mutations are applied to DOM:\n1. Created via `use_effect()` in component\n2. Queued in `Runtime::pending_effects`\n3. After render, `finish_render()` sends `SchedulerMsg::EffectQueued`\n4. `poll_tasks()` pops effects and calls `effect.run()`\n\n## Suspense\n\n### SuspenseContext\n- Tracks suspended futures and placeholder nodes\n- `suspended_tasks: RefCell<Vec<SuspendedFuture>>`\n- `suspended_nodes: RefCell<Option<VNode>>`\n- `frozen: Cell<bool>` for server-side locking\n\n### Suspension Flow\n1. Component calls `suspend()` → `Err(SuspendedFuture)`\n2. `Element::Err(RenderError::Suspended)` propagates up\n3. Nearest `SuspenseBoundary` catches it\n4. Boundary renders placeholder\n5. Future added to boundary's tasks\n6. Boundary marked dirty when future completes\n\n## VNodes & Templates\n\n### VNode Structure\n```rust\npub struct VNode {\n    vnode: Rc<VNodeInner>,\n    mount: Cell<MountId>,\n}\n\npub struct VNodeInner {\n    pub key: Option<String>,\n    pub template: Template,\n    pub dynamic_nodes: Box<[DynamicNode]>,\n    pub dynamic_attrs: Box<[Box<[Attribute]>]>,\n}\n```\n\n### Template (Static)\n```rust\npub struct Template {\n    pub roots: &'static [TemplateNode],\n    pub node_paths: &'static [&'static [u8]],\n    pub attr_paths: &'static [&'static [u8]],\n}\n```\n\n### TemplateNode Variants\n- `Element { tag, namespace, attrs, children }` - Static element\n- `Text { text }` - Static text\n- `Dynamic { id }` - Index into dynamic_nodes\n\n### DynamicNode Variants\n- `Component(VComponent)` - Child component\n- `Text(VText)` - Text node\n- `Placeholder(VPlaceholder)` - Suspense/empty marker\n- `Fragment(Vec<VNode>)` - Multiple children\n\n## Error Boundaries\n\n### ErrorContext\n- `error: Rc<RefCell<Option<CapturedError>>>`\n- `subscribers: Subscribers` for listening components\n\n### Error Flow\n1. Component returns `Element::Err(error)`\n2. Nearest `ErrorBoundary` catches\n3. `ErrorContext` stores error\n4. Boundary re-renders with `error_context.error()` available\n\n## Root Wrapper\n\nDefault scope hierarchy:\n```\nScopeId(0): RootScopeWrapper\n└── ScopeId(1): SuspenseBoundary\n    └── ScopeId(2): ErrorBoundary\n        └── ScopeId(3): User's Root App\n```\n\nProvides default suspense and error handling for the entire tree.\n"
  },
  {
    "path": "notes/architecture/02-CLI.md",
    "content": "# Dioxus CLI Architecture\n\nThe `dx` CLI is a comprehensive build tool for Dioxus applications, supporting development, building, bundling, and cross-platform deployment.\n\n## Command Structure\n\nEntry point: `main.rs` with async tokio runtime and `TraceController` for error handling.\n\n### Commands Enum\n- `Serve` - Watch and serve with hot-reloading\n- `Build` - Full production build\n- `Bundle` - Package for distribution\n- `Run` - Run without hot-reload\n- `Create` / `Init` - Project scaffolding\n- `Check` - Lint/validate RSX\n- `Translate` - Convert HTML/JSX to RSX\n- `Autoformat` - Format RSX code\n- `Config` - Manage Dioxus.toml\n- `Doctor` - Diagnose environment\n- `Components` - Component registry\n- `SelfUpdate` - Update CLI\n- `Tools` - Internal tools (build-assets, hotpatch)\n\n## Platform Support\n\n### Platform Enum\n- `Web` - wasm32-unknown-unknown\n- `MacOS` / `Windows` / `Linux` - Desktop webview\n- `Ios` / `Android` - Mobile webview\n- `Server` - SSR/fullstack server\n- `Liveview` - WebSocket streaming\n- `Unknown` - Auto-select\n\n### CommandWithPlatformOverrides\nAllows separate client/server args for fullstack:\n```bash\ndx serve @client --features web @server --features server\n```\n\nDecomposes into client BuildArgs, server BuildArgs, and shared args.\n\n## Build System\n\n### BuildRequest\nThe \"plan\" for a single build:\n```\nBuildRequest\n├── workspace: Workspace metadata\n├── config: DioxusConfig\n├── package: Package details\n├── features: Vec<String>\n├── target: Triple\n├── bundle: BundleFormat\n├── profile: Profile\n├── rustflags: Vec<String>\n└── custom_linker: Option<PathBuf>\n```\n\nResponsible for:\n- Writing build artifacts\n- Processing assets\n- Writing platform-specific metadata (Info.plist, AndroidManifest.xml)\n- Platform-specific linking (Android uses dx as opaque linker, hotpatching uses custom linker)\n\n### BuildMode Enum\n- `Base { run: bool }` - Normal cargo rustc build\n- `Fat` - Full build with all symbols for hot-patching\n- `Thin { rustc_args, changed_files, aslr_reference, cache }` - Fast rebuild\n\n### BuildContext\nRuntime configuration with progress channel:\n- `ProgressTx` for status updates\n- `BuildId` enum: PRIMARY (client), SECONDARY (server)\n- Helper methods: `compiling()`, `linking()`, `bindgen()`, etc.\n\n### AppBuilder\nState machine managing ongoing builds:\n- Build task handle and artifacts\n- App child process (stdout/stderr)\n- Hot-patching state (patches, module cache, ASLR offset)\n- Build metadata: stage, compiled crates, timing\n\n## Dev Server (Serve)\n\n### AppServer (runner.rs)\nPrimary orchestration:\n```\nAppServer\n├── workspace: Workspace metadata\n├── client / server: AppBuilder instances\n├── watcher: File system watcher\n├── file_map: Cached RSX files for hot-reload diffing\n├── devserver: WebServer\n└── flags: use_hotpatch_engine, automatic_rebuilds, hot_reload, etc.\n```\n\n### WebServer (server.rs)\nHTTP + WebSocket server providing:\n- **Hot-reload socket** - RSX and asset updates\n- **Build-status socket** - Compilation progress\n- **HTTP server** - Static assets (public folder, WASM)\n- **Proxy support** - Fullstack backend\n- **DevTools** - Connected clients with build_id, ASLR reference, PID\n\n### Output (output.rs)\nTerminal UI with ratatui:\n- Build progress with crate-by-crate counts\n- Asset copying progress\n- Colored output, throbber animations\n- Keyboard input (r=rebuild, p=pause, v=verbose)\n- 100ms tick-based rendering\n\n### File Change Handling\n```\nFile modified → detect type\n├── Rust file → diff RSX using dioxus_rsx_hotreload::diff_rsx()\n│   ├── Extract changed templates\n│   ├── Build HotReloadMsg\n│   └── Send via WebSocket\n├── Asset file → copy to bundle, signal reload\n└── If can't hot-reload → queue full rebuild\n```\n\n### serve_all() Main Loop\nUses `tokio::select!` to multiplex:\n- Builder updates (compilation, process output)\n- WebServer events (connections, messages)\n- Output (TUI updates, input)\n- TraceController (panic handling)\n- File watcher (filesystem changes)\n\n## Asset Processing\n\n### Pipeline (build/assets.rs)\n1. **Manganis Integration** - Extract `__ASSETS__` symbols from binary\n2. **Asset Hashing** - Compute hashes for cache-busting\n3. **Two Format Support** - Legacy (0.7.0-0.7.1) and New (0.7.2+) serialization\n4. **Platform Paths** - Assets relative to executable; Android uses \"asset root\"\n\n### Processing by Type\n- **Images**: Resize, convert format (Avif, WebP), optimize\n- **CSS/JS**: Minify, hash-suffix naming\n- **Folders**: Recursive copy\n- **Unknown**: Direct copy with optional hash\n\n## Bundling\n\n### Bundle System\nPlatform-specific packaging:\n- **Web/Server**: No special bundling, outputs to web/ directory\n- **Desktop**: Uses tauri-bundler (dmg, msi, appimage)\n- **iOS**: App bundle format with codesigning warnings\n- **Android**: Runs `gradle bundleRelease` for AAB\n\n### BundleConfig\n```\nBundleConfig\n├── identifier: String\n├── publisher: String\n├── icon: Vec<PathBuf>\n├── resources: Vec<PathBuf>\n├── deb / macos / windows / android: Platform-specific\n```\n\n## Hot-Patching\n\n### HotpatchModuleCache\nCaches compiled dependency symbols:\n- Symbol table with function/data info\n- Pre-computed for Fat builds\n- Reused in Thin builds for fast linking\n\n### Patch Creation Flow\n1. Compile app with Fat mode (all symbols)\n2. On change: compile Thin (small, fast)\n3. Analyze binary diff → create JumpTable\n4. Apply patches to running executable\n5. If patch fails → fall back to full rebuild\n\n### Platform Implementations\n- `create_windows_jump_table()` - x86/x64 jump stubs\n- `create_native_jump_table()` - macOS/Linux function overrides\n- `create_wasm_jump_table()` - WASM indirect call table updates\n\n## Configuration\n\n### DioxusConfig (Dioxus.toml)\n```toml\n[application]\nasset_dir = \"assets\"\nout_dir = \"dist\"\ntailwind_input/output = \"...\"\nios_info_plist = \"Info.plist\"\nandroid_manifest = \"AndroidManifest.xml\"\n\n[web]\napp.title = \"My App\"\nhttps.enabled = true\npre_compress = false\n\n[bundle]\nidentifier = \"com.example.app\"\npublisher = \"Company\"\n```\n\n### Resolution Order\n1. Default values\n2. Dioxus.toml in project root\n3. CLI argument overrides\n4. Environment variables\n\n## Workspace\n\n### Workspace Struct (Singleton)\n```\nWorkspace\n├── krates: Krates (cargo metadata, dependency graph)\n├── settings: CliSettings (user settings ~/.config/dioxus/)\n├── sysroot: Rust target sysroot\n├── rustc_version: Version\n├── wasm_opt: Optional path\n├── cargo_toml: Parsed Cargo.toml\n└── android_tools: Optional NDK/SDK\n```\n\nLazy-loaded via `Workspace::current()` with tokio Mutex.\n\n## Extension Points\n\n### Adding New Bundlers\n1. New variant in `BundleFormat` enum\n2. New variant in `Renderer` enum\n3. New config struct (e.g., `MyPlatformSettings`)\n4. Implementation in `Bundle::bundle()`\n5. Asset directory structure definition\n6. Linking strategy if needed\n\n### Adding New Commands\n1. New variant in `Commands` enum\n2. New command module in `cli/`\n3. Register in `TraceController::main()` match\n4. Optional `StructuredOutput` variant for results\n\n### Customizing Build Hooks\n- Pre/post-build scripts in Dioxus.toml\n- Custom linker integration\n- Asset transformation pipeline\n\n## Key Patterns\n\n### Channel-Based Progress\n```\nBuild Task → BuilderUpdate Channel → AppBuilder → ServeUpdate → Output TUI\n```\nDecouples build process from UI for real-time updates.\n\n### Lazy Workspace Loading\nSingle workspace load with tokio Mutex, shows spinner if cargo-metadata takes >1 second.\n\n### Renderer/Bundle Inference\n1. Explicit flags: `--web`, `--webview`, `--native`\n2. Platform aliases: `--ios`, `--android`, `--desktop`\n3. Feature detection from Cargo.toml\n4. Default: Infer from target triple\n"
  },
  {
    "path": "notes/architecture/03-RSX.md",
    "content": "# RSX and Autofmt Architecture\n\nThe `dioxus-rsx` crate parses JSX-like syntax into Rust code, while `dioxus-autofmt` provides formatting capabilities.\n\n## RSX Parsing\n\n### Entry Point: CallBody\nRoot struct for `rsx! {}` macro contents:\n```\nCallBody\n├── TemplateBody (list of BodyNode roots)\n├── template_idx: Cell<usize>\n└── span: Option<Span>\n```\n\n`CallBody::new()` initializes template indices and cascades hotreload info through nested structures.\n\n### BodyNode Enum\nSix variants representing RSX content:\n- `Element(Element)` - HTML elements (div, span)\n- `Component(Component)` - User components\n- `Text(TextNode)` - String literals with interpolation\n- `RawExpr(ExprNode)` - Braced expressions `{expr}`\n- `ForLoop(ForLoop)` - `for pat in expr { body }`\n- `IfChain(IfChain)` - `if cond { } else { }`\n\n### Parsing Priority\n1. Peek for LitStr → TextNode\n2. Peek for `for` → ForLoop\n3. Peek for `if` → IfChain\n4. Peek for `match` → RawExpr\n5. Peek for Brace → RawExpr\n6. Web components: Ident + `-` → Element\n7. Lowercase ident → Element\n8. Otherwise → Component (fallback)\n\n## Key AST Types\n\n### Element\n```\nElement\n├── name: ElementName (Ident or Custom)\n├── raw_attributes: Vec<Attribute>\n├── merged_attributes: Vec<Attribute>  // After combining duplicates\n├── spreads: Vec<Spread>\n├── children: Vec<BodyNode>\n├── brace: Option<Brace>\n└── diagnostics: Diagnostics\n```\n\n`merge_attributes()` collapses duplicate attribute names using `IfmtInput` with space delimiter.\n\n### Attribute\n```\nAttribute\n├── name: AttributeName (BuiltIn, Custom, or Spread)\n├── value: AttributeValue\n├── colon: Option<Token![:]>\n├── dyn_idx: DynIdx\n└── el_name: Option<ElementName>\n```\n\n### AttributeValue Variants\n- `Shorthand(Ident)` - attribute without value\n- `AttrLiteral(HotLiteral)` - hotreloadable literal\n- `EventTokens(PartialClosure)` - event handlers\n- `IfExpr(IfAttributeValue)` - conditional attributes\n- `AttrExpr(PartialExpr)` - arbitrary expressions\n\n### HotLiteral\n```\nHotLiteral\n├── Fmted(HotReloadFormattedSegment)  // \"{expr}\" interpolation\n├── Float(LitFloat)\n├── Int(LitInt)\n└── Bool(LitBool)\n```\n\n### IfmtInput (Formatted Strings)\nParses string contents into segments:\n- `Segment::Literal(String)` - plain text\n- `Segment::Formatted(FormattedSegment)` - `{expr}` interpolation\n\nParsing rules:\n- `{{` → literal `{`\n- `}}` → literal `}`\n- `{expr}` → formatted segment\n- `{expr:format_args}` → formatted with format spec\n\n### Component\n```\nComponent\n├── name: syn::Path\n├── generics: Option<AngleBracketedGenericArguments>\n├── fields: Vec<Attribute>\n├── component_literal_dyn_idx: Vec<DynIdx>\n├── spreads: Vec<Spread>\n├── children: TemplateBody\n├── dyn_idx: DynIdx\n└── diagnostics: Diagnostics\n```\n\n### ForLoop\n```\nForLoop\n├── for_token, pat, in_token\n├── expr: Box<Expr>\n├── body: TemplateBody\n└── dyn_idx: DynIdx\n```\nGenerates: `(expr).into_iter().map(|pat| { body })`\n\n### IfChain\n```\nIfChain\n├── if_token, cond: Box<Expr>\n├── then_branch: TemplateBody\n├── else_if_branch: Option<Box<IfChain>>\n├── else_branch: Option<TemplateBody>\n└── dyn_idx: DynIdx\n```\n\n### DynIdx\n`Cell<Option<usize>>` for tracking dynamic indices. Transparent in PartialEq/Eq/Hash. Used for hot-reload mapping.\n\n## Code Generation\n\n### TemplateBody → VNode::Template\nGenerated output structure:\n1. `__TEMPLATE_ROOTS` - Static array of TemplateNode\n2. **Dynamic nodes** - Components, interpolated text, loops, conditionals\n3. **Dynamic attributes** - Non-static attribute values\n4. **Dynamic literal pool** - In debug, vec of formatted values\n5. **Dynamic value pool** - Maps literal indices to values\n\n### Template Structure\n```rust\ndioxus_core::Element::Ok({\n    #[cfg(debug_assertions)]\n    fn __original_template() -> &'static HotReloadedTemplate { ... }\n\n    let __dynamic_nodes: [DynamicNode; N] = [ ... ];\n    let __dynamic_attributes: [Box<[Attribute]>; M] = [ ... ];\n    static __TEMPLATE_ROOTS: &[TemplateNode] = &[ ... ];\n\n    // Template reference and rendering\n})\n```\n\n### Generated TemplateNode Types\n- `Element { tag, namespace, attrs, children }` - Static element\n- `Text { text }` - Static text\n- `Dynamic { id }` - References dynamic node pool\n\n## Template System\n\n### TemplateBody Structure\n```\nTemplateBody\n├── roots: Vec<BodyNode>\n├── template_idx: DynIdx\n├── node_paths: Vec<Vec<u8>>      // Path to each dynamic node\n├── attr_paths: Vec<(Vec<u8>, usize)>  // Path and attribute index\n├── dynamic_text_segments: Vec<FormattedSegment>\n└── diagnostics: Diagnostics\n```\n\n### Template ID Assignment\n- `CallBody::next_template_idx()` generates sequential IDs\n- Each nested structure gets unique ID\n- Combined with `file!()`, `line!()`, `column!()` for source location\n\n### HotReloadFormattedSegment\nWraps `IfmtInput` with `dynamic_node_indexes: Vec<DynIdx>`:\n- One DynIdx per `Segment::Formatted` entry\n- Maps formatted segments to dynamic nodes during hot-reload\n\n## Autofmt System\n\n### Entry Points\n- `try_fmt_file(contents, &syn::File, IndentOptions)` → Vec<FormattedBlock>\n- `fmt_block(block_str, indent_level, IndentOptions)` → Option<String>\n- `write_block_out(body)` → Option<String>\n\n### FormattedBlock\n```\nFormattedBlock\n├── formatted: String\n├── start: usize (byte offset)\n└── end: usize (byte offset)\n```\n\n### Writer State\n```\nWriter\n├── raw_src: &str\n├── src: Vec<&str>  // Lines\n├── out: Buffer\n├── cached_formats: HashMap<LineColumn, String>\n└── invalid_exprs: Vec<Span>\n```\n\n### Buffer\n```\nBuffer\n├── buf: String\n├── indent_level: usize\n└── indent: IndentOptions\n```\n\n### Optimization Levels\n1. **Empty**: `div {}` (no space inside)\n2. **Oneliner**: `div { class: \"x\", child {} }` (single line)\n3. **PropsOnTop**: Props multiline, children follow\n4. **NoOpt**: Everything multiline\n\n### Short-Circuit Optimization\n```rust\nif formatted.len() <= 80\n    && !formatted.contains('\\n')\n    && !body_is_solo_expr\n    && !formatted.trim().is_empty()\n{\n    formatted = format!(\" {formatted} \");  // Collapse to single line\n}\n```\n\n## Whitespace Handling\n\nWhitespace is significant in RSX:\n- Text nodes preserve exact whitespace\n- Comments must be preserved\n- Formatting must not change text node content\n\n### Comment Preservation\n- `write_comments()` accumulates full-line comments before spans\n- `write_inline_comments()` preserves end-of-line comments\n- Comments tracked via `LineColumn` from Span\n\n## Expression Formatting\n\n### write_partial_expr() Strategy\n1. Use `prettier_please` to unparse expressions\n2. Handle nested rsx! macros specially\n3. `unparse_expr()` visits macro calls:\n   - Format nested rsx! blocks\n   - Replace macros with marker unicode\n   - Apply formatted blocks back\n\n### Marker Replacement\nUses marker `\"𝕣𝕤𝕩\"` to replace macros during unparse, avoiding conflicts with actual code.\n\n## Indentation System\n\n### IndentOptions\n```\nIndentOptions\n├── width: usize\n├── indent_string: String (\"\\t\" or spaces)\n└── split_line_attributes: bool\n```\n\n### Functions\n- `indent_str()` → returns indent string\n- `count_indents(line)` → estimates indent level\n- `line_length(line)` → estimates visible length\n\n## Attribute Formatting\n\n### write_attributes(props_same_line)\n- `true`: attrs on one line with spaces\n- `false`: each attr on new line, indented\n\n### write_attribute_value()\n- `Shorthand` → just ident\n- `AttrLiteral` → uses Display impl\n- `EventTokens` → write_partial_expr()\n- `IfExpr` → write_attribute_if_chain()\n- `AttrExpr` → write_partial_expr()\n\n## Extension Points\n\n### Adding New Node Types\n1. Add variant to BodyNode enum\n2. Implement Parse trait\n3. Implement ToTokens for code gen\n4. Add write_* method to Writer\n\n### Adding New Attribute Types\n1. Add variant to AttributeValue\n2. Implement Parse for detection\n3. Add merge handling if needed\n4. Add write_attribute_value case\n\n### Changing Formatting Heuristics\n- Modify ShortOptimization logic\n- Adjust threshold constants (80, 100 chars)\n- Modify estimation functions\n"
  },
  {
    "path": "notes/architecture/04-SIGNALS.md",
    "content": "# Signals and State Management\n\nDioxus state management is built on a layered architecture: generational-box for memory, signals for reactivity, and stores for nested data.\n\n## Generational Box\n\n### Core Concept\nProvides `Copy` semantics for references through generation-based validation:\n```\nGenerationalBox<T, S>\n├── raw: GenerationalPointer<S>\n│   ├── storage: &'static S\n│   └── location: GenerationalLocation\n│       ├── generation: NonZeroU64\n│       └── created_at: &'static Location (debug)\n└── _marker: PhantomData<T>\n```\n\n### Memory Management\n\n**Allocation:**\n```rust\npub trait Storage<Data> {\n    fn new(value: Data, caller: Location) -> GenerationalPointer<Self>;\n    fn new_rc(value: Data, caller: Location) -> GenerationalPointer<Self>;\n}\n```\n\n**Storage Variants:**\n- `UnsyncStorage` - Single-threaded, uses `RefCell` (fast, no locking)\n- `SyncStorage` - Multi-threaded, uses `RwLock` (thread-safe)\n\n**StorageEntry:**\n```\nStorageEntry<Data>\n├── generation: NonZeroU64\n├── refcount: u32\n└── data: Data  // Empty, Data, Rc, or Reference\n```\n\n### Generation Tracking\n1. Caller provides pointer with generation X\n2. Storage checks: `entry.generation == X`\n3. If yes: data valid, proceed\n4. If no: return `BorrowError::Dropped`\n\nWhen dropped: generation incremented, data cleared, existing pointers invalidated.\n\n### Owner Pattern\n```rust\npub struct Owner<S> {\n    owned: Vec<GenerationalPointer<S>>,\n}\n\nimpl<S> Drop for Owner<S> {\n    fn drop(&mut self) {\n        for location in self.owned.drain(..) {\n            location.recycle();  // Invalidates all pointers\n        }\n    }\n}\n```\n\n## Signals\n\n### Architecture\n```\nSignal<T, S>\n└── inner: CopyValue<SignalData<T>, S>\n    └── SignalData<T>\n        ├── value: T\n        └── subscribers: Arc<Mutex<HashSet<ReactiveContext>>>\n```\n\n### Signal Types\n\n**Signal<T, S>** - Primary mutable reactive primitive:\n- Implements `Readable` for subscribed reads\n- Implements `Writable` for reactive updates\n- `.read()` subscribes current scope\n- `.peek()` reads without subscribing\n\n**Memo<T>** - Derived reactive value:\n- Wraps `Signal<T>` for value storage\n- Contains `UpdateInformation` tracking dirty state\n- Lazy evaluation: only recomputes when dependencies change\n- Uses `PartialEq` to skip updates if unchanged\n\n**CopyValue<T, S>** - Generic mutable wrapper:\n- Lower-level than Signal\n- No built-in reactivity\n- Building block for Signal\n\n**Global<T, R>** - Lazy singleton:\n- Created once per app on first access\n- Stored in `ScopeId::ROOT` context\n- Uses `InitializeFromFunction` trait\n\n**ReadSignal<T>** / **WriteSignal<T>** - Type-erased boxed signals:\n- Store `Box<dyn DynReadable>`\n- Flexible APIs accepting any readable type\n\n**MappedSignal<O, V, F>** - Derived readonly:\n- Maps inner signal through function F\n- `signal.map(|x| &x.field)`\n\n### Reactivity System\n\n**Automatic Subscription:**\n```\nsignal.read()\n  → try_read_unchecked()\n  → Check ReactiveContext::current()\n  → If exists: reactive_context.subscribe(signal.subscribers)\n  → Later writes call mark_dirty() on all subscribers\n```\n\n**Update Propagation:**\n```\nsignal.write()\n  → WriteLock created\n  → User modifies value\n  → WriteLock dropped → SignalSubscriberDrop::drop()\n  → signal.update_subscribers():\n    → Get subscriber snapshot (brief lock)\n    → Call mark_dirty() on each\n    → Re-extend subscriber list\n```\n\n### Global Signals\n\n**GlobalLazyContext:**\n```\nGlobalLazyContext\n└── map: Rc<RefCell<HashMap<GlobalKey, Box<dyn Any>>>>\n```\n\n**Resolution:**\n1. Static const defines `GlobalSignal<T>`\n2. First access calls `resolve()`:\n   - Check HashMap by key\n   - If found: return clone\n   - If not: run constructor in ROOT scope\n   - Store result\n3. Subsequent accesses return same instance\n\n**Key Types:**\n- `GlobalKey::File { file, line, column, index }`\n- `GlobalKey::Raw(&'static str)`\n\n## Hooks\n\n### Core Hook Pattern\n```rust\n#[track_caller]\npub fn use_hook<T>(f: impl FnOnce() -> T) -> T {\n    let component_id = current_scope_id();\n    let mut hooks = get_hooks(component_id);\n\n    if hooks.len() <= hook_index {\n        hooks.push(Box::new(f()));  // First render\n    }\n    hooks[hook_index].clone()  // Return existing\n}\n```\n\n### Hook Types\n\n**use_signal<T>() -> Signal<T>**\n- Creates local signal owned by component\n- State persists across renders\n\n**use_memo<R>() -> Memo<R>**\n- Creates memoized computation\n- Reruns when read signals change\n\n**use_effect(callback)**\n- Runs side effects when dependencies change\n- Creates `ReactiveContext` to track reads\n- Queues effect rerun on dependency notification\n\n**use_resource<T, F>(future_fn) -> Resource<T>**\n- Async state management\n- Watches dependencies, reruns future when they change\n- Returns `Resource<T>` with value, state, task\n\n**use_callback<I, O>() -> Callback<I, O>**\n- Memoized callback\n- Prevents unnecessary closures\n\n**use_coroutine(init)**\n- Long-lived task\n- Spawned once at component creation\n\n**use_context<T>() -> T**\n- Retrieves ancestor context\n\n### Hook Rules\n1. Must call same hooks every render\n2. Must call in same order\n3. Order determines hook identity\n4. Breaking rules causes panic or bugs\n\n## Stores\n\n### Purpose\nSignals work for scalar state. Stores provide:\n- Granular reactivity per field\n- Lazy signal creation\n- Ergonomic field access\n\n### Store Architecture\n```\nStore<T, Lens>\n└── selector: SelectorScope<Lens>\n    ├── subscriptions: StoreSubscriptions\n    ├── path: TinyVec<u16>\n    └── value: Lens\n```\n\n### Subscription Tree\n```\nStoreSubscriptions\n└── inner: CopyValue<StoreSubscriptionsInner>\n    └── root: SelectorNode\n        ├── subscribers: HashSet<ReactiveContext>\n        └── root: HashMap<PathKey, SelectorNode>\n            └── [0] → SelectorNode\n            └── [1] → SelectorNode\n```\n\n### Path Tracking\n```rust\nlet store = Store::new(vec![a, b, c]);\nlet item_1 = store[1];    // path = [1]\nlet field = item_1.name;  // path = [1, field_hash]\n\n// Writing store[1].name only marks dirty:\n// - subscribers at path [1]\n// - subscribers at path [1, field_hash]\n// - NOT path [0] or [2]\n```\n\n### Store Macro\n```rust\n#[derive(Store)]\nstruct TodoItem {\n    checked: bool,\n    contents: String,\n}\n\n// Generates:\npub trait TodoItemStoreExt<__Lens> {\n    fn checked(self) -> Store<bool, __Lens::MappedSignal>;\n    fn contents(self) -> Store<String, __Lens::MappedSignal>;\n    fn transpose(self) -> TodoItemStoreTransposed;\n}\n\npub struct TodoItemStoreTransposed {\n    pub checked: Store<bool>,\n    pub contents: Store<String>,\n}\n```\n\n### Enum Support\n```rust\n#[derive(Store)]\nenum Status {\n    Loading,\n    Ready(String),\n    Error(String),\n}\n\n// Generates:\nfn is_loading(self) -> bool;\nfn ready(self) -> Option<Store<String, ...>>;\nfn transpose(self) -> StatusStoreTransposed;\n```\n\n## Key Traits\n\n### Readable\n```rust\npub trait Readable {\n    type Target;\n    type Storage;\n    fn try_read_unchecked(&self) -> Result<ReadableRef<T>>;\n    fn try_peek_unchecked(&self) -> Result<ReadableRef<T>>;\n    fn subscribers(&self) -> Subscribers;\n}\n```\n\n### Writable\n```rust\npub trait Writable: Readable {\n    type WriteMetadata;\n    fn try_write_unchecked(&self) -> Result<WritableRef<T>>;\n}\n```\n\n### Storage (AnyStorage)\n```rust\npub trait AnyStorage {\n    type Ref<'a, T>: Deref<Target = T>;\n    type Mut<'a, T>: DerefMut<Target = T>;\n    fn map<T, U>(ref_: Ref<T>) -> Ref<U>;\n    fn map_mut<T, U>(mut_ref: Mut<T>) -> Mut<U>;\n}\n```\n\n## Comparison\n\n| Feature | Signal | Memo | Store |\n|---------|--------|------|-------|\n| Mutability | Mutable | Read-only | Mutable |\n| Dependencies | None | Tracks reads | Implicit via paths |\n| Granularity | Single value | Computation | Per-field |\n| When to use | Direct state | Derived values | Nested structures |\n| Subscriptions | Simple HashSet | Via context | Tree structure |\n| Performance | O(1) update | O(deps) recompute | O(path) update |\n\n## Memory Model\n\n1. **Heap**: Values in storage singletons per-scope\n2. **Stack**: Only pointers (GenerationalBox) in components\n3. **Cleanup**: Owner drops when scope dies\n4. **Validity**: Generation checking on each access\n5. **Sharing**: Arc+Mutex for subscribers, Rc+RefCell for ownership\n"
  },
  {
    "path": "notes/architecture/05-FULLSTACK.md",
    "content": "# Dioxus Fullstack Architecture\n\nThe fullstack ecosystem enables seamless client-server communication through server functions, SSR, and hydration.\n\n## Server Functions (#[server])\n\n### Macro Generation\n\nThe `#[server]` macro generates dual code paths:\n\n**On Client:**\n```rust\n// Creates request encoding/decoding\nlet client = ClientRequest::new(Method::POST, endpoint, &query);\nlet response = ServerFnEncoder::fetch_client(client, args, unpack).await;\nlet result = ServerFnDecoder::decode_client_response(response).await;\n```\n\n**On Server:**\n```rust\n// Creates Axum handler\nfn __inner__function__(state, request) -> Response {\n    // Extract arguments from request\n    // Call actual function\n    // Serialize response\n}\n\n// Register globally\ninventory::submit! {\n    ServerFunction::new(Method::POST, path, handler)\n}\n```\n\n### Configuration Options\n- `endpoint` - Custom URL path prefix (default: `/api`)\n- `input` - Request encoding (Json, Cbor, MessagePack)\n- `output` - Response encoding\n- `middleware` - Tower middleware layers\n- Server-only extractors: `headers: HeaderMap`, `cookies: Cookies`\n\n### Wire Protocol\n\n**Request:**\n- Method: POST\n- URL: `/api/{function_name}?{query_params}`\n- Content-Type: application/json\n- Body: JSON-serialized struct with arguments\n\n**Response:**\n```rust\nenum RestEndpointPayload<T, E> {\n    Success(T),\n    Error(ErrorPayload<E>),\n}\n\nstruct ErrorPayload<E> {\n    message: String,\n    code: u16,\n    data: Option<E>,\n}\n```\n\n### Handler Registration\n1. `ServerFunction` struct holds metadata\n2. `inventory::submit!` for compile-time registration\n3. `ServerFunction::collect()` returns all handlers\n4. `DioxusRouterExt::register_server_functions()` registers on Axum\n\n## SSR (Server-Side Rendering)\n\n### SsrRendererPool\n- Thread-safe pool of `dioxus_ssr::Renderer` instances\n- Manages concurrent rendering\n- Optional incremental cache for static generation\n\n### Rendering Pipeline\n1. Create VirtualDom with component function\n2. Render to HTML string\n3. Inject hydration data and scripts\n4. Stream chunks via futures channel\n\n### Streaming Modes\n\n**Disabled (default):**\n- All server futures resolved before sending\n- Simple, SEO-friendly\n\n**OutOfOrder:**\n- Initial chunk sent with placeholders\n- Suspense boundaries stream independently\n- Uses `StreamingRenderer`\n\n**Streaming Chunk Structure:**\n```html\n<!-- Initial -->\n<div>Header</div>\n<div>Loading...</div>\n\n<!-- Later -->\n<script>window.dx_hydrate(mount_id, \"data\");</script>\n<div hidden id=\"ds-1-r\">Resolved content</div>\n```\n\n## Hydration\n\n### Server-Side\n1. `HydrationContext` created at render start\n2. Hooks serialize data: `use_server_future`, `use_server_cached`, `use_loader`\n3. Data stored with unique IDs\n4. Serialized to base64-encoded CBOR\n5. Injected as `window.__DIOXUS_HYDRATION_DATA__`\n\n### Client-Side\n1. JavaScript reads hydration data from HTML\n2. Runtime provides `HydrationContext` to components\n3. Components call same hooks, check for cached data\n4. Data deserialized and populated\n5. Event listeners attached without re-rendering\n\n### Key Types\n- `SerializeContextEntry<T>` - Entry in hydration context\n- `SerializedHydrationData` - Base64-encoded CBOR\n- `TakeDataError` - Why data couldn't be retrieved\n\n## Server Architecture\n\n### FullstackState\n- Wraps `FullstackContext` and runtime handle\n- Passed as State to Axum handlers\n- Allows request context access\n\n### DioxusRouterExt Trait\nExtension methods on `axum::Router`:\n- `serve_static_assets()` - Serves `/public`\n- `serve_dioxus_application(cfg, component)` - Full SSR + server functions\n- `register_server_functions()` - Registers collected handlers\n- `serve_api_application(cfg, component)` - API-only\n\n### Handler Wrapping\n```\nHandler wrapped in:\n1. FullstackContext::scope() - provides context\n2. LocalPool::spawn_pinned() - enables !Send futures\n3. Middleware layers\n4. Response header injection\n```\n\n### FullstackContext\nTask-local context providing:\n- Request headers via RwLock\n- Streaming status (RenderingInitialChunk, Committed, Complete)\n- Response headers\n- HTTP status codes\n- Reactive subscriptions\n\n### ServeConfig\n- `index` - Custom IndexHtml\n- `incremental` - ISR cache\n- `context_providers` - Injectable contexts\n- `streaming_mode` - Disabled or OutOfOrder\n\n## Client-Server Communication\n\n### ClientRequest\n```\nClientRequest\n├── url: String\n├── method: Method\n├── headers: HeaderMap\n└── extensions: Extensions\n```\n\n### Encoding Traits\n- `EncodeRequest<In, Out, R>` - Serializes and makes request\n- `IntoRequest<R>` - Converts to request\n- `Encoding` trait - Defines format (Json, Cbor)\n\n### Suspense Integration\n- Server functions automatically suspend\n- `use_server_future` - Waits on server, caches for client\n- `use_loader` - Enhanced error handling\n- `use_server_cached` - Caches computed values\n\n### Error Handling\n- Server errors serialized with message, code, details\n- `ServerFnError` enum standardizes representation\n- `AsStatusCode` trait maps to HTTP status\n- Supports `anyhow::Error`, `StatusCode`, custom types\n\n## Extension Points\n\n### Adding Database Support\n```rust\nconfig.provide_context(|| {\n    Box::new(Database::new()) as Box<dyn Any>\n});\n\n#[server]\nasync fn get_user(id: i32) -> Result<User> {\n    let db = consume_context::<Database>();\n    db.query(...).await\n}\n```\n\n### Adding Authentication\n```rust\n#[server(auth: AuthExtractor)]\nasync fn protected(data: String, auth: AuthExtractor) -> Result<String> {\n    // auth extracted from request\n}\n```\n\nOr via middleware:\n```rust\n#[server]\n#[middleware(AuthLayer::new())]\nasync fn protected_fn() -> Result<String> { }\n```\n\n### Custom Encoding\n```rust\npub struct CustomEncoding;\nimpl Encoding for CustomEncoding {\n    fn content_type() -> &'static str { \"application/custom\" }\n    fn encode(data: impl Serialize, buf: &mut Vec<u8>) -> Option<usize> { }\n    fn decode<O: DeserializeOwned>(bytes: Bytes) -> Option<O> { }\n}\n\n#[server(input = CustomEncoding, output = CustomEncoding)]\nasync fn my_fn(arg: String) -> Result<String> { }\n```\n\n### Response Customization\n```rust\n#[server]\nasync fn custom_response() -> Result<String> {\n    let ctx = FullstackContext::current().unwrap();\n    ctx.set_response_headers(headers);\n    Ok(\"data\".to_string())\n}\n```\n\n## Data Flow\n\n### Server Function Call\n```\nClient Component\n  → Call #[server] fn\n  → Create ClientRequest, serialize args\n  → HTTP POST /api/function_name\n  → Server Router\n  → FullstackContext::scope()\n  → Extract args, call function\n  → Serialize Result\n  → HTTP Response\n  → Client deserialize\n  → Component receives value\n```\n\n### SSR Hydration\n```\nServer:\n  Page Request → VirtualDom::new()\n  → HydrationContext created\n  → Render components\n  → use_server_future serializes results\n  → Inject __DIOXUS_HYDRATION__ script\n  → HTTP 200 + HTML\n\nClient:\n  Receive HTML\n  → Parse hydration data\n  → VirtualDom::new()\n  → HydrationContext populated\n  → Components render, hooks get cached data\n  → Event listeners attached\n  → Hydration complete\n```\n\n## Key Patterns\n\n### Inventory System\nGlobal registration via `inventory::submit!` at compile time. Enables dynamic discovery without explicit registration.\n\n### Magic Trait Deref\nMultiple `&` deref layers select correct trait impl:\n- Prefers `FromRequest` over `DeserializeOwned`\n- Prefers `FromResponse` over serialization\n\n### Hash-based Routing\nRoutes hashed using xxhash64 of module path, preventing collisions with same-name functions in different modules.\n\n### Error Type Tiering\nAuto-deref tiers implementations:\n1. Typed error struct\n2. ServerFnError wrapper\n3. anyhow::Error\n"
  },
  {
    "path": "notes/architecture/06-RENDERERS.md",
    "content": "# Dioxus Renderers Architecture\n\nDioxus supports multiple rendering backends through a common trait-based abstraction. Each renderer implements the same interfaces to enable cross-platform component reuse.\n\n## Core Abstraction\n\n### WriteMutations Trait\nThe bridge between VirtualDOM and real DOM implementations:\n- `append_children(id, count)` - Add N nodes to element\n- `assign_node_id(path, id)` - Mark element at template path\n- `create_placeholder(id)` - Create marker node\n- `create_text_node(value, id)` - Create text node\n- `load_template(template, index, id)` - Clone from template cache\n- `replace_node_with(id, count)` - Replace element\n- `set_attribute(name, ns, value, id)` - Update attribute\n- `create_event_listener(name, id)` - Register listener\n- `remove_node(id)` - Delete element\n\n### HtmlEventConverter Trait\nMaps platform-specific events to standardized Dioxus types:\n- Each renderer provides its own implementation\n- Converts `PlatformEventData` (renderer-specific) to typed event data\n- Enables event polymorphism across platforms\n\n### Event Flow Pattern\n```\nPlatform Event → Captured by renderer\n    → Converted via HtmlEventConverter\n    → runtime.handle_event(name, event, element_id)\n    → VirtualDOM handlers invoked\n    → State changes → Re-render\n    → WriteMutations applied\n```\n\n## Web Renderer (dioxus-web)\n\n### WebsysDom Structure\n```\nWebsysDom\n├── interpreter: Sledgehammer JS interpreter\n├── document: web_sys::Document reference\n├── root: Root DOM node\n├── templates: HashMap<Template, u16>\n├── runtime: Rc<Runtime>\n└── (hydration): skip_mutations, suspense_hydration_ids\n```\n\n### Mutation Implementation\n- Directly delegates to JavaScript interpreter via wasm-bindgen\n- Templates serialized once, stored in JS, instantiated by reference\n- Sledgehammer interpreter maintains stack of nodes being constructed\n- Binary protocol enables efficient mutation batching\n\n### Event Handling\n- Single delegated listener on root element\n- Event.target walked up DOM tree for `data-dioxus-id` attribute\n- `WebEventConverter` converts web_sys events to Dioxus types\n- Supports all standard DOM events (mouse, keyboard, touch, etc.)\n\n### Hydration System\n1. SSR server renders HTML with `dio_el` data attributes\n2. Client receives base64-encoded hydration context\n3. VirtualDOM rebuilt with `skip_mutations = true`\n4. Client traverses pre-rendered DOM assigning element IDs\n5. Streaming hydration for suspense boundaries via `rehydrate_streaming()`\n\n### Launch Flow\n```\nlaunch(root_component, contexts, config)\n  → Create VirtualDom\n  → Create WebsysDom wrapper\n  → If hydrate: Deserialize data, rebuild with skip_mutations\n  → Otherwise: vdom.rebuild(&mut websys_dom)\n  → Main loop: wait_for_work() → render_immediate() → flush_edits()\n```\n\n## Desktop Renderer (dioxus-desktop)\n\n### Wry/Tao Integration\nUses Wry webview library with Tao window management.\n\n### App Structure\n```\nApp\n├── unmounted_dom: Cell<Option<VirtualDom>>\n├── webviews: HashMap<WindowId, WebviewInstance>\n├── shared: Rc<SharedContext>\n│   ├── event_handlers: WindowEventHandlers\n│   ├── pending_webviews: Vec<PendingWebview>\n│   ├── shortcut_manager: ShortcutRegistry\n│   └── websocket: EditWebsocket\n└── control_flow: ControlFlow\n```\n\n### WebviewEdits\nImplements WriteMutations for wry-based rendering:\n- Delegates to `WryQueue` managing mutation batch\n- WebSocket server on random port for mutation transmission\n- Binary protocol via Sledgehammer interpreter\n\n### IPC (Interprocess Communication)\n```\nBrowser event → JavaScript → window.postMessage()\n    → Wry intercepts request\n    → Extract dioxus-data header (base64 JSON)\n    → IpcMessage { method, params }\n    → Handle: UserEvent, Query, BrowserOpen, Initialize\n```\n\n### Protocol Handler\n- `dioxus://` custom protocol for asset serving\n- Handles `__events` path for event processing\n- `__file_dialog` for file selection\n- Custom handler namespaces for user-provided handlers\n- Checks `dioxus_asset_resolver` for bundled assets\n\n### Native Features\n- Menu integration via muda crate\n- System tray via trayicon\n- Global hotkeys via global_hotkey crate\n- File dialogs and drag-drop support\n- Headless mode for testing\n\n### Configuration\n```\nConfig\n├── WindowBuilder customization\n├── Custom event loop\n├── Protocols and async protocols\n├── Pre-rendered HTML template\n├── Context menu disable flag\n├── Background color (RGBA)\n└── Devtools support toggle\n```\n\n## Native Renderer (dioxus-native)\n\n### Blitz Integration\n- Blitz layout engine for CSS layout\n- Vello for GPU-accelerated vector rendering\n- Winit for cross-platform window management\n- Not a browser engine - custom native rendering pipeline\n\n### DioxusNativeWindowRenderer\n```\nDioxusNativeWindowRenderer\n├── anyrender-vello wrapper\n├── WindowRenderer trait implementation\n├── GPU features configurable\n└── Custom paint support\n```\n\n### Rendering Pipeline\n```\nVirtualDOM components\n    → DioxusNativeDOM\n    → Blitz DOM tree with CSS\n    → Blitz layout engine\n    → Vello renderer\n    → GPU rendering\n```\n\n### Layout and CSS\n- CSS 2.1+ parser (not full CSS 3)\n- Flexbox-based layout model\n- Computed styles attached to element nodes\n- Layout computed bottom-up during mutation\n\n### Application Handler (Winit)\n```\nEvent::NewEvents(StartCause::Init)\n    → Create initial window\n    → DioxusDocument with VirtualDOM\n\nResumed → Renderer.resume()\n\nWindowEvent::RedrawRequested\n    → VirtualDOM.render_immediate()\n    → Mutations applied to Blitz DOM\n    → Layout computed\n    → Render frame\n\nWindowEvent::Resized → Queue redraw\n```\n\n## LiveView Renderer (dioxus-liveview)\n\n### Server-Rendered Architecture\n```\nClient (Browser with WebSocket)\n    ↓ events\nServer (VirtualDOM runs here)\n    ↓ mutations (binary protocol)\nClient receives mutations → Sledgehammer applies\n```\n\n### LiveViewPool\n- Thread pool using `tokio_util::task::LocalPoolHandle`\n- Each client spawns pinned task\n- VirtualDOM runs on task's executor\n- Pool handles multiple concurrent clients\n\n### Per-Client Lifecycle\n```\nLiveViewPool::launch_virtualdom(ws, || VirtualDom::new())\n    → Create MutationState, QueryEngine\n    → vdom.rebuild() → Send initial HTML\n    → Loop:\n        tokio::select! {\n            ws.next() → Handle event/query\n            vdom.wait_for_work() → Has work\n            query_rx.recv() → JS query\n            hot_reload_rx.recv() → Hot reload\n        }\n        render_immediate() → Send mutations\n```\n\n### Binary Protocol\n```\nMutationState::write_memory_into(&mut bytes)\n    → Sledgehammer binary encoding\n    → WebSocket transmission\n    → Client: window.interpreter.handleEdits(bytes)\n```\n\n### Message Types\n1. **Binary Frames** (mutations)\n2. **Text Frames** (queries/metadata)\n3. **Incoming Events** (user interactions)\n\n### Mounted Element Queries\n```\nLiveviewElement methods:\n    scroll_offset() → JS: getScrollLeft/Top()\n    scroll_size() → JS: getScrollWidth/Height()\n    client_rect() → JS: getBoundingClientRect()\n    scroll_to(options) → JS: element.scrollTo()\n```\n\n## Interpreter Package (dioxus-interpreter-js)\n\n### Sledgehammer Framework\nUltra-compact binary protocol for DOM mutations shared across renderers.\n\n### Core Components\n1. **INTERPRETER_JS** - Base interpreter class\n2. **NATIVE_JS** - Platform-specific extensions\n3. **SLEDGEHAMMER_JS** - Generated bindings from Rust\n\n### MutationState\n- Implements WriteMutations for binary serialization\n- Channel-based mutation recording\n- Template caching and deduplication\n\n### Stack Machine Operations\n```\ncreate_text_node(value, id) → Push text node\ncreate_placeholder(id) → Push comment node\nappend_children(parent_id, count) → Pop and append\nreplace_with(id, count) → Replace element\nset_attribute(id, name, value, ns) → Set DOM attribute\nnew_event_listener(name, id, bubbles) → Register listener\n```\n\n## Common Patterns\n\n### 1. WriteMutations Implementation\nEvery renderer implements the trait differently:\n- **Web**: Delegates to Sledgehammer JS\n- **Desktop**: Accumulates in MutationState, sends via WebSocket\n- **Native**: Applies directly to Blitz DOM\n- **Liveview**: Accumulates and sends via WebSocket\n\n### 2. Configuration Pattern\nRenderers use `Box<dyn Any>` for extensible config:\n```rust\nlaunch(app, configs: Vec<Box<dyn Any>>)\n    → For each config: try downcast to expected type\n```\n\n### 3. Lazy Initialization\nMost renderers delay context setup until first window/request.\n\n### 4. Event Loop Integration\n- **Web**: WASM bindgen + browser event loop\n- **Desktop**: Tao event loop with UserWindowEvent\n- **Native**: Winit ApplicationHandler\n- **Liveview**: Tokio select! macro\n\n### 5. Mutation Batching\nAll non-web renderers batch mutations for efficiency:\n- Accumulate in temporary structure\n- Periodically flush to transport\n\n## Adding a New Renderer\n\n1. **Implement WriteMutations**\n   - Define how platform applies DOM-like mutations\n   - Handle template system\n   - Manage stack machine state\n\n2. **Implement HtmlEventConverter**\n   - Map platform events to Dioxus types\n   - Handle serialization if needed\n\n3. **Create Launch Function**\n   - Create VirtualDOM\n   - Initialize renderer structures\n   - Enter platform event loop\n   - Call `vdom.render_immediate(&mut mutations)`\n\n4. **Define Configuration**\n   - Create Config struct\n   - Support `Box<dyn Any>` downcasting\n\n5. **Optional Enhancements**\n   - Custom mount event data\n   - Document integration\n   - History support\n   - Asset serving\n"
  },
  {
    "path": "notes/architecture/07-HOTRELOAD.md",
    "content": "# Hot-Reload and Hot-Patching Architecture\n\nDioxus supports two complementary hot-reload systems: RSX template hot-reload for UI changes, and Subsecond hot-patching for Rust code changes.\n\n## Subsecond Hot-Patching\n\n### Jump Table Architecture\n\nSubsecond implements hot-patching through **jump table indirection**:\n\n1. All hot-reloadable functions called through `subsecond::call()` or `HotFn::current()`\n2. Runtime looks up function pointer in global `APP_JUMP_TABLE`\n3. Jump table points to latest compiled version\n4. When patch applied, only jump table is updated\n5. Original executable remains untouched\n\n**Advantage**: Decouples running binary from patched code. Safe memory model.\n\n### Patch Application Flow\n\n```\n1. ThinLink compiles only modified functions → patch dylib\n2. Patch sent via devtools WebSocket\n3. subsecond::apply_patch() loads via libloading::Library\n4. Base address calculated using main as anchor\n5. Jump table updated with old→new address mappings\n```\n\n### ASLR Handling\n\nOperating systems randomize memory addresses (ASLR), so compiled addresses don't match runtime.\n\n**Solution**:\n1. Running app captures real address of `main` via `dlsym()`/`GetProcAddress()`\n2. ASLR reference sent to devserver\n3. Devserver computes offset: `old_offset = aslr_reference - table.aslr_reference`\n4. All jump table addresses adjusted by offset\n5. Patch library base address calculated similarly\n6. Final: `(old_address + old_offset) → (new_address + new_offset)`\n\n### Thread-Local Storage (TLS)\n\nTLS presents challenges for hot-patching:\n\n- **Globals and statics**: Generally supported\n- **Thread-locals in tip crate**: Reset to initial value on new patches\n- **Thread-locals in dependencies**: Work correctly (not re-patched)\n\n**Why**: New thread-local variables exist in patch library's TLS segment, separate from main executable's TLS. Subsecond doesn't rebind TLS accesses.\n\n### Limitations\n\n1. **Struct changes**: Not supported - size/alignment changes cause crashes\n2. **Pointer versioning**: Not implemented - all function pointers considered \"new\"\n3. **Workspace support**: Only tip crate patches, library crates ignored\n4. **Static initializers**: Changes not observed\n5. **Global destructors**: Newly added globals have destructors that never run\n\n### Platform Support\n\n- **Desktop**: Linux, macOS, Windows (x86_64, aarch64)\n- **Mobile**: Android (arm64-v8a, armeabi-v7a), iOS Simulator\n- **Web**: WASM32 (limited module reloading)\n- **Not supported**: iOS device (code-signing)\n\n## RSX Hot Reload\n\n### Hot Literal System\n\nRSX hot reload is **orthogonal** to subsecond. While subsecond reloads Rust functions, RSX hot reload handles template literal values:\n\n**Hot-reloadable**:\n- Formatted segments: `\"{variable}\"`\n- Literal component properties: `Component { value: 123 }`\n- Dynamic text nodes: `\"{expression}\"`\n\n**Not hot-reloadable**:\n- Rust code changes\n- Component structure changes\n- Control flow changes\n\n### Template Diffing Algorithm\n\n**Conservative approach**: If Rust code changes, not hot-reloadable.\n\n```\n1. Parse old and new files\n2. Extract all rsx! macro invocations\n3. Replace all rsx! bodies with empty rsx! {}\n4. Remove doc comments\n5. Compare modified files\n6. If identical → Rust unchanged → proceed with template diff\n7. If different → requires full rebuild\n```\n\n### Dynamic Pool System\n\nThree pools of reusable items from last build:\n1. **Dynamic text segments**: `\"{class}\"`, `\"{id}\"`\n2. **Dynamic nodes**: Components, for loops, if chains\n3. **Dynamic attributes**: Spread operators, dynamic values\n\nEach item tracks usage with `Cell<bool>` for waste scoring.\n\n### Greedy Matching Algorithm\n\nFor each new dynamic node:\n1. Find all candidates from last build with compatible structure\n2. Attempt to create hot-reloaded template using candidate's pool\n3. Score: Count unused dynamic items remaining\n4. Select candidate with least unused items\n5. Greedy approach optimal because it maximizes reuse\n\n### Change Detection\n\n**NOT possible**:\n- Number of `rsx!` macros changes\n- Rust expressions change\n- Component structure changes\n- Control flow conditions change\n\n**IS possible**:\n- Component children content\n- Reordering attributes\n- Adding dynamic text segments from pool\n- Changing literal values\n- Shuffling template structure\n\n## Devtools Protocol\n\n### WebSocket Communication\n\nBidirectional WebSocket between app and devserver.\n\n**Connection**:\n- URL: `ws://localhost:3000/_dioxus`\n- Query params: `build_id`, `pid`, `aslr_reference`\n- Persistent during development\n\n### Message Types\n\n```rust\npub enum DevserverMsg {\n    HotReload(HotReloadMsg),  // Templates + optional jump table\n    HotPatchStart,            // Binary patching starting\n    FullReloadStart,          // Rebuilding entire app\n    FullReloadFailed,         // Build failed\n    FullReloadCommand,        // Full page reload needed\n    Shutdown,                 // Devserver shutting down\n}\n```\n\n### HotReloadMsg Structure\n\n```rust\npub struct HotReloadMsg {\n    pub templates: Vec<HotReloadTemplateWithLocation>,\n    pub assets: Vec<PathBuf>,\n    pub ms_elapsed: u64,\n    pub jump_table: Option<JumpTable>,\n    pub for_build_id: Option<u64>,\n    pub for_pid: Option<u32>,\n}\n```\n\n### Message Processing\n\n1. `connect(callback)` - Generic connection\n2. `connect_subsecond()` - Subsecond-specific with jump tables\n3. `apply_changes(dom, msg)`:\n   - Updates signal-based template cache\n   - Applies jump table if PID matches\n   - Marks all components dirty\n\n### WASM-Specific Handling\n\n- Connection at `ws://host/_dioxus?build_id={build_id}`\n- Supports playground mode (iframe via postMessage)\n- Console logging sent to devserver\n- Page reload on `FullReloadCommand`\n- Asset cache invalidation via `dx_force_reload` query params\n\n## Integration Flow\n\n### Subsecond in Dioxus Apps\n\n```rust\nfn main() {\n    dioxus::launch(app);\n    // Devtools automatically connects during init\n}\n```\n\nFor non-Dioxus apps:\n```rust\nfn main() {\n    dioxus_devtools::connect_subsecond();\n\n    loop {\n        dioxus_devtools::subsecond::call(|| {\n            handle_request()\n        });\n    }\n}\n```\n\n### Async Integration\n\n```rust\n#[tokio::main]\nasync fn main() {\n    dioxus_devtools::serve_subsecond_with_args(\n        state,\n        |state| async {\n            app_main(state).await\n        }\n    ).await;\n}\n```\n\n**Process**:\n1. Catches patch message\n2. Applies jump table\n3. Drops current future\n4. Creates new future with hot function\n5. Continues execution\n\n## Build System Integration\n\n### Fat vs Thin Builds\n\n**Fat Build** (initial):\n- Full build with all symbols\n- Required for initial symbol table\n- Creates `HotpatchModuleCache`\n\n**Thin Build** (patches):\n- Compiles only modified functions\n- Uses cached dependency symbols\n- Produces minimal patch dylib\n\n### JumpTable Structure\n\n```rust\npub struct JumpTable {\n    pub lib: PathBuf,              // Path to patch dylib\n    pub map: HashMap<u64, u64>,    // old_addr → new_addr\n    pub aslr_reference: u64,\n    pub new_base_address: u64,\n    pub ifunc_count: u32,\n}\n```\n\n### Platform-Specific Patching\n\n- `create_windows_jump_table()` - x86/x64 jump stubs\n- `create_native_jump_table()` - macOS/Linux function overrides\n- `create_wasm_jump_table()` - WASM indirect call table updates\n\n## Key Design Decisions\n\n1. **Jump table indirection**: Safe, no memory corruption\n2. **Conservative RSX diffing**: Rust changes trigger rebuild\n3. **Greedy pool matching**: Optimal template reuse\n4. **WebSocket protocol**: Real-time bidirectional updates\n5. **PID filtering**: Correct process receives patches\n6. **Dual system**: RSX for templates, Subsecond for logic\n\n## Future Considerations\n\n### Workspace Support\n- Dependency graph analysis for affected crates\n- Incremental library crate compilation\n- Cross-crate function pointer resolution\n\n### Remote Hot-Reloading\n- SCP transport for bandwidth efficiency\n- Binary diff for minimal transfers\n- Cryptographic verification\n\n### CLI Tunnels\n- SSH/TCP protocol wrapping\n- Connection persistence\n- Latency-tolerant queueing\n"
  },
  {
    "path": "notes/architecture/08-ASSETS.md",
    "content": "# Manganis Asset System Architecture\n\nManganis provides compile-time asset management through the `asset!()` macro, enabling type-safe asset references with automatic optimization and cache-busting.\n\n## Asset Macro Architecture\n\n### Compile-Time Expansion\n\n```\nasset!(\"/assets/image.png\", AssetOptions::image())\n    ↓\n1. Path Resolution\n   - Resolves relative to CARGO_MANIFEST_DIR\n   - Validates path exists and stays within crate\n\n2. File Hashing\n   - Creates DefaultHasher from path + options + span\n   - Produces 16-character hex hash\n\n3. Generate Link Section\n   - Creates __ASSETS__{hash} symbol\n   - Creates __MANGANIS__{hash} for legacy CLI\n   - Both use CBOR serialization via const_serialize\n\n4. Code Generation\n   - Emits BundledAsset const with PLACEHOLDER_HASH\n   - Creates Asset struct with function pointers\n   - Uses volatile reads to prevent optimization\n```\n\n### Link Section Generation\n\nTwo static link sections for backward compatibility:\n\n1. **Legacy Format** (`__MANGANIS__{hash}`):\n   - Uses const_serialize_07\n   - For older CLIs (0.7.0-0.7.1)\n\n2. **New Format** (`__ASSETS__{hash}`):\n   - Uses const_serialize_08 (CBOR)\n   - For dx >= 0.7.2\n\nBoth start with `PLACEHOLDER_HASH` sentinel, replaced by CLI during build.\n\n## Asset Types and Options\n\n### Type Hierarchy\n\n```\nAssetOptions\n├── ImageAssetOptions\n│   ├── format: ImageFormat (Png, Jpg, Webp, Avif)\n│   ├── size: ImageSize (Manual | Automatic)\n│   └── preload: bool\n│\n├── CssAssetOptions\n│   ├── minify: bool (default: true)\n│   ├── preload: bool\n│   └── static_head: bool\n│\n├── JsAssetOptions\n│   ├── minify: bool (default: true)\n│   ├── preload: bool\n│   └── static_head: bool\n│\n├── CssModuleAssetOptions\n│   ├── minify: bool\n│   └── preload: bool\n│\n├── FolderAssetOptions\n│   └── (no custom options)\n│\n└── Unknown (generic binary)\n```\n\nAll options serializable at const-time via `SerializeConst` derive.\n\n### CSS Module Integration\n\n```rust\ncss_module!(Styles = \"/my.module.css\", AssetOptions::css_module());\n\n// Generates:\nstruct Styles {}\nimpl Styles {\n    pub const header: &str = \"abc[hash]\";  // Unique scoped class\n    pub const button: &str = \"def[hash]\";\n}\n```\n\n**CSS Identifier Collection**:\n- Scans for `.className` and `#idName` patterns\n- Converts to snake_case\n- Creates ConstStr values that auto-inject stylesheet on deref\n\n## Const Serialization System\n\n### CBOR Format (RFC 8949 Subset)\n\n**Supported Major Types**:\n- UnsignedInteger (0)\n- NegativeInteger (1)\n- Bytes (2)\n- String (3)\n- Array (4)\n- Map (5)\n\n**Not Supported**: Tagged values (6), floating point (7)\n\n### Const Serialization Mechanism\n\nLayout-based binary copying at compile time:\n\n```rust\nunsafe trait SerializeConst: Sized {\n    const MEMORY_LAYOUT: Layout;\n}\n\nenum Layout {\n    Enum(EnumLayout),       // repr(C, u8) required\n    Struct(StructLayout),   // Field offsets\n    Array(ArrayLayout),     // Fixed-size\n    Primitive(PrimitiveLayout),\n    List(ListLayout),       // Variable length\n}\n```\n\n**Process**:\n1. Calculate total size from MEMORY_LAYOUT\n2. Copy bytes from source following layout\n3. Apply transformations (endianness)\n4. Append to ConstVec buffer\n\n### ConstStr Implementation\n\n```rust\npub struct ConstStr {\n    bytes: [MaybeUninit<u8>; 256],  // Fixed 256-byte buffer\n    len: u32,\n}\n```\n\nUsed for asset paths and CSS identifiers. Serialized as List layout.\n\n## Asset Resolution Pipeline\n\n### Build-Time Processing (dx CLI)\n\n**Phase 1: Binary Scanning**\n```\n1. Read compiled binary\n2. Find __ASSETS__ symbols via objfile parser\n3. Platform-specific methods:\n   - Native: object crate symbol tables\n   - Windows PE: PDB file parsing\n   - WASM: walrus data section parsing\n   - Android: NDK handling\n```\n\n**Phase 2: Asset Deserialization**\n```\nFor each __ASSETS__{hash}:\n1. Read serialized bytes from section\n2. Deserialize BundledAsset via const_serialize_08\n3. Fall back to const_serialize_07 if fails\n4. Extract: absolute_source_path, bundled_path, options\n```\n\n**Phase 3: Unique Asset Collection**\n- Deduplicate by (absolute_path, options) pair\n- Create AssetManifest with unique set\n\n**Phase 4: Asset Optimization**\n\n| Type | Processing |\n|------|------------|\n| Image | Resize, convert format, optimize |\n| CSS | Minify if enabled |\n| JS | Minify if enabled |\n| Folder | Recursive copy |\n| Unknown | Direct copy with optional hash |\n\n**Phase 5: Binary Patching**\n```\nFor each processed asset:\n1. Compute final hash (content + options + version)\n2. Create new BundledAsset with:\n   - bundled_path: \"/assets/{output-filename}\"\n3. Serialize to CBOR\n4. Locate __ASSETS__{hash} in binary\n5. Overwrite bytes at symbol offset\n```\n\n### Runtime Resolution\n\n**Development Mode** (`!is_bundled_app()`):\n```\nAsset::resolve()\n    → Returns bundled().absolute_source_path\n    → Browser/native accesses original files\n```\n\n**Production Mode** (`is_bundled_app()`):\n```\nAsset::resolve()\n    → Constructs path from:\n       * base_path() (e.g., \"/app\")\n       * bundled_path from BundledAsset\n    → Returns \"/app/assets/{output-filename}\"\n```\n\n### Platform-Specific Resolution\n\n**Web (WASM)**:\n- `resolve_web_asset()`: Fetches via HTTP `fetch()` API\n- Supports CORS headers\n- Returns `Vec<u8>`\n\n**Desktop**:\n- `resolve_asset_path_from_filesystem()`\n- Bundle structure varies:\n  - macOS: `../Resources/assets/`\n  - Linux: `../lib/{product}/assets/`\n  - Windows: `assets/` (same directory as exe)\n\n**Android**:\n- `to_java_load_asset()`: Uses NDK AssetManager\n- Accesses APK's assets/ directory\n- Debug fallback: `/data/local/tmp/dx/`\n\n## Hash-Based Cache Busting\n\n### Hash Computation Layers\n\n**INPUT_HASH** (macro generation):\n```\n= DefaultHasher(span.debug_string + options.string + asset_path)\nUsed for: __ASSETS__{INPUT_HASH} symbol\n```\n\n**CONTENT_HASH** (build time):\n```\n= Hash(source_contents + applied_options + manganis_version)\nUsed for: Final output filename\nExample: image.png → image-a1b2c3d4e5f6g7h8.webp\n```\n\n**CSS_MODULE_HASH**:\n```\n= Hash(css_module_options + content_hash)\nUsed for: Scoped CSS identifiers\n```\n\n### Filename Generation\n\n```\nIMAGE: /assets/photo.png → /assets/photo-{hash}.webp\nCSS/JS: /assets/style.css → /assets/style-{hash}.css\nFOLDER: /assets (folder) → /assets/ (unchanged)\n```\n\n## Key Architectural Patterns\n\n### Const-Time Code Generation\n- Uses proc_macro for compile-time expansion\n- Leverages const fn exclusively (no allocators)\n- Binary layout determined at compile time\n\n### Volatile Reads for Correctness\n- `std::ptr::read_volatile()` prevents optimizing away link section reads\n- Critical because link section gets patched post-compilation\n\n### Layout-Respecting Serialization\n- Types must have well-defined memory layout\n- `repr(C, u8)` for enums\n- CBOR for variable-length fields\n\n### Dual Path Resolution\n- Dev: Returns absolute_source_path\n- Prod: Returns bundled_path with base_path\n- Same Asset instance works across configurations\n\n### Symbol-Based Discovery\n- No manifest file needed\n- Binary symbols serve as asset registry\n- Scales with multi-crate applications\n\n## Extension Points\n\n### Adding New Asset Types\n\n1. **Create Options Struct**:\n```rust\n#[derive(SerializeConst, ...)]\npub struct VideoAssetOptions {\n    format: VideoFormat,\n    preload: bool,\n}\n```\n\n2. **Add AssetVariant Case**:\n```rust\npub enum AssetVariant {\n    Video(VideoAssetOptions),\n}\n```\n\n3. **Implement Builder**:\n```rust\npub const fn video() -> AssetOptionsBuilder<VideoAssetOptions> {\n    AssetOptionsBuilder::variant(VideoAssetOptions::default())\n}\n```\n\n4. **Register in CLI** (`assets.rs`):\n```rust\nAssetVariant::Video(opts) => {\n    // Video-specific processing\n}\n```\n\n### Future Concepts\n\n**IAAC (Infrastructure as Code)**:\n```rust\nconst DATABASE_CONFIG: Asset = asset!(\"/config/db.yaml\");\n// CLI extracts, validates, deploys\n```\n\n**secret!() Macro**:\n```rust\nconst API_KEY: Secret = secret!(\"DIOXUS_API_KEY\");\n// Compile-time validation\n// Runtime injection from secure store\n```\n\n## Version Compatibility\n\n**Legacy (0.7.0-0.7.1)**:\n- const_serialize_07\n- Symbol: `__MANGANIS__{hash}`\n\n**Current (0.7.2+)**:\n- const_serialize_08 (CBOR)\n- Symbol: `__ASSETS__{hash}`\n\n**Migration**: CLI tries new format first, falls back to legacy if deserialization fails or PLACEHOLDER_HASH still present.\n"
  },
  {
    "path": "notes/architecture/09-ROUTER.md",
    "content": "# Dioxus Router Architecture\n\nThe Dioxus router provides type-safe, declarative routing with support for nested layouts, dynamic parameters, and platform-agnostic navigation.\n\n## Route Definition\n\n### Routable Trait\n\nRoutes are defined using Rust enums with the `#[derive(Routable)]` macro:\n\n```rust\n#[derive(Routable, Clone, PartialEq, Debug)]\nenum Route {\n    #[route(\"/\")]\n    Home {},\n    #[route(\"/blog/:blog_id\")]\n    Blog { blog_id: usize },\n    #[route(\"/edit?:blog_id\")]\n    Edit { blog_id: usize },\n    #[route(\"/hashtag/#:hash\")]\n    Hash { hash: String },\n}\n```\n\nThe `Routable` trait provides:\n- **`const SITE_MAP`**: Static map of all route segments\n- **`render(level: usize) -> Element`**: Renders component at nesting level\n- **`FromStr` implementation**: Parses URLs into route enums\n- **`Display` implementation**: Converts routes to URLs\n- **Helper methods**: `is_child_of()`, `parent()`, `static_routes()`\n\n### Route Matching Priority\n\n1. Query routes (`/?:query`)\n2. Static routes (`/route`)\n3. Dynamic routes (`/:route`)\n4. Catch-all routes (`/:..route`)\n\n### Segment Types\n\n- **Static**: Literal path segments like `/blog`\n- **Dynamic**: Single parameters like `/:id` (implements `FromRouteSegment`)\n- **CatchAll**: Multiple segments like `/:..segments` (implements `FromRouteSegments`)\n\n### Query Parameters\n\n- **Spread query**: `/?:..query` (entire query string, uses `FromQuery`)\n- **Segmented query**: `/?:param1&:param2` (individual params, use `FromQueryArgument`)\n\n### Hash Fragments\n\n`/#:hash_segment` (uses `FromHashFragment`)\n\n## Navigation\n\n### RouterContext\n\nManages navigation state with these core methods:\n\n```rust\nimpl RouterContext {\n    // Navigation\n    pub fn push(&self, target: impl Into<NavigationTarget>);\n    pub fn replace(&self, target: impl Into<NavigationTarget>);\n\n    // History\n    pub fn go_back(&self);\n    pub fn go_forward(&self);\n    pub fn can_go_back(&self) -> bool;\n    pub fn can_go_forward(&self) -> bool;\n\n    // Introspection\n    pub fn current<R: Routable>(&self) -> R;\n    pub fn full_route_string(&self) -> String;\n    pub fn prefix(&self) -> Option<String>;\n\n    // Site map\n    pub fn site_map(&self) -> &'static [SiteMapSegment];\n}\n```\n\nThe router context is:\n1. Created in `Router` component with `RouterConfig`\n2. Provided via Dioxus context to children\n3. Uses **signals** for reactive updates\n4. Tracks **subscribers** for re-rendering\n5. Integrates with **history provider**\n\n### NavigationTarget\n\n```rust\npub enum NavigationTarget<R = String> {\n    Internal(R),      // Route parsed by router\n    External(String), // External URL (browser handles)\n}\n```\n\n## Link Component\n\nDeclarative navigation:\n\n```rust\nLink {\n    to: NavigationTarget,\n    active_class: Option<String>,  // Applied when matches current\n    class: Option<String>,\n    new_tab: bool,\n    onclick: Option<EventHandler>,\n    children: Element,\n}\n```\n\n**Behavior**:\n1. Accesses router without subscribing\n2. Checks if target matches current for `active_class`\n3. On click (left button, no modifiers):\n   - Prevents default\n   - Routes via `router.push_any(target)`\n   - Executes optional onclick\n4. Generates `<a>` tag with `href` for SEO\n5. External links use default browser navigation\n\n## Nested Routes\n\n### Layouts and Outlets\n\n```rust\n#[derive(Routable)]\nenum Route {\n    #[nest(\"/admin\")]\n        #[layout(AdminLayout)]\n            #[route(\"/\")]\n            AdminHome {},\n            #[route(\"/users\")]\n            Users {},\n        #[end_layout]\n    #[end_nest]\n\n    #[route(\"/\")]\n    Home {},\n}\n\n#[component]\nfn AdminLayout() -> Element {\n    rsx! {\n        header { \"Admin Panel\" }\n        Outlet::<Route> {}  // Children render here\n    }\n}\n```\n\n### Outlet Component\n\n- Renders child routes at correct nesting depth\n- Uses `OutletContext` to track depth level\n- Works with layout wrapping via macro\n\n### VirtualDOM Integration\n\n1. `Routable::render(level)` matches route at nesting level\n2. Each layout increments nesting level\n3. Router changes mark context-accessing components dirty\n4. Outlet at correct depth renders matched component\n\n## Route Parameters\n\n### Parameter Traits\n\n| Component | Trait | Example |\n|-----------|-------|---------|\n| Path segment | `FromRouteSegment` / `ToRouteSegment` | `/users/:id` |\n| Multiple segments | `FromRouteSegments` / `ToRouteSegments` | `/files/:..path` |\n| Query full | `FromQuery` | `/?:q=search` |\n| Query arg | `FromQueryArgument` | `/?page=1&sort=name` |\n| Hash | `FromHashFragment` | `/#section-id` |\n\n### Auto-Implementation\n\nTraits auto-implement for types with standard traits:\n- `FromRouteSegment`: `FromStr + Default`\n- `FromQueryArgument`: `FromStr + Default`\n- `FromQuery`: `From<&str>`\n- `FromHashFragment`: `FromStr + Default`\n\n### URL Encoding\n\n- Path segments: `PATH_ASCII_SET` (excludes `/`, `?`, `#`)\n- Query strings: `QUERY_ASCII_SET` (excludes `&`, `=`)\n- Hash fragments: `FRAGMENT_ASCII_SET`\n- Auto encode/decode during parse/display\n\n## Router Macro\n\n### Code Generation\n\n`#[derive(Routable)]` generates three implementations:\n\n**1. FromStr Implementation**:\n```rust\nimpl std::str::FromStr for Route {\n    fn from_str(s: &str) -> Result<Self, Self::Err> {\n        // Split URL into path, query, hash\n        // Percent-decode segments\n        // Try matching against all routes (specificity order)\n        // Return error with attempted routes if no match\n    }\n}\n```\n\n**2. Display Implementation**:\n```rust\nimpl std::fmt::Display for Route {\n    fn fmt(&self, f: &mut Formatter) -> Result {\n        match self {\n            Route::Blog { blog_id } => {\n                write!(f, \"/blog/{}\", blog_id)?;\n                // Percent-encode as needed\n            }\n        }\n    }\n}\n```\n\n**3. Routable Trait Implementation**:\n```rust\nimpl Routable for Route {\n    const SITE_MAP: &'static [SiteMapSegment] = &[...];\n\n    fn render(&self, level: usize) -> Element {\n        match (level, self) {\n            (0, Self::Blog { .. }) => rsx! { Layout { Outlet {} } }\n            (1, Self::Blog { blog_id }) => rsx! { Blog { blog_id: *blog_id } }\n            _ => VNode::empty()\n        }\n    }\n}\n```\n\n### Error Type Generation\n\nEach route generates detailed error enum:\n```rust\npub enum RouteMatchError {\n    Home(HomeParseError),\n    Blog(BlogParseError),\n    BlogIdParseError(ParseIntError),\n}\n```\n\n### Segment Parsing\n\n```rust\npub enum RouteSegment {\n    Static(String),        // \"/blog\" -> Static(\"blog\")\n    Dynamic(Ident, Type),  // \"/:id\" -> Dynamic(id, u32)\n    CatchAll(Ident, Type), // \"/:..paths\" -> CatchAll(paths, Vec<String>)\n}\n```\n\n**Process**:\n1. Split URL into path, query, hash\n2. Remove trailing slashes\n3. Percent-decode components\n4. Split path by `/`\n5. Classify segments (static/dynamic/catch-all)\n6. Match field names to route parameters\n7. Validate types exist in struct fields\n\n## History Abstraction\n\n### History Trait\n\nPlatform abstraction for navigation:\n\n```rust\npub trait History {\n    fn current_route(&self) -> String;\n    fn current_prefix(&self) -> Option<String>;\n\n    fn can_go_back(&self) -> bool;\n    fn can_go_forward(&self) -> bool;\n    fn go_back(&self);\n    fn go_forward(&self);\n\n    fn push(&self, route: String);\n    fn replace(&self, path: String);\n    fn external(&self, url: String) -> bool;\n\n    fn updater(&self, callback: Arc<dyn Fn() + Send + Sync>);\n    fn include_prevent_default(&self) -> bool;\n}\n```\n\n### MemoryHistory\n\nIn-memory fallback implementation:\n\n```rust\npub struct MemoryHistory {\n    state: RefCell<MemoryHistoryState>,\n    base_path: Option<String>,\n}\n\nstruct MemoryHistoryState {\n    current: String,\n    history: Vec<String>,   // For back\n    future: Vec<String>,    // For forward\n}\n```\n\n**Behavior**:\n- Default starts at `/`\n- `push`: Adds current to history, sets new as current, clears future\n- `replace`: Only changes current\n- `go_back/forward`: Swaps between stacks\n- Does not navigate external URLs\n\n**Use Cases**:\n- Testing/SSR without browser history\n- In-memory fullstack navigation\n- Development without browser\n\n### Platform Implementations\n\n**Web**: Browser History API, popstate events\n**Desktop**: Platform-specific window/navigation APIs\n**LiveView**: WebSocket with server-managed state\n\n## VirtualDOM Integration\n\n1. **Initialization**:\n   - `Router` creates `RouterContext` with config\n   - Provides to descendants via context\n   - Gets current route from history\n   - Syncs with history if needed\n\n2. **URL Change**:\n   - `RouterContext::push_any(NavigationTarget)` called\n   - History provider's method invoked\n   - `change_route()` callback fires\n   - Subscribers marked dirty\n\n3. **Re-render**:\n   - Components using `use_router()`/`use_navigator()` marked dirty\n   - `Routable::render(level)` generates new tree\n   - Layouts render at nesting levels\n   - `Outlet` renders final component\n\n4. **Parameter Passing**:\n   - Route parameters part of enum variant\n   - Passed as component props\n   - Type-safe at compile time\n\n## Key Design Decisions\n\n1. **Trait-Based Extensibility**: Custom types implement parameter traits\n2. **Type Safety**: Routes are enums - invalid routes are compile errors\n3. **Macro-Driven**: Reduces boilerplate, efficient matching\n4. **Platform Abstraction**: History trait enables different renderers\n5. **Reactive Updates**: Signals and subscriptions for efficient re-renders\n6. **Nested Layouts**: Outlets allow composition without complexity\n7. **URL Encoding**: Automatic percent-encoding/decoding\n8. **SEO-Friendly**: Links generate real `<a>` tags with hrefs\n"
  },
  {
    "path": "notes/architecture/10-WASM-SPLIT.md",
    "content": "# WASM Code Splitting Architecture\n\nThe WASM-Split system enables code splitting for large WebAssembly applications, allowing developers to lazily load feature chunks on demand.\n\n## Overview\n\n### Problem Solved\n\nLarge WASM binaries can impact initial load times. WASM-Split produces:\n- **main.wasm** - Core application\n- **module_N_*.wasm** - Lazy-loaded feature chunks\n- **chunk_N_*.wasm** - Shared code across modules\n- **__wasm_split.js** - Runtime loader\n\n### Output Structure\n\n```\ndist/\n├── main.wasm           # Core application\n├── module_1_*.wasm     # Feature chunk 1\n├── module_2_*.wasm     # Feature chunk 2\n├── chunk_1_*.wasm      # Shared code\n└── __wasm_split.js     # JavaScript loader\n```\n\n## Macro Usage\n\n### `#[wasm_split]` Attribute Macro\n\nMarks async functions as split boundaries:\n\n```rust\n#[wasm_split(my_feature)]\nasync fn load_feature() -> i32 {\n    // This code will be in module_my_feature.wasm\n    expensive_computation()\n}\n```\n\n**Generated Names** (pattern: `__wasm_split_00<module>00_<type>_<hash>_<function>`):\n- `__wasm_split_00my_feature00_import_<hash>_load_feature` - FFI import\n- `__wasm_split_00my_feature00_export_<hash>_load_feature` - FFI export\n\n**Transformation**:\n1. Generates `extern \"C\"` export with implementation\n2. Creates import declaration from `__wasm_split.js`\n3. Creates thread-local `LazySplitLoader` with load function\n4. Awaits loader before calling imported version\n\n### `#[lazy_loader]` Macro\n\nFor libraries creating lazy-loadable wrappers:\n\n```rust\n#[lazy_loader(extern \"auto\")]\nfn my_lazy_fn(x: i32) -> i32;\n```\n\nReturns `LazyLoader<Args, Ret>` with:\n- `.load().await` - Async module loading\n- `.call(args)` - Sync invocation after loading\n\n**\"auto\" ABI**: Automatically combines all modules into one.\n\n## Build Integration\n\n### Input Requirements\n\nCLI takes two binary inputs:\n1. **original.wasm** - Pre-wasm-bindgen (with `--emit-relocs`)\n2. **bindgened.wasm** - Post-wasm-bindgen\n\nCompilation requirements:\n- `--emit-relocs` - Relocation information\n- Debug symbols - Function name resolution\n- LTO - Symbol consistency\n\n### Processing Pipeline\n\n**Phase 1: Discovery and Graph Building**\n\n```\n1. Scan imports/exports for __wasm_split_00<module>00_* pattern\n2. Parse relocations from original.wasm\n3. Build call graph from:\n   - CODE section relocations\n   - DATA section relocations\n   - Direct function calls via IR walking\n4. Build parent graph (inverse for reachability)\n5. Compute reachability for each split point\n```\n\n**Phase 2: Chunk Identification**\n\n- Identify functions used by multiple modules\n- Extract into shared chunks\n- Build chunk dependency graph\n\n**Phase 3: Module Emission**\n\nThree output types (parallel via rayon):\n\n**A. Main Module**:\n- Remove split point exports\n- Replace element segments with dummy functions\n- Create indirect function table\n- Convert split imports to stub functions\n- Re-export memories, globals, tables\n- Run garbage collection\n\n**B. Split Modules** (per split point):\n- Identify unique vs shared symbols\n- Convert chunk functions to imports\n- Clear and reconstruct data segments\n- Create element segment initializers\n- Export main entry function\n- Run GC\n\n**C. Chunk Modules** (shared code):\n- Similar to split modules\n- No main export function\n- Contains commonly-used functions\n\n## Walrus Operations\n\nThe system uses **walrus** for WASM binary manipulation:\n\n### Function Table Management\n\n```\n1. Ensure funcref table exists (__indirect_function_table)\n2. Expand table for split modules + shared functions\n3. Create passive element segments for initialization\n```\n\n### Stub Function Creation\n\nStubs perform indirect calls:\n```\n1. Push function arguments onto stack\n2. Push table index (pointing to real function)\n3. Call via CallIndirect with table\n```\n\n### Import/Export Manipulation\n\n- Convert imports to local stub functions\n- Re-export shared resources\n- Use `__wasm_split` namespace for shared imports\n\n### Graph Analysis\n\n**Node Types**:\n- `Node::Function(FunctionId)`\n- `Node::DataSymbol(usize)`\n\n**Graphs**:\n- **Call Graph**: Function → callees\n- **Parent Graph**: Inverse (what calls them)\n- **Reachability Graph**: Per split point, all reachable symbols\n- **Main Graph**: All reachable from main exports\n\n## Runtime Loading\n\n### LazyLoader Structure\n\n```rust\npub struct LazyLoader<Args, Ret> {\n    // Generic loader for function (Args) -> Ret\n}\n\nimpl LazyLoader {\n    pub async fn load(&self) -> bool;  // Load module\n    pub fn call(&self, args: Args) -> Result<Ret, SplitLoaderError>;\n}\n```\n\n### LazySplitLoader State Machine\n\nThree states:\n1. **Deferred** - Not initiated, holds load function\n2. **Pending** - Load initiated, waiting for callback\n3. **Completed** - Loaded (with success boolean)\n\n**Async Interface**:\n- `SplitLoaderFuture` implements `Future<Output = bool>`\n- `poll()` handles state transitions\n- Uses `Waker` to resume on callback\n\n### JavaScript Runtime (`__wasm_split.js`)\n\n**`makeLoad()` Function**:\n```javascript\nasync function(callbackIndex, callbackData) {\n    // 1. Await chunk dependencies\n    // 2. Check if already loaded\n    // 3. Fetch module binary\n    // 4. Call initSync from main.wasm\n    // 5. Construct import object:\n    //    - Memory from main module\n    //    - Indirect function table\n    //    - Stack pointers and TLS base\n    //    - Main module exports as imports\n    //    - Fused imports from other modules\n    // 6. WebAssembly.instantiateStreaming()\n    // 7. Add exports to fusedImports\n    // 8. Invoke callback with table index\n}\n```\n\n**Callback Mechanism**:\n```javascript\n__indirect_function_table.get(callbackIndex)(callbackData, true)\n```\nWakes up Rust Future waiting in loader.\n\n## Integration Flow\n\n```\nCompile Phase: Rust code with #[wasm_split]\n         ↓\nMacro Expansion: FFI functions + LazyLoader\n         ↓\nBuild Phase: wasm-split CLI processes binaries\n         ↓\nParse Relocations → Build Call Graph → Identify Split Points\n         ↓\nCompute Reachability → Parallel Emission\n         ↓\nOutput: main.wasm + module_*.wasm + chunk_*.wasm + __wasm_split.js\n         ↓\nRuntime: JavaScript fetch → Instantiate → Callback → Future wakes\n         ↓\nLazy functions available synchronously\n```\n\n## Key Transformations\n\n### Main Module\n\n1. Remove split point exports\n2. Replace element segments with dummies\n3. Create indirect function table\n4. Convert imports to stubs (call indirect table)\n5. Re-export shared resources\n6. Remove relocation/linking sections\n7. GC unused code\n\n### Split Module\n\n1. Partition functions (unique vs shared)\n2. Convert chunk functions to imports\n3. Clear data segments, reconstruct needed data\n4. Create element segment initializers\n5. Create FFI imports from main\n6. Export entry function\n7. Remove start function\n8. GC unreachable symbols\n\n## Usage Example\n\n```rust\n// Define lazy-loadable feature\n#[wasm_split(admin_panel)]\nasync fn load_admin_panel() -> AdminPanel {\n    AdminPanel::new()\n}\n\n// Use in application\nasync fn handle_route(route: Route) {\n    match route {\n        Route::Admin => {\n            let panel = load_admin_panel().await;\n            panel.render();\n        }\n        _ => // ...\n    }\n}\n```\n\n## Design Considerations\n\n### Split Point Granularity\n\n- One `#[wasm_split]` per feature chunk\n- Too fine-grained = many small fetches\n- Too coarse = large chunks\n\n### Shared Code Optimization\n\n- Common functions extracted to chunks\n- Prevents duplication across modules\n- Chunks loaded before dependent modules\n\n### Memory Sharing\n\n- Single memory instance shared across all modules\n- Indirect function table shared for cross-module calls\n- TLS base pointer coordinated\n\n### Error Handling\n\n```rust\npub enum SplitLoaderError {\n    NotLoaded,\n    LoadFailed,\n}\n```\n\nLoader returns `Result` for robust error handling.\n\n## Limitations\n\n1. **Async boundaries**: Split points must be async functions\n2. **Call graph static**: Determined at build time\n3. **No dynamic imports**: All split points known at compile time\n4. **WASM-specific**: Only works with wasm32 target\n\n## Future Considerations\n\n- Route-based automatic splitting\n- Size-based chunk optimization\n- Prefetching hints\n- Integration with service workers for caching\n"
  },
  {
    "path": "notes/architecture/11-NATIVE-PLUGIN-FFI.md",
    "content": "# Native Plugin System Design Document\n\n## Summary\n\nBuild a system that lets Rust projects include Swift/Kotlin/C source files via macros, with metadata emitted through the linker. A bundler extracts this metadata and compiles/links the native code into the final app.\n\nWe're adding this feature to manganis by leveraging the recently added cbor support for general assets.\n\n## Core Concept\n\n```rust\n#[manganis::ffi(\"/src/android\")]\nextern \"Kotlin\" {\n    fn do_thing() -> JObject;\n}\n\n#[manganis::ffi(\"/src/ios\")]\nextern \"Swift\" {\n    fn do_thing() -> NSObject;\n}\n```\n\nThe macro:\n1. Emits a new asset type (source folder / source file)\n2. Generates the relevant ffi functions\n3. The bundler later extracts metadata and compiles the sources\n\n## Why This Approach\n\n| Problem with build.rs | Our solution                                |\n| --------------------- | ------------------------------------------- |\n| Tied to Cargo         | Linker metadata works with any build system |\n| Slows rust-analyzer   | Zero impact on IDE                          |\n| Finicky caching       | Bundler handles caching                     |\n| Must run before rustc | Bundler runs after rustc                    |\n\nThe tradeoff: Rust can't use *outputs* of native compilation (generated headers, etc.). For our use case (runtime FFI via JNI/ObjC), this is fine.\n\n---\n\n## Phase 1: The Macro\n\n### Basic API\n\n```rust\n// Annotate an extern block with hardcoded supported languages (swift, kotlin, java, etc)\n// Additional props may be passed in as necessary.\n#[manganis::ffi(\"/src/ios\", some_prop = \"123\")]\nextern \"Swift\" {\n    // objects that may be declared, using `type` syntax\n    pub type SomeSwiftObject;\n\n    // functions associated with said object\n    // values passed must be pointer-like or values that can be coerced between languages\n    //\n    // calling this goes through runtime lookup instead of actually being linked\n    // for kotlin, this would be a jni lookup and call\n    pub fn do_thing_with_swift_object_swift(this: &SomeSwiftObject) -> Option<u32>;\n}\n\n// User can extend the extern types with their own rusty methods\nimpl SomeSwiftObject {\n    pub fn new() -> Self {\n        // objc code, constructor maybe?\n    }\n\n    // custom extensions\n    pub fn custom_wrapper(&mut self) {\n        _ = self.do_thing_with_swift_object_swift().unwrap();\n    }\n}\n```\n\n---\n\n## Implementation Checklist\n\n### Macro\n\n- [ ] Add new the manganis types to manganis-core for emitting source folders\n- [ ] Come up with more work to make the macro ready\n\n### Bundler\n\n- [ ] Extract ffi blocks from the manganis extractor\n- [ ] Parse PluginMeta entries\n- [ ] Deduplicate paths\n- [ ] Swift compilation\n  - [ ] Invoke swiftc\n  - [ ] Handle iOS vs macOS targets\n  - [ ] Framework/module configuration\n- [ ] Kotlin compilation\n  - [ ] Invoke kotlinc or gradle\n  - [ ] Handle Android-specific setup\n  - [ ] DEX generation for Android\n- [ ] Link outputs into final binary/bundle\n- [ ] Caching (hash sources, skip if unchanged)\n\n### Integration with dx (your bundler)\n\n- [ ] Hook into post-rustc phase\n- [ ] Call plugin extraction\n- [ ] Call compilation pipeline\n- [ ] Include outputs in app bundle\n\n---\n\n## Platform-Specific Notes\n\n### Swift Compilation\n\n```bash\n# iOS\nswiftc -target arm64-apple-ios15.0 \\\n  -sdk /path/to/iPhoneOS.sdk \\\n  -emit-library \\\n  -o libplugins.a \\\n  LocationManager.swift\n\n# macOS\nswiftc -emit-library \\\n  -o libplugins.dylib \\\n  LocationManager.swift\n```\n\n### Kotlin Compilation\n\nFor Android, you need to produce DEX:\n\n```bash\n# Compile to class files\nkotlinc Geolocator.kt -include-runtime -d classes/\n\n# Convert to DEX\nd8 classes/*.class --output dex/\n```\n\nOr use Gradle if dependencies are needed.\n\n### C Compilation\n\n```bash\n# Android\n$NDK/toolchains/llvm/prebuilt/*/bin/clang \\\n  --target=aarch64-linux-android21 \\\n  -c fast_math.c -o fast_math.o\n\n# iOS\nclang -target arm64-apple-ios15.0 \\\n  -isysroot /path/to/iPhoneOS.sdk \\\n  -c fast_math.c -o fast_math.o\n```\n\n---\n\n## Testing Strategy\n\n1. **Macro tests**: Verify correct link_section output using `trybuild` or manual inspection\n2. **Extraction tests**: Create test binaries, verify metadata extraction works\n3. **Compilation tests**: Verify Swift/Kotlin/C compilation produces valid artifacts\n4. **Integration tests**: End-to-end build of sample app with plugins\n"
  },
  {
    "path": "notes/architecture/12-MANIFEST-SYSTEM.md",
    "content": "# Dioxus Manifest & Permissions System\n\nDeclare permissions, deep links, background modes, and platform-specific manifest settings through `Dioxus.toml` configuration.\n\n## Overview\n\nDioxus provides a unified configuration system for cross-platform app manifest customization. Instead of using macros or platform-specific files, all manifest configuration is centralized in your `Dioxus.toml` file.\n\nThe system follows a **unified-first, platform-override** pattern:\n1. Define cross-platform settings in unified sections (`[permissions]`, `[deep_links]`, `[background]`)\n2. Override or extend with platform-specific sections (`[ios]`, `[android]`, `[macos]`)\n\n## Permissions\n\nAdd a `[permissions]` section to your `Dioxus.toml`:\n\n```toml\n[bundle]\nidentifier = \"com.example.myapp\"\n\n[permissions]\nlocation = { precision = \"fine\", description = \"Track your location for navigation\" }\ncamera = { description = \"Take photos for your profile\" }\nmicrophone = { description = \"Record voice messages\" }\n```\n\nThe CLI automatically maps these unified permissions to platform-specific identifiers:\n\n| Unified Permission | Android | iOS/macOS |\n|-------------------|---------|-----------|\n| `location.fine` | `ACCESS_FINE_LOCATION` | `NSLocationWhenInUseUsageDescription` |\n| `location.coarse` | `ACCESS_COARSE_LOCATION` | `NSLocationWhenInUseUsageDescription` |\n| `background_location` | `ACCESS_BACKGROUND_LOCATION` | `NSLocationAlwaysAndWhenInUseUsageDescription` |\n| `camera` | `CAMERA` | `NSCameraUsageDescription` |\n| `microphone` | `RECORD_AUDIO` | `NSMicrophoneUsageDescription` |\n| `notifications` | `POST_NOTIFICATIONS` | (runtime only) |\n| `photos.read` | `READ_MEDIA_IMAGES` | `NSPhotoLibraryUsageDescription` |\n| `photos.write` | `WRITE_EXTERNAL_STORAGE` | `NSPhotoLibraryAddUsageDescription` |\n| `bluetooth` | `BLUETOOTH_CONNECT` | `NSBluetoothAlwaysUsageDescription` |\n| `contacts` | `READ_CONTACTS` | `NSContactsUsageDescription` |\n| `calendar` | `READ_CALENDAR` | `NSCalendarsUsageDescription` |\n| `biometrics` | `USE_BIOMETRIC` | `NSFaceIDUsageDescription` |\n| `nfc` | `NFC` | `NFCReaderUsageDescription` |\n| `motion` | `ACTIVITY_RECOGNITION` | `NSMotionUsageDescription` |\n| `health` | `BODY_SENSORS` | `NSHealthShareUsageDescription` |\n| `speech` | `RECORD_AUDIO` | `NSSpeechRecognitionUsageDescription` |\n\n## Deep Linking\n\nConfigure URL schemes and universal/app links with the unified `[deep_links]` section:\n\n```toml\n[deep_links]\n# Custom URL schemes (e.g., myapp://path)\nschemes = [\"myapp\", \"com.example.myapp\"]\n\n# Universal links / App Links hosts\nhosts = [\"example.com\", \"*.example.com\"]\n\n# Path patterns (optional, matches all paths if empty)\npaths = [\"/app/*\", \"/share/*\"]\n```\n\nPlatform-specific overrides extend (not replace) the unified config:\n\n```toml\n# iOS-only additional schemes\n[ios]\nurl_schemes = [\"myapp-ios\"]\n\n# Android-specific intent filters for advanced cases\n[[android.intent_filters]]\nactions = [\"android.intent.action.VIEW\"]\ncategories = [\"android.intent.category.DEFAULT\", \"android.intent.category.BROWSABLE\"]\nauto_verify = true  # For App Links (requires assetlinks.json)\n\n[[android.intent_filters.data]]\nscheme = \"https\"\nhost = \"example.com\"\npath_prefix = \"/app\"\n```\n\n## Background Modes\n\nConfigure background execution capabilities with the unified `[background]` section:\n\n```toml\n[background]\nlocation = true           # Background location updates\naudio = true              # Background audio playback\nfetch = true              # Background data fetch\nremote-notifications = true  # Remote push notification processing\nvoip = true               # VoIP calls\nbluetooth = true          # Bluetooth LE accessories\nprocessing = true         # Background processing tasks\n```\n\nPlatform-specific overrides:\n\n```toml\n# iOS: Additional background modes\n[ios]\nbackground_modes = [\"newsstand-content\", \"external-accessory\"]\n\n# Android: Foreground service types\n[android]\nforeground_service_types = [\"location\", \"mediaPlayback\", \"phoneCall\"]\n```\n\n## Platform-Specific Configuration\n\n### iOS Configuration\n\n```toml\n[ios]\ndeployment_target = \"15.0\"\nurl_schemes = [\"myapp-ios\"]  # Platform-specific URL schemes\nbackground_modes = [\"location\", \"fetch\"]  # Additional background modes\n\n# Document types the app can open\n[[ios.document_types]]\nname = \"My Document\"\nextensions = [\"mydoc\", \"mydocx\"]\nrole = \"Editor\"\n\n# Add Info.plist entries as key-value pairs\n[ios.plist]\nITSAppUsesNonExemptEncryption = false\n\n# Add entitlements\n[ios.entitlements]\n\"com.apple.security.application-groups\" = [\"group.com.example.app\"]\n\"aps-environment\" = \"development\"\n\n# Raw XML for advanced cases\n[ios.raw]\ninfo_plist = \"\"\"\n<key>CustomKey</key>\n<string>custom-value</string>\n\"\"\"\n```\n\n### Android Configuration\n\n```toml\n[android]\nmin_sdk = 24\ntarget_sdk = 34\nfeatures = [\"android.hardware.location.gps\"]\nurl_schemes = [\"myapp-android\"]  # Platform-specific URL schemes\nforeground_service_types = [\"location\", \"mediaPlayback\"]\n\n# Intent filters for deep linking\n[[android.intent_filters]]\nactions = [\"android.intent.action.VIEW\"]\ncategories = [\"android.intent.category.DEFAULT\", \"android.intent.category.BROWSABLE\"]\nauto_verify = true\n\n[[android.intent_filters.data]]\nscheme = \"https\"\nhost = \"example.com\"\n\n# Package visibility queries (Android 11+)\n[android.queries]\npackages = [\"com.other.app\"]\n\n# Additional permissions not covered by unified permissions\n[android.permissions]\n\"android.permission.FOREGROUND_SERVICE\" = { description = \"Run background service\" }\n\n# Raw manifest XML for advanced cases\n[android.raw]\nmanifest = \"\"\"\n<uses-feature android:name=\"android.hardware.touchscreen\" android:required=\"false\" />\n\"\"\"\n```\n\n### macOS Configuration\n\n```toml\n[macos]\nminimum_system_version = \"11.0\"\nframeworks = [\"CoreLocation.framework\"]\nurl_schemes = [\"myapp-macos\"]  # Platform-specific URL schemes\ncategory = \"public.app-category.productivity\"\n\n# Document types (same format as iOS)\n[[macos.document_types]]\nname = \"My Format\"\nextensions = [\"myfmt\"]\n\n# Add Info.plist entries\n[macos.plist]\nLSUIElement = true\n\n# Add entitlements\n[macos.entitlements]\n\"com.apple.security.app-sandbox\" = true\n\"com.apple.security.network.client\" = true\n\n# Raw XML for advanced cases\n[macos.raw]\ninfo_plist = \"\"\"\n<key>CustomKey</key>\n<string>custom-value</string>\n\"\"\"\n```\n\n## Complete Example\n\nHere's a complete example for a geolocation app:\n\n```toml\n[application]\nname = \"GeoTracker\"\n\n[bundle]\nidentifier = \"com.example.geotracker\"\n\n# Unified permissions - automatically mapped to each platform\n[permissions]\nlocation = { precision = \"fine\", description = \"Track your precise location for navigation\" }\nnotifications = { description = \"Send alerts when you arrive at destinations\" }\n\n# iOS-specific settings\n[ios]\ndeployment_target = \"15.0\"\n\n[ios.plist]\nUIBackgroundModes = [\"location\"]\n\n[ios.entitlements]\n\"com.apple.developer.healthkit\" = false\n\n# Android-specific settings\n[android]\nmin_sdk = 24\ntarget_sdk = 34\nfeatures = [\"android.hardware.location.gps\"]\n\n# macOS-specific settings\n[macos]\nminimum_system_version = \"11.0\"\n\n[macos.entitlements]\n\"com.apple.security.app-sandbox\" = true\n```\n\n## How it Works\n\n1. **Parse**: The CLI parses `Dioxus.toml` and extracts all permission and platform-specific configuration\n2. **Map**: The `PermissionMapper` converts unified permissions to platform-specific identifiers\n3. **Generate**: Handlebars templates inject the permissions and configuration into platform manifests:\n   - `AndroidManifest.xml` for Android\n   - `Info.plist` for iOS and macOS\n4. **Bundle**: The final app bundle includes the configured permissions\n\n## Migration from Macro-Based System\n\nIf you were previously using the `permission!()` macro, migrate to `Dioxus.toml`:\n\n**Before (deprecated):**\n```rust\nuse manganis::permission;\n\nconst LOCATION: Permission = permission!(\n    PermissionBuilder::location(LocationPrecision::Fine)\n        .with_description(\"Track your runs\")\n        .build()\n);\n```\n\n**After:**\n```toml\n# Dioxus.toml\n[permissions]\nlocation = { precision = \"fine\", description = \"Track your runs\" }\n```\n\nThe `permission!()` macro has been removed. All permissions should now be declared in `Dioxus.toml`.\n\n## FFI Integration\n\nFor native plugins that require specific permissions, declare them in your library's documentation and let the app developer add them to their `Dioxus.toml`. The `#[manganis::ffi]` macro for FFI bindings is still available for Swift/Kotlin integration.\n"
  },
  {
    "path": "notes/releases/0.7.0-alpha.0.md",
    "content": "## Hot-Patching, Native Renderer, Bundle Splitting, Radix-UI, more!\n\n> [!NOTE]\n> These release notes are a draft for the full release and thus are incomplete. Not all features might be merged yet!\n> We are releasing v0.7.0-alpha.0 with many docs and features incomplete, please be patient while we fill everything out.\n\nWelcome back to another Dioxus release! If you’re new here, Dioxus (dye • ox • us) is a framework for building cross-platform apps in Rust. We make it easy to ship fullstack web, desktop, and mobile apps with a single codebase.\n\nDioxus 0.7 delivers on a number of promises we made to expand the capabilities of Rust GUI. Mature frameworks like Flutter and React Native sport capable hot-reload systems, popular component frameworks, and render natively. Now, Dioxus is on par with the “state of the art”, and in many ways, is even better.\n\nIn this release, we’re shipping some incredible features. The highlights of this release include:\n\n- Dioxus Native: WGPU-based HTML/CSS Dioxus renderer built on Firefox’s Gecko engine\n- Subsecond: Hot-patching of Rust code at runtime\n- WASM-Split: Code splitting and tree shaking for WebAssembly\n- Dioxus-UI: Shadcn-UI implementation for Dioxus\n\nDioxus 0.7 also brings a number of other exciting new features:\n\n- Automatic tailwind: zero-setup tailwind support built-in!\n- LLMs.txt: first-party context file to supercharge AI coding models\n- MCP Server: add context, resources, and tools to VSCode, Cursor, and Claude\n- Blitz: our modular HTML/CSS renderer powering Dioxus Native, available for everyone!\n- Dioxus Playground: online WASM/WASI playground with integrated hot-patching\n- Fullstack WebSockets: websockets in a single line of code\n- Integrated Debugger Support: open CodeLLDB or nvim DAP with a single keystroke\n- Fullstack status codes: Integration of status codes and custom errors in fullstack\n- Configurable Mobile Builds: Customize your AndroidManifest and Info.plist\n\nPlus, a number of quality-of-life upgrades:\n\n- one-line installer\n- `dx self-update` and update notifications\n- automatically open simulators\n- `dx` compatibility with non-dioxus projects\n- Better log coloring\n- desktop and mobile toasts\n- improved website landing page and migration to dioxus.dev\n- Reduced flicker on CSS hot-reloads\n- HTML streaming now waits for the router to render\n- Axum 0.8 upgrade\n\nAnd many, many bugs fixed:\n\n- Issues with synchronous multi-window\n- Tab focusing\n- Hot-reloaded assets not being re-processed\n\n## Note from the author\n\nDioxus 0.7 marks the second anniversary of me (Jonathan Kelley) going full time on Dioxus. How time flies! In the past two years we shipped so much:\n\n- Template Hot-Reloading and Autoformatting\n- Migration to Signals\n- First-party Android and iOS tooling\n- Server Function integration\n- Linker-based asset system\n- and so much more!\n\nThe road here has been long and frankly, lots of work. When we started out, the Rust ecosystem had very few good solutions to the basic problems in application development. Even now, the Rust hotpatching and native renderers - while incredible achievements on their own - are just “par for the course” for application development.\n\nWith Dioxus 0.7, I feel like the Dioxus foundations are finally solid. We have excellent developer tools, lightning-fast hotpatching, a great asset system, a solid RPC solution, bundle splitting, automatic optimizations, autocomplete, autoformatting, a capable state management solution, comprehensive docs, and funding for the foreseeable future. It’s always nice to see that decisions to adopt industry-standard tech pay-off (Rust GUIs in 2025 article).\n\nWhat of the future? I finally feel like we’re on the “other side” of the once-impossible problems. With hot-patching and the native renderer behind us, we’re quite free to work on smaller projects. We could definitely use better marketing, more tutorial videos, better starter templates, and ecosystem growth (native APIs in 0.8!). Thanks for all the support so far!\n\n## Rust Hot-patching\n\nThe biggest feature of this release: Dioxus now supports hot-patching of Rust code at runtime! You can now iterate on your app’s frontend and backend *simultaneously* without skipping a beat.\n\n\nWe’ve been working on this feature for almost an *entire year,* so this is a very special release for us. The tool powering this hot-patching is called *Subsecond* and works across all major platforms: Web (WASM), Desktop (macOS, Linux, Windows), and even mobile (iOS, Android):\n\n\nSubsecond works in tandem with the Dioxus CLI to enable hot-patching for any Rust project. Simply run `dx serve` on your project and all `subsecond::call` sites will be hot-patched. For example, here’s Subsecond working with a Ratatui app:\n\n\nThe infrastructure to support Subsecond is quite complex; consequently, we plan to only ship the Subsecond engine within the Dioxus CLI itself. However, we still want the ecosystem to experience the magic of Subsecond, so we’ve done two things:\n\n- Make `dx` a standalone runner, not tied to Dioxus\n- Integrated hotpatching with our new Dioxus Playground\n\nHot-patching Rust code is no simple feat. To achieve a segfault-free experience, we recommend framework authors to tie into Subsecond’s minimal runtime. For application developers, you can simply use `subsecond::call(some_fn)` at clean integration points to take advantage of hot-patching. If you use Dioxus, hot-patching comes directly integrated with components and server functions.\n\n```rust\npub fn launch() {\n    loop {\n        std::thread::sleep(std::time::Duration::from_secs(1));\n        subsecond::call(|| tick());\n    }\n}\n\nfn tick() {\n    println!(\"edit me to see the loop in action!!!!!!!!! \");\n}\n```\n\nWhile in theory we could *implicitly* override calls to `tick` with function detouring, we instead chose *explicit* integration points. Hot-patching encounters a significant challenge with changes to struct layout and alignment, and implicit patching exacerbates these safety issues. Explicit integration provides an opportunity frameworks to “re-instance” changed structs and guarantees a segfault-free experience at the cost of losing some runtime state.\n\nWe expect folks to use Subsecond outside of Dioxus, namely in web development, so we’ve provided a few starter-integrations for popular libraries:\n\n- Axum\n- Bevy\n- Ratatui\n\nHot-patching covers nearly *every* case in Dioxus - there’s so much you can hot-reload:\n\n\n\nUnder the hood, we implemented a form of incremental linking / binary patching tailored for running apps. This is not too distant from the idea laid out by Andrew Kelley for Zig. We have yet to release an in-depth technical writeup about how Subsecond works, but if you’re really interested, come join us at the Seattle RustConf and learn about it during our talk!\n\n## Dioxus Native\n\n## WASM Bundle Splitting and Lazy Loading\n\n## Component Library: Radix Primitives and ShadCN-UI\n\n## LLMs.txt, Cursor Rules, MCP Server, and Vibe-Coding\n\n## Automatic Tailwind\n\n- `dx` now detects a `tailwind.css` file in the root of your crate\n- customize the input and output files in your dioxus.toml\n- Automatically downloads the tailwind binary in the background\n\n## Blitz 0.1\n\nWe’re *extremely* excited to release Blitz: our modular HTML/CSS rendering engine.\n\nBlitz combines a number of exciting projects to bring customizable HTML rendering engine to everyone. Blitz is a result of collaboration across many projects: Firefox, Google, Servo, and Bevy. We’re leveraging a number of powerful libraries:\n\n- Taffy: our high-performance flexbox layout engine\n- Stylo: Firefox and Servo’s shared CSS resolution engine\n- Vello: Google’s GPU compute renderer\n\nBlitz is an extremely capable renderer, often producing results indistinguishable from browsers like Chrome and Safari:\n\n\nNot every CSS feature is supported yet, with some bugs like incorrect writing direction or the occasional layout quirk. Our support matrix is here: https://blitz-website.fly.dev/status/css\n\nThe samples that Blitz can create are quite incredible. Servo’s website:\n\n\nHackernews:\n\n\nThe BBC:\n\n\nDo note that Blitz is still very young and doesn’t always produce the best outputs, especially on pages that require JavaScript to function properly or use less-popular CSS features:\n\nBlitz also provides a pluggable layer for interactivity, supporting actions like text inputs, pluggable widgets, form submissions, hover styling, and more. Here’s Dioxus-Motion working alongside our interactivity layer to provide high quality animations:\n\n\nBear in mind that Blitz is still considered a “work in progress.” We have not focused on performance\n\n## Integrated Debugger\n\nTo date, debugging Rust apps with VSCode hasn’t been particularly easy. Each combination of launch targets, flags, and arguments required a new entry into your `vscode.json` or `nvim.dap` file. With Dioxus 0.7, we wanted to improve debugging and now ship an integrated debugger. Simply press `d` and the current LLDB / DAP instance will attach to the app running under `dx serve`. By default, LLDB provides rather cryptic names and values while debugging, so we’ve built a prettifier that improves the rendering of Enums and Signals in the debugger.\n\nThe integrated debugger works with VSCode and DAP setups and can be used to debug both the server and client simultaneously.\n\n## Improved Version Management Experience\n\n- one-line installer\n- `dx self-update` and update notifications\n\n## Dioxus Playground\n\n## Automatically open simulators\n\n## Desktop and Mobile toasts\n\n## Reduced flicker on CSS Hot-reloads\n\n## Better log coloring\n\n## Various Quality of Life Upgrades\n\n## Axum 0.8 Upgrade and Fullstack Improvements\n\n- HTML streaming now waits for the router to render\n- Axum 0.8 upgrade\n\n## Playground and Migration to `dioxus.dev`\n\n## Fullstack WebSockets, improved HTML streaming, and custom Error types\n\n## ADB Reverse Proxy for Device Hot-Reload\n\n## DX Compatibility with *any* project\n\nDioxus’ `dx` tooling is now usable with any Rust project, not just Dioxus projects. You can use `dx` alongside many of the popular Rust projects.\n\n- Hot-Reload\n- Packaging/Bundling for Web/Desktop/Mobile\n- Assets / Mangnais\n\nCustomize AndroidManifest.xml and Info.plist\n\n- pass in your own\n- or configure them via our top-level config for both\n\n## iPad Support\n\n## Hot-Dog Tutorial\n"
  },
  {
    "path": "notes/translations/fa-ir/README.md",
    "content": "<p>\n    <p align=\"center\" >\n      <img src=\"../../notes/header-light.svg#gh-light-mode-only\" >\n      <img src=\"../../notes/header-dark.svg#gh-dark-mode-only\" >\n      <a href=\"https://dioxuslabs.com\">\n          <img src=\"../../notes/dioxus_splash_8.avif\">\n      </a>\n    </p>\n</p>\n<div align=\"center\">\n  <!-- Crates version -->\n  <a href=\"https://crates.io/crates/dioxus\">\n    <img src=\"https://img.shields.io/crates/v/dioxus.svg?style=flat-square\"\n    alt=\"Crates.io version\" />\n  </a>\n  <!-- Downloads -->\n  <a href=\"https://crates.io/crates/dioxus\">\n    <img src=\"https://img.shields.io/crates/d/dioxus.svg?style=flat-square\"\n      alt=\"Download\" />\n  </a>\n  <!-- docs -->\n  <a href=\"https://docs.rs/dioxus\">\n    <img src=\"https://img.shields.io/badge/docs-latest-blue.svg?style=flat-square\"\n      alt=\"docs.rs docs\" />\n  </a>\n  <!-- CI -->\n  <a href=\"https://github.com/jkelleyrtp/dioxus/actions\">\n    <img src=\"https://github.com/dioxuslabs/dioxus/actions/workflows/main.yml/badge.svg\"\n      alt=\"CI status\" />\n  </a>\n\n  <!--Awesome -->\n  <a href=\"https://dioxuslabs.com/awesome\">\n    <img src=\"https://cdn.rawgit.com/sindresorhus/awesome/d7305f38d29fed78fa85652e3a63e154dd8e8829/media/badge.svg\" alt=\"Awesome Page\" />\n  </a>\n  <!-- Discord -->\n  <a href=\"https://discord.gg/XgGxMSkvUM\">\n    <img src=\"https://img.shields.io/discord/899851952891002890.svg?logo=discord&style=flat-square\" alt=\"Discord Link\" />\n  </a>\n</div>\n\n<div align=\"center\">\n  <h3>\n    <a href=\"https://dioxuslabs.com\"> Website </a>\n    <span> | </span>\n    <a href=\"https://github.com/DioxusLabs/example-projects\"> Examples </a>\n    <span> | </span>\n    <a href=\"https://dioxuslabs.com/learn/0.7/tutorial\"> Guide </a>\n    <span> | </span>\n    <a href=\"https://github.com/DioxusLabs/dioxus/blob/main/notes/translations/zh-cn/README.md\"> 中文 </a>\n    <span> | </span>\n    <a href=\"https://github.com/DioxusLabs/dioxus/blob/main/notes/translations/pt-br/README.md\"> PT-BR </a>\n    <span> | </span>\n    <a href=\"https://github.com/DioxusLabs/dioxus/blob/main/notes/translations/ja-jp/README.md\"> 日本語 </a>\n    <span> | </span>\n    <a href=\"https://github.com/DioxusLabs/dioxus/blob/main/notes/translations/tr-tr\"> Türkçe </a>\n     <span> | </span>\n    <a href=\"https://github.com/DioxusLabs/dioxus/blob/main/notes/translations/fa-ir\"> فارسی </a>\n  </h3>\n</div>\n<br>\n<br>\n\n<div dir=\"rtl\">\n    <blockquote>\n        یادداشت مترجم: اصطلاحات فنی را به زبان اصلی استفاده کرده و تا حد امکان توضیح داده‌ام تا هم برای افراد باتجربه و هم برای تازه‌کارها ساده‌تر شود. زبان تخصصی ما انگلیسی است، بنابراین اطلاعات داخل پرانتز عمدتاً برای دوستان تازه‌کار است. دوستان باتجربه معمولاً با اصطلاحات و معانی آن‌ها آشنا هستند. از آنجا که اصطلاحات فنی در زندگی روزمره نیز به همان شکل اصلی استفاده می‌شوند، ترجمه آن‌ها به فارسی کمی عجیب به نظر می‌رسد. البته ضروری هم نبود، اما تلاش کردم تا برای افرادی که آشنا نیستند، تصوری ایجاد کنم. وگرنه هیچ‌کس به جای frontend از کلمه‌ای فارسی یا به جای framework از جایگزینی دیگر استفاده نمی‌کند، من هم کاملاً این را می‌دانم. برای جلوگیری از ایجاد حس ناخوشایند، فقط در اولین برخورد با این مفاهیم آن‌ها را توضیح داده‌ام و در ادامه از شکل اصلی استفاده کرده‌ام. اگر خطایی وجود دارد، لطفاً ببخشید.\n    </blockquote>\n</div>\n\n<div dir=\"rtl\">\n    وب، دسکتاپ و موبایل؛ همه را تنها با یک زیرساخت کدنویسی تولید کنید. نصب آسان، بارگذاری مجدد خودکار (hotreloading) که به‌طور خودکار با شناسایی تغییرات فعال می‌شود، و مدیریت وضعیت مبتنی بر سیگنال. با استفاده از توابع سرور، ویژگی‌های backend (بخش پشتی) را اضافه کنید و با CLI (رابط خط فرمان) بسته‌بندی کنید (با استفاده از دستور `dx bundle`).\n</div>\n\n```rust\nfn app() -> Element {\n    let mut count = use_signal(|| 0);\n\n    rsx! {\n        h1 { \"High-Five counter: {count}\" }\n        button { onclick: move |_| count += 1, \"Up high!\" }\n        button { onclick: move |_| count -= 1, \"Down low!\" }\n    }\n}\n```\n\n<div dir=\"rtl\">\n\n## ⭐ ویژگی‌های منحصر به فرد:\n\n- تولید برنامه‌های چندپلتفرمی (وب، دسکتاپ، موبایل، سرور و موارد دیگر) تنها با ۳ خط کد.\n- [مدیریت وضعیت ارگونومیک](https://dioxuslabs.com/blog/release-050) با ترکیب بهترین ویژگی‌های React، Solid و Svelte.\n- عملکرد فوق‌العاده با سریع‌ترین فریمورک wasm (WebAssembly) در Rust [sledgehammer](https://dioxuslabs.com/blog/templates-diffing).\n- بسته‌بندی یکپارچه برای انتشار در وب، macOS، Linux و Windows (با دستور `dx bundle` به‌سادگی قابل انجام است).\n- و موارد بیشتر! گشتی بزنید -> [تور Dioxus](https://dioxuslabs.com/learn/0.7/).\n\n## بارگذاری مجدد (hot-reloading) آنی\n\nتنها با یک دستور `dx serve` برنامه خود را اجرا کنید. تغییرات در مارک‌آپ و استایل‌ها (مانند HTML و CSS، یا در Dioxus با استفاده از rsx) را انجام دهید و نتیجه را به‌صورت آنی مشاهده کنید. اگرچه قابلیت‌های hot-reloading در Rust هنوز به‌طور کامل بهینه نیستند، اما با استفاده از [hot-lib-reloader](https://docs.rs/hot-lib-reloader/latest/hot_lib_reloader/) ممکن است.\n\n<div align=\"center\">\n  <img src=\"../../notes/hotreload.gif\" alt=\"بارگذاری مجدد آنی\">\n</div>\n\n## بسته‌بندی برای وب و دسکتاپ\n\nتنها با اجرای دستور `dx bundle` برنامه شما با حداکثر بهینه‌سازی کامپایل و بسته‌بندی می‌شود. از قابلیت‌های ایجاد فایل‌های [`.avif`، فشرده‌سازی `.wasm` و کوچک‌سازی](https://dioxuslabs.com/learn/0.7/tutorial/assets) و موارد بیشتر بهره‌مند شوید. برنامه‌های سبک وب (کمتر از [50kb](https://github.com/ealmloff/tiny-dioxus/)) و برنامه‌های دسکتاپ/موبایل با حجم کمتر از 15mb تولید کنید.\n\n<div align=\"center\">\n  <img src=\"../../notes/bundle.gif\" alt=\"بسته‌بندی برنامه\">\n</div>\n\n## مستندات فوق‌العاده\n\nما زمان زیادی صرف کردیم تا مستنداتی واضح، خوانا و جامع ارائه دهیم. تمامی عناصر HTML و لیسنرها (listeners) با استفاده از MDN مستند شده‌اند ([برای جزئیات بیشتر کلیک کنید](https://developer.mozilla.org)) و برای به‌روز بودن، مستندات با خود Dioxus به‌صورت مداوم ادغام می‌شوند. برای راهنماها، مرجع‌ها، آموزش‌ها و موارد بیشتر به [وبسایت Dioxus](https://dioxuslabs.com/learn/0.7/) سر بزنید. نکته جالب: ما از وبسایت Dioxus به‌عنوان آزمایشگاهی برای تست ویژگی‌های جدید استفاده می‌کنیم -> [مشاهده کنید!](https://github.com/dioxusLabs/docsite).\n\n<div align=\"center\">\n  <img src=\"../../notes/docs.avif\" alt=\"مستندات Dioxus\">\n</div>\n\n## تمرکز بر تجربه توسعه‌دهنده\n\nDioxus تمرکز زیادی بر تجربه توسعه‌دهنده دارد و ابزارهای متعددی برای بهبود آن ارائه می‌دهد. [افزونه VSCode](https://marketplace.visualstudio.com/items?itemName=DioxusLabs.dioxus) را توسعه دادیم که به شما امکان می‌دهد کدهای RSX را به‌صورت خودکار قالب‌بندی کنید، HTML را به RSX تبدیل کنید و موارد بیشتر. همچنین، CLI قدرتمندی توسعه دادیم که به شما کمک می‌کند برنامه‌های جدید بسازید، آن‌ها را سرو کنید و برای پلتفرم‌های مختلف بسته‌بندی کنید. در نقشه راه ما قابلیت‌های بیشتری مانند انتشار نیز پیش‌بینی شده است.\n\n<div align=\"center\">\n  <img src=\"../../notes/autofmt.gif\" alt=\"فرمت‌دهی خودکار\">\n</div>\n\n## جامعه\n\nDioxus یک پروژه با جامعه‌ای فعال و پویا در [Discord](https://discord.gg/XgGxMSkvUM) و [GitHub](https://github.com/DioxusLabs/dioxus/issues) است. ما همیشه آماده پاسخگویی به سوالات شما هستیم، کمک می‌کنیم و برای شروع پروژه‌های شما آماده‌ایم. [SDK ما](https://github.com/DioxusLabs/dioxus-std) توسط جامعه توسعه داده شده و از طریق [سازمان GitHub](https://github.com/dioxus-community/) به بهترین برنامه‌های Dioxus دسترسی پیدا می‌کنید.\n\n</div>\n\n<div align=\"center\">\n  <img src=\"../../notes/dioxus-community.avif\">\n</div>\n\n<div dir=\"rtl\">\n\n## تیم اصلی تمام‌وقت\n\nDioxus که به عنوان یک پروژه جانبی آغاز شد، اکنون به تیم کوچکی تبدیل شده که به‌صورت تمام‌وقت روی آن کار می‌کند. با حمایت FutureWei، Satellite.im و برنامه Github Accelerator، توانسته‌ایم به‌طور تمام‌وقت روی Dioxus تمرکز کنیم. برنامه بلندمدت ما ارائه ابزارهای شرکتی باکیفیت و پولی است تا Dioxus به پروژه‌ای خودکفا تبدیل شود. اگر شرکت شما علاقه‌مند به استفاده از Dioxus است و تمایل به همکاری با ما دارد، لطفاً با ما تماس بگیرید!\n\n## پلتفرم‌های پشتیبانی‌شده\n\n</div>\n\n<div dir=\"rtl\" align=\"center\">\n  <table style=\"width:100%\">\n    <tr>\n      <td>\n        <b>وب</b>\n        <br />\n        <em>پشتیبانی سطح ۱</em>\n      </td>\n      <td>\n        <ul>\n          <li>رندر مستقیم به DOM با استفاده از WebAssembly.</li>\n          <li>پیش‌پردازش با رندر سمت سرور (SSR) و بازسازی سمت کاربر.</li>\n          <li>برنامه‌های سبک (مانند \"سلام دنیا\") مشابه React، با حجمی حدود 50 کیلوبایت.</li>\n          <li>سرور توسعه یکپارچه (`dx serve`) و بارگذاری مجدد آنی.</li>\n        </ul>\n      </td>\n    </tr>\n    <tr>\n      <td>\n        <b>فول‌استک (Frontend و Backend)</b>\n        <br />\n        <em>پشتیبانی سطح ۱</em>\n      </td>\n      <td>\n        <ul>\n          <li>قابلیت‌های رندر سمت سرور، بازسازی و fallback با استفاده از Suspense.</li>\n          <li>توابع سرور داخلی برای مدیریت بک‌اند.</li>\n          <li>ادغام با Extractors، Middleware و Routing.</li>\n          <li>سازگاری با موبایل و دسکتاپ.</li>\n        </ul>\n      </td>\n    </tr>\n    <tr>\n      <td>\n        <b>دسکتاپ</b>\n        <br />\n        <em>پشتیبانی سطح ۱</em>\n      </td>\n      <td>\n        <ul>\n          <li>رندر با استفاده از Webview یا - به‌صورت آزمایشی - WGPU یا Freya (Skia).</li>\n          <li>راه‌اندازی آسان تنها با `cargo run` یا `dx serve`.</li>\n          <li>دسترسی مستقیم به سیستم بدون نیاز به IPC.</li>\n          <li>پشتیبانی از macOS، Linux و Windows با فایل‌های اجرایی سبک (کمتر از 3 مگابایت).</li>\n        </ul>\n      </td>\n    </tr>\n    <tr>\n      <td>\n        <b>رندر زنده (Liveview)</b>\n        <br />\n        <em>پشتیبانی سطح ۱</em>\n      </td>\n      <td>\n        <ul>\n          <li>رندر کامل برنامه یا تنها بخشی از آن در سمت سرور.</li>\n          <li>ادغام با فریمورک‌های محبوب Rust مانند Axum و Warp.</li>\n          <li>پشتیبانی از بیش از 10,000 برنامه و تأخیر بسیار کم.</li>\n        </ul>\n      </td>\n    </tr>\n    <tr>\n      <td>\n        <b>موبایل</b>\n        <br />\n        <em>پشتیبانی سطح ۲</em>\n      </td>\n      <td>\n        <ul>\n          <li>رندر با استفاده از Webview یا - به‌صورت آزمایشی - WGPU یا Skia.</li>\n          <li>پشتیبانی از iOS و Android.</li>\n          <li>این ویژگی در حال حاضر بسیار آزمایشی است و به‌روزرسانی‌های بیشتری در طول سال 2024 ارائه خواهد شد.</li>\n        </ul>\n      </td>\n    </tr>\n    <tr>\n      <td>\n        <b>ترمینال</b>\n        <br />\n        <em>پشتیبانی سطح ۲</em>\n      </td>\n      <td>\n        <ul>\n          <li>رندر برنامه‌ها مستقیماً در ترمینال، مشابه ink.js.</li>\n          <li>بر اساس مدل‌های Flexbox و CSS مشابه مرورگرها.</li>\n          <li>ابزارهای داخلی مانند ورودی متن، دکمه‌ها و سیستم فوکوس.</li>\n        </ul>\n      </td>\n    </tr>\n  </table>\n</div>\n\n<div dir=\"rtl\">\n\n## اجرای مثال‌ها\n\nبرای اجرای مثال‌های موجود در پوشه اصلی، کافی است دستور `cargo run --example <نام مثال>` را اجرا کنید. با این حال، پیشنهاد ما این است که dioxus-cli را نصب کنید و مثال‌ها را با استفاده از `dx serve` اجرا کنید، زیرا بسیاری از این مثال‌ها از پلتفرم وب نیز پشتیبانی می‌کنند. اگر می‌خواهید این مثال‌ها را برای وب اجرا کنید، باید تغییراتی در فایل Cargo.toml اعمال کنید یا ویژگی پیش‌فرض دسکتاپ را غیرفعال کنید.\n\n## مقایسه Dioxus با دیگر فریمورک‌ها\n\nما تمامی فریمورک‌ها را دوست داریم و از پیشرفت آن‌ها در اکوسیستم Rust لذت می‌بریم. در این راستا، بسیاری از پروژه‌های ما توسط فریمورک‌های دیگر استفاده می‌شوند. به عنوان مثال، کتابخانه flex-box ما [Taffy](https://github.com/DioxusLabs/taffy) در پروژه‌هایی مانند [Bevy](https://bevyengine.org/)، [Zed](https://zed.dev/)، [Lapce](https://lapce.dev/)، [Iced](https://github.com/iced-rs/iced) و بسیاری دیگر استفاده شده است.\n\nچند ویژگی که Dioxus را از دیگر فریمورک‌ها متمایز می‌کند:\n\n- **مشابه React**: ما بر اساس مفاهیمی مانند کامپوننت‌ها، props (مشابه آرگومان‌های تابع) و hooks (دسترسی کامپوننت‌ها به ویژگی‌های فریمورک) ساخته‌ایم. این امر ما را بیشتر به Svelte نزدیک می‌کند تا SolidJS.\n- **HTML و CSS**: برای تطبیق بهتر و دیگر ویژگی‌ها، ما HTML و CSS را به عنوان پایه انتخاب کرده‌ایم.\n- **پلتفرم مستقل (Renderer-agnostic)**: [VirtualDOM سریع ما](https://dioxuslabs.com/blog/templates-diffing) به شما این امکان را می‌دهد که رندر را برای هر پلتفرمی تغییر دهید.\n- **حمایت از همکاری (Collaborative)**: ما کتابخانه‌هایی مانند [Taffy](https://github.com/DioxusLabs/taffy)، [manganis](https://github.com/DioxusLabs/manganis)، [include_mdbook](https://github.com/DioxusLabs/include_mdbook)، و [blitz](http://github.com/dioxusLabs/blitz) تولید کرده‌ایم تا اکوسیستم را به صورت اشتراکی رشد دهیم.\n\n### مقایسه Dioxus با Tauri\n\nTauri فریمورکی برای ایجاد برنامه‌های دسکتاپ (و به زودی موبایل) است که از فریمورک‌های وب مانند React، Vue و Svelte برای frontend استفاده می‌کند. هر زمان که نیاز به اجرای یک عملیات native داشته باشید، باید توابع Rust بنویسید و از frontend آن‌ها را فراخوانی کنید.\n\n- **به صورت کامل Rust (Natively Rust)**: معماری Tauri شما را به استفاده از JavaScript یا WebAssembly محدود می‌کند. در Dioxus، کد Rust شما مستقیماً روی سیستم کاربر اجرا می‌شود. اعمالی مانند تولید thread، دسترسی به فایل‌ها و غیره بدون استفاده از IPC bridge امکان‌پذیر هستند.\n- **اهداف مختلف**: Tauri نیازمند پشتیبانی از JavaScript و ابزارهای ساخت آن است. این محدودیت‌هایی بر عملیات شما اعمال می‌کند. Dioxus با تمرکز بر Rust، ویژگی‌هایی مانند توابع سرور، بسته‌بندی پیشرفته و رندر native را فراهم می‌کند.\n- **کدهای مشترک (DNA مشترک)**: اگرچه Tauri و Dioxus پروژه‌های متفاوتی هستند، اما از کتابخانه‌های مشترک مانند Tao و Wry برای مدیریت پنجره‌ها و webview استفاده می‌کنند.\n\n### مقایسه Dioxus با Leptos\n\nLeptos یک فریمورک مشابه SolidJS و SolidStart برای توسعه برنامه‌های وب fullstack است. Dioxus و Leptos اهداف مشابهی برای وب دارند، اما تفاوت‌هایی کلیدی دارند:\n\n- **مدل بازفعال‌سازی (Reactivity model)**: در حالی که Leptos از signals برای reactivity استفاده می‌کند، Dioxus از Virtual DOM و re-render بهره می‌برد. اگرچه signals از نظر تئوری کارآمدتر است، اما در عمل [الگوریتم‌های Virtual DOM Dioxus](https://dioxuslabs.com/blog/templates-diffing) تفاوت قابل توجهی ایجاد نمی‌کنند. [مقایسه‌ها نشان داده‌اند](https://krausest.github.io/js-framework-benchmark/2024/table_chrome_123.0.6312.59.html) که حتی Dioxus سریع‌تر است.\n- **کنترل جریان (Control flow)**: در Leptos، signals باعث محدود شدن به primitives خاص می‌شود. این موضوع ممکن است اشکال‌زدایی خطاهای رابط کاربری (UI) را دشوار کند. در Dioxus، از iterators، حلقه‌های `for` معمولی و شرایط `if` Rust استفاده می‌کنید، و همچنان برنامه شما reactive باقی می‌ماند.\n\n</div>\n\n```rust\nfn Counters() -> Element {\n  let mut counters = use_signal(|| vec![0; initial_length]);\n\n  rsx! {\n    button { onclick: move |_| counters.push(counters.len()); \"Add Counter\" }\n    ul {\n      for idx in 0..counters.len() {\n        li {\n          button { onclick: move |_| counters[idx] += 1; \"{counters[idx]}\" }\n          button { onclick: move |_| { counters.write().remove(idx); } \"Remove\" }\n        }\n      }\n    }\n  }\n}\n```\n\n<div dir=\"rtl\">\n  <blockquote>\n    <a href=\"https://book.leptos.dev/view/04_iteration.html#dynamic-rendering-with-the-for-component\">\n      اما برای Leptos این وضعیت نیازمند ردیابی کلیدها (key tracking)، استفاده از کامپوننت <code>&lt;For&gt;</code>، ایجاد سیگنال‌های جدید و در نهایت پاک‌سازی دستی حافظه است.\n    </a>\n  </blockquote>\n</div>\n\n```rust\nfn Counters() -> Element {\n    let initial_counters = (0..initial_length)\n        .map(|id| (id, create_signal(id + 1)))\n        .collect::<Vec<_>>();\n\n    let (counters, set_counters) = create_signal(initial_counters);\n\n    let add_counter = move |_| {\n        let sig = create_signal(next_counter_id + 1);\n        set_counters.update(move |counters| counters.push((next_counter_id, sig)));\n        next_counter_id += 1;\n    };\n\n    view! {\n        <div>\n            <button on:click=add_counter>\n                \"Add Counter\"\n            </button>\n            <ul>\n                <For\n                    each=counters\n                    key=|counter| counter.0\n                    children=move |(id, (count, set_count))| {\n                        view! {\n                            <li>\n                                <button\n                                    on:click=move |_| set_count.update(|n| *n += 1)\n                                >\n                                    {count}\n                                </button>\n                                <button\n                                    on:click=move |_| {\n                                        set_counters.update(|counters| {\n                                            counters.retain(|(counter_id, (signal, _))| {\n\n                                                if counter_id == &id {\n                                                    signal.dispose();\n                                                }\n                                                counter_id != &id\n                                            })\n                                        });\n                                    }\n                                >\n                                    \"Remove\"\n                                </button>\n                            </li>\n                        }\n                    }\n                />\n            </ul>\n        </div>\n    }\n}\n```\n\n<div dir=\"rtl\">\n\n- **حالت `Copy` (روش ایجاد کپی)**: در نسخه‌های 0.1 تا 0.4 Dioxus، برای افزایش انعطاف‌پذیری مکانیزم borrow checker (سیستمی برای اطمینان از اعتبار متغیرها در استانداردهای Rust)، از ویژگی‌های lifetime (طول عمر در Rust، سیستمی برای کنترل موجودیت متغیرها در بازه مورد نیاز) استفاده می‌شد. این روش برای مدیریت event handlers (مدیریت‌کننده‌های رویداد) به‌خوبی عمل می‌کرد، اما در زمینه async (غیرهمزمان) پیچیدگی‌هایی ایجاد می‌کرد. با انتشار نسخه 0.5 Dioxus، مدل [`Copy`](https://crates.io/crates/generational-box) از Leptos وام گرفته شد.\n\n- **اهداف متفاوت**: Dioxus یک رندرر برای وب، دسکتاپ، موبایل، LiveView و پلتفرم‌های متعدد دیگر ارائه می‌دهد. همچنین، کتابخانه‌های جامعه‌محور و یک SDK چندپلتفرمی را توسعه می‌دهیم. این دامنه وسیع منجر به کندتر شدن انتشار نسخه‌های جدید نسبت به Leptos می‌شود. Leptos بیشتر بر fullstack و وب تمرکز دارد و ویژگی‌هایی مانند HTML streaming (پخش HTML)، islands (جزایر: گروه‌بندی کامپوننت‌ها برای ارسال همراه با WebAssembly)، و کامپوننت‌هایی مانند `<Suspense />` و `<Form />` ارائه می‌دهد که مختص وب هستند. به‌طور کلی، برنامه‌های وب ساخته‌شده با Leptos، footprint (حجم کمتری) خواهند داشت.\n\n- **زبان‌های خاص دامنه (DSLs)**: هر دو فریمورک برای وب مناسب هستند، اما Dioxus برای ایجاد UI از یک DSL مشابه Rust استفاده می‌کند، در حالی که Leptos ساختاری شبیه به HTML دارد. دلیل انتخاب این روش توسط Dioxus، استفاده از امکاناتی مانند code folding (باز و بسته کردن بخش‌هایی از کد) و syntax highlighting (برجسته‌سازی نحو کد) در محیط‌های توسعه یکپارچه (IDE) است. به‌طور کلی، DSL در Dioxus امکانات بیشتری ارائه می‌دهد. برای مثال، Dioxus به‌طور خودکار متون را ترکیب می‌کند، در حالی که Leptos نیازمند استفاده از closures (توابع ناشناس)، و ماکروهایی مانند `format!` یا `format_args!` است.\n\n</div>\n\n```rust\n// dioxus\nrsx! {\n  div { class: \"my-class\", enabled: true, \"سلام، {name}\" }\n}\n\n// leptos\nview! {\n  <div class=\"my-class\" enabled={true}>\n    \"سلام \"\n    {move || name()}\n  </div>\n}\n```\n\n<div dir=\"rtl\">\n### مقایسه Dioxus و Yew\n\nYew که برای توسعه برنامه‌های تک‌صفحه‌ای وب طراحی شده است، یکی از منابع الهام برای Dioxus بوده است. با این حال، محدودیت‌های معماری Yew باعث شد که Dioxus توسعه یابد.\n\n- **برنامه‌های تک‌صفحه‌ای وب**: Yew به‌طور خاص برای توسعه برنامه‌های تک‌صفحه‌ای وب طراحی شده است و به همین دلیل محدود به وب است. اما Dioxus به دلیل پشتیبانی از برنامه‌های چندپلتفرمی fullstack، برای وب، دسکتاپ، موبایل و سرور مناسب است.\n\n- **ابزارهای توسعه‌دهنده**: Dioxus ابزارهایی مانند فرمت خودکار، بارگذاری مجدد فوری و بسته‌بندی (bundler) ارائه می‌دهد.\n\n- **پشتیبانی مداوم**: Dioxus با افزودن ویژگی‌های جدید و رفع اشکالات به‌صورت روزانه، به‌طور فعال در حال توسعه است.\n\n### مقایسه Dioxus و egui\n\negui یک کتابخانه Rust برای ایجاد رابط‌های کاربری گرافیکی (GUI) چندپلتفرمی است که پروژه‌هایی مانند [Rerun.io](https://www.rerun.io) از آن استفاده می‌کنند.\n\n- **Immediate vs Retained (به‌روزرسانی فوری در مقابل نگهداری)**: egui برای ایجاد رندر مجدد در هر فریم طراحی شده است. این معماری برای بازی‌ها و برنامه‌های تعاملی مناسب است، اما استایل و طرح‌بندی بین فریم‌ها حفظ نمی‌شود. در مقابل، Dioxus به‌عنوان یک فریمورک رابط کاربری retained عمل می‌کند، یعنی UI فقط یک‌بار تولید شده و در بین فریم‌ها به‌روزرسانی می‌شود. این روش باعث می‌شود Dioxus از فناوری‌های بومی وب مانند HTML و CSS استفاده کند و عملکرد بهتری در زمینه عمر باتری و کارایی داشته باشد.\n\n- **سفارشی‌سازی**: در حالی که egui سیستم استایل و طرح‌بندی خاص خود را دارد، Dioxus به شما اجازه می‌دهد از HTML و CSS استفاده کنید. این ویژگی امکان استفاده از کتابخانه‌هایی مانند Tailwind و Material UI را فراهم می‌کند.\n\n- **مدیریت وضعیت (State Management)**: egui از یک شیء وضعیت جهانی استفاده می‌کند، اما Dioxus از کامپوننت‌ها و props برای کپسوله‌سازی وضعیت‌ها و قابلیت استفاده مجدد از آنها بهره می‌برد.\n\n### مقایسه Dioxus و Iced\n\nIced یک کتابخانه رابط کاربری گرافیکی چندپلتفرمی است که از Elm الهام گرفته شده و از WGPU برای رندر بومی و همچنین DOM nodes برای وب پشتیبانی می‌کند.\n\n- **مدیریت وضعیت Elm**: Iced از مدل مدیریت وضعیت Elm استفاده می‌کند که شامل reducers (توابع) و پیام‌رسانی است. این معماری در مقایسه با Dioxus می‌تواند پرجزئیات‌تر و خسته‌کننده‌تر باشد.\n\n- **رابط کاربری بومی**: Dioxus از webview به‌عنوان رندرر استفاده می‌کند و ویژگی‌هایی مانند کپی/چسباندن، دسترسی‌پذیری و متون بومی را ارائه می‌دهد. رندرر Iced به دلیل عدم پشتیبانی از این ویژگی‌ها، کمتر بومی به نظر می‌رسد.\n\n- **WGPU**: رندرر WGPU در Dioxus هنوز به بلوغ کافی نرسیده و برای توسعه محصول مناسب نیست. اما رندرر WGPU در Iced بسیار پیشرفته‌تر است و برای توسعه محصول استفاده می‌شود.\n\n### مقایسه Dioxus و Electron\n\nDioxus و Electron دو پروژه با اهداف مشابه اما معماری کاملاً متفاوت هستند. Electron به توسعه‌دهندگان اجازه می‌دهد با استفاده از فناوری‌های وب مانند HTML، CSS و JavaScript برنامه‌های دسکتاپ چندپلتفرمی ایجاد کنند.\n\n- **سبک‌تر بودن**: Dioxus از webview بومی سیستم یا به‌صورت اختیاری از WGPU برای رندر رابط کاربری استفاده می‌کند. به‌طور مثال، در macOS، یک برنامه Electron معمولاً 100 مگابایت فضا اشغال می‌کند، در حالی که برنامه مشابه Dioxus فقط 15 مگابایت است.\n\n- **بلوغ**: Electron با داشتن جامعه‌ای بزرگ و ابزارهای فراوان، یک پروژه بسیار بالغ است. در مقابل، Dioxus همچنان جوان است و نیاز به کار بیشتری دارد.\n\n## مشارکت\n\n- برای اطلاعات بیشتر درباره مشارکت، به [صفحه مشارکت در سایت ما](https://dioxuslabs.com/learn/0.7/beyond/contributing) مراجعه کنید.\n- مشکلات خود را در [ردیاب مشکلات](https://github.com/dioxuslabs/dioxus/issues) گزارش دهید.\n- به Discord [بپیوندید](https://discord.gg/XgGxMSkvUM) و سوالات خود را مطرح کنید!\n\n<a href=\"https://github.com/dioxuslabs/dioxus/graphs/contributors\">\n  <img src=\"https://contrib.rocks/image?repo=dioxuslabs/dioxus&max=30&columns=10\" />\n</a>\n\n## مجوز\n\nاین پروژه تحت [مجوز MIT] ارائه شده است.\n\n[مجوز MIT]: https://github.com/DioxusLabs/dioxus/blob/master/LICENSE-MIT\n\nمگر اینکه به‌طور خاص ذکر شود، تمام مشارکت‌های شما در Dioxus تحت این مجوز خواهد بود.\n\n</div>\n"
  },
  {
    "path": "notes/translations/ja-jp/README.md",
    "content": "<p>\n    <p align=\"center\" >\n      <!-- <img src=\"./notes/header-light-updated.svg#gh-light-mode-only\" >\n      <img src=\"./notes/header-dark-updated.svg#gh-dark-mode-only\" > -->\n      <!-- <a href=\"https://dioxuslabs.com\">\n          <img src=\"./notes/flat-splash.avif\">\n      </a> -->\n      <img src=\"../../notes/splash-header-darkmode.svg#gh-dark-mode-only\" style=\"width: 80%; height: auto;\">\n      <img src=\"../../notes/splash-header.svg#gh-light-mode-only\" style=\"width: 80%; height: auto;\">\n      <img src=\"../../notes/image-splash.avif\">\n      <br>\n    </p>\n</p>\n<div align=\"center\">\n  <!-- Crates version -->\n  <a href=\"https://crates.io/crates/dioxus\">\n    <img src=\"https://img.shields.io/crates/v/dioxus.svg?style=flat-square\"\n    alt=\"Crates.io version\" />\n  </a>\n  <!-- Downloads -->\n  <a href=\"https://crates.io/crates/dioxus\">\n    <img src=\"https://img.shields.io/crates/d/dioxus.svg?style=flat-square\"\n      alt=\"Download\" />\n  </a>\n  <!-- docs -->\n  <a href=\"https://docs.rs/dioxus\">\n    <img src=\"https://img.shields.io/badge/docs-latest-blue.svg?style=flat-square\"\n      alt=\"docs.rs docs\" />\n  </a>\n  <!-- CI -->\n  <a href=\"https://github.com/jkelleyrtp/dioxus/actions\">\n    <img src=\"https://github.com/dioxuslabs/dioxus/actions/workflows/main.yml/badge.svg\"\n      alt=\"CI status\" />\n  </a>\n\n  <!--Awesome -->\n  <a href=\"https://dioxuslabs.com/awesome\">\n    <img src=\"https://cdn.rawgit.com/sindresorhus/awesome/d7305f38d29fed78fa85652e3a63e154dd8e8829/media/badge.svg\" alt=\"Awesome Page\" />\n  </a>\n  <!-- Discord -->\n  <a href=\"https://discord.gg/XgGxMSkvUM\">\n    <img src=\"https://img.shields.io/discord/899851952891002890.svg?logo=discord&style=flat-square\" alt=\"Discord Link\" />\n  </a>\n</div>\n\n<div align=\"center\">\n  <h3>\n    <a href=\"https://dioxuslabs.com\"> Website </a>\n    <span> | </span>\n    <a href=\"https://github.com/DioxusLabs/dioxus/tree/main/examples\"> Examples </a>\n    <span> | </span>\n    <a href=\"https://dioxuslabs.com/learn/0.7/tutorial\"> Guide </a>\n    <span> | </span>\n    <a href=\"https://github.com/DioxusLabs/dioxus/blob/main/notes/translations/zh-cn/README.md\"> 中文 </a>\n    <span> | </span>\n    <a href=\"https://github.com/DioxusLabs/dioxus/blob/main/notes/translations/pt-br/README.md\"> PT-BR </a>\n    <span> | </span>\n    <a href=\"https://github.com/DioxusLabs/dioxus/blob/main/notes/translations/ja-jp/README.md\"> 日本語 </a>\n    <span> | </span>\n    <a href=\"https://github.com/DioxusLabs/dioxus/blob/main/notes/translations/tr-tr\"> Türkçe </a>\n    <span> | </span>\n    <a href=\"https://github.com/DioxusLabs/dioxus/blob/main/notes/translations/ko-kr\"> 한국어 </a>\n  </h3>\n</div>\n<br>\n<p align=\"center\">\n  <a href=\"https://dioxuslabs.com/blog/release-060/\">✨ Dioxus 0.6 is released - check it out here! ✨</a>\n</p>\n<br>\n\nWeb、デスクトップ、モバイルなど、単一のコードベースで構築します。ゼロコンフィグのセットアップ、統合されたホットリロード、シグナルベースの状態管理。サーバー機能を追加し、CLIでバンドルします。\n\n```rust\nfn app() -> Element {\n    let mut count = use_signal(|| 0);\n\n    rsx! {\n        h1 { \"High-Five counter: {count}\" }\n        button { onclick: move |_| count += 1, \"Up high!\" }\n        button { onclick: move |_| count -= 1, \"Down low!\" }\n    }\n}\n```\n\n## ⭐️ ユニークな機能:\n\n- 3行のコードでクロスプラットフォームアプリ（Web、デスクトップ、モバイル、サーバーなど）\n- [人間工学に基づいた状態管理](https://dioxuslabs.com/blog/release-050)は、React、Solid、Svelteの長所を組み合わせています\n- Rustの最速のwasmフレームワーク[sledgehammer](https://dioxuslabs.com/blog/templates-diffing)によって駆動される非常に高いパフォーマンス\n- Web、macOS、Linux、Windowsにデプロイするための統合バンドラー\n- そしてもっと！[Dioxusのツアーを見てみましょう](https://dioxuslabs.com/learn/0.7/)。\n\n## インスタントホットリロード\n\n1つのコマンド`dx serve`でアプリを実行します。マークアップとスタイルを編集し、結果をリアルタイムで確認します。\n\n<div align=\"center\">\n  <img src=\"https://raw.githubusercontent.com/DioxusLabs/screenshots/refs/heads/main/blitz/hotreload-video.webp\">\n</div>\n\n\n## ファーストクラスのAndroidおよびiOSサポート\n\nDioxusは、Rustでネイティブモバイルアプリを構築する最速の方法です。単に`dx serve --platform android`を実行すると、数秒でエミュレーターまたはデバイスでアプリが実行されます。JNIおよびネイティブAPIに直接呼び出します。\n\n<div align=\"center\">\n  <img src=\"../../notes/android_and_ios2.avif\" width=\"500\">\n</div>\n\n## Web、デスクトップ、モバイル用のバンドル\n\n単に`dx bundle`を実行すると、アプリがビルドされ、最大化された最適化でバンドルされます。Webでは、[`.avif`生成、`.wasm`圧縮、最小化](https://dioxuslabs.com/learn/0.7/tutorial/assets)などを活用します。[50kb未満](https://github.com/ealmloff/tiny-dioxus/)のWebアプリや5mb未満のデスクトップ/モバイルアプリを構築します。\n\n<div align=\"center\">\n  <img src=\"../../notes/bundle.gif\">\n</div>\n\n\n## 素晴らしいドキュメント\n\n私たちは、クリーンで読みやすく包括的なドキュメントを作成するために多大な努力を払いました。すべてのHTML要素とリスナーはMDNドキュメントで記録されており、私たちのドキュメントサイトはDioxus自体と継続的に統合されており、ドキュメントが常に最新であることを保証します。ガイド、リファレンス、レシピなどについては、[Dioxusウェブサイト](https://dioxuslabs.com/learn/0.7/)をご覧ください。面白い事実：私たちはDioxusウェブサイトを新しいDioxus機能のテストベッドとして使用しています - [チェックしてみてください！](https://github.com/dioxusLabs/docsite)\n\n<div align=\"center\">\n  <img src=\"../../notes/docs.avif\">\n</div>\n\n## コミュニティ\n\nDioxusはコミュニティ主導のプロジェクトであり、非常に活発な[Discord](https://discord.gg/XgGxMSkvUM)および[GitHub](https://github.com/DioxusLabs/dioxus/issues)コミュニティを持っています。私たちは常に助けを求めており、質問に答え、あなたが始めるのを手伝うことを喜んでいます。[私たちのSDK](https://github.com/DioxusLabs/dioxus-std)はコミュニティによって運営されており、無料のアップグレードとサポートを受ける最高のDioxusクレートのための[GitHub組織](https://github.com/dioxus-community/)もあります。\n\n<div align=\"center\">\n  <img src=\"../../notes/dioxus-community.avif\">\n</div>\n\n## フルタイムのコアチーム\n\nDioxusは、副業プロジェクトからフルタイムのエンジニアの小さなチームに成長しました。FutureWei、Satellite.im、GitHub Acceleratorプログラムの寛大なサポートのおかげで、私たちはDioxusにフルタイムで取り組むことができました。私たちの長期的な目標は、高品質の有料エンタープライズツールを提供することでDioxusを自立させることです。あなたの会社がDioxusの採用に興味があり、私たちと一緒に働きたい場合は、お問い合わせください！\n\n## サポートされているプラットフォーム\n\n<div align=\"center\">\n  <table style=\"width:100%\">\n    <tr>\n      <td>\n      <b>Web</b>\n      </td>\n      <td>\n        <ul>\n          <li>WebAssemblyを使用してDOMに直接レンダリング</li>\n          <li>SSRで事前レンダリングし、クライアントで再ハイドレーション</li>\n          <li>Reactと同等の約50kbのシンプルな「Hello World」</li>\n          <li>迅速な反復のための組み込みの開発サーバーとホットリロード</li>\n        </ul>\n      </td>\n    </tr>\n    <tr>\n      <td>\n      <b>デスクトップ</b>\n      </td>\n      <td>\n        <ul>\n          <li>Webviewを使用してレンダリングするか、実験的にWGPUまたは<a href=\"https://freyaui.dev\">Freya</a>（Skia）を使用してレンダリング</li>\n          <li>ゼロコンフィグのセットアップ。単に`cargo run`または`dx serve`でアプリをビルド</li>\n          <li>IPCなしでネイティブシステムアクセスを完全にサポート</li>\n          <li>macOS、Linux、Windowsをサポート。ポータブル<3mbバイナリ</li>\n        </ul>\n      </td>\n    </tr>\n    <tr>\n      <td>\n      <b>モバイル</b>\n      </td>\n      <td>\n        <ul>\n          <li>Webviewを使用してレンダリングするか、実験的にWGPUまたはSkiaを使用してレンダリング</li>\n          <li>iOSおよびAndroidのサポート</li>\n          <li>「Hello World」から数秒でデバイス上で実行</li>\n        </ul>\n      </td>\n    </tr>\n    <tr>\n      <td>\n      <b>サーバーサイドレンダリング</b>\n      </td>\n      <td>\n        <ul>\n          <li>サスペンス、ハイドレーション、サーバーサイドレンダリング</li>\n          <li>サーバー機能を使用してバックエンド機能を迅速に追加</li>\n          <li>エクストラクタ、ミドルウェア、およびルーティングの統合</li>\n          <li>静的サイト生成およびインクリメンタル再生成</li>\n        </ul>\n      </td>\n    </tr>\n  </table>\n</div>\n\n## 例を実行する\n\n> このリポジトリのメインブランチにある例は、dioxusのgitバージョンとCLIを対象としています。最新の安定版dioxusと互換性のある例を探している場合は、[0.6ブランチ](https://github.com/DioxusLabs/dioxus/tree/v0.6/examples)を確認してください。\n\nこのリポジトリのトップレベルにある例は、次のように実行できます：\n\n```sh\ncargo run --example <example>\n```\n\nただし、dioxus-cliをダウンロードすることをお勧めします。gitバージョンのdioxusを実行している場合は、次のコマンドで一致するバージョンのCLIをインストールできます：\n\n```sh\ncargo install --git https://github.com/DioxusLabs/dioxus dioxus-cli --locked\n```\n\nCLIを使用すると、Webプラットフォームで例を実行することもできます。このコマンドを使用してデフォルトのデスクトップ機能を無効にし、Web機能を有効にします：\n\n```sh\ndx serve --example <example> --platform web -- --no-default-features\n```\n\n## Dioxusと他のフレームワークの比較\n\n私たちはすべてのフレームワークを愛しており、Rustエコシステムの革新を見守るのを楽しんでいます。実際、私たちの多くのプロジェクトは他のフレームワークと共有されています。たとえば、私たちのフレックスボックスライブラリ[Taffy](https://github.com/DioxusLabs/taffy)は、[Bevy](https://bevyengine.org/)、[Zed](https://zed.dev/)、[Lapce](https://lapce.dev/)、[Iced](https://github.com/iced-rs/iced)などで使用されています。\n\nDioxusは、他のフレームワークとは異なるいくつかの重要なポイントに重点を置いています：\n\n- **Reactのように**：コンポーネント、props、フックなどの概念を使用してUIを構築し、状態管理はSolidJSよりもSvelteに近いです。\n- **HTMLとCSS**：HTMLとCSSを完全に活用し、quirks（癖）もすべて受け入れます。\n- **レンダラーに依存しない**：高速なVirtualDOMのおかげで、任意のプラットフォームのレンダラーに置き換えることができます。\n- **協力的**：可能な限り、[Taffy](https://github.com/DioxusLabs/taffy)、[manganis](https://github.com/DioxusLabs/manganis)、[include_mdbook](https://github.com/DioxusLabs/include_mdbook)、[blitz](http://github.com/dioxusLabs/blitz)などのクレートを分割して、エコシステムが一緒に成長できるようにします。\n\n### DioxusとTauriの比較\n\nTauriは、React、Vue、SvelteなどのWebベースのフレームワークを使用してフロントエンドが記述されたデスクトップ（およびモバイル）アプリを構築するためのフレームワークです。ネイティブの作業が必要な場合は、Rust関数を記述し、フロントエンドから呼び出すことができます。\n\n- **ネイティブRust**：Tauriのアーキテクチャは、UIをJavaScriptまたはWebAssemblyに制限します。Dioxusを使用すると、Rustコードがユーザーのマシンでネイティブに実行され、スレッドの生成、ファイルシステムへのアクセスなどの操作をIPCブリッジなしで実行できます。これにより、アプリケーションのアーキテクチャが大幅に簡素化され、構築が容易になります。Dioxus-Webをフロントエンドとして使用してTauriアプリを構築することもできます。\n\n- **異なる範囲**：TauriはJavaScriptとその複雑なビルドツールチェーンをサポートする必要があるため、使用できる範囲が制限されます。DioxusはRustに完全に焦点を当てているため、サーバー関数、高度なバンドル、ネイティブレンダラーなどの追加のユーティリティを提供できます。\n\n- **共有DNA**：TauriとDioxusは別々のプロジェクトですが、Tauriチームが維持するウィンドウとWebviewライブラリであるTaoとWryなどのライブラリを共有しています。\n\n### DioxusとLeptosの比較\n\nLeptosは、SolidJSおよびSolidStartに似たフルスタックWebアプリケーションを構築するためのライブラリです。2つのライブラリはWeb上で同様の目標を共有していますが、いくつかの重要な違いがあります：\n\n- **リアクティビティモデル**：Leptosは信号を基本的なリアクティビティとして使用し、DioxusはVirtualDomと再レンダリングを選択します。理論的には信号がより効率的ですが、実際には、[block-domに触発されたテンプレート](https://dioxuslabs.com/blog/templates-diffing)のおかげで、DioxusのVirtualDomは実際の違いをほとんど生じさせず、[実際にはLeptosよりも高速です](https://krausest.github.io/js-framework-benchmark/2024/table_chrome_123.0.6312.59.html)。\n\n- **制御フロー**：Leptosは信号を使用してリアクティビティを実現するため、`for`ループや`if`文などのLeptosのプリミティブに制限されます。これを間違えると、アプリケーションはリアクティビティを失い、デバッグが難しいUIの問題が発生します。Dioxusを使用すると、イテレータ、通常のRustの`for`ループ、および`if`文を使用でき、アプリケーションは引き続きリアクティブです。実際には、リストにカウンターを挿入するDioxusコンポーネントは次のようになります：\n\n```rust\nfn Counters() -> Element {\n    let mut counters = use_signal(|| vec![0; 10]);\n\n    rsx! {\n        button { onclick: move |_| counters.push(counters.len()), \"Add Counter\" }\n        ul {\n            for idx in 0..counters.len() {\n                li {\n                    button { onclick: move |_| counters.write()[idx] += 1, \"{counters.index(idx)}\" }\n                    button { onclick: move |_| { counters.remove(idx); }, \"Remove\" }\n                }\n            }\n        }\n    }\n}\n```\n\n[Leptosでは、`<For>`コンポーネントを使用します。](https://book.leptos.dev/view/04_iteration.html#dynamic-rendering-with-the-for-component):\n\n```rust\nfn Counters() -> impl IntoView {\n    let counters = RwSignal::new(vec![0; 10]);\n\n    view! {\n        <button on:click=move |_| counters.update(|n| n.push(n.len()))>\"Add Counter\"</button>\n        <For\n            each=move || 0..counters.with(Vec::len)\n            key=|idx| *idx\n            let:idx\n        >\n            <li>\n                <button on:click=move |_| counters.update(|n| n[idx] += 1)>\n                    {Memo::new(move |_| counters.with(|n| n[idx]))}\n                </button>\n                <button on:click=move |_| counters.update(|n| { n.remove(idx); })>\n                    \"Remove\"\n                </button>\n            </li>\n        </For>\n    }\n}\n```\n\n- **`Copy`状態**：Dioxus 0.1から0.4までは、Rustの借用チェッカーのルールを緩和するためにライフタイムに依存していました。これはイベントハンドラにはうまく機能しましたが、非同期の面ではうまく機能しませんでした。Dioxus 0.5では、Leptosから借用した[`Copy`状態モデル](https://crates.io/crates/generational-box)に移行しました。\n\n- **異なる範囲**：Dioxusは、Web、デスクトップ、モバイル、LiveViewなどのレンダラーを提供します。また、コミュニティライブラリとクロスプラットフォームSDKを維持しています。この作業の範囲は広く、歴史的にLeptosよりもリリースサイクルが遅くなっています。LeptosはフルスタックWebに焦点を当てており、Dioxusにはないislands、`<Form />`コンポーネント、およびその他のWeb専用のユーティリティなどの機能を提供します。一般的に、Leptosで構築されたWebアプリケーションは、より小さなフットプリントを持ちます。\n\n- **異なるDSL**：2つのフレームワークはどちらもWebを対象としていますが、DioxusはUIを構築するために独自のカスタムRust風DSLを使用し、LeptosはよりHTML風の構文を使用します。これは、コード折りたたみや構文ハイライトなどのIDE機能との互換性を維持するために選択されました。一般的に、DioxusはそのDSLでより多くの「魔法」を活用します。たとえば、dioxusは文字列を自動的にフォーマットしますが、Leptosはクロージャと`format!`または`format_args!`マクロを使用する必要があります。\n\n```rust\n// dioxus\nrsx! {\n  div { class: \"my-class\", enabled: true, \"Hello, {name}\" }\n}\n\n// leptos\nview! {\n  <div class=\"my-class\" enabled={true}>\n    \"Hello \"\n    {move || name()}\n  </div>\n}\n```\n\n### DioxusとYewの比較\n\nYewは、シングルページWebアプリケーションを構築するためのフレームワークであり、最初はDioxusのインスピレーションとなりました。残念ながら、Yewのアーキテクチャは私たちが望むさまざまな機能をサポートしていなかったため、Dioxusが誕生しました。\n\n- **シングルページアプリケーション**：YewはシングルページWebアプリケーション専用に設計されており、Webプラットフォームに本質的に結びついています。Dioxusはフルスタックでクロスプラットフォームであり、Web、デスクトップ、モバイル、サーバーアプリケーションの構築に適しています。\n\n- **開発者ツール**：Dioxusは、オートフォーマット、ホットリロード、バンドラーなどの多くのユーティリティを提供します。\n\n- **継続的なサポート**：Dioxusは、新機能とバグ修正が毎日追加され、非常に積極的に維持されています。\n\n### Dioxusとeguiの比較\n\neguiは、[Rerun.io](https://www.rerun.io)のようなツールを駆動するRust用のクロスプラットフォームGUIライブラリです。\n\n- **Immediate vs Retained**：eguiは、各フレームで再レンダリングされるように設計されています。これはゲームやその他のインタラクティブなアプリケーションに適していますが、フレーム間でスタイルとレイアウトの状態を保持しません。Dioxusは、UIが一度構築され、その後フレーム間で変更される保持されたUIフレームワークです。これにより、DioxusはHTMLやCSSなどのネイティブWeb技術を使用でき、バッテリー寿命とパフォーマンスが向上します。\n\n- **カスタマイズ可能**：eguiは独自のスタイリングとレイアウトソリューションを提供しますが、Dioxusは組み込みのHTMLとCSSの使用を期待しています。これにより、dioxusアプリケーションはTailwindやMaterial UIなどの任意のCSSライブラリを使用できます。\n\n- **状態管理**：eguiの状態管理は、単一のグローバル状態オブジェクトに基づいています。Dioxusは、コンポーネントとプロパティを使用して状態のカプセル化を奨励し、コンポーネントの再利用性を高めます。\n\n### DioxusとIcedの比較\n\nIcedは、Elmに触発されたクロスプラットフォームGUIライブラリです。IcedはWGPUを使用してネイティブにレンダリングし、DOMノードを使用してWebをサポートします。\n\n- **Elm状態管理**：Icedは、メッセージパッシングとリデューサーに基づくElmの状態管理モデルを使用します。これは単にDioxusとは異なる状態管理モデルであり、時にはかなり冗長になることがあります。\n\n- **ネイティブな感触**：Dioxusは、レンダラーとしてwebviewを使用するため、ネイティブのテキスト入力、ペースト処理、およびアクセシビリティなどの他のネイティブ機能を自動的に取得します。Icedのレンダラーは現在これらの機能を実装していないため、あまりネイティブに感じられません。\n\n- **WGPU**：DioxusのWGPUレンダラーは現在かなり未熟であり、まだプロダクションでの使用には準備ができていません。IcedのWGPUレンダラーははるかに成熟しており、プロダクションで使用されています。これにより、Icedで構築できるGPUアクセスが必要な特定のタイプのアプリケーションが可能になりますが、現在のところDioxusでは構築できません。\n\n### DioxusとElectronの比較\n\nDioxusとElectronは、目標が似ている完全に異なる2つのプロジェクトです。Electronは、開発者がHTML、CSS、JavaScriptなどのWeb技術を使用してクロスプラットフォームのデスクトップアプリケーションを構築できるようにします。\n\n- **軽量**：Dioxusは、システムのネイティブWebView、またはオプションでWGPUレンダラーを使用してUIをレンダリングします。これにより、典型的なDioxusアプリケーションはmacOSで約15mbであり、Electronは100mbです。Electronは、ホストOSとシステムリソースをDioxusと同じように共有できない埋め込みのChromiumインスタンスも提供します。\n\n- **成熟度**：Electronは、大規模なコミュニティと多くのツールを備えた成熟したプロジェクトです。Dioxusは、Electronに比べてまだ非常に若いです。deeplinkingなどの機能を実装するために追加の作業が必要な場合があります。\n\n## コントリビュート\n\n- [コントリビュートに関するセクション](https://dioxuslabs.com/learn/0.7/beyond/contributing)を参照してください。\n- [issue tracker](https://github.com/dioxuslabs/dioxus/issues)で問題を報告してください。\n- Discordに[参加](https://discord.gg/XgGxMSkvUM)して質問してください！\n\n<a href=\"https://github.com/dioxuslabs/dioxus/graphs/contributors\">\n  <img src=\"https://contrib.rocks/image?repo=dioxuslabs/dioxus&max=30&columns=10\" />\n</a>\n\n## ライセンス\n\nこのプロジェクトは[MITライセンス]の下でライセンスされています。\n\n[MITライセンス]: https://github.com/DioxusLabs/dioxus/blob/master/LICENSE-MIT\n\nあなたが明示的に別段の定めをしない限り、あなたによってDioxusに含めるために意図的に提出されたすべてのコントリビュートは、追加の条件なしにMITライセンスの下でライセンスされるものとします。\n"
  },
  {
    "path": "notes/translations/ko-kr/README.md",
    "content": "<p>\n    <p align=\"center\" >\n      <img src=\"../../notes/header-light.svg#gh-light-mode-only\" >\n      <img src=\"../../notes/header-dark.svg#gh-dark-mode-only\" >\n      <a href=\"https://dioxuslabs.com\">\n          <img src=\"../../notes/dioxus_splash_8.avif\">\n      </a>\n    </p>\n</p>\n<div align=\"center\">\n  <!-- Crates version -->\n  <a href=\"https://crates.io/crates/dioxus\">\n    <img src=\"https://img.shields.io/crates/v/dioxus.svg?style=flat-square\"\n    alt=\"Crates.io version\" />\n  </a>\n  <!-- Downloads -->\n  <a href=\"https://crates.io/crates/dioxus\">\n    <img src=\"https://img.shields.io/crates/d/dioxus.svg?style=flat-square\"\n      alt=\"Download\" />\n  </a>\n  <!-- docs -->\n  <a href=\"https://docs.rs/dioxus\">\n    <img src=\"https://img.shields.io/badge/docs-latest-blue.svg?style=flat-square\"\n      alt=\"docs.rs docs\" />\n  </a>\n  <!-- CI -->\n  <a href=\"https://github.com/jkelleyrtp/dioxus/actions\">\n    <img src=\"https://github.com/dioxuslabs/dioxus/actions/workflows/main.yml/badge.svg\"\n      alt=\"CI status\" />\n  </a>\n\n  <!--Awesome -->\n  <a href=\"https://dioxuslabs.com/awesome\">\n    <img src=\"https://cdn.rawgit.com/sindresorhus/awesome/d7305f38d29fed78fa85652e3a63e154dd8e8829/media/badge.svg\" alt=\"Awesome Page\" />\n  </a>\n  <!-- Discord -->\n  <a href=\"https://discord.gg/XgGxMSkvUM\">\n    <img src=\"https://img.shields.io/discord/899851952891002890.svg?logo=discord&style=flat-square\" alt=\"Discord Link\" />\n  </a>\n</div>\n\n<div align=\"center\">\n  <h3>\n    <a href=\"https://dioxuslabs.com\"> Website </a>\n    <span> | </span>\n    <a href=\"https://github.com/DioxusLabs/dioxus/tree/main/examples\"> Examples </a>\n    <span> | </span>\n    <a href=\"https://dioxuslabs.com/learn/0.7/tutorial\"> Guide </a>\n    <span> | </span>\n    <a href=\"https://github.com/DioxusLabs/dioxus/blob/main/notes/translations/zh-cn/README.md\"> 中文 </a>\n    <span> | </span>\n    <a href=\"https://github.com/DioxusLabs/dioxus/blob/main/notes/translations/pt-br/README.md\"> PT-BR </a>\n    <span> | </span>\n    <a href=\"https://github.com/DioxusLabs/dioxus/blob/main/notes/translations/ja-jp/README.md\"> 日本語 </a>\n    <span> | </span>\n    <a href=\"https://github.com/DioxusLabs/dioxus/blob/main/notes/translations/tr-tr\"> Türkçe </a>\n    <span> | </span>\n    <a href=\"https://github.com/DioxusLabs/dioxus/blob/main/notes/translations/ko-kr\"> 한국어 </a>\n  </h3>\n</div>\n<br>\n<br>\n\n웹, 데스크톱, 모바일 등 다양한 플랫폼을 단일 코드베이스로 구축하세요. 제로 설정 환경, 통합 핫 리로딩, 시그널 기반 상태 관리. 서버 함수로 백엔드 기능을 추가하고 CLI를 사용하여 번들링하세요.\n\n```rust\nfn app() -> Element {\n    let mut count = use_signal(|| 0);\n\n    rsx! {\n        h1 { \"High-Five counter: {count}\" }\n        button { onclick: move |_| count += 1, \"Up high!\" }\n        button { onclick: move |_| count -= 1, \"Down low!\" }\n    }\n}\n```\n\n## ⭐️ 고유 기능:\n\n- 세 줄의 코드로 크로스 플랫폼 앱 개발 (웹, 데스크톱, 모바일, 서버 등)\n- [인체공학적 상태 관리](https://dioxuslabs.com/blog/release-050)은 React, Solid, Svelte의 장점을 결합했습니다\n- 극도로 높은 성능을 자랑하며 Rust의 가장 빠른 wasm 프레임워크 [sledgehammer](https://dioxuslabs.com/blog/templates-diffing)에 의해 구동됩니다\n- 웹, macOS, Linux, Windows에 배포할 수 있는 통합 번들러\n- 그 외에도 많은 기능! [Dioxus 투어를 살펴보세요](https://dioxuslabs.com/learn/0.7/).\n\n## 핫 리로딩\n\n한 개의 명령어, `dx serve`로 앱을 실행하세요. 마크업과 스타일을 수정하면 실시간으로 결과를 확인할 수 있습니다. Rust 코드 핫 리로딩은 아직 1차 지원은 아니지만 [hot-lib-reloader](https://docs.rs/hot-lib-reloader/latest/hot_lib_reloader/)를 통해 가능합니다.\n\n<div align=\"center\">\n  <img src=\"../../notes/hotreload.gif\">\n</div>\n\n## 웹 및 데스크톱 배포를 위한 번들러\n\n단순히 `dx bundle`을 실행하면 앱이 빌드되고 최적화된 번들로 패키징됩니다. 웹에서는 [`.avif` 생성, `.wasm` 압축, 최소화](https://dioxuslabs.com/learn/0.7/tutorial/assets) 등을 활용할 수 있습니다. [50kb 미만](https://github.com/ealmloff/tiny-dioxus/)의 웹앱과 15mb 미만의 데스크톱/모바일 앱을 빌드하세요.\n\n<div align=\"center\">\n  <img src=\"../../notes/bundle.gif\">\n</div>\n\n## 환상적인 문서\n\n깨끗하고 읽기 쉽고 포괄적인 문서를 구축하는 데 많은 노력을 기울였습니다. 모든 HTML 요소와 리스너는 MDN 문서로 문서화되었으며, 저희 문서 사이트는 Dioxus 자체와 지속적인 통합을 실행하여 문서가 항상 최신 상태로 유지되도록 합니다. 가이드, 참조, 레시피 등을 보려면 [Dioxus 웹사이트](https://dioxuslabs.com/learn/0.7/)를 확인하세요. 재미있는 사실: 저희는 Dioxus 웹사이트를 새로운 Dioxus 기능의 테스트베드로 사용합니다 - [확인해보세요!](https://github.com/dioxusLabs/docsite)\n\n<div align=\"center\">\n  <img src=\"../../notes/docs.avif\">\n</div>\n\n## 개발자 경험에 중점\n\nDioxus는 개발자 경험을 우선시하며, end-to-end 도구 개발에 많은 노력을 기울였습니다. RSX 코드를 자동으로 포맷하고 HTML을 RSX로 변환하는 등 다양한 기능을 제공하는 [VSCode 확장 프로그램](https://marketplace.visualstudio.com/items?itemName=DioxusLabs.dioxus)을 제작했습니다. 또한 새로운 앱 생성, 서비스 실행, 크로스 플랫폼 번들링을 지원하는 매우 강력한 CLI 를 개발했으며, 배포는 향후 로드맵에 포함되어 있습니다.\n\n<div align=\"center\">\n  <img src=\"../../notes/autofmt.gif\">\n</div>\n\n## 커뮤니티\n\nDioxus는 커뮤니티 중심의 프로젝트로, 매우 활발한 [Discord](https://discord.gg/XgGxMSkvUM) 및 [GitHub](https://github.com/DioxusLabs/dioxus/issues) 커뮤니티를 보유하고 있습니다. 우리는 항상 도움을 구하고 있으며, 질문에 답변하고 시작하는 데 도움을 드릴 준비가 되어 있습니다. [우리의 SDK](https://github.com/DioxusLabs/dioxus-std)는 커뮤니티에서 운영되며, 무료 업그레이드와 지원을 받는 최고의 Dioxus 크레이트를 위한 [GitHub 조직](https://github.com/dioxus-community/)도 운영하고 있습니다.\n\n<div align=\"center\">\n  <img src=\"../../notes/dioxus-community.avif\">\n</div>\n\n## 풀타임 코어 팀\n\nDioxus는 사이드 프로젝트에서 소수의 전임 엔지니어 팀으로 성장했습니다. FutureWei, Satellite.im, GitHub Accelerator 프로그램의 후원 덕분에 Dioxus를 풀타임으로 개발할 수 있게 되었습니다. 저희의 장기 목표는 유료 고품질 엔터프라이즈 도구를 제공하여 Dioxus가 자립할 수 있도록 하는 것입니다. 귀사가 Dioxus 도입에 관심이 있고 저희와 함께 일하고 싶다면, 연락해 주세요!\n\n## 지원 플랫폼\n\n<div align=\"center\">\n  <table style=\"width:100%\">\n    <tr>\n      <td>\n      <b>웹</b>\n      <br />\n      <em>Tier 1 지원</em>\n      </td>\n      <td>\n        <ul>\n          <li>WebAssembly을 사용하여 DOM에 직접 렌더링</li>\n          <li>SSR로 사전 렌더링하고 클라이언트에서 리하이드레이션</li>\n          <li>React와 유사한 약 50kb 크기의 간단한 \"Hello World\"</li>\n          <li>내장된 개발 서버와 핫 리로딩으로 빠른 반복</li>\n        </ul>\n      </td>\n    </tr>\n    <tr>\n      <td>\n      <b>풀스택</b>\n      <br />\n      <em>Tier 1 지원</em>\n      </td>\n      <td>\n        <ul>\n          <li>서스펜스, 리하이드레이션, 서버 사이드 렌더링</li>\n          <li>서버 함수로 백엔드 기능을 빠르게 추가</li>\n          <li>추출기, 미들웨어, 라우팅 통합</li>\n          <li>데스크톱 및 모바일과 호환 가능!</li>\n        </ul>\n      </td>\n    </tr>\n    <tr>\n      <td>\n      <b>데스크톱</b>\n      <br />\n      <em>Tier 1 지원</em>\n      </td>\n      <td>\n        <ul>\n          <li>Webview를 사용하여 렌더링하거나 실험적으로 WGPU 또는 <a href=\"https://freyaui.dev\">Freya</a> (skia)와 함께 렌더링</li>\n          <li>제로 설정. 간단히 `cargo run` 또는 `dx serve`로 앱을 빌드</li>\n          <li>IPC 없이 네이티브 시스템 접근에 대한 완전한 지원</li>\n          <li>macOS, Linux, Windows를 지원합니다. 3MB 미만의 포터블 바이너리</li>\n        </ul>\n      </td>\n    </tr>\n    <tr>\n      <td>\n      <b>Liveview</b>\n      <br />\n      <em>Tier 1 지원</em>\n      </td>\n      <td>\n        <ul>\n          <li>앱 전체 또는 단일 컴포넌트만을 서버에서 완전히 렌더링</li>\n          <li>Axum, Warp와 같은 인기 있는 Rust 프레임워크와의 통합</li>\n          <li>극도로 낮은 지연 시간과 10,000개 이상의 동시 앱 지원 가능</li>\n        </ul>\n      </td>\n    </tr>\n    <tr>\n      <td>\n      <b>모바일</b>\n      <br />\n      <em>Tier 2 지원</em>\n      </td>\n      <td>\n        <ul>\n          <li>Webview를 사용하여 렌더링하거나 실험적으로 WGPU 또는 Skia와 함께 렌더링</li>\n          <li>iOS 및 Android 지원</li>\n          <li>현재는 꽤 실험적이며, 2024년 내내 많은 개선 예정</li>\n        </ul>\n      </td>\n    </tr>\n    <tr>\n      <td>\n      <b>터미널</b>\n      <br />\n      <em>Tier 2 지원</em>\n      </td>\n      <td>\n        <ul>\n          <li><a href=\"https://github.com/vadimdemedes/ink\">ink.js</a>와 유사하게 앱을 터미널에 직접 렌더링</li>\n          <li>익숙한 브라우저의 flexbox 및 CSS 모델로 구동</li>\n          <li>텍스트 입력, 버튼, 포커스 시스템과 같은 내장 위젯</li>\n        </ul>\n      </td>\n    </tr>\n  </table>\n</div>\n\n## 예제 실행하기\n\n> 이 저장소의 메인 브랜치에 있는 예제들은 dioxus의 git 버전과 CLI를 대상으로 합니다. 최신 안정 버전의 dioxus와 호환되는 예제를 찾고 있다면 [0.5 브랜치](https://github.com/DioxusLabs/dioxus/tree/v0.6/examples)를 확인하세요.\n\n이 저장소 최상위에 있는 예제들은 다음과 같이 실행할 수 있습니다:\n\n```sh\ncargo run --example <example>\n```\n\n그러나 dioxus-cli 다운로드를 권장합니다. git 버전의 dioxus를 사용 중인 경우, 다음 명령어로 CLI의 일치하는 버전을 설치할 수 있습니다:\n\n```sh\ncargo install --git https://github.com/DioxusLabs/dioxus dioxus-cli --locked\n```\n\nCLI를 사용하면 웹 플랫폼에서 예제를 실행할 수도 있습니다. 이 명령어로 기본 데스크톱 기능을 비활성화하고 웹 기능을 활성화하면 됩니다:\n\n```sh\ndx serve --example <example> --platform web -- --no-default-features\n```\n\n## Dioxus vs 다른 프레임워크\n\n우리는 모든 프레임워크를 사랑하며 Rust 생태계의 혁신을 지켜보는 것을 즐깁니다. 실제로, 많은 프로젝트가 다른 프레임워크와 공유되고 있습니다. 예를 들어, 우리의 flex-box 라이브러리 [Taffy](https://github.com/DioxusLabs/taffy)는 [Bevy](https://bevyengine.org/), [Zed](https://zed.dev/), [Lapce](https://lapce.dev/), [Iced](https://github.com/iced-rs/iced) 등에서 사용되고 있습니다.\n\nDioxus는 다른 프레임워크와 차별화되는 몇 가지 핵심 사항에 중점을 둡니다:\n\n- **React처럼**: 우리는 컴포넌트, props, 훅과 같은 개념을 사용하여 UI를 구축하며, 상태 관리는 SolidJS보다 Svelte에 더 가깝습니다.\n- **HTML과 CSS**: 우리는 HTML과 CSS를 완전히 활용하며, 특이점도 모두 수용합니다.\n- **독립적 렌더러**: [우리의 빠른 VirtualDOM](https://dioxuslabs.com/blog/templates-diffing) 덕분에 원하는 플랫폼의 렌더러로 교체할 수 있습니다.\n- **협업적**: 가능한 한 [Taffy](https://github.com/DioxusLabs/taffy), [magnanis](https://github.com/DioxusLabs/manganis), [include_mdbook](https://github.com/DioxusLabs/include_mdbook), [blitz](http://github.com/dioxusLabs/blitz)와 같은 크레이트를 분리하여 생태계가 함께 성장할 수 있도록 합니다.\n\n### Dioxus vs Tauri\n\nTauri는 프론트엔드가 React, Vue, Svelte 등의 웹 기반 프레임워크로 작성된 데스크톱(모바일 지원 예정) 앱을 구축하기 위한 프레임워크입니다. 네이티브 작업이 필요할 때마다 Rust 함수를 작성하여 프론트엔드에서 호출할 수 있습니다.\n\n- **네이티브 Rust**: Tauri의 아키텍처는 UI를 JavaScript 또는 WebAssembly로 제한합니다. 반면 Dioxus에서는 Rust 코드가 사용자의 머신에서 네이티브로 실행되어 스레드 생성, 파일 시스템 접근 등의 작업을 IPC 브리지를 사용하지 않고 수행할 수 있습니다. 이는 앱의 아키텍처를 대폭 단순화하고 구축을 용이하게 합니다. 원하신다면 Dioxus-Web을 프론트엔드로 사용하여 Tauri 앱을 구축할 수도 있습니다.\n- **범위 차이**: Tauri는 JavaScript와 복잡한 빌드 도구를 지원해야 하므로 할 수 있는 작업의 범위가 제한됩니다. Dioxus는 Rust에 전적으로 집중하고 있기 때문에 Server Functions, 고급 번들링, 네이티브 렌더러와 같은 추가 유틸리티를 제공할 수 있습니다.\n- **공유된 DNA**: Tauri와 Dioxus는 별도의 프로젝트이지만, Tao와 Wry와 같은 라이브러리를 공유합니다. 이들은 Tauri 팀에서 유지 관리하는 윈도잉 및 웹뷰 라이브러리입니다.\n\n### Dioxus vs Leptos\n\nLeptos는 SolidJS와 SolidStart와 유사하게 풀스택 웹 앱을 구축하기 위한 라이브러리입니다. 두 라이브러리는 웹에서 유사한 목표를 공유하지만 몇 가지 주요 차이점이 있습니다:\n\n- **반응성 모델**: Leptos는 기본 반응성으로 시그널을 사용하는 반면, Dioxus는 VirtualDom과 리렌더링을 선택합니다. 이론적으로 시그널이 더 효율적이지만, 실제로 Dioxus의 VirtualDom은 [block-dom 영감을 받은 템플릿](https://dioxuslabs.com/blog/templates-diffing) 덕분에 실제 차이점을 거의 수행하지 않으며 [실제로 Leptos보다 빠릅니다](https://krausest.github.io/js-framework-benchmark/2024/table_chrome_123.0.6312.59.html).\n\n- **제어 흐름**: Leptos가 반응성으로 시그널을 사용하기 때문에, `for` 루프와 `if` 문과 같은 Leptos의 원시 구조에 제한됩니다. 이를 잘못 사용하면 앱의 반응성이 상실되어 디버깅이 어려운 UI 문제가 발생할 수 있습니다. Dioxus에서는 이터레이터, 일반 Rust의 `for` 루프 및 `if` 문을 사용할 수 있으며, 앱은 여전히 반응성을 유지합니다. 실제로, 목록에 카운터를 삽입하는 Dioxus 컴포넌트는 다음과 같을 수 있습니다:\n\n```rust\nfn Counters() -> Element {\n    let mut counters = use_signal(|| vec![0; 10]);\n\n    rsx! {\n        button { onclick: move |_| counters.push(counters.len()), \"Add Counter\" }\n        ul {\n            for idx in 0..counters.len() {\n                li {\n                    button { onclick: move |_| counters.write()[idx] += 1, \"{counters.index(idx)}\" }\n                    button { onclick: move |_| { counters.remove(idx); }, \"Remove\" }\n                }\n            }\n        }\n    }\n}\n```\n\n[Leptos에서는 `<For>` 컴포넌트를 사용합니다.](https://book.leptos.dev/view/04_iteration.html#dynamic-rendering-with-the-for-component):\n\n```rust\nfn Counters() -> impl IntoView {\n    let counters = RwSignal::new(vec![0; 10]);\n\n    view! {\n        <button on:click=move |_| counters.update(|n| n.push(n.len()))>\"Add Counter\"</button>\n        <For\n            each=move || 0..counters.with(Vec::len)\n            key=|idx| *idx\n            let:idx\n        >\n            <li>\n                <button on:click=move |_| counters.update(|n| n[idx] += 1)>\n                    {Memo::new(move |_| counters.with(|n| n[idx]))}\n                </button>\n                <button on:click=move |_| counters.update(|n| { n.remove(idx); })>\n                    \"Remove\"\n                </button>\n            </li>\n        </For>\n    }\n}\n```\n\n- **`Copy` 상태**: Dioxus 0.1부터 0.4까지는 Rust의 borrow 검사기의 규칙을 완화하기 위해 생명주기에 의존했습니다. 이는 이벤트 핸들러에는 잘 작동했지만, 비동기 작업에서는 어려움을 겪었습니다. Dioxus 0.5에서는 Leptos에서 차용한 [`Copy` 상태 모델](https://crates.io/crates/generational-box)로 전환했습니다.\n\n- **범위 차이**: Dioxus는 웹, 데스크톱, 모바일, LiveView 등을 위한 렌더러를 제공합니다. 또한 커뮤니티 라이브러리와 크로스 플랫폼 SDK를 유지 관리합니다. 이 작업의 범위가 방대하여 역사적으로 Leptos보다 느린 주기로 릴리스되었습니다. Leptos는 전체 스택 웹에 중점을 두며, Dioxus에는 없는 islands, `<Form />` 컴포넌트 및 기타 웹 전용 기능과 같은 기능을 제공합니다. 일반적으로 Leptos로 구축한 웹 앱은 더 작은 용량을 가집니다.\n\n- **다른 DSL**: 두 프레임워크 모두 웹을 목표로 하지만, Dioxus는 UI 구축을 위해 자체적인 Rust 유사 DSL을 사용하는 반면, Leptos는 더 HTML 유사한 구문을 사용합니다. 이는 코드 접기 및 구문 강조와 같은 IDE 기능과의 호환성을 유지하기 위해 선택한 것입니다. 일반적으로 Dioxus는 DSL에서 더 많은 \"매직\"을 활용합니다. 예를 들어, dioxus는 문자열을 자동으로 포맷해주지만, Leptos는 문자열을 정적 및 동적 세그먼트로 분할할 수 있습니다.\n\n```rust\n// dioxus\nrsx! {\n  div { class: \"my-class\", enabled: true, \"Hello, {name}\" }\n}\n\n// leptos\nview! {\n  <div class=\"my-class\" enabled={true}>\n    \"Hello \"\n    {name}\n  </div>\n}\n```\n\n### Dioxus vs Yew\n\nYew는 싱글 페이지 웹 앱을 구축하기 위한 프레임워크로, 처음에는 Dioxus에 영감을 주었습니다. 불행히도 Yew의 아키텍처는 우리가 원하는 다양한 기능을 지원하지 않아 Dioxus가 탄생하게 되었습니다.\n\n- **싱글 페이지 앱**: Yew는 싱글 페이지 웹 앱 전용으로 설계되었으며 웹 플랫폼에 본질적으로 묶여 있습니다. Dioxus는 풀스택 및 크로스 플랫폼으로, 웹, 데스크톱, 모바일, 서버 앱을 구축하는 데 적합합니다.\n\n- **개발자 도구**: Dioxus는 자동 포맷팅, 핫 리로딩, 번들러 등 다양한 유틸리티를 제공합니다.\n\n- **지속적인 지원**: Dioxus는 새로운 기능과 버그 수정이 매일 추가되며 매우 적극적으로 유지 관리되고 있습니다.\n\n### Dioxus vs egui\n\negui는 [Rerun.io](https://www.rerun.io)와 같은 도구를 구동하는 Rust용 크로스 플랫폼 GUI 라이브러리입니다.\n\n- **Immediate vs Retained**: egui는 매 프레임마다 리렌더링되도록 설계되었습니다. 이는 게임 및 기타 인터랙티브 애플리케이션에 적합하지만, 프레임 간에 스타일 및 레이아웃 상태를 유지하지 않습니다. Dioxus는 UI가 한 번 구축된 후 프레임 간에 수정되는 retained UI 프레임워크입니다. 이를 통해 Dioxus는 HTML 및 CSS와 같은 네이티브 웹 기술을 사용하여 더 나은 배터리 수명과 성능을 제공합니다.\n\n- **커스터마이징 가능**: egui는 자체 스타일링 및 레이아웃 솔루션을 제공하는 반면, Dioxus는 내장된 HTML 및 CSS 사용을 기대합니다. 이를 통해 Dioxus 앱은 Tailwind 또는 Material UI와 같은 CSS 라이브러리를 사용할 수 있습니다.\n\n- **상태 관리**: egui의 상태 관리는 단일 전역 상태 객체를 기반으로 합니다. Dioxus는 컴포넌트와 props를 사용하여 상태의 캡슐화를 장려하여 컴포넌트의 재사용성을 높입니다.\n\n### Dioxus vs Iced\n\nIced는 Elm에서 영감을 받은 크로스 플랫폼 GUI 라이브러리입니다. Iced는 WGPU로 네이티브 렌더링을 지원하며, DOM 노드를 사용하여 웹을 지원합니다.\n\n- **Elm 상태 관리**: Iced는 메시지 전달과 리듀서를 기반으로 하는 Elm의 상태 관리 모델을 사용합니다. 이는 Dioxus와는 단순히 다른 상태 관리 모델이며, 때로는 다소 장황할 수 있습니다.\n\n- **네이티브 느낌**: Dioxus는 렌더러로 webview를 사용하기 때문에 네이티브 텍스트 입력, 붙여넣기 처리, 접근성과 같은 기타 네이티브 기능을 자동으로 지원합니다. 현재 Iced의 렌더러는 이러한 기능을 구현하지 않아 덜 네이티브하게 느껴집니다.\n\n- **WGPU**: Dioxus의 WGPU 렌더러는 현재 상당히 미성숙하며 아직 프로덕션 사용에 준비되지 않았습니다. Iced의 WGPU 렌더러는 훨씬 더 성숙하며 프로덕션에서 사용되고 있습니다. 이는 GPU 접근이 필요한 특정 유형의 앱을 Iced로 구축할 수 있게 해주지만, 현재 Dioxus로는 구축할 수 없습니다.\n\n### Dioxus vs Electron\n\nDioxus와 Electron은 유사한 목표를 가진 완전히 다른 두 프로젝트입니다. Electron은 개발자가 HTML, CSS, JavaScript와 같은 웹 기술을 사용하여 크로스 플랫폼 데스크톱 앱을 구축할 수 있도록 합니다.\n\n- **경량화**: Dioxus는 시스템의 네이티브 WebView 또는 선택적으로 WGPU 렌더러를 사용하여 UI를 렌더링합니다. 이로 인해 일반적인 Dioxus 앱은 macOS에서 약 15MB인 반면, Electron은 100MB입니다. Electron은 또한 Dioxus와 동일한 방식으로 호스트 OS와 시스템 리소스를 공유할 수 없는 내장된 Chromium 인스턴스를 포함하고 있습니다.\n\n- **성숙도**: Electron은 대규모 커뮤니티와 많은 도구를 갖춘 성숙한 프로젝트입니다. Dioxus는 Electron에 비해 아직 꽤 젊은 편입니다. deeplinking과 같은 기능을 구현하기 위해 추가 작업이 필요할 수 있습니다.\n\n## 기여하기\n\n- 웹사이트의 [기여 섹션](https://dioxuslabs.com/learn/0.7/beyond/contributing)을 확인하세요.\n- [이슈 트래커](https://github.com/dioxuslabs/dioxus/issues)에 이슈를 보고하세요.\n- [Discord](https://discord.gg/XgGxMSkvUM)에 참여하여 질문하세요!\n\n<a href=\"https://github.com/dioxuslabs/dioxus/graphs/contributors\">\n  <img src=\"https://contrib.rocks/image?repo=dioxuslabs/dioxus&max=30&columns=10\" />\n</a>\n\n## 라이선스\n\n이 프로젝트는 [MIT 라이선스] 또는 [Apache-2 라이선스] 중 하나로 라이선스됩니다.\n\n[apache-2 라이선스]: https://github.com/DioxusLabs/dioxus/blob/master/LICENSE-APACHE\n[mit 라이선스]: https://github.com/DioxusLabs/dioxus/blob/master/LICENSE-MIT\n\n별도로 명시하지 않는 한, 귀하가 Dioxus에 포함되도록 의도적으로 제출한 모든 기여는 추가 조건 없이 MIT 또는 Apache-2 라이선스로 라이선스됩니다.\n"
  },
  {
    "path": "notes/translations/pt-br/README.md",
    "content": "<p align=\"center\">\n  <img src=\"../../notes/header.svg\">\n</p>\n\n<div align=\"center\">\n  <!-- Crates version -->\n  <a href=\"https://crates.io/crates/dioxus\">\n    <img src=\"https://img.shields.io/crates/v/dioxus.svg?style=flat-square\"\n    alt=\"Crates.io version\" />\n  </a>\n  <!-- Downloads -->\n  <a href=\"https://crates.io/crates/dioxus\">\n    <img src=\"https://img.shields.io/crates/d/dioxus.svg?style=flat-square\"\n      alt=\"Download\" />\n  </a>\n  <!-- docs -->\n  <a href=\"https://docs.rs/dioxus\">\n    <img src=\"https://img.shields.io/badge/docs-latest-blue.svg?style=flat-square\"\n      alt=\"docs.rs docs\" />\n  </a>\n  <!-- CI -->\n  <a href=\"https://github.com/jkelleyrtp/dioxus/actions\">\n    <img src=\"https://github.com/dioxuslabs/dioxus/actions/workflows/main.yml/badge.svg\"\n      alt=\"CI status\" />\n  </a>\n\n  <!--Awesome -->\n  <a href=\"https://github.com/dioxuslabs/awesome-dioxus\">\n    <img src=\"https://cdn.rawgit.com/sindresorhus/awesome/d7305f38d29fed78fa85652e3a63e154dd8e8829/media/badge.svg\" alt=\"Awesome Page\" />\n  </a>\n  <!-- Discord -->\n  <a href=\"https://discord.gg/XgGxMSkvUM\">\n    <img src=\"https://img.shields.io/discord/899851952891002890.svg?logo=discord&style=flat-square\" alt=\"Discord Link\" />\n  </a>\n</div>\n\n<div align=\"center\">\n  <h3>\n    <a href=\"https://dioxuslabs.com\"> Website </a>\n    <span> | </span>\n    <a href=\"https://github.com/DioxusLabs/example-projects\"> Exemplos </a>\n    <span> | </span>\n    <a href=\"https://dioxuslabs.com/learn/0.7/\"> Guia </a>\n    <span> | </span>\n    <a href=\"https://github.com/DioxusLabs/dioxus/blob/main/notes/translations/zh-cn/README.md\"> 中文 </a>\n    <span> | </span>\n    <a href=\"https://github.com/DioxusLabs/dioxus/blob/main/notes/translations/ja-jp/README.md\"> 日本語 </a>\n  </h3>\n</div>\n\n<br/>\n\n> 💡 Este é um documento traduzido voluntariamente. Se você viu erros de tradução e/ou erros de digitação, entre em contato: [@amindWalker](https://github.com/amindWalker), [GMail](bhrochamail@gmail.com) ou **_melhor ainda, envie um PR_**.\n\n<br>\n\n**Dioxus** é um framework ergonômico para construir interfaces de forma portátil, rápida, escalável e robusta com a linguagem de programação Rust.\n\n```rust\nfn app() -> Element {\n    let mut count = use_signal(|| 0);\n\n    rsx! {\n        h1 { \"High-Five counter: {count}\" }\n        button { onclick: move |_| count += 1, \"Up high!\" }\n        button { onclick: move |_| count -= 1, \"Down low!\" }\n    })\n}\n```\n\nO Dioxus pode ser usado para desenvolver aplicativos Web, Desktop, sites estáticos, TUI, LiveView e mais. O Dioxus é inteiramente agnóstico de renderizador e pode ser usado como uma plataforma para qualquer renderizador.\n\nSe você conhece React, então você já conhece o Dioxus.\n\n### Funções Únicas:\n\n- Aplicativos Desktop rodam nativamente (sem ElectronJS!) em menos de 10 linhas de código.\n- Incrivelmente ergonômico e um poderoso gerenciador de estados.\n- Documentação compreensiva - guias e explicações ao apontar o mouse para todos os elementos HTML e eventos.\n- Extremamente eficiente em memória - 0 alocações globais para componentes com estado-estável.\n- Agendamento assíncrono de canais-múltiplos para suporte de `async` de primera-classe.\n- E mais! Leia as [publicações de lançamento](https://dioxuslabs.com/blog/introducing-dioxus/).\n\n## Projetos de Exemplo:\n\n| Navegador de Arquivos (Desktop)                                                                                                                                                                  | WiFi Scanner (Desktop)                                                                                                     | TodoMVC (Todas as Plataformas)                                                                                                                                          | E-commerce com Tailwind (SSR/LiveView)                                                                                                                                                   |\n| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| [![Explorador de Arquivos](https://github.com/DioxusLabs/example-projects/blob/master/file-explorer/assets/image.png)](https://github.com/DioxusLabs/example-projects/blob/master/file-explorer) | [![Wifi Scanner Demo](../../../examples/01-app-demos/bluetooth-scanner/demo_small.png)](../../../examples/01-app-demos/bluetooth-scanner/) | [![TodoMVC example](https://github.com/DioxusLabs/example-projects/raw/master/todomvc/example.png)](https://github.com/DioxusLabs/example-projects/blob/master/todomvc) | [![Exemplo de E-commerce](https://github.com/DioxusLabs/example-projects/raw/master/ecommerce-site/demo.png)](https://github.com/DioxusLabs/example-projects/blob/master/ecommerce-site) |\n\nVeja a página [awesome-dioxus](https://github.com/DioxusLabs/awesome-dioxus) para uma lista curada do conteúdo dentro do ecossistema do Dioxus.\n\n## Porquê o Dioxus e porquê o Rust?\n\nTypeScript é uma adição fantástica ao JavaScript, mas ainda é fundamentalmente JavaScript. TS executa ligeiramente mais devagar, tem várias opções de configurações diferentes e nem todos os pacotes estão propriamente tipados.\n\nApenas por usar o Rust, nós ganhamos:\n\n- Tipos estáticos para _todas_ as bibliotecas por padrão\n- Imutabilidade por padrão\n- Um sistema de módulos simples e intuitivo\n- Documentação integrada na própria linguagem (`go to source` _de fato vai até a fonte_)\n- Padrões de combinação avançados\n- Iteradores limpos, eficientes e combináveis\n- Testes de Unidade/Integração em linha integrados à linguagem\n- O melhor da classe em Tratamento de Erros\n- Biblioteca padrão poderosa e sensata\n- Sistema de macros flexível\n- Acesso ao `crates.io`\n\nEspecificamente, o Dioxus providencia para nós muitas outras garantias:\n\n- Estrutura de Dados imutável apropriada\n- Garantias para Tratamento de Erros (assim você pode dormir em paz à noite sem se preocupar com erros do tipo `undefined`)\n- Desempenho móvel nativo\n- Acesso direto ao sistema de Entrada/Saída (IO)\n\nE muito mais. Dioxus faz com que aplicativos em Rust sejam rápidos de escrever como os de React, mas permite mais robustez dando ao sua equipe frontend mais confiança em desenvolver grandes mudanças em pouco tempo.\n\n## Porquê não o Dioxus?\n\nVocê não deve usar o Dioxus se:\n\n- Você não gosta da metodologia do React Hooks para o frontend\n- Você precisa de um renderizador personalizado\n- Você quer suporte para navegadores que não tenham suporte ao WASM ou asm.js\n- Você precisa de uma solução `Send` + `Sync` (o Dioxus ainda não é `thread-safe`)\n\n## Comparação com outras frameworks de UI em Rust:\n\nDioxus primeiramente enfatiza a **experiência do desenvolvedor** e a **familiaridade com os princípios do React**.\n\n- [Yew](https://github.com/yewstack/yew): prefere o padrão `elm`, não há `props` emprestadas, suporta SSR (sem `hydration`), sem suporte direto para Desktop/Móvel.\n- [Percy](https://github.com/chinedufn/percy): suporta SSR, mas com menos ênfase em gerenciamento de estado e tratamento de eventos.\n- [Sycamore](https://github.com/sycamore-rs/sycamore): sem `VirtualDOM` usando controle preciso de reatividade, mas sem suporte direto à aplicativos Desktop/Móvel.\n- [Dominator](https://github.com/Pauan/rust-dominator): alternativa zero-custo baseada em sinais, menos ênfase em comunidade e documentação.\n- [Azul](https://azul.rs): renderizador HTML/CSS totalmente nativo para aplicações Desktop, sem suporte para Web/SSR.\n\n## Paridade com React e Progresso\n\nDioxus é fortemente inspirado pelo React, mas nós queremos que sua transição pareça como um aprimoramento. Dioxus está _quase_ lá, mas ainda faltam alguma funções chave. Isto inclui:\n\n- Portais\n- Suspensão integrada ao SSR\n- Componentes de Servidor / Segmentador de Pacotes / Execução Tardia (Lazy)\n\nDioxus é único no ecossistema do Rust por suportar:\n\n- Componentes com propriedade que são emprestadas dos seus parentes\n- SSR com `hydration` feito pelo Cliente\n- Suporte à aplicação Desktop\n\nPara mais informações sobre quais funções estão atualmente disponíveis e para o progresso futuro, veja [O Guia](https://dioxuslabs.com/learn/0.7/).\n\n## Projeto dentro do ecossistema Dioxus\n\nQuer adentrar e ajudar a construir o futuro do frontend em Rust? Há um vasto número de lugares em que você pode contribuir e fazer uma grande diferença:\n\n- [Ferramentas CLI](https://github.com/dioxusLabs/dioxus/tree/main/packages/cli)\n- [Documentação e Exemplos de Projeto](https://github.com/dioxusLabs/docsite)\n- LiveView e Servidor Web\n- Sistema de Componentes prontos\n\n## Licença\n\nEste projeto é licenciado sob a licença MIT.\n\n[licença mit]: https://github.com/DioxusLabs/dioxus/blob/master/LICENSE-MIT\n\n### Contribuições\n\nA menos que você explicitamente ateste o contrário, qualquer contribuição feita ao Dioxus por você será licenciada de acordo com a licença MIT sem nenhum outro termo ou condição.\n"
  },
  {
    "path": "notes/translations/tr-tr/README.md",
    "content": "<p>\n    <p align=\"center\" >\n      <img src=\"../../notes/header-light.svg#gh-light-mode-only\" >\n      <img src=\"../../notes/header-dark.svg#gh-dark-mode-only\" >\n      <a href=\"https://dioxuslabs.com\">\n          <img src=\"../../notes/dioxus_splash_8.avif\">\n      </a>\n    </p>\n</p>\n<div align=\"center\">\n  <!-- Crates version -->\n  <a href=\"https://crates.io/crates/dioxus\">\n    <img src=\"https://img.shields.io/crates/v/dioxus.svg?style=flat-square\"\n    alt=\"Crates.io version\" />\n  </a>\n  <!-- Downloads -->\n  <a href=\"https://crates.io/crates/dioxus\">\n    <img src=\"https://img.shields.io/crates/d/dioxus.svg?style=flat-square\"\n      alt=\"Download\" />\n  </a>\n  <!-- docs -->\n  <a href=\"https://docs.rs/dioxus\">\n    <img src=\"https://img.shields.io/badge/docs-latest-blue.svg?style=flat-square\"\n      alt=\"docs.rs docs\" />\n  </a>\n  <!-- CI -->\n  <a href=\"https://github.com/jkelleyrtp/dioxus/actions\">\n    <img src=\"https://github.com/dioxuslabs/dioxus/actions/workflows/main.yml/badge.svg\"\n      alt=\"CI status\" />\n  </a>\n\n  <!--Awesome -->\n  <a href=\"https://dioxuslabs.com/awesome\">\n    <img src=\"https://cdn.rawgit.com/sindresorhus/awesome/d7305f38d29fed78fa85652e3a63e154dd8e8829/media/badge.svg\" alt=\"Awesome Page\" />\n  </a>\n  <!-- Discord -->\n  <a href=\"https://discord.gg/XgGxMSkvUM\">\n    <img src=\"https://img.shields.io/discord/899851952891002890.svg?logo=discord&style=flat-square\" alt=\"Discord Link\" />\n  </a>\n</div>\n\n<div align=\"center\">\n  <h3>\n    <a href=\"https://dioxuslabs.com\"> Website </a>\n    <span> | </span>\n    <a href=\"https://github.com/DioxusLabs/example-projects\"> Examples </a>\n    <span> | </span>\n    <a href=\"https://dioxuslabs.com/learn/0.7/tutorial\"> Guide </a>\n    <span> | </span>\n    <a href=\"https://github.com/DioxusLabs/dioxus/blob/main/notes/translations/zh-cn/README.md\"> 中文 </a>\n    <span> | </span>\n    <a href=\"https://github.com/DioxusLabs/dioxus/blob/main/notes/translations/pt-br/README.md\"> PT-BR </a>\n    <span> | </span>\n    <a href=\"https://github.com/DioxusLabs/dioxus/blob/main/notes/translations/ja-jp/README.md\"> 日本語 </a>\n    <span> | </span>\n    <a href=\"https://github.com/DioxusLabs/dioxus/blob/main/notes/translations/tr-tr\"> Türkçe </a>\n  </h3>\n</div>\n<br>\n<br>\n\n> Çevirmen Notu (Translator Note): Teknik terimleri orijinal haliyle kullanıp, olabildiğince açıklamaya çalıştım ki hem tecrübeli hem de yeni başlayan arkadaşlar için daha kolay olsun diye. Sektör dilimiz İngilizce olduğundan parantez içindeki bilgiler çoğunlukla yeni dostlar için, tecrübeli arkadaşlar zaten terimlere de ne anlama geldiklerine de aşina. Teknik terimleri günlük hayatta da orijinal halinde kullandığımızdan, kavramları Türkçe'ye çevirme kısmı garip oldu. Gerekli de değildi ama yine de bilmeyen insanların da kafalarında bir şeyler canlandırmaya çalıştım. Yoksa kimse frontend için önyüz demiyor yahut framework yerine Türkçe bir şeyler kullanmıyoruz, ben de farkındayım. Çok da rahatsız etmemek adına sadece kavramlarla ilk karşılaştığınız yerlerde açıkladım devamında orijinal haliyle kullandım. Hatalar varsa affola.\n\nWeb, masaüstü ve mobil; tek bir kod altyapısıyla üret. Eforsuz kurulum, entegre hotreloading(sürekli kendin derlemek yerine, güncelleme tespit edildikçe otomatik aktifleşmesi) ve sinyal temelli durum yönetimi. Sunucu fonksiyonları ile backend (arkayüz) özellikleri ekle ve CLI (Command-line Interface(Komut-satırı Arayüzü, terminal uygulaması))ile paketle (`dx bundle` sayesinde).\n\n```rust\nfn app() -> Element {\n    let mut count = use_signal(|| 0);\n\n    rsx! {\n        h1 { \"High-Five counter: {count}\" }\n        button { onclick: move |_| count += 1, \"Up high!\" }\n        button { onclick: move |_| count -= 1, \"Down low!\" }\n    }\n}\n```\n\n## ⭐️ Nevi Şahsına Münhasır Özellikler:\n\n- 3 satır kod ile çapraz platform uygulamalar (web, masaüstü, mobil, sunucu ve daha fazlası).\n- [Ergonomik durum yönetimi](https://dioxuslabs.com/blog/release-050) React, Solid ve Svelte'nin en iyi özelliklerini birleştirdik.\n- Rust'ın en en hızlı wasm(WebAssembly)-framework'ü (yazılım geliştirmek için kullanılan kütüphanelerin bir araya gelmesi ile oluşan yapılar) ile yüksek performans [sledgehammer](https://dioxuslabs.com/blog/templates-diffing).\n- Web, macOS, Linux ve Windows üzerinde deploy (yayınlamak) için entegre paketleyici (burada belirtilen `dx bundle` bizim için işleri hallediyor).\n- Ve daha fazlası! Gözat -> [Dioxus turu](https://dioxuslabs.com/learn/0.7/).\n\n## Anında hot-reloading\n\nTek bir `dx serve` komutu ile uygulamanı çalıştır. Markup ve styles(html ve css aklınıza gelebilir burada, Dioxus da rsx ile yapıyorsunuz bu tarz şeyleri. Ekstradan css düzenlemeleri de yapabilirsiniz) düzenle ve sonucunu gerçek zamanlı gör. Rust'ın hotreloading özellikleri halen birinci sınıf olmasa da [hot-lib-reloader](https://docs.rs/hot-lib-reloader/latest/hot_lib_reloader/) ile mümkün.\n\n<div align=\"center\">\n  <img src=\"../../notes/hotreload.gif\">\n</div>\n\n## Web ve Masaüstü için Paketleyici\n\nBasitçe `dx bundle` komutunu çalıştır ve uygulaman maksimum optimizasyon ile derlenip paketlensin. Web üzerindeki [`.avif` oluşturma, `.wasm` sıkıştırma, küçültme](https://dioxuslabs.com/learn/0.7/tutorial/assets) ve daha fazlası ile avantajı yakala. [50kb'dan daha küçük](https://github.com/ealmloff/tiny-dioxus/) hafif web uygulamaları ve 15mb'dan daha az boyutlarda masaüstü/mobil uygulamalar üret.\n\n<div align=\"center\">\n  <img src=\"../../notes/bundle.gif\">\n</div>\n\n## Fantastik Dökümantasyon\n\nTemiz, okunabilir ve kapsayıcı döküman için bir ton uğraştık. Bütün html elementleri ve listeners(takipçiler) MDN (geliştiriciler için dökümantasyon detay için [tıkla](https://developer.mozilla.org)) ile dökümente edildi, ve güncelliğinden emin olmak için docsite(dökümanlar) Dioxus'un kendisi ile sürekli entegrasyon halinde. Rehbeler, referanslar, tarifler, ve daha fazlası için göz at [Dioxus websitesi](https://dioxuslabs.com/learn/0.7/). İlginç gerçek: Dioxus'un sitesini yeni özelliklerimizi test etmek için deneme tahtası olarak kullanıyoruz -> [Göz at!](https://github.com/dioxusLabs/docsite)\n\n<div align=\"center\">\n  <img src=\"../../notes/docs.avif\">\n</div>\n\n## Geliştirici Deneyimini Önemse\n\nDioxus geliştirici deneyimini önceliklendirir, bu kapsamda end-to-end tooling (uçtan uca araç desteği) için tonla efor harcadık. [VSCode eklentisi](https://marketplace.visualstudio.com/items?itemName=DioxusLabs.dioxus) geliştirdik bu sayede RSX kodlarınızı otomatik formatlayabilir, HTML', RSX e çevirebilir ve daha fazlasını yapabilirsiniz. Aynı zamanda geliştirdiğimiz çok güçlü CLI sayesinde yeni uygulamalar üretmenize yardım ediyor, onları serve (sunmanızı) ve çapraz platform çalışacak şekilde paketlemenizi sağlıyoruz. Yol haritamızda deployment da mevcut.\n\n<div align=\"center\">\n  <img src=\"../../notes/autofmt.gif\">\n</div>\n\n## Topluluk\n\nDioxus topluluk tarafından yürütülen epey aktif [Discord](https://discord.gg/XgGxMSkvUM) ve [GitHub](https://github.com/DioxusLabs/dioxus/issues) topluluğuna sahip bir proje. Her zaman yardım alıyoruz, sorularınızı cevaplamaktan mutlu oluyoruz ve projelerinize başlamanız için size destek oluyoruz. [SDK'mız (Software Development Kit (Yazılım Geliştirme Paketi))](https://github.com/DioxusLabs/dioxus-std) topluluk tarafından geliştiriliyor ve hatta [GitHub organizasyonu](https://github.com/dioxus-community/) ile ücretsiz güncelleme ve destek alan, en iyi Dioxus uygulamalarına ulaşabilirsiniz.\n\n<div align=\"center\">\n  <img src=\"../../notes/dioxus-community.avif\">\n</div>\n\n## Tam Zamanlı Çalışan Çekirdek Kadro\n\nEk proje olarak başlayan Dioxus küçük bir takımın tam zamanlı çalıştığı bir hale geldi. FutureWei, Satellite.im ve Github Accelerator programları sayesinde Dioxus üzerinde tam zamanlı olarak çalışabiliyoruz. Uzun vade planımız ise yüksek kaliteli ücretli kurumsal araçlar sunarak, Dioxus'u kendine yetebilen bir hale getirmek. Eğer şirketiniz Dioxus kullanmak ve bizimle çalışmak isterse, lütfen bize ulaşın!\n\n## Desteklenen Platformlar\n\n<div align=\"center\">\n  <table style=\"width:100%\">\n    <tr>\n      <td>\n      <b>Web</b>\n      <br />\n      <em>1. Seviye Destek</em>\n      </td>\n      <td>\n        <ul>\n          <li>WebAssembly kullanarak doğrudan DOM'a (Document Object Model (Döküman Objesi Modeli, sayfaları manipüle etmenizi sağlayan yapı)) render(işle).</li>\n          <li>SSR(Serve Side Rendering (Sunucu Tarafında İşleme)) ile ön işleme yap ve kullanıcı tarafında rehydrate(yeniden destekle).</li>\n          <li>React ile kıyaslanır seviyede basit bir \"Merhaba Dünya\" kodu 50kb civarında.</li>\n          <li>Entegre geliştirme sunucusu (`dx serve`) ve hot reloading ile hızlı iterasyonlar gerçekleştir.</li>\n        </ul>\n      </td>\n    </tr>\n    <tr>\n      <td>\n      <b>Fullstack(önyüz ve arkayüzün birlikte var olduğu)</b>\n      <br />\n      <em>1. Seviye Destek</em>\n      </td>\n      <td>\n        <ul>\n          <li>Suspense(yapılması uzun sürecek işlemler için fallback(geçici gösterge diyebiliriz sanırım, yükleme ekranı örneği verilebilir) imkanı), hydration(besleme, destekleme), and server-side rendering(sunucuda işleme)</li>\n          <li>Sunucu fonksiyonları sayesinde dahili backend</li>\n          <li>Extractors(Ayırıcılar), middleware(ara katman), and routing(yönlendirici) entegrasyonları</li>\n          <li>Mobil ve masaüstü ile uyumlu!</li>\n        </ul>\n      </td>\n    </tr>\n    <tr>\n      <td>\n      <b>Masaüstü</b>\n      <br />\n      <em>1. Seviye Destek</em>\n      </td>\n      <td>\n        <ul>\n          <li>Webview(arayüzü websiteye benzer şekilde gösteren yapı) ya da - deneysel olarak - WGPU(Web GPU(web temelli grafik arabirimi ile grafik kartını kullanarak işleme kabiliyeti sağlayan yapı)) veya <a href=\"https://freyaui.dev\">Freya</a> (skia) ile işleme yap </li>\n          <li>Kolay kurulum. Basitçe `cargo run` ya da `dx serve` ile uygulamanı derle. </li>\n          <li>IPC(Inter Process Communication(İşlemleri birbiriyle konuşturan sistem)) kullanmadan doğdurdan sistem erişimi. </li>\n          <li>macOS, Linux ve Windows desteklenir. Taşınabilir <3mb binaries(çalıştırılan dosya (exe örneğini vermiş olalım)) </li>\n        </ul>\n      </td>\n    </tr>\n    <tr>\n      <td>\n      <b>Liveview(uygulamanın sunucuda çalışıp kullanıcı tarafında işlendiği yapı)</b>\n      <br />\n      <em>1. Seviye Destek</em>\n      </td>\n      <td>\n        <ul>\n          <li>Uygulamayı - ya da sadece bir componenti(komponent) - tamamıyla sunucuda işle.</li>\n          <li>Axum ve Warp gibi popüler Rust frameworkleri ile entegrasyon.</li>\n          <li>10.000+ uygulamayı destekleyebilme ve aşırı düşük gecikme.</li>\n        </ul>\n      </td>\n    </tr>\n    <tr>\n      <td>\n      <b>Mobil</b>\n      <br />\n      <em>2. Seviye Destek</em>\n      </td>\n      <td>\n        <ul>\n          <li>Webview ya da - deneysel olarak - WGPU yahut Skia ile işleme.</li>\n          <li>iOS ve Android desteği. </li>\n          <li>Şuanda oldukça deneysel, birsürü geliştirme 2024 boyunca gelmeye devam edecek. </li>\n        </ul>\n      </td>\n    </tr>\n    <tr>\n      <td>\n      <b>Terminal</b>\n      <br />\n      <em>2. Seviye Destek</em>\n      </td>\n      <td>\n        <ul>\n          <li><a href=\"https://github.com/vadimdemedes/ink\"> ink.js</a> ile benzer şekilde uygulamaları doğrudan terminalde render et.</li>\n          <li>Tarayıcıdaki flexbox ve CSS modeline benzer yapılardan güç alır.</li>\n          <li>Metin girdisi, butonlar ve odak sistemi gibi dahili widgets(araçlar).</li>\n        </ul>\n      </td>\n    </tr>\n  </table>\n</div>\n\n## Örnekleri Çalıştırma\n\nEn üst klasördeki örnekleri çalıştırmak için `cargo run --example <örnek adı>` yeterli olacaktır. Ancak, size tavsiyemiz dioxus-cli indirerek örnekleri `dx serve` ile çalıştırmanızdır, çünkü birçok örnek aynı zamanda web platformunu da destekliyor. Web için çalıştırmak isterseniz Cargo.toml üzerinde düzenleme yapmanız ya da varsayılan masaüstü özelliğini devredışı bırakmanız gerekir.\n\n## Dioxus ve Diğer Frameworkleri Kıyaslayalım\n\nBizler bütün frameworkleri seviyor ve Rust ekosisteminde gelişimlerini izlemekten keyif alıyoruz. Bu bağlamda bizim projelerimizin de birçoğu diğer frameworkler tarafından kullanılıyor. Örnek vermek gerekirse, flex-box kütüphanemiz [Taffy](https://github.com/DioxusLabs/taffy); [Bevy](https://bevyengine.org/), [Zed](https://zed.dev/), [Lapce](https://lapce.dev/), [Iced](https://github.com/iced-rs/iced), ve çok daha fazla yerde kullanılmakta.\n\nDioxus'u diğer frameworklerden ayıran birkaç vurguladığı nokta var:\n\n- **React-benzeri**: Components, props(fonksiyon argümanlarına benzer olan yapılar) ve hooks(fonksiyon componentlerinin diğer framework özelliklerine erişmesini sağlayan yapılar) gibi konseptler üzerine bina ettik. Bu durum bizi SolidJS'ten ziyade Svelte'ye yaklaştırdı.\n- **HTML ve CSS**: Quirks(geriye uyumluluk) ve diğer özellikler için, HTML ve CSS'i temel alıyoruz.\n- **Renderer-agnostic(İşleyen Bağımsız)**: [Ürettiğimiz Hızlı VirtualDOM](https://dioxuslabs.com/blog/templates-diffing) sayesinde istediğiniz platform için renderer değiştebilirsiniz.\n- **Collaborative(Birlikte çalışmayı destekleyen)**: Mümkün olduğu sürece bu örneklerdeki gibi crates(basitçe Rust üzerindeki kütüphaneler gibi düşünün) üretiyoruz: [Taffy](https://github.com/DioxusLabs/taffy), [manganis](https://github.com/DioxusLabs/manganis), [include_mdbook](https://github.com/DioxusLabs/include_mdbook), ve [blitz](http://github.com/dioxusLabs/blitz). Böylece ekosistemi birlikte büyütebiliriz.\n\n### Dioxus ile Tauri'yi Kıyaslayalım\n\nTauri masaüstü -ve yakında mobil de dahil edilecek- üzerine frontend(önyüz) için React, Vue, Svelte vb. web temelli frameworklerini kullanan bir frameworktür. Ne zaman native (doğrudan çalışan, burada kastedilen işletim sistemine doğrudan bir işlem gerçekleştirmek istediğinizde) bir iş yapmak isterseniz Rust fonksiyonları yazarsınız ve bunları frontend üzerinden çağırmanız gerekir.\n\n- **Natively Rust(Doğrudan Rust ile geliştirildi)**: Tauri'nin mimarisi sizi JavaScript ya da WebAssembly ile sınırlar. Dioxus ile Rust kodunuz kullanıcının makinesinde doğrudan çalışır. Thread spawn(iş parçacığı üretme), dosya sistemine erişme işlemlerini IPC köprü kullanmadan yapabilirsiniz.\n\n- **Farklı Amaçlar**: Tauri JavaScript'i ve onun karmaşık build tooling(derleme araçlarını) desteklemek zorunda. Bu durum yapabileceğiniz işlemlerin kısıtlanmasına sebebiyet vermekte. Dioxus özel olarak Rust'a odaklandığından, server functions, gelişmiş bundling ve native renderer gibi ekstra özellikleri sunabiliyoruz.\n\n- **Ortak DNA**: Tauri ve Dioxus farklı projeler olsa da windowing(pencere işlemleri) ve webview için Tao ve Wry gibi ortak olarak paylaştığı kütüphaneler mevcut.\n\n### Dioxus ve Leptos'u Kıyaslayalım\n\nLeptos SolidJS ve SoldStart benzeri fullstack web uygulamları geliştirebildiğiniz bir kütüphane. Dioxus ve Leptos web üzerinde benzer amaçlarla var olan 2 kütüphane ancak birkaç önemli nüans mevcut.\n\n- **Reactivity model(Yeniden-aktifleştirme modeli)**: Leptops reactivity için signals (sinyaller) kullanırken, Dioxus Virtual(Sanal) DOM ve re-render(yeniden işleme) kullanır. Teoride signals çok daha verimli olurken pratikte [block-dom'dan ilham alınan taslaklar](https://dioxuslabs.com/blog/templates-diffing) sayesinde Virtual DOM neredeyse hiç fark oluşturmuyor. Kıyaslamada da görebileceğiniz gibi [aslında Leptos'tan hızlıyız](https://krausest.github.io/js-framework-benchmark/2024/table_chrome_123.0.6312.59.html).\n\n- **Control flow(Karar verme mekanizmaları)**: Leptops reactivity için signals kullandığından, Leptos'un primitives (ilkelleri(her kodlama dilinde var olan, en temel şeyler için kullanılır genelde, farklı kullanımları da var tabiki. Örnek olarak veri tiplerine integer(tamsayı), bool(sadece 1 ve 0 değerini alan veri tipi) verebiliriz. Kontrol mekanizmaları için de for, if yapıları verilebilir.)) içerisinde sıkışırsınız. Örnek vermek gerekirse `for` döngüleri ve `if` ifadeleri. Eğer bu yapılarda bir hata meydana gelirse uygulamanız bütün reactivity(tepkiselliğini) özelliklerini kaybeder, bu durum UI(User Interface (Kullanıcı Arayüzü)) hatalarını debug(hata ayıklama) yapmanızı zorlaştırır. Dioxus ile birlikte iteratörler, sıradan Rust `for` döngüleri ve `if` durumlarını kullanabilir ve uygulamanız reactive kalmaya devam edebilir. Pratikte bir listeye counters(sayaçlar) eklemek istediğimiz bir örnek şu şekilde görünebilir:\n\n```rust\nfn Counters() -> Element {\n  let mut counters = use_signal(|| vec![0; initial_length]);\n\n  rsx! {\n    button { onclick: move |_| counters.push(counters.len()); \"Add Counter\" }\n    ul {\n      for idx in 0..counters.len() {\n        li {\n          button { onclick: move |_| counters[idx] += 1; \"{counters[idx]}\" }\n          button { onclick: move |_| { counters.write().remove(idx); } \"Remove\" }\n        }\n      }\n    }\n  }\n}\n```\n\n[Ancak Leptos için bu durum önce key tracking (anahtarları izlemenizi) yapmanızı, `<For>` component kullanmanızı, yeni singnals oluşturmanızı ve sonunda da hafızayı manuel bir şekilde temizlemenizi gerektirir.](https://book.leptos.dev/view/04_iteration.html#dynamic-rendering-with-the-for-component):\n\n```rust\nfn Counters() -> Element {\n    let initial_counters = (0..initial_length)\n        .map(|id| (id, create_signal(id + 1)))\n        .collect::<Vec<_>>();\n\n    let (counters, set_counters) = create_signal(initial_counters);\n\n    let add_counter = move |_| {\n        let sig = create_signal(next_counter_id + 1);\n        set_counters.update(move |counters| counters.push((next_counter_id, sig)));\n        next_counter_id += 1;\n    };\n\n    view! {\n        <div>\n            <button on:click=add_counter>\n                \"Add Counter\"\n            </button>\n            <ul>\n                <For\n                    each=counters\n                    key=|counter| counter.0\n                    children=move |(id, (count, set_count))| {\n                        view! {\n                            <li>\n                                <button\n                                    on:click=move |_| set_count.update(|n| *n += 1)\n                                >\n                                    {count}\n                                </button>\n                                <button\n                                    on:click=move |_| {\n                                        set_counters.update(|counters| {\n                                            counters.retain(|(counter_id, (signal, _))| {\n\n                                                if counter_id == &id {\n                                                    signal.dispose();\n                                                }\n                                                counter_id != &id\n                                            })\n                                        });\n                                    }\n                                >\n                                    \"Remove\"\n                                </button>\n                            </li>\n                        }\n                    }\n                />\n            </ul>\n        </div>\n    }\n}\n```\n\n- **`Copy` state(Kopya oluşturma yöntemi)**: Dioxus 0.1 - 0.4 sürümleri boyunca Rust'ın borrow checker(ödünç almayı kontrol eden yapı (kısaca: Değişkenlerin geçerli olup olmadığını Rust standartlarında garanti eden yapı)) mekanizmasını esnetmek için lifetime(Rust lifetime yapıları(kısaca: Değişkenlerin ihtiyaç duyduğumuz zaman var olmasını kontrol eden bir sistem)) kullanılıyordu. Bu durum event handlers (olay yönetim yapıları) için iyi çalıştı ancak async (asenkron) tarafında işleri zora soktu. Dioxus 0.5 ile birlikte Leptos'tan ödünç aldığımız [`Copy`(Kopya oluşturma) modeline](https://crates.io/crates/generational-box) geçtik.\n\n- **Farklı Amaçlar**: Dioxus web, masaüstü, mobil, LiveView ve çok daha fazla platform için renderer sunar. Aynı zamanda topluluk kütüphanelerini ve bir çapraz platform SDK'yi de geliştiriyoruz. Bu işlerin kapsadığı alan çok fazla ve bu durum Leptos'tan daha yavaş yeni sürüm yayınlamamıza sebebiyet veriyor. Leptos fullstack ve web'e odaklanırken aynı zamanda Dioxus'ta bulunmayan `<Suspense />` temelli HTML streaming(akış), islands(adalar(componentleri gruplandırıp wasm ile birlikte kullanıcının tarayıcısına gönderen yapı)), `<Form />`(HTML'ye benzer şekilde kullanıcı tarafında rooting(yönlendirme) yapmanızı sağlayan yapı) komponenetleri, ve başka webe özel özellikler. Genel olarak Leptos ile üreteceğiniz web uygulamaları daha küçük footprint'e(ayak izi) sahip olacaktır.\n\n- **Farklı DSLs(Domain Specific Language(Bölgeye Özel Dil(Belirli alan içerisinde geçerli olan dil, meta language(dil) şeklinde de denk gelebilirsiniz))**: İki framework de web için kullanılabilir durumdadır. Dioxus UI geliştirmek için Rust benzeri DSL kullanırken Leptos daha çok HTML benzeri yapılar kullanır. Bu yöntemi seçmemizin sebebi ise codefolding(görsel olarak kodun bazı parçalarını kapatıp açmayı sağlayan özellik) ve syntax highlighting(yazım vurgulama(kod içerisinde fonksiyonları falan normal yazıdan daha ayırıcı -renkli, altı çizi- gösteren özellik)) gibi IDE(Integrated Development Environment (Entegre Geliştirme Ortamı)) tarafından sağlanan özelliklerinden yararlanabilmek. Genel olarak Dioxus DSL ile daha fazla özellik sağlamayı hedefler. Örnek vermek gerekirse Dioxus otomatik olarak stringleri(metinleri) birleştirirken Leptos closures(anonim fonksiyonlar) ile format!(Rust'ın string format makrosu(birleştirici yapı)) ya da format_args!(Rust'ın format! makrosunun temel aldığı daha genel makro) kullanmanızı bekler.\n\n```rust\n// dioxus\nrsx! {\n  div { class: \"my-class\", enabled: true, \"Hello, {name}\" }\n}\n\n// leptos\nview! {\n  <div class=\"my-class\" enabled={true}>\n    \"Hello \"\n    {move || name()}\n  </div>\n}\n```\n\n### Dioxus ve Yew'i Kıyaslayalım\n\nTek sayfalı web uygulamaları geliştirmeyi sağlayan Yew, Dioxus için de ilham olmuştur. Ancak Yew'in mimarisi istediğimiz bazı özellikleri karşılamadığı için Dioxus'un doğumuna sebep oldu.\n\n- **Tek Sayfa Web Uygulamaları**: Yew özel olarak tek sayfa web uygulamaları geliştirmek için tasarlandı ve doğal olarak bu onu web ile sınırlı kıldı. Dioxus ise çapraz platform fullstack uygulamalar geliştirilebilmesi sebebiyle web, masaüstü, mobil ve sunucu uygulamaları için uygun bir seçimdir.\n\n- **Geliştirici Araçları**: Dioxus otomatik formatlama, hotreloading ve bundler gibi birçok araç sunar.\n\n- **Devamlı Destek**: Dioxus yeni özelliklerle ve günlük olarak yapılan bug(hata) düzeltmeleri ile epey aktif bir şekilde geliştirilmekte.\n\n### Dioxus ve egui'yi Kıyaslayalım\n\negui Rust için çapraz platform GUI(Grafical User Interface(Grafik Kullanıcı Arayüzü)) kütüphanesidir. [Rerun.io](https://www.rerun.io) gibi projelere de güç verir.\n\n- **Immediate vs Retained(Kısaca: Her frame(kare) için sürekli yeni frame üreten mimari ile Değişiklik yapılmadıkça güncellenmeyen mimari)**: egui her frame için re-render yapılmak üzere tasarlanmıştır. Bu durum oyunlar ve başka interaktif uygulamalar için uygun olsa da style(stil) ve layout(ana yapı) frameler arasında korunmaz. Dioxus ise retained yapıda bir UI frameworktür, bu ise UI'ın bir kez üretilip frameler arasında güncelleneceği anlamına gelir. Bu durum Dioxus'un HTML, CSS gibi native web teknolojilerini kullanabilmesini, daha iyi pil ömrü ve performans göstermesini sağlar.\n\n- **Özelleştirilebilirlik**: egui kendi styling ve layout çözümüyle gelirken Dioxus dahili olarak HTML ve CSS kullanmanızı bekler. Bu ise Dioxus uygulamalarının Tailwind ve Material UI gibi herhangi bir CSS kütüphanesini kullanabilmesine olanak sağlar.\n\n- **State(Durum) Yönetimi**: egui'nin state yönetimi tek bir global state objesi üzerinden gerçekleşir. Dioxus ise bu bağlamda components ve props ile birlikte statelerin encapsulation(kapsülleme) işlemiyle tekrar tekrar kullanılabilmesini destekler.\n\n### Dioxus ve Iced'ı Kıyaslayalım\n\nIced Elm'den esinlenmiş çapraz platform GUI kütüphanesidir. Iced WGPU ile native render sunar ve DOM nodes(düğümleri) ile webi de destekler.\n\n- **Elm state Yönetimi**: Iced Elm state yönetim modelini sunar, bu ise reducers(fonksiyon gibi düşünebiliriz) ve mesaj iletim yoluyla gerçekleşir. Basitçe bu mimari Dioxus'tan farklıdır ve zaman zaman oldukça verbose (ayrıntılı, yorucu) olabilir.\n\n- **Native Görünüm**: Dioxus'un renderer olarak webview kullanması sebebiyle otomatik olarak native metin kutularını, kopyala yapıştır fonksiyonlarını ve accessibility(erişilebilirlik) gibi diğer native özellikler sunar. Iced rendererı şuan için bu özellikleri içermemesi sebebiyle daha az native hissettirir.\n\n- **WGPU**: Dioxus'un WGPU renderer özelliği şuan için çok olgunlaşmış değil ve ürün geliştirmeye uygun değil. Iced'ın WGPU rendererı ise çok daha olgun durumda ve ürün geliştirmek için kullanılabilir. Bu durumda bazı GPU(Graphical Processing Unit(Grafik İşleme Birimi, ekran kartınız aklınıza gelsin)) erişimi gerektiren uygulamalar için Iced kullanılabilirken Dioxus kullanılamaz.\n\n### Dioxus ve Electron'u Kıyaslayalım\n\nDioxus ve Electron benzer amaçlara sahip tamamıyla farklı iki projedir. Electron geliştiricilerin HTML, CSS ve JavaScript gibi web teknolojilerini kullanarak çapraz platform masaüstü uygulamaları geliştirmelerini sağlar.\n\n- **Hafif**: Dioxus sistemin native webview ya da opsiyonel olarak WGPU rendererını kullanarak UI render eder. Kıyaslamamız gerekirse bu durum tipik bir macOS uygulamasında Electron'un 100mb yer kapladığı yerde Dioxus uygulamasının 15mb ile var olmasını sağlar. Electron dahili olarak sistem kaynaklarını Dioxus'un paylaştığı gibi paylaşmayan gömülü bir chromium ile birlikte gelir.\n\n- **Olgunluk**: Electron büyük bir topluluk ve birçok araç ile beraber olgun bir proje. Dioxus Electron ile kıyaslandığında halen oldukça genç. Deep link(Bazı protokoller için varsayılan olarak çalışma) gibi fazladan iş gerektiren özellikler için çalışmamız gerekiyor.\n\n## Katkı Sağlama\n\n- [Katkı sağlamayla alakalı kısım](https://dioxuslabs.com/learn/0.7/beyond/contributing) için web sitemizi ziyaret edin.\n- [Issue tracker(sorun takipçisi)](https://github.com/dioxuslabs/dioxus/issues) üzerinden sorunlarınızı raporlayabilirsiniz.\n- Discord'a [katıl](https://discord.gg/XgGxMSkvUM) ve sorularını sor!\n\n<a href=\"https://github.com/dioxuslabs/dioxus/graphs/contributors\">\n  <img src=\"https://contrib.rocks/image?repo=dioxuslabs/dioxus&max=30&columns=10\" />\n</a>\n\n## Lisans\n\nBu proje [MIT lisansı] altındadır.\n\n[mit lisansı]: https://github.com/DioxusLabs/dioxus/blob/master/LICENSE-MIT\n\nÖzel olarak belirmediğiniz sürece Dioxus'a tarafınızca sağladığınız her katkı ekstra koşul ve madde olmaksızın MIT lisansı ile lisanslanacaktır.\n"
  },
  {
    "path": "notes/translations/zh-cn/README.md",
    "content": "<p>\n    <p align=\"center\" >\n      <!-- <img src=\"./notes/header-light-updated.svg#gh-light-mode-only\" >\n      <img src=\"./notes/header-dark-updated.svg#gh-dark-mode-only\" > -->\n      <!-- <a href=\"https://dioxuslabs.com\">\n          <img src=\"./notes/flat-splash.avif\">\n      </a> -->\n      <img src=\"../../notes/splash-header-darkmode.svg#gh-dark-mode-only\" style=\"width: 80%; height: auto;\">\n      <img src=\"../../notes/splash-header.svg#gh-light-mode-only\" style=\"width: 80%; height: auto;\">\n      <img src=\"../../notes/image-splash.avif\">\n      <br>\n    </p>\n</p>\n<div align=\"center\">\n  <!-- Crates version -->\n  <a href=\"https://crates.io/crates/dioxus\">\n    <img src=\"https://img.shields.io/crates/v/dioxus.svg?style=flat-square\"\n    alt=\"Crates.io version\" />\n  </a>\n  <!-- Downloads -->\n  <a href=\"https://crates.io/crates/dioxus\">\n    <img src=\"https://img.shields.io/crates/d/dioxus.svg?style=flat-square\"\n      alt=\"Download\" />\n  </a>\n  <!-- docs -->\n  <a href=\"https://docs.rs/dioxus\">\n    <img src=\"https://img.shields.io/badge/docs-latest-blue.svg?style=flat-square\"\n      alt=\"docs.rs docs\" />\n  </a>\n  <!-- CI -->\n  <a href=\"https://github.com/jkelleyrtp/dioxus/actions\">\n    <img src=\"https://github.com/dioxuslabs/dioxus/actions/workflows/main.yml/badge.svg\"\n      alt=\"CI status\" />\n  </a>\n\n  <!--Awesome -->\n  <a href=\"https://dioxuslabs.com/awesome\">\n    <img src=\"https://cdn.rawgit.com/sindresorhus/awesome/d7305f38d29fed78fa85652e3a63e154dd8e8829/media/badge.svg\" alt=\"Awesome Page\" />\n  </a>\n  <!-- Discord -->\n  <a href=\"https://discord.gg/XgGxMSkvUM\">\n    <img src=\"https://img.shields.io/discord/899851952891002890.svg?logo=discord&style=flat-square\" alt=\"Discord Link\" />\n  </a>\n</div>\n\n<div align=\"center\">\n  <h3>\n    <a href=\"https://dioxuslabs.com\"> 官网 </a>\n    <span> | </span>\n    <a href=\"https://github.com/DioxusLabs/dioxus/tree/main/examples\"> 示例 </a>\n    <span> | </span>\n    <a href=\"https://dioxuslabs.com/learn/0.7/tutorial\"> 指南 </a>\n    <span> | </span>\n    <a href=\"https://github.com/DioxusLabs/dioxus/blob/main/notes/translations/zh-cn/README.md\"> 中文 </a>\n    <span> | </span>\n    <a href=\"https://github.com/DioxusLabs/dioxus/blob/main/notes/translations/pt-br/README.md\"> PT-BR </a>\n    <span> | </span>\n    <a href=\"https://github.com/DioxusLabs/dioxus/blob/main/notes/translations/ja-jp/README.md\"> 日本語 </a>\n    <span> | </span>\n    <a href=\"https://github.com/DioxusLabs/dioxus/blob/main/notes/translations/tr-tr\"> Türkçe </a>\n    <span> | </span>\n    <a href=\"https://github.com/DioxusLabs/dioxus/blob/main/notes/translations/ko-kr\"> 한국어 </a>\n  </h3>\n</div>\n<br>\n<p align=\"center\">\n  <a href=\"https://dioxuslabs.com/blog/release-060/\">✨ Dioxus 0.6 已经发布 - 在此查看! ✨</a>\n</p>\n<br>\n\n使用单一代码库构建 Web、桌面端、移动端以及更多平台的应用。零配置、集成热重载以及基于信号 (Signals) 的状态管理。通过服务器函数添加后端功能，并使用我们的 CLI 进行打包。\n\n```rust\nfn app() -> Element {\n    let mut count = use_signal(|| 0);\n\n    rsx! {\n        h1 { \"High-Five counter: {count}\" }\n        button { onclick: move |_| count += 1, \"Up high!\" }\n        button { onclick: move |_| count -= 1, \"Down low!\" }\n    }\n}\n```\n\n## ⭐️ 独特功能:\n\n- 三行代码即可构建跨平台应用（Web、桌面、移动端、服务器等）\n- [符合人体工程学的状态管理](https://dioxuslabs.com/blog/release-050)，结合了 React、Solid 和 Svelte 的优点\n- 类型安全的路由和服务器函数，由 Rust 强大的编译时能力保证\n- 集成 Web、MacOS、Linux 和 Windows 的打包工具\n- 还有更多！ [开启 Dioxus 之旅](https://dioxuslabs.com/learn/0.7/).\n\n## 即时热重载\n\n只需一个命令 `dx serve`，你的应用即可运行。编辑标记（markup）和样式并立刻看到结果。\n\n<div align=\"center\">\n  <img src=\"https://raw.githubusercontent.com/DioxusLabs/screenshots/refs/heads/main/blitz/hotreload-video.webp\">\n  <!-- <video src=\"https://private-user-images.githubusercontent.com/10237910/386919031-6da371d5-3340-46da-84ff-628216851ba6.mov\" width=\"500\"></video> -->\n  <!-- <video src=\"https://private-user-images.githubusercontent.com/10237910/386919031-6da371d5-3340-46da-84ff-628216851ba6.mov\" width=\"500\"></video> -->\n</div>\n\n## 一流的 Android 和 iOS 支持\n\nDioxus 是使用 Rust 构建原生移动应用的最快方式。只需运行 `dx serve --platform android`，你的应用就会几秒钟在模拟器或实机设备上运行，并能够直接调用 JNI 和原生 API。\n\n<div align=\"center\">\n  <img src=\"../../notes/android_and_ios2.avif\" width=\"500\">\n</div>\n\n## 为 Web、桌面和移动端打包\n\n只需运行 `dx bundle`，你的应用将被构建和打包，并进行最大优化。\n在 Web 端，得益于 [`.avif` 生成, `.wasm` 压缩, 代码最小化](https://dioxuslabs.com/learn/0.7/tutorial/assets)等功能， Web 端的构建[体积小于 50kb](https://github.com/ealmloff/tiny-dioxus/) ，桌面/移动端体积小于 5mb。\n\n<div align=\"center\">\n  <img src=\"../../notes/bundle.gif\">\n</div>\n\n## 出色的文档\n\n我们投入了大量精力来构建清晰、易读且全面的文档。所有 HTML 元素和监听器都附有 MDN 文档，并且我们的文档网站与 Dioxus 本身进行持续集成，以确保文档始终保持最新。请查看 [Dioxus 网站](https://dioxuslabs.com/learn/0.7/) 获取指南、参考、示例代码等。\n有趣的是：我们使用 Dioxus 网站来测试 Dioxus 的新功能 - [快来看看吧!](https://github.com/dioxusLabs/docsite)\n\n<div align=\"center\">\n  <img src=\"../../notes/docs.avif\">\n</div>\n\n## 社区\n\nDioxus 是一个社区驱动的项目，拥有非常活跃的 [Discord](https://discord.gg/XgGxMSkvUM) 和 [GitHub](https://github.com/DioxusLabs/dioxus/issues) 社区。 我们一直在寻求帮助，乐于回答问题并帮助你入门。[我们的 SDK](https://github.com/DioxusLabs/dioxus-std) 由社区运营，我们甚至有一个 [GitHub 组织](https://github.com/dioxus-community/) 用于那些获得免费升级和支持的最佳 Dioxus crates。\n\n<div align=\"center\">\n  <img src=\"../../notes/dioxus-community.avif\">\n</div>\n\n## 全职核心团队\n\nDioxus 已从一个业余项目发展成为一个小型的全职工程师团队。感谢 FutureWei、Satellite.im 和 GitHub Accelerator 项目的慷慨支持，我们能够全职投入 Dioxus 的开发。我们的长远目标是通过提供高质量的付费企业级工具，使 Dioxus 实现自我维持。如果您的公司有兴趣使用 Dioxus 并希望与我们合作，请联系我们！\n\n## 支持的平台\n\n<div align=\"center\">\n  <table style=\"width:100%\">\n    <tr>\n      <td>\n      <b>Web 端</b>\n      </td>\n      <td>\n        <ul>\n          <li>使用 WebAssembly 直接渲染到 DOM</li>\n          <li>通过 SSR 进行预渲染并在客户端进行水合 (rehydrate)</li>\n          <li>简单的 \"hello world\" 约 50kb，与 React 相当</li>\n          <li>内置的开发服务器和热重载以实现快速迭代</li>\n        </ul>\n      </td>\n    </tr>\n    <tr>\n      <td>\n      <b>桌面端</b>\n      </td>\n      <td>\n        <ul>\n          <li>使用 Webview 渲染，或实验性地使用 WGPU 或  <a href=\"https://freyaui.dev\">Freya</a> (Skia) 渲染</li>\n          <li>零配置，只需 `cargo run` 或 `dx serve` 即可构建您的应用 </li>\n          <li>完全支持原生系统访问，无需 IPC </li>\n          <li>支持 MacOS, Linux 和 Windows，或者便携式的 <3mb 的二进制文件 </li>\n        </ul>\n      </td>\n    </tr>\n    <tr>\n      <td>\n      <b>移动端</b>\n      </td>\n      <td>\n        <ul>\n          <li>使用 Webview 渲染，或实验性地使用 WGPU 或 Skia 渲染</li>\n          <li>为 iOS 和 Android 构建 .ipa 和 .apk 文件</li>\n          <li>以最小的开销直接调用 Java 和 Objective-C</li>\n          <li>从 \"hello world\" 到在实机设备上运行仅需几秒钟</li>\n        </ul>\n      </td>\n    </tr>\n    <tr>\n      <td>\n      <b>服务器端渲染</b>\n      </td>\n      <td>\n        <ul>\n          <li>Suspense，水合 (hydration) 和服务器端渲染</li>\n          <li>通过服务器函数快速添加后端功能</li>\n          <li>集成提取器（extractor）, 中间件（middleware）和路由</li>\n          <li>静态站点生成和增量生成</li>\n        </ul>\n      </td>\n    </tr>\n  </table>\n</div>\n\n## 运行示例\n\n> 本仓库 main 分支中的示例针对的是 Dioxus 的 git 版本和 CLI。如果您正在寻找适用于 Dioxus 最新稳定版本的示例，请查看 [0.6 分支](https://github.com/DioxusLabs/dioxus/tree/v0.6/examples)。\n\n本仓库顶层目录中的示例可以通过以下命令运行：\n\n```sh\ncargo run --example <example>\n```\n\n但是，我们鼓励你下载 dioxus-cli。如果你在使用 Dioxus 的 git 版本，可以使用以下命令安装相应版本的 CLI：\n\n```sh\ncargo install --git https://github.com/DioxusLabs/dioxus dioxus-cli --locked\n```\n\n你也使用 CLI 在 Web 平台上运行示例。只需通过如下命令禁用默认的桌面特性并启用 Web 特性：\n\n```sh\ndx serve --example <example> --platform web -- --no-default-features\n```\n\n## Dioxus 与其他框架的比较\n\n我们热爱 Rust 生态系统中的所有框架，并享受着他们所带来的创新。实际上，我们的许多项目都与其他框架共享。例如，我们的 flex-box 库 [Taffy](https://github.com/DioxusLabs/taffy) 被 [Bevy](https://bevyengine.org/), [Zed](https://zed.dev/), [Lapce](https://lapce.dev/), [Iced](https://github.com/iced-rs/iced) 等许多项目使用。\n\nDioxus 强调以下几个关键点，使其与其他框架有所不同：\n\n- **类 React**: 我们依赖组件、props 和 hooks 等概念来构建 UI，我们的状态管理更接近 Svelte 而非 SolidJS。\n- **HTML 和 CSS**: 我们完全拥抱 HTML 和 CSS，包括其所有特性和怪癖。\n- **渲染器无关**: 得益于[我们快速的虚拟 DOM](https://dioxuslabs.com/blog/templates-diffing)，你可以为任何你想要的平台替换渲染器。\n- **协作性**: 只要有可能，我们就会将像 [Taffy](https://github.com/DioxusLabs/taffy), [manganis](https://github.com/DioxusLabs/manganis), [include_mdbook](https://github.com/DioxusLabs/include_mdbook), 和 [blitz](http://github.com/dioxusLabs/blitz) 这样的 crates 分离出来，以便生态系统能够共同成长。\n\n### Dioxus vs Tauri\n\nTauri 是一个用于构建桌面移动应用的框架，其前端使用基于 Web 的框架（如 React、Vue、Svelte 等）。当你需要进行原生操作时，可以编写 Rust 函数并从前端调用它们。\n\n- **原生 Rust**: Tauri 的架构将您的 UI 限制在 JavaScript 或 WebAssembly。使用 Dioxus，你的 Rust 代码可以在用户的设备上原生运行，在你执行像创建线程、访问文件系统等操作时，无需任何 IPC 桥接。这极大地简化了您的应用架构，使其更易于构建。如果您愿意，也可以使用 Dioxus-Web 作为前端来构建 Tauri 应用。\n\n- **不同的范围**: Tauri 需要支持 JavaScript 及其复杂的构建工具链，这限制了您可以用它做的事情的范围。由于 Dioxus 专注于 Rust，我们能够提供额外的实用工具，如服务器函数、高级打包和原生渲染器。\n\n- **共享 DNA**: 虽然 Tauri 和 Dioxus 是独立的项目，但它们确实共享诸如 Tao 和 Wry 这样的库：由 Tauri 团队维护的窗口和 Webview 库。\n\n### Dioxus vs Leptos\n\nLeptos 是一个用于构建全栈 Web 应用的库，类似于 SolidJS 和 SolidStart。这两个库在 Web 端有着相似的目标，但存在一些关键差异：\n\n- **响应式模型**: Leptos 使用信号 (signals) 来驱动响应和渲染，而 Dioxus 仅将信号用于响应。为了管理重新渲染，Dioxus 使用了高度优化的虚拟 DOM 来支持桌面和移动端架构。Dioxus 和 Leptos 都非常快。\n\n- **不同的范围**: Dioxus 为 Web、桌面端、移动端、LiveView 等提供渲染器。我们还维护社区库和跨平台 SDK。 Leptos 更专注于全栈 Web，并拥有一些 Dioxus 所没有的功能，比如 islands、`<Form />` 组件和其他 Web 特有的实用工具.\n\n- **不同的 DSL**: Dioxus 使用其自定义的类似 Rust 的 DSL 来构建 UI，而 Leptos 使用类似 HTML 的语法。我们选择这样做是为了保持与 IDE 功能（如代码折叠和语法高亮）的兼容性。通常，Dioxus 的 DSL 倾向于更多的“魔法”，包括字符串的自动格式化和简单 Rust 表达式的热重载。\n\n```rust\n// dioxus\nrsx! {\n  div {\n    class: \"my-class\",\n    enabled: true,\n    \"Hello, {name}\"\n  }\n}\n\n// leptos\nview! {\n  <div class=\"my-class\" enabled={true}>\n    \"Hello \"\n    {name}\n  </div>\n}\n```\n\n### Dioxus vs Yew\n\nYew 是一个用于构建单页 Web 应用的框架，最初是 Dioxus 的灵感来源。不幸的是，Yew 的架构不支持我们想要的各种功能，因此 Dioxus 应运而生。\n\n- **单页应用**: Yew 专为单页 Web 应用设计，并与 Web 平台紧密绑定。Dioxus 是全栈和跨平台的，使其适用于构建 Web、桌面、移动端和服务器应用。\n\n- **开发者工具**: Dioxus 提供了许多实用工具，如自动格式化、热重载和打包工具。\n\n- **持续支持**: Dioxus 得到非常积极的维护，每天都在添加新功能和修复错误。\n\n### Dioxus vs egui\n\negui 是一个跨平台 GUI 库，为 Rust 驱动的工具提供支持，比如 [Rerun.io](https://www.rerun.io)。\n\n- **立即模式 (Immediate) vs 保留模式 (Retained)**: egui 设计为每一帧都重新渲染。这更适用于游戏和其他交互式应用，但它不会在帧之间保留样式和布局状态。Dioxus 是一个保留模式的 UI 框架，这意味着 UI 构建一次后，在帧之间进行修改。这使得 Dioxus 能够使用原生的 Web 技术（如 HTML 和 CSS），并具有更好的电池寿命和性能。\n\n- **可定制性**: egui 带来了自己的样式和布局解决方案，而 Dioxus 则希望您使用内置的 HTML 和 CSS。这使得 Dioxus 应用可以使用任何 CSS 库，例如 Tailwind 或 Material UI。\n\n- **状态管理**: egui 的状态管理基于单个全局状态对象。Dioxus 鼓励通过使用组件和 props 来封装状态，使组件更具可复用性。\n\n### Dioxus vs Iced\n\nIced 是一个受 Elm 启发的跨平台 GUI 库。Iced 使用 WGPU 进行原生渲染，并使用 DOM 节点支持 Web。\n\n- **Elm 状态管理**: Iced 使用 Elm 的状态管理模型，该模型基于消息传递和 reducers。这不同于 Dioxus 的状态管理模型，它有时可能会相当冗长。\n\n- **原生体验**: 由于 Dioxus 使用 Webview 作为其渲染器，它自动获得了原生文本输入、粘贴处理以及其他原生功能（比如无障碍）。Iced 的渲染器目前尚未实现这些功能，使其感觉不那么原生。\n\n- **WGPU**: Dioxus 的 WGPU 渲染器目前还很不成熟，尚未准备好用于生产环境。Iced 的 WGPU 渲染器则成熟得多，并已在生产中使用。这使得某些需要 GPU 访问的应用可以使用 Iced 构建，而目前无法使用 Dioxus 构建。\n\n### Dioxus vs Electron\n\nDioxus 和 Electron 是两个目标相似但完全不同的项目。Electron 使开发人员能够使用 HTML、CSS 和 JavaScript 等 Web 技术构建跨平台桌面应用。\n\n- **轻量级**: Dioxus 使用系统原生 Webview（或者可选 WGPU 渲染器）来渲染 UI。这使得典型的 Dioxus 应用在 macOS 上大约为 15MB，而 Electron 则为 100MB。Electron 还捆绑了一个嵌入式的 Chromium 实例，它无法像 Dioxus 那样与宿主操作系统共享系统资源。\n\n- **成熟度**: Electron 是一个成熟的项目，拥有庞大的社区和许多工具。相比，Dioxus 与 Electron 相比还很年轻。如果遇到诸如深层链接 (deep-linking) 之类的功能，这些功能需要额外的工作来实现。\n\n## 贡献\n\n- 查看网站上[关于贡献的部分](https://dioxuslabs.com/learn/0.7/beyond/contributing).\n- 在我们的 [issues](https://github.com/dioxuslabs/dioxus/issues) 上报告问题.\n- [加入](https://discord.gg/XgGxMSkvUM) Discord 并提问！\n\n<a href=\"https://github.com/dioxuslabs/dioxus/graphs/contributors\">\n  <img src=\"https://contrib.rocks/image?repo=dioxuslabs/dioxus&max=30&columns=10\" />\n</a>\n\n## 开源许可\n\n此项目基于 [MIT license] 或 [Apache-2 License] 发布。\n\n[apache-2 license]: https://github.com/DioxusLabs/dioxus/blob/master/LICENSE-APACHE\n[mit license]: https://github.com/DioxusLabs/dioxus/blob/master/LICENSE-MIT\n\n除非您明确声明，否则您有意提交给 Dioxus 的任何贡献，均应按 MIT 或 Apache-2 授权，不附带任何附加条款或条件。\n"
  },
  {
    "path": "packages/asset-resolver/Cargo.toml",
    "content": "[package]\nname = \"dioxus-asset-resolver\"\nedition = \"2021\"\nversion.workspace = true\nauthors = [\"Jonathan Kelley\", \"Dioxus Labs\"]\ndescription = \"Cross-platform asset resolver for manganis and dioxus\"\nlicense = \"MIT OR Apache-2.0\"\nrepository = \"https://github.com/DioxusLabs/dioxus/\"\nhomepage = \"https://dioxuslabs.com\"\nkeywords = [\"web\", \"desktop\", \"mobile\", \"gui\", \"wasm\"]\nrust-version = \"1.79.0\"\n\n[dependencies]\nthiserror = { workspace = true }\n\n# native/fs resolver dependencies\nhttp = { workspace = true, optional = true }\npercent-encoding = { workspace = true, optional = true }\ninfer = { workspace = true, optional = true }\ndioxus-cli-config = { workspace = true, optional = true }\ntokio = { workspace = true, features = [\"rt\"], optional = true }\n\n# browser resolver dependencies\nweb-sys = { workspace = true, features = ['Headers', 'Request', 'RequestInit', 'Response', 'Window'], optional = true }\njs-sys = { workspace = true, optional = true }\nwasm-bindgen-futures = { workspace = true, optional = true }\n\n[target.'cfg(target_os = \"android\")'.dependencies]\njni = { version = \"0.21.1\" }\nndk = { version = \"0.9.0\" }\nndk-sys = { version = \"0.6.0\" }\nndk-context = { version = \"0.1.1\" }\n\n[dev-dependencies]\ndioxus = { workspace = true }\nserde_json = { workspace = true }\n\n[features]\ndefault = []\nweb = [\"dep:web-sys\", \"dep:js-sys\", \"dep:wasm-bindgen-futures\"]\nnative = [\"dep:http\", \"dep:percent-encoding\", \"dep:infer\", \"dep:dioxus-cli-config\", \"dep:tokio\"]\n"
  },
  {
    "path": "packages/asset-resolver/assets/data.json",
    "content": "{\n  \"key\": \"value\"\n}\n"
  },
  {
    "path": "packages/asset-resolver/src/lib.rs",
    "content": "#![warn(missing_docs)]\n//! The asset resolver for the Dioxus bundle format. Each platform has its own way of resolving assets. This crate handles\n//! resolving assets in a cross-platform way.\n//!\n//! There are two broad locations for assets depending on the platform:\n//! - **Web**: Assets are stored on a remote server and fetched via HTTP requests.\n//! - **Native**: Assets are read from the local bundle. Each platform has its own bundle structure which may store assets\n//!   as a file at a specific path or in an opaque format like Android's AssetManager.\n//!\n//! [`read_asset_bytes`]( abstracts over both of these methods, allowing you to read the bytes of an asset\n//! regardless of the platform.\n//!\n//! If you know you are on a desktop platform, you can use [`asset_path`] to resolve the path of an asset and read\n//! the contents with [`std::fs`].\n//!\n//! ## Example\n//! ```rust\n//! # async fn asset_example() {\n//! use dioxus::prelude::*;\n//!\n//! // Bundle the static JSON asset into the application\n//! static JSON_ASSET: Asset = asset!(\"/assets/data.json\");\n//!\n//! // Read the bytes of the JSON asset\n//! let bytes = dioxus::asset_resolver::read_asset_bytes(&JSON_ASSET).await.unwrap();\n//!\n//! // Deserialize the JSON data\n//! let json: serde_json::Value = serde_json::from_slice(&bytes).unwrap();\n//! assert_eq!(json[\"key\"].as_str(), Some(\"value\"));\n//! # }\n//! ```\n\nuse std::{fmt::Debug, path::PathBuf};\n\n#[cfg(feature = \"native\")]\npub mod native;\n\n#[cfg(feature = \"web\")]\nmod web;\n\n/// An error that can occur when resolving an asset to a path. Not all platforms can represent assets as paths,\n/// an error may mean that the asset doesn't exist or it cannot be represented as a path.\n#[non_exhaustive]\n#[derive(Debug, thiserror::Error)]\npub enum AssetPathError {\n    /// The asset was not found by the resolver.\n    #[error(\"Failed to find the path in the asset directory\")]\n    NotFound,\n\n    /// The asset may exist, but it cannot be represented as a path.\n    #[error(\"Asset cannot be represented as a path\")]\n    CannotRepresentAsPath,\n}\n\n/// Tries to resolve the path of an asset from a given URI path. Depending on the platform, this may\n/// return an error even if the asset exists because some platforms cannot represent assets as paths.\n/// You should prefer [`read_asset_bytes`] to read the asset bytes directly\n/// for cross-platform compatibility.\n///\n/// ## Platform specific behavior\n///\n/// This function will only work on desktop platforms. It will always return an error in web and Android\n/// bundles. On Android assets are bundled in the APK, and cannot be represented as paths. In web bundles,\n/// Assets are fetched via HTTP requests and don't have a filesystem path.\n///\n/// ## Example\n/// ```rust\n/// use dioxus::prelude::*;\n///\n/// // Bundle the static JSON asset into the application\n/// static JSON_ASSET: Asset = asset!(\"/assets/data.json\");\n///\n/// // Resolve the path of the asset. This will not work in web or Android bundles\n/// let path = dioxus::asset_resolver::asset_path(&JSON_ASSET).unwrap();\n///\n/// println!(\"Asset path: {:?}\", path);\n///\n/// // Read the bytes of the JSON asset\n/// let bytes = std::fs::read(path).unwrap();\n///\n/// // Deserialize the JSON data\n/// let json: serde_json::Value = serde_json::from_slice(&bytes).unwrap();\n/// assert_eq!(json[\"key\"].as_str(), Some(\"value\"));\n/// ```\n///\n/// ## Resolving assets from a folder\n///\n/// To resolve an asset from a folder, you can pass the path of the file joined with your folder asset as a string:\n/// ```rust\n/// # async fn asset_example() {\n/// use dioxus::prelude::*;\n///\n/// // Bundle the whole assets folder into the application\n/// static ASSETS: Asset = asset!(\"/assets\");\n///\n/// // Resolve the path of the asset. This will not work in web or Android bundles\n/// let path = dioxus::asset_resolver::asset_path(format!(\"{ASSETS}/data.json\")).unwrap();\n///\n/// println!(\"Asset path: {:?}\", path);\n///\n/// // Read the bytes of the JSON asset\n/// let bytes = std::fs::read(path).unwrap();\n///\n/// // Deserialize the JSON data\n/// let json: serde_json::Value = serde_json::from_slice(&bytes).unwrap();\n/// assert_eq!(json[\"key\"].as_str(), Some(\"value\"));\n/// # }\n/// ```\n#[allow(unused)]\npub fn asset_path(asset: impl ToString) -> Result<PathBuf, AssetPathError> {\n    #[cfg(all(feature = \"web\", target_arch = \"wasm32\"))]\n    return Err(AssetPathError::CannotRepresentAsPath);\n\n    #[cfg(feature = \"native\")]\n    return native::resolve_native_asset_path(asset.to_string().as_str());\n\n    Err(AssetPathError::NotFound)\n}\n\n/// An error that can occur when resolving an asset.\n#[non_exhaustive]\n#[derive(Debug, thiserror::Error)]\npub enum AssetResolveError {\n    /// An error occurred while resolving a native asset.\n    #[error(\"Failed to resolve native asset: {0}\")]\n    Native(#[from] NativeAssetResolveError),\n\n    /// An error occurred while resolving a web asset.\n    #[error(\"Failed to resolve web asset: {0}\")]\n    Web(#[from] WebAssetResolveError),\n\n    /// An error that occurs when no asset resolver is available for the current platform.\n    #[error(\"Asset resolution is not supported on this platform\")]\n    UnsupportedPlatform,\n}\n\n/// Read the bytes of an asset. This will work on both web and native platforms. On the web,\n/// it will fetch the asset via HTTP, and on native platforms, it will read the asset from the filesystem or bundle.\n///\n/// ## Errors\n/// This function will return an error if the asset cannot be found or if it fails to read which may be due to I/O errors or\n/// network issues.\n///\n/// ## Example\n///\n/// ```rust\n/// # async fn asset_example() {\n/// use dioxus::prelude::*;\n///\n/// // Bundle the static JSON asset into the application\n/// static JSON_ASSET: Asset = asset!(\"/assets/data.json\");\n///\n/// // Read the bytes of the JSON asset\n/// let bytes = dioxus::asset_resolver::read_asset_bytes(&JSON_ASSET).await.unwrap();\n///\n/// // Deserialize the JSON data\n/// let json: serde_json::Value = serde_json::from_slice(&bytes).unwrap();\n/// assert_eq!(json[\"key\"].as_str(), Some(\"value\"));\n/// # }\n/// ```\n///\n/// ## Loading assets from a folder\n///\n/// To load an asset from a folder, you can pass the path of the file joined with your folder asset as a string:\n/// ```rust\n/// # async fn asset_example() {\n/// use dioxus::prelude::*;\n///\n/// // Bundle the whole assets folder into the application\n/// static ASSETS: Asset = asset!(\"/assets\");\n///\n/// // Read the bytes of the JSON asset\n/// let bytes = dioxus::asset_resolver::read_asset_bytes(format!(\"{ASSETS}/data.json\")).await.unwrap();\n///\n/// // Deserialize the JSON data\n/// let json: serde_json::Value = serde_json::from_slice(&bytes).unwrap();\n/// assert_eq!(json[\"key\"].as_str(), Some(\"value\"));\n/// # }\n/// ```\n#[allow(unused)]\npub async fn read_asset_bytes(asset: impl ToString) -> Result<Vec<u8>, AssetResolveError> {\n    let path = asset.to_string();\n\n    #[cfg(feature = \"web\")]\n    return web::resolve_web_asset(&path)\n        .await\n        .map_err(AssetResolveError::Web);\n\n    #[cfg(feature = \"native\")]\n    return tokio::task::spawn_blocking(move || native::resolve_native_asset(&path))\n        .await\n        .map_err(|err| AssetResolveError::Native(NativeAssetResolveError::JoinError(err)))\n        .and_then(|result| result.map_err(AssetResolveError::Native));\n\n    Err(AssetResolveError::UnsupportedPlatform)\n}\n\n/// An error that occurs when resolving a native asset.\n#[non_exhaustive]\n#[derive(Debug, thiserror::Error)]\npub enum NativeAssetResolveError {\n    /// An I/O error occurred while reading the asset from the filesystem.\n    #[error(\"Failed to read asset: {0}\")]\n    IoError(#[from] std::io::Error),\n\n    /// The asset resolver failed to complete and could not be joined.\n    #[cfg(feature = \"native\")]\n    #[error(\"Asset resolver join failed: {0}\")]\n    JoinError(tokio::task::JoinError),\n}\n\n/// An error that occurs when resolving an asset on the web.\npub struct WebAssetResolveError {\n    #[cfg(feature = \"web\")]\n    error: js_sys::Error,\n}\n\nimpl Debug for WebAssetResolveError {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        let mut debug = f.debug_struct(\"WebAssetResolveError\");\n        #[cfg(feature = \"web\")]\n        debug.field(\"name\", &self.error.name());\n        #[cfg(feature = \"web\")]\n        debug.field(\"message\", &self.error.message());\n        debug.finish()\n    }\n}\n\nimpl std::fmt::Display for WebAssetResolveError {\n    #[allow(unreachable_code)]\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        #[cfg(feature = \"web\")]\n        return write!(f, \"{}\", self.error.message());\n        write!(f, \"WebAssetResolveError\")\n    }\n}\n\nimpl std::error::Error for WebAssetResolveError {}\n"
  },
  {
    "path": "packages/asset-resolver/src/native.rs",
    "content": "//! Native specific utilities for resolving assets in a bundle. This module is intended for use in renderers that\n//! need to resolve asset bundles for resources like images, and fonts.\n\nuse http::{status::StatusCode, Response};\nuse std::path::{Path, PathBuf};\n\nuse crate::{AssetPathError, NativeAssetResolveError};\n\n/// An error that can occur when serving an asset.\n#[non_exhaustive]\n#[derive(Debug, thiserror::Error)]\npub enum AssetServeError {\n    /// The asset path could not be resolved.\n    #[error(\"Failed to resolve asset: {0}\")]\n    ResolveError(#[from] NativeAssetResolveError),\n\n    /// An error occurred while constructing the HTTP response.\n    #[error(\"Failed to construct response: {0}\")]\n    ResponseError(#[from] http::Error),\n}\n\n/// Try to resolve the path of an asset from a given URI path.\npub(crate) fn resolve_native_asset_path(path: &str) -> Result<PathBuf, AssetPathError> {\n    #[allow(clippy::unnecessary_lazy_evaluations)]\n    resolve_asset_path_from_filesystem(path).ok_or_else(|| {\n        #[cfg(target_os = \"android\")]\n        {\n            // If the asset exists in the Android asset manager, return the can't be represented as a path\n            // error instead\n            if to_java_load_asset(path).is_some() {\n                return AssetPathError::CannotRepresentAsPath;\n            }\n        }\n\n        AssetPathError::NotFound\n    })\n}\n\n/// Try to resolve the path of an asset from a given URI path.\nfn resolve_asset_path_from_filesystem(path: &str) -> Option<PathBuf> {\n    // If the user provided a custom asset handler, then call it and return the response if the request was handled.\n    // The path is the first part of the URI, so we need to trim the leading slash.\n    let mut uri_path = PathBuf::from(\n        percent_encoding::percent_decode_str(path)\n            .decode_utf8()\n            .expect(\"expected URL to be UTF-8 encoded\")\n            .as_ref(),\n    );\n\n    // If the asset doesn't exist, or starts with `/assets/`, then we'll try to serve out of the bundle\n    // This lets us handle both absolute and relative paths without being too \"special\"\n    // It just means that our macos bundle is a little \"special\" because we need to place an `assets`\n    // dir in the `Resources` dir.\n    //\n    // If there's no asset root, we use the cargo manifest dir as the root, or the current dir\n    if !uri_path.exists() || uri_path.starts_with(\"/assets/\") {\n        let bundle_root = get_asset_root();\n        let relative_path = uri_path.strip_prefix(\"/\").unwrap();\n        uri_path = bundle_root.join(relative_path);\n    }\n\n    // If the asset exists, return it\n    uri_path.exists().then_some(uri_path)\n}\n\nstruct ResolvedAsset {\n    mime_type: &'static str,\n    body: Vec<u8>,\n}\n\nimpl ResolvedAsset {\n    fn new(mime_type: &'static str, body: Vec<u8>) -> Self {\n        Self { mime_type, body }\n    }\n\n    fn into_response(self) -> Result<Response<Vec<u8>>, AssetServeError> {\n        Ok(Response::builder()\n            .header(\"Content-Type\", self.mime_type)\n            .header(\"Access-Control-Allow-Origin\", \"*\")\n            .body(self.body)?)\n    }\n}\n\n/// Read the bytes for an asset\npub(crate) fn resolve_native_asset(path: &str) -> Result<Vec<u8>, NativeAssetResolveError> {\n    // Attempt to serve from the asset dir on android using its loader\n    #[cfg(target_os = \"android\")]\n    {\n        if let Some(asset) = to_java_load_asset(path) {\n            return Ok(asset);\n        }\n    }\n\n    let Some(uri_path) = resolve_asset_path_from_filesystem(path) else {\n        return Err(NativeAssetResolveError::IoError(std::io::Error::new(\n            std::io::ErrorKind::NotFound,\n            \"Asset not found\",\n        )));\n    };\n    Ok(std::fs::read(uri_path)?)\n}\n\n/// Resolve the asset and its mime type\nfn resolve_asset(path: &str) -> Result<Option<ResolvedAsset>, NativeAssetResolveError> {\n    // Attempt to serve from the asset dir on android using its loader\n    #[cfg(target_os = \"android\")]\n    {\n        if let Some(asset) = to_java_load_asset(path) {\n            let extension = path.rsplit_once('.').and_then(|(_, ext)| Some(ext));\n            let mime_type = get_mime_from_ext(extension);\n            return Ok(Some(ResolvedAsset::new(mime_type, asset)));\n        }\n    }\n\n    let Some(uri_path) = resolve_asset_path_from_filesystem(path) else {\n        return Ok(None);\n    };\n    let mime_type = get_mime_from_path(&uri_path)?;\n    let body = std::fs::read(uri_path)?;\n    Ok(Some(ResolvedAsset::new(mime_type, body)))\n}\n\n/// Serve an asset from the filesystem or a custom asset handler.\n///\n/// This method properly accesses the asset directory based on the platform and serves the asset\n/// wrapped in an HTTP response.\n///\n/// Platform specifics:\n/// - On the web, this returns AssetServerError since there's no filesystem access. Use `fetch` instead.\n/// - On Android, it attempts to load assets using the Android AssetManager.\n/// - On other platforms, it serves assets from the filesystem.\npub fn serve_asset(path: &str) -> Result<Response<Vec<u8>>, AssetServeError> {\n    match resolve_asset(path)? {\n        Some(asset) => asset.into_response(),\n        None => Ok(Response::builder()\n            .status(StatusCode::NOT_FOUND)\n            .body(String::from(\"Not Found\").into_bytes())?),\n    }\n}\n\n/// Get the asset directory, following tauri/cargo-bundles directory discovery approach\n///\n/// Currently supports:\n/// - [x] macOS\n/// - [x] iOS\n/// - [x] Windows\n/// - [x] Linux (appimage)\n/// - [ ] Linux (rpm)\n/// - [x] Linux (deb)\n/// - [ ] Android\n#[allow(unreachable_code)]\nfn get_asset_root() -> PathBuf {\n    let cur_exe = std::env::current_exe().unwrap();\n\n    #[cfg(target_os = \"macos\")]\n    {\n        return cur_exe\n            .parent()\n            .unwrap()\n            .parent()\n            .unwrap()\n            .join(\"Resources\");\n    }\n\n    #[cfg(target_os = \"linux\")]\n    {\n        // In linux bundles, the assets are placed in the lib/$product_name directory\n        // bin/\n        //   main\n        // lib/\n        //   $product_name/\n        //     assets/\n        if let Some(product_name) = dioxus_cli_config::product_name() {\n            let lib_asset_path = || {\n                let path = cur_exe.parent()?.parent()?.join(\"lib\").join(product_name);\n                path.exists().then_some(path)\n            };\n            if let Some(asset_dir) = lib_asset_path() {\n                return asset_dir;\n            }\n        }\n    }\n\n    // For all others, the structure looks like this:\n    // app.(exe/appimage)\n    //   main.exe\n    //   assets/\n    cur_exe.parent().unwrap().to_path_buf()\n}\n\n/// Get the mime type from a path-like string\nfn get_mime_from_path(asset: &Path) -> std::io::Result<&'static str> {\n    if asset.extension().is_some_and(|ext| ext == \"svg\") {\n        return Ok(\"image/svg+xml\");\n    }\n\n    match infer::get_from_path(asset)?.map(|f| f.mime_type()) {\n        Some(f) if f != \"text/plain\" => Ok(f),\n        _other => Ok(get_mime_by_ext(asset)),\n    }\n}\n\n/// Get the mime type from a URI using its extension\nfn get_mime_by_ext(trimmed: &Path) -> &'static str {\n    let ext = trimmed.extension().as_ref().and_then(|ext| ext.to_str());\n    get_mime_from_ext(ext)\n}\n\n/// Get the mime type from a URI using its extension\npub fn get_mime_from_ext(ext: Option<&str>) -> &'static str {\n    match ext {\n        // The common assets are all utf-8 encoded\n        Some(\"js\") => \"text/javascript; charset=utf-8\",\n        Some(\"css\") => \"text/css; charset=utf-8\",\n        Some(\"json\") => \"application/json; charset=utf-8\",\n        Some(\"svg\") => \"image/svg+xml; charset=utf-8\",\n        Some(\"html\") => \"text/html; charset=utf-8\",\n\n        // the rest... idk? probably not\n        Some(\"mjs\") => \"text/javascript; charset=utf-8\",\n        Some(\"bin\") => \"application/octet-stream\",\n        Some(\"csv\") => \"text/csv\",\n        Some(\"ico\") => \"image/vnd.microsoft.icon\",\n        Some(\"jsonld\") => \"application/ld+json\",\n        Some(\"rtf\") => \"application/rtf\",\n        Some(\"mp4\") => \"video/mp4\",\n        // Assume HTML when a TLD is found for eg. `dioxus:://dioxuslabs.app` | `dioxus://hello.com`\n        Some(_) => \"text/html; charset=utf-8\",\n        // https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types\n        // using octet stream according to this:\n        None => \"application/octet-stream\",\n    }\n}\n\n#[cfg(target_os = \"android\")]\npub(crate) fn to_java_load_asset(filepath: &str) -> Option<Vec<u8>> {\n    let normalized = filepath\n        .trim_start_matches(\"/assets/\")\n        .trim_start_matches('/');\n\n    // in debug mode, the asset might be under `/data/local/tmp/dx/` - attempt to read it from there if it exists\n    #[cfg(debug_assertions)]\n    {\n        let path = dioxus_cli_config::android_session_cache_dir().join(normalized);\n        if path.exists() {\n            return std::fs::read(path).ok();\n        }\n    }\n\n    use std::ptr::NonNull;\n\n    let ctx = ndk_context::android_context();\n    let vm = unsafe { jni::JavaVM::from_raw(ctx.vm().cast()) }.unwrap();\n    let mut env = vm.attach_current_thread().unwrap();\n\n    // Query the Asset Manager\n    let asset_manager_ptr = env\n        .call_method(\n            unsafe { jni::objects::JObject::from_raw(ctx.context().cast()) },\n            \"getAssets\",\n            \"()Landroid/content/res/AssetManager;\",\n            &[],\n        )\n        .expect(\"Failed to get asset manager\")\n        .l()\n        .expect(\"Failed to get asset manager as object\");\n\n    unsafe {\n        let asset_manager =\n            ndk_sys::AAssetManager_fromJava(env.get_native_interface(), *asset_manager_ptr);\n\n        let asset_manager = ndk::asset::AssetManager::from_ptr(\n            NonNull::new(asset_manager).expect(\"Invalid asset manager\"),\n        );\n\n        let cstr = std::ffi::CString::new(normalized).unwrap();\n\n        let mut asset = asset_manager.open(&cstr)?;\n        Some(asset.buffer().unwrap().to_vec())\n    }\n}\n"
  },
  {
    "path": "packages/asset-resolver/src/web.rs",
    "content": "use js_sys::{\n    wasm_bindgen::{JsCast, JsValue},\n    ArrayBuffer, Uint8Array,\n};\nuse wasm_bindgen_futures::JsFuture;\nuse web_sys::{Request, Response};\n\nuse crate::WebAssetResolveError;\n\nimpl From<js_sys::Error> for WebAssetResolveError {\n    fn from(error: js_sys::Error) -> Self {\n        WebAssetResolveError { error }\n    }\n}\n\nimpl WebAssetResolveError {\n    fn from_js_value(value: JsValue) -> Self {\n        if let Some(error) = value.dyn_ref::<js_sys::Error>() {\n            WebAssetResolveError::from(error.clone())\n        } else {\n            unreachable!(\"Expected a js_sys::Error, got: {:?}\", value)\n        }\n    }\n}\n\npub(crate) async fn resolve_web_asset(path: &str) -> Result<Vec<u8>, WebAssetResolveError> {\n    let url = if path.starts_with(\"/\") {\n        path.to_string()\n    } else {\n        format!(\"/{path}\")\n    };\n\n    let request = Request::new_with_str(&url).map_err(WebAssetResolveError::from_js_value)?;\n\n    let window = web_sys::window().unwrap();\n    let response_promise = JsFuture::from(window.fetch_with_request(&request))\n        .await\n        .map_err(WebAssetResolveError::from_js_value)?;\n    let response = response_promise.unchecked_into::<Response>();\n\n    let array_buffer_promise = response\n        .array_buffer()\n        .map_err(WebAssetResolveError::from_js_value)?;\n    let array_buffer: ArrayBuffer = JsFuture::from(array_buffer_promise)\n        .await\n        .map_err(WebAssetResolveError::from_js_value)?\n        .unchecked_into();\n    let bytes = Uint8Array::new(&array_buffer);\n    Ok(bytes.to_vec())\n}\n"
  },
  {
    "path": "packages/autofmt/.vscode/settings.json",
    "content": "{\n    // dont let our extension kick on the rsx files - we're fixing it!\n    \"dioxus.formatOnSave\": \"disabled\",\n    // enable this when modifying tab-based indentation\n    // When inside .rsx files, dont automatically use spaces\n    // \"files.autoSave\": \"off\"\n}\n"
  },
  {
    "path": "packages/autofmt/Cargo.toml",
    "content": "[package]\nname = \"dioxus-autofmt\"\nversion = { workspace = true }\nedition = \"2021\"\nauthors = [\"Jonathan Kelley\"]\ndescription = \"Autofomatter for Dioxus RSX\"\nlicense = \"MIT OR Apache-2.0\"\nrepository = \"https://github.com/DioxusLabs/dioxus/\"\nhomepage = \"https://dioxuslabs.com\"\nkeywords = [\"web\", \"desktop\", \"mobile\", \"gui\", \"wasm\"]\n\n[dependencies]\ndioxus-rsx = { workspace = true }\nproc-macro2 = { workspace = true, features = [\"span-locations\"] }\nquote = { workspace = true }\nsyn = { workspace = true, features = [\n    \"full\",\n    \"extra-traits\",\n    \"visit\",\n    \"visit-mut\",\n] }\nserde = { workspace = true, features = [\"derive\"] }\nprettyplease = { workspace = true }\nregex = \"1.11.1\"\n\n[dev-dependencies]\npretty_assertions = { workspace = true }\n\n[package.metadata.docs.rs]\ncargo-args = [\"-Zunstable-options\", \"-Zrustdoc-scrape-examples\"]\n"
  },
  {
    "path": "packages/autofmt/README.md",
    "content": "# dioxus-autofmt\n\n[![Crates.io][crates-badge]][crates-url]\n[![MIT licensed][mit-badge]][mit-url]\n[![Build Status][actions-badge]][actions-url]\n[![Discord chat][discord-badge]][discord-url]\n\n[crates-badge]: https://img.shields.io/crates/v/dioxus-autofmt.svg\n[crates-url]: https://crates.io/crates/dioxus-autofmt\n[mit-badge]: https://img.shields.io/badge/license-MIT-blue.svg\n[mit-url]: https://github.com/dioxuslabs/dioxus/blob/main/LICENSE-MIT\n[actions-badge]: https://github.com/dioxuslabs/dioxus/actions/workflows/main.yml/badge.svg\n[actions-url]: https://github.com/dioxuslabs/dioxus/actions?query=workflow%3ACI+branch%3Amaster\n[discord-badge]: https://img.shields.io/discord/899851952891002890.svg?logo=discord&style=flat-square\n[discord-url]: https://discord.gg/XgGxMSkvUM\n\n[Website](https://dioxuslabs.com) |\n[Guides](https://dioxuslabs.com/learn/0.7/) |\n[API Docs](https://docs.rs/dioxus-autofmt/latest/dioxus_autofmt) |\n[Chat](https://discord.gg/XgGxMSkvUM)\n\n## Overview\n\n`dioxus-autofmt` provides a pretty printer for the `rsx` syntax tree.\n\nThis is done manually with a via set of formatting rules. The output is not guaranteed to be stable between minor versions of the crate as we might tweak the output.\n\n`dioxus-autofmt` provides an API to perform precision edits as well as just spit out a block of formatted RSX from any RSX syntax tree. This is used by the `rsx-rosetta` crate which can accept various input languages and output valid RSX.\n\n## Contributing\n\n- Report issues on our [issue tracker](https://github.com/dioxuslabs/dioxus/issues).\n- Join the discord and ask questions!\n\n## License\n\nThis project is licensed under the [MIT license].\n\n[mit license]: https://github.com/dioxuslabs/dioxus/blob/main/LICENSE-MIT\n\nUnless you explicitly state otherwise, any contribution intentionally submitted\nfor inclusion in Dioxus by you shall be licensed as MIT without any additional\nterms or conditions.\n"
  },
  {
    "path": "packages/autofmt/src/buffer.rs",
    "content": "//! The output buffer that supports some helpful methods\n//! These are separate from the input so we can lend references between the two\n\nuse std::fmt::{Result, Write};\n\nuse dioxus_rsx::IfmtInput;\n\nuse crate::indent::IndentOptions;\n\n/// The output buffer that tracks indent and string\n#[derive(Debug, Default)]\npub struct Buffer {\n    pub buf: String,\n    pub indent_level: usize,\n    pub indent: IndentOptions,\n}\n\nimpl Buffer {\n    // Create a new line and tab it to the current tab level\n    pub fn tabbed_line(&mut self) -> Result {\n        self.new_line()?;\n        self.tab()\n    }\n\n    // Create a new line and tab it to the current tab level\n    pub fn indented_tabbed_line(&mut self) -> Result {\n        self.new_line()?;\n        self.indented_tab()\n    }\n\n    pub fn tab(&mut self) -> Result {\n        self.write_tabs(self.indent_level)\n    }\n\n    pub fn indented_tab(&mut self) -> Result {\n        self.write_tabs(self.indent_level + 1)\n    }\n\n    pub fn write_tabs(&mut self, num: usize) -> std::fmt::Result {\n        for _ in 0..num {\n            write!(self.buf, \"{}\", self.indent.indent_str())?\n        }\n        Ok(())\n    }\n\n    pub fn new_line(&mut self) -> Result {\n        writeln!(self.buf)\n    }\n\n    pub fn write_text(&mut self, text: &IfmtInput) -> Result {\n        write!(self.buf, \"{}\", text.to_string_with_quotes())\n    }\n}\n\nimpl std::fmt::Write for Buffer {\n    fn write_str(&mut self, s: &str) -> std::fmt::Result {\n        self.buf.push_str(s);\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "packages/autofmt/src/collect_macros.rs",
    "content": "//! Collect macros from a file\n//!\n//! Returns all macros that match a pattern. You can use this information to autoformat them later\n\nuse proc_macro2::LineColumn;\nuse syn::{visit::Visit, File, Macro, Meta};\n\ntype CollectedMacro<'a> = &'a Macro;\n\npub fn collect_from_file(file: &File) -> Vec<CollectedMacro<'_>> {\n    let mut macros = vec![];\n    let mut collector = MacroCollector::new(&mut macros);\n    MacroCollector::visit_file(&mut collector, file);\n    macros\n}\n\nstruct MacroCollector<'a, 'b> {\n    macros: &'a mut Vec<CollectedMacro<'b>>,\n    skip_count: usize,\n}\n\nimpl<'a, 'b> MacroCollector<'a, 'b> {\n    fn new(macros: &'a mut Vec<CollectedMacro<'b>>) -> Self {\n        Self {\n            macros,\n            skip_count: 0,\n        }\n    }\n}\n\nimpl<'b> Visit<'b> for MacroCollector<'_, 'b> {\n    fn visit_macro(&mut self, i: &'b Macro) {\n        // Visit the regular stuff - this will also ensure paths/attributes are visited\n        syn::visit::visit_macro(self, i);\n\n        let name = &i.path.segments.last().map(|i| i.ident.to_string());\n        if let Some(\"rsx\" | \"render\") = name.as_deref() {\n            if self.skip_count == 0 {\n                self.macros.push(i)\n            }\n        }\n    }\n\n    // attributes can occur on stmts and items - we need to make sure the stack is reset when we exit\n    // this means we save the skipped length and set it back to its original length\n    fn visit_stmt(&mut self, i: &'b syn::Stmt) {\n        let skipped_len = self.skip_count;\n        syn::visit::visit_stmt(self, i);\n        self.skip_count = skipped_len;\n    }\n\n    fn visit_item(&mut self, i: &'b syn::Item) {\n        let skipped_len = self.skip_count;\n        syn::visit::visit_item(self, i);\n        self.skip_count = skipped_len;\n    }\n\n    fn visit_attribute(&mut self, i: &'b syn::Attribute) {\n        // we need to communicate that this stmt is skipped up the tree\n        if attr_is_rustfmt_skip(i) {\n            self.skip_count += 1;\n        }\n\n        syn::visit::visit_attribute(self, i);\n    }\n}\n\npub fn byte_offset(input: &str, location: LineColumn) -> usize {\n    let mut offset = 0;\n    for _ in 1..location.line {\n        offset += input[offset..].find('\\n').unwrap() + 1;\n    }\n    offset\n        + input[offset..]\n            .chars()\n            .take(location.column)\n            .map(char::len_utf8)\n            .sum::<usize>()\n}\n\n/// Check if an attribute is a rustfmt skip attribute\nfn attr_is_rustfmt_skip(i: &syn::Attribute) -> bool {\n    match &i.meta {\n        Meta::Path(path) => {\n            path.segments.len() == 2\n                && matches!(i.style, syn::AttrStyle::Outer)\n                && path.segments[0].ident == \"rustfmt\"\n                && path.segments[1].ident == \"skip\"\n        }\n        _ => false,\n    }\n}\n\n#[test]\nfn parses_file_and_collects_rsx_macros() {\n    let contents = include_str!(\"../tests/samples/long.rsx\");\n    let parsed = syn::parse_file(contents).expect(\"parse file okay\");\n    let macros = collect_from_file(&parsed);\n    assert_eq!(macros.len(), 3);\n}\n\n/// Ensure that we only collect non-skipped macros\n#[test]\nfn dont_collect_skipped_macros() {\n    let contents = include_str!(\"../tests/samples/skip.rsx\");\n    let parsed = syn::parse_file(contents).expect(\"parse file okay\");\n    let macros = collect_from_file(&parsed);\n    assert_eq!(macros.len(), 2);\n}\n"
  },
  {
    "path": "packages/autofmt/src/indent.rs",
    "content": "#[derive(Clone, Copy, PartialEq, Eq, Debug)]\npub enum IndentType {\n    Spaces,\n    Tabs,\n}\n\n#[derive(Debug, Clone)]\npub struct IndentOptions {\n    width: usize,\n    indent_string: String,\n    split_line_attributes: bool,\n}\n\nimpl IndentOptions {\n    pub fn new(ty: IndentType, width: usize, split_line_attributes: bool) -> Self {\n        assert_ne!(width, 0, \"Cannot have an indent width of 0\");\n        Self {\n            width,\n            indent_string: match ty {\n                IndentType::Tabs => \"\\t\".into(),\n                IndentType::Spaces => \" \".repeat(width),\n            },\n            split_line_attributes,\n        }\n    }\n\n    /// Gets a string containing one indent worth of whitespace\n    pub fn indent_str(&self) -> &str {\n        &self.indent_string\n    }\n\n    /// Computes the line length in characters, counting tabs as the indent width.\n    pub fn line_length(&self, line: &str) -> usize {\n        line.chars()\n            .map(|ch| if ch == '\\t' { self.width } else { 1 })\n            .sum()\n    }\n\n    /// Estimates how many times the line has been indented.\n    pub fn count_indents(&self, mut line: &str) -> usize {\n        let mut indent = 0;\n        while !line.is_empty() {\n            // Try to count tabs\n            let num_tabs = line.chars().take_while(|ch| *ch == '\\t').count();\n            if num_tabs > 0 {\n                indent += num_tabs;\n                line = &line[num_tabs..];\n                continue;\n            }\n\n            // Try to count spaces\n            let num_spaces = line.chars().take_while(|ch| *ch == ' ').count();\n            if num_spaces >= self.width {\n                // Intentionally floor here to take only the amount of space that matches an indent\n                let num_space_indents = num_spaces / self.width;\n                indent += num_space_indents;\n                line = &line[num_space_indents * self.width..];\n                continue;\n            }\n\n            // Line starts with either non-indent characters or an unevent amount of spaces,\n            // so no more indent remains.\n            break;\n        }\n        indent\n    }\n\n    pub fn split_line_attributes(&self) -> bool {\n        self.split_line_attributes\n    }\n}\n\nimpl Default for IndentOptions {\n    fn default() -> Self {\n        Self::new(IndentType::Spaces, 4, false)\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn count_indents() {\n        assert_eq!(\n            IndentOptions::new(IndentType::Spaces, 4, false).count_indents(\"no indentation here!\"),\n            0\n        );\n        assert_eq!(\n            IndentOptions::new(IndentType::Spaces, 4, false).count_indents(\"    v += 2\"),\n            1\n        );\n        assert_eq!(\n            IndentOptions::new(IndentType::Spaces, 4, false).count_indents(\"        v += 2\"),\n            2\n        );\n        assert_eq!(\n            IndentOptions::new(IndentType::Spaces, 4, false).count_indents(\"          v += 2\"),\n            2\n        );\n        assert_eq!(\n            IndentOptions::new(IndentType::Spaces, 4, false).count_indents(\"\\t\\tv += 2\"),\n            2\n        );\n        assert_eq!(\n            IndentOptions::new(IndentType::Spaces, 4, false).count_indents(\"\\t\\t  v += 2\"),\n            2\n        );\n        assert_eq!(\n            IndentOptions::new(IndentType::Spaces, 2, false).count_indents(\"    v += 2\"),\n            2\n        );\n    }\n}\n"
  },
  {
    "path": "packages/autofmt/src/lib.rs",
    "content": "#![doc = include_str!(\"../README.md\")]\n#![doc(html_logo_url = \"https://avatars.githubusercontent.com/u/79236386\")]\n#![doc(html_favicon_url = \"https://avatars.githubusercontent.com/u/79236386\")]\n\nuse crate::writer::*;\nuse dioxus_rsx::{BodyNode, CallBody};\nuse proc_macro2::{LineColumn, Span};\nuse syn::parse::Parser;\n\nmod buffer;\nmod collect_macros;\nmod indent;\nmod prettier_please;\nmod writer;\n\npub use indent::{IndentOptions, IndentType};\n\n/// A modification to the original file to be applied by an IDE\n///\n/// Right now this re-writes entire rsx! blocks at a time, instead of precise line-by-line changes.\n///\n/// In a \"perfect\" world we would have tiny edits to preserve things like cursor states and selections. The API here makes\n/// it possible to migrate to a more precise modification approach in the future without breaking existing code.\n///\n/// Note that this is tailored to VSCode's TextEdit API and not a general Diff API. Line numbers are not accurate if\n/// multiple edits are applied in a single file without tracking text shifts.\n#[derive(serde::Deserialize, serde::Serialize, Clone, Debug, PartialEq, Eq, Hash)]\npub struct FormattedBlock {\n    /// The new contents of the block\n    pub formatted: String,\n\n    /// The line number of the first line of the block.\n    pub start: usize,\n\n    /// The end of the block, exclusive.\n    pub end: usize,\n}\n\n/// Format a file into a list of `FormattedBlock`s to be applied by an IDE for autoformatting.\n///\n/// It accepts\n#[deprecated(note = \"Use try_fmt_file instead - this function panics on error.\")]\npub fn fmt_file(contents: &str, indent: IndentOptions) -> Vec<FormattedBlock> {\n    let parsed =\n        syn::parse_file(contents).expect(\"fmt_file should only be called on valid syn::File files\");\n    try_fmt_file(contents, &parsed, indent).expect(\"Failed to format file\")\n}\n\n/// Format a file into a list of `FormattedBlock`s to be applied by an IDE for autoformatting.\n///\n/// This function expects a complete file, not just a block of code. To format individual rsx! blocks, use fmt_block instead.\n///\n/// The point here is to provide precise modifications of a source file so an accompanying IDE tool can map these changes\n/// back to the file precisely.\n///\n/// Nested blocks of RSX will be handled automatically\n///\n/// This returns an error if the rsx itself is invalid.\n///\n/// Will early return if any of the expressions are not complete. Even though we *could* return the\n/// expressions, eventually we'll want to pass off expression formatting to rustfmt which will reject\n/// those.\npub fn try_fmt_file(\n    contents: &str,\n    parsed: &syn::File,\n    indent: IndentOptions,\n) -> syn::Result<Vec<FormattedBlock>> {\n    let mut formatted_blocks = Vec::new();\n\n    let macros = collect_macros::collect_from_file(parsed);\n\n    // No macros, no work to do\n    if macros.is_empty() {\n        return Ok(formatted_blocks);\n    }\n\n    let mut writer = Writer::new(contents, indent);\n\n    // Don't parse nested macros\n    let mut end_span = LineColumn { column: 0, line: 0 };\n    for item in macros {\n        let macro_path = &item.path.segments[0].ident;\n\n        // this macro is inside the last macro we parsed, skip it\n        if macro_path.span().start() < end_span {\n            continue;\n        }\n\n        let body = item.parse_body_with(CallBody::parse_strict)?;\n\n        let rsx_start = macro_path.span().start();\n\n        writer.out.indent_level = writer\n            .out\n            .indent\n            .count_indents(writer.src.get(rsx_start.line - 1).unwrap_or(&\"\"));\n\n        // TESTME\n        // Writing *should* not fail but it's possible that it does\n        if writer.write_rsx_call(&body).is_err() {\n            let span = writer.invalid_exprs.pop().unwrap_or_else(Span::call_site);\n            return Err(syn::Error::new(span, \"Failed emit valid rsx - likely due to partially complete expressions in the rsx! macro\"));\n        }\n\n        // writing idents leaves the final line ended at the end of the last ident\n        if writer.out.buf.contains('\\n') {\n            _ = writer.out.new_line();\n            _ = writer.out.tab();\n        }\n\n        let span = item.delimiter.span().join();\n        let mut formatted = writer.out.buf.split_off(0);\n\n        let start = collect_macros::byte_offset(contents, span.start()) + 1;\n        let end = collect_macros::byte_offset(contents, span.end()) - 1;\n\n        // Rustfmt will remove the space between the macro and the opening paren if the macro is a single expression\n        let body_is_solo_expr = body.body.roots.len() == 1\n            && matches!(body.body.roots[0], BodyNode::RawExpr(_) | BodyNode::Text(_));\n\n        // If it's short, and it's not a single expression, and it's not empty, then we can collapse it\n        if formatted.len() <= 80\n            && !formatted.contains('\\n')\n            && !body_is_solo_expr\n            && !formatted.trim().is_empty()\n        {\n            formatted = format!(\" {formatted} \");\n        }\n\n        end_span = span.end();\n\n        if contents[start..end] == formatted {\n            continue;\n        }\n\n        formatted_blocks.push(FormattedBlock {\n            formatted,\n            start,\n            end,\n        });\n    }\n\n    Ok(formatted_blocks)\n}\n\n/// Write a Callbody (the rsx block) to a string\n///\n/// If the tokens can't be formatted, this returns None. This is usually due to an incomplete expression\n/// that passed partial expansion but failed to parse.\npub fn write_block_out(body: &CallBody) -> Option<String> {\n    let mut buf = Writer::new(\"\", IndentOptions::default());\n    buf.write_rsx_call(body).ok()?;\n    buf.consume()\n}\n\npub fn fmt_block(block: &str, indent_level: usize, indent: IndentOptions) -> Option<String> {\n    let body = CallBody::parse_strict.parse_str(block).unwrap();\n\n    let mut buf = Writer::new(block, indent);\n    buf.out.indent_level = indent_level;\n    buf.write_rsx_call(&body).ok()?;\n\n    // writing idents leaves the final line ended at the end of the last ident\n    if buf.out.buf.contains('\\n') {\n        buf.out.new_line().unwrap();\n    }\n\n    buf.consume()\n}\n\n// Apply all the blocks\npub fn apply_formats(input: &str, blocks: Vec<FormattedBlock>) -> String {\n    let mut out = String::new();\n\n    let mut last = 0;\n\n    for FormattedBlock {\n        formatted,\n        start,\n        end,\n    } in blocks\n    {\n        let prefix = &input[last..start];\n        out.push_str(prefix);\n        out.push_str(&formatted);\n        last = end;\n    }\n\n    let suffix = &input[last..];\n    out.push_str(suffix);\n\n    out\n}\n"
  },
  {
    "path": "packages/autofmt/src/prettier_please.rs",
    "content": "use dioxus_rsx::CallBody;\nuse syn::{parse::Parser, visit_mut::VisitMut, Expr, File, Item, MacroDelimiter};\n\nuse crate::{IndentOptions, Writer};\n\nimpl Writer<'_> {\n    pub fn unparse_expr(&mut self, expr: &Expr) -> String {\n        unparse_expr(expr, self.raw_src, &self.out.indent)\n    }\n}\n\n// we use weird unicode alternatives to avoid conflicts with the actual rsx! macro\nconst MARKER: &str = \"𝕣𝕤𝕩\";\nconst MARKER_REPLACE: &str = \"𝕣𝕤𝕩! {}\";\n\npub fn unparse_expr(expr: &Expr, src: &str, cfg: &IndentOptions) -> String {\n    struct ReplaceMacros<'a> {\n        src: &'a str,\n        formatted_stack: Vec<String>,\n        cfg: &'a IndentOptions,\n    }\n\n    impl VisitMut for ReplaceMacros<'_> {\n        fn visit_macro_mut(&mut self, i: &mut syn::Macro) {\n            // replace the macro with a block that roughly matches the macro\n            if let Some(\"rsx\" | \"render\") = i\n                .path\n                .segments\n                .last()\n                .map(|i| i.ident.to_string())\n                .as_deref()\n            {\n                // format the macro in place\n                // we'll use information about the macro to replace it with another formatted block\n                // once we've written out the unparsed expr from prettyplease, we can replace\n                // this dummy block with the actual formatted block\n                let body = CallBody::parse_strict.parse2(i.tokens.clone()).unwrap();\n                let multiline = !Writer::is_short_rsx_call(&body.body.roots);\n                let mut formatted = {\n                    let mut writer = Writer::new(self.src, self.cfg.clone());\n                    _ = writer.write_body_nodes(&body.body.roots).ok();\n                    writer.consume()\n                }\n                .unwrap();\n\n                i.path = syn::parse_str(MARKER).unwrap();\n                i.tokens = Default::default();\n\n                // make sure to transform the delimiter to a brace so the marker can be found\n                // an alternative approach would be to use multiple different markers that are not\n                // sensitive to the delimiter.\n                i.delimiter = MacroDelimiter::Brace(Default::default());\n\n                // Push out the indent level of the formatted block if it's multiline\n                if multiline || formatted.contains('\\n') {\n                    formatted = formatted\n                        .lines()\n                        .map(|line| {\n                            // Don't add indentation to blank lines (avoid trailing whitespace)\n                            if line.is_empty() {\n                                String::new()\n                            } else {\n                                format!(\"{}{line}\", self.cfg.indent_str())\n                            }\n                        })\n                        .collect::<Vec<_>>()\n                        .join(\"\\n\");\n                }\n\n                // Save this formatted block for later, when we apply it to the original expr\n                self.formatted_stack.push(formatted)\n            }\n\n            syn::visit_mut::visit_macro_mut(self, i);\n        }\n    }\n\n    // Visit the expr and replace the macros with formatted blocks\n    let mut replacer = ReplaceMacros {\n        src,\n        cfg,\n        formatted_stack: vec![],\n    };\n\n    // builds the expression stack\n    let mut modified_expr = expr.clone();\n    replacer.visit_expr_mut(&mut modified_expr);\n\n    // now unparsed with the modified expression\n    let mut unparsed = unparse_inner(&modified_expr);\n\n    // now we can replace the macros with the formatted blocks\n    for fmted in replacer.formatted_stack.drain(..) {\n        let is_multiline = fmted.ends_with('}') || fmted.contains('\\n');\n        let is_empty = fmted.trim().is_empty();\n\n        let mut out_fmt = String::from(\"rsx! {\");\n        if is_multiline {\n            out_fmt.push('\\n');\n        } else if !is_empty {\n            out_fmt.push(' ');\n        }\n\n        let mut whitespace = 0;\n\n        for line in unparsed.lines() {\n            if line.contains(MARKER) {\n                whitespace = line.matches(cfg.indent_str()).count();\n                break;\n            }\n        }\n\n        let mut lines = fmted.lines().enumerate().peekable();\n\n        while let Some((_idx, fmt_line)) = lines.next() {\n            // Push the indentation (but not for blank lines - avoid trailing whitespace)\n            if is_multiline && !fmt_line.is_empty() {\n                out_fmt.push_str(&cfg.indent_str().repeat(whitespace));\n            }\n\n            // Calculate delta between indentations - the block indentation is too much\n            out_fmt.push_str(fmt_line);\n\n            // Push a newline if there's another line\n            if lines.peek().is_some() {\n                out_fmt.push('\\n');\n            }\n        }\n\n        if is_multiline {\n            out_fmt.push('\\n');\n            out_fmt.push_str(&cfg.indent_str().repeat(whitespace));\n        } else if !is_empty {\n            out_fmt.push(' ');\n        }\n\n        // Replace the dioxus_autofmt_block__________ token with the formatted block\n        out_fmt.push('}');\n\n        unparsed = unparsed.replacen(MARKER_REPLACE, &out_fmt, 1);\n        continue;\n    }\n\n    // stylistic choice to trim whitespace around the expr\n    if unparsed.starts_with(\"{ \") && unparsed.ends_with(\" }\") {\n        let mut out_fmt = String::new();\n        out_fmt.push('{');\n        out_fmt.push_str(&unparsed[2..unparsed.len() - 2]);\n        out_fmt.push('}');\n        out_fmt\n    } else {\n        unparsed\n    }\n}\n\n/// Unparse an expression back into a string\n///\n/// This creates a new temporary file, parses the expression into it, and then formats the file.\n/// This is a bit of a hack, but dtonlay doesn't want to support this very simple usecase, forcing us to clone the expr\npub fn unparse_inner(expr: &Expr) -> String {\n    let file = wrapped(expr);\n    let wrapped = prettyplease::unparse(&file);\n    unwrapped(wrapped)\n}\n\n// Split off the fn main and then cut the tabs off the front\nfn unwrapped(raw: String) -> String {\n    let mut o = raw\n        .strip_prefix(\"fn main() {\\n\")\n        .unwrap()\n        .strip_suffix(\"}\\n\")\n        .unwrap()\n        .lines()\n        .map(|line| line.strip_prefix(\"    \").unwrap_or_default()) // todo: set this to tab level\n        .collect::<Vec<_>>()\n        .join(\"\\n\");\n\n    // remove the semicolon\n    if o.ends_with(';') {\n        o.pop();\n    }\n\n    o\n}\n\nfn wrapped(expr: &Expr) -> File {\n    File {\n        shebang: None,\n        attrs: vec![],\n        items: vec![\n            //\n            Item::Verbatim(quote::quote! {\n                fn main() {\n                    #expr;\n                }\n            }),\n        ],\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use proc_macro2::TokenStream;\n\n    fn fmt_block_from_expr(raw: &str, tokens: TokenStream, cfg: IndentOptions) -> Option<String> {\n        let body = CallBody::parse_strict.parse2(tokens).unwrap();\n        let mut writer = Writer::new(raw, cfg);\n        writer.write_body_nodes(&body.body.roots).ok()?;\n        writer.consume()\n    }\n\n    #[test]\n    fn unparses_raw() {\n        let expr = syn::parse_str(\"1 + 1\").expect(\"Failed to parse\");\n        let unparsed = prettyplease::unparse(&wrapped(&expr));\n        assert_eq!(unparsed, \"fn main() {\\n    1 + 1;\\n}\\n\");\n    }\n\n    #[test]\n    fn weird_ifcase() {\n        let contents = r##\"\n        fn main() {\n            move |_| timer.with_mut(|t| if t.started_at.is_none() { Some(Instant::now()) } else { None })\n        }\n    \"##;\n\n        let expr: File = syn::parse_file(contents).unwrap();\n        let out = prettyplease::unparse(&expr);\n        println!(\"{}\", out);\n    }\n\n    #[test]\n    fn multiline_madness() {\n        let contents = r##\"\n        {\n        {children.is_some().then(|| rsx! {\n            span {\n                class: \"inline-block ml-auto hover:bg-gray-500\",\n                onclick: move |evt| {\n                    evt.cancel_bubble();\n                },\n                icons::icon_5 {}\n                {rsx! {\n                    icons::icon_6 {}\n                }}\n            }\n        })}\n        {children.is_some().then(|| rsx! {\n            span {\n                class: \"inline-block ml-auto hover:bg-gray-500\",\n                onclick: move |evt| {\n                    evt.cancel_bubble();\n                },\n                icons::icon_10 {}\n            }\n        })}\n\n        }\n\n        \"##;\n\n        let expr: Expr = syn::parse_str(contents).unwrap();\n        let out = unparse_expr(&expr, contents, &IndentOptions::default());\n        println!(\"{}\", out);\n    }\n\n    #[test]\n    fn write_body_no_indent() {\n        let src = r##\"\n            span {\n                class: \"inline-block ml-auto hover:bg-gray-500\",\n                onclick: move |evt| {\n                    evt.cancel_bubble();\n                },\n                icons::icon_10 {}\n                icons::icon_10 {}\n                icons::icon_10 {}\n                icons::icon_10 {}\n                div { \"hi\" }\n                div { div {} }\n                div { div {} div {} div {} }\n                {children}\n                {\n                    some_big_long()\n                        .some_big_long()\n                        .some_big_long()\n                        .some_big_long()\n                        .some_big_long()\n                        .some_big_long()\n                }\n                div { class: \"px-4\", {is_current.then(|| rsx! { {children} })} }\n                Thing {\n                    field: rsx! {\n                        div { \"hi\" }\n                        Component {\n                            onrender: rsx! {\n                                div { \"hi\" }\n                                Component {\n                                    onclick: move |_| {\n                                        another_macro! {\n                                            div { class: \"max-w-lg lg:max-w-2xl mx-auto mb-16 text-center\",\n                                                \"gomg\"\n                                                \"hi!!\"\n                                                \"womh\"\n                                            }\n                                        };\n                                        rsx! {\n                                            div { class: \"max-w-lg lg:max-w-2xl mx-auto mb-16 text-center\",\n                                                \"gomg\"\n                                                \"hi!!\"\n                                                \"womh\"\n                                            }\n                                        };\n                                        println!(\"hi\")\n                                    },\n                                    onrender: move |_| {\n                                        let _ = 12;\n                                        let r = rsx! {\n                                            div { \"hi\" }\n                                        };\n                                        rsx! {\n                                            div { \"hi\" }\n                                        }\n                                    }\n                                }\n                                {\n                                    rsx! {\n                                        BarChart {\n                                            id: \"bar-plot\".to_string(),\n                                            x: value,\n                                            y: label\n                                        }\n                                    }\n                                }\n                            }\n                        }\n                    }\n                }\n            }\n        \"##;\n\n        let tokens: TokenStream = syn::parse_str(src).unwrap();\n        let out = fmt_block_from_expr(src, tokens, IndentOptions::default()).unwrap();\n        println!(\"{}\", out);\n    }\n\n    #[test]\n    fn write_component_body() {\n        let src = r##\"\n    div { class: \"px-4\", {is_current.then(|| rsx! { {children} })} }\n    \"##;\n\n        let tokens: TokenStream = syn::parse_str(src).unwrap();\n        let out = fmt_block_from_expr(src, tokens, IndentOptions::default()).unwrap();\n        println!(\"{}\", out);\n    }\n\n    #[test]\n    fn weird_macro() {\n        let contents = r##\"\n        fn main() {\n            move |_| {\n                drop_macro_semi! {\n                    \"something_very_long_something_very_long_something_very_long_something_very_long\"\n                };\n                let _ = drop_macro_semi! {\n                    \"something_very_long_something_very_long_something_very_long_something_very_long\"\n                };\n                drop_macro_semi! {\n                    \"something_very_long_something_very_long_something_very_long_something_very_long\"\n                };\n            };\n        }\n    \"##;\n\n        let expr: File = syn::parse_file(contents).unwrap();\n        let out = prettyplease::unparse(&expr);\n        println!(\"{}\", out);\n    }\n\n    #[test]\n    fn comments_on_nodes() {\n        let src = r##\"// hiasdasds\n    div {\n        attr: \"value\", // comment\n        div {}\n        \"hi\" // hello!\n        \"hi\" // hello!\n        \"hi\" // hello!\n        // hi!\n    }\n    \"##;\n\n        let tokens: TokenStream = syn::parse_str(src).unwrap();\n        let out = fmt_block_from_expr(src, tokens, IndentOptions::default()).unwrap();\n        println!(\"{}\", out);\n    }\n}\n"
  },
  {
    "path": "packages/autofmt/src/writer.rs",
    "content": "use crate::{buffer::Buffer, IndentOptions};\nuse dioxus_rsx::*;\nuse proc_macro2::{LineColumn, Span};\nuse quote::ToTokens;\nuse regex::Regex;\nuse std::{\n    borrow::Cow,\n    collections::{HashMap, HashSet, VecDeque},\n    fmt::{Result, Write},\n};\nuse syn::{spanned::Spanned, token::Brace, Expr};\n\n#[derive(Debug)]\npub struct Writer<'a> {\n    pub raw_src: &'a str,\n    pub src: Vec<&'a str>,\n    pub cached_formats: HashMap<LineColumn, String>,\n    pub out: Buffer,\n    pub invalid_exprs: Vec<Span>,\n}\n\nimpl<'a> Writer<'a> {\n    pub fn new(raw_src: &'a str, indent: IndentOptions) -> Self {\n        Self {\n            src: raw_src.lines().collect(),\n            raw_src,\n            out: Buffer {\n                indent,\n                ..Default::default()\n            },\n            cached_formats: HashMap::new(),\n            invalid_exprs: Vec::new(),\n        }\n    }\n\n    pub fn consume(self) -> Option<String> {\n        Some(self.out.buf)\n    }\n\n    pub fn write_rsx_call(&mut self, body: &CallBody) -> Result {\n        if body.body.roots.is_empty() {\n            return Ok(());\n        }\n\n        if Self::is_short_rsx_call(&body.body.roots) {\n            write!(self.out, \" \")?;\n            self.write_ident(&body.body.roots[0])?;\n            write!(self.out, \" \")?;\n        } else {\n            self.out.new_line()?;\n            self.write_body_indented(&body.body.roots)?;\n            self.write_trailing_body_comments(body)?;\n        }\n\n        Ok(())\n    }\n\n    fn write_trailing_body_comments(&mut self, body: &CallBody) -> Result {\n        if let Some(span) = body.span {\n            self.out.indent_level += 1;\n            let comments = self.accumulate_full_line_comments(span.span().end());\n            if !comments.is_empty() {\n                self.out.new_line()?;\n                self.apply_line_comments(comments)?;\n                self.out.buf.pop(); // remove the trailing newline, forcing us to end at the end of the comment\n            }\n            self.out.indent_level -= 1;\n        }\n        Ok(())\n    }\n\n    // Expects to be written directly into place\n    pub fn write_ident(&mut self, node: &BodyNode) -> Result {\n        match node {\n            BodyNode::Element(el) => self.write_element(el),\n            BodyNode::Component(component) => self.write_component(component),\n            BodyNode::Text(text) => self.write_text_node(text),\n            BodyNode::RawExpr(expr) => self.write_expr_node(expr),\n            BodyNode::ForLoop(forloop) => self.write_for_loop(forloop),\n            BodyNode::IfChain(ifchain) => self.write_if_chain(ifchain),\n        }?;\n\n        let span = Self::final_span_of_node(node);\n\n        self.write_inline_comments(span.end(), 0)?;\n\n        Ok(())\n    }\n\n    /// Check if the rsx call is short enough to be inlined\n    pub(crate) fn is_short_rsx_call(roots: &[BodyNode]) -> bool {\n        // eventually I want to use the _text length, so shutup now\n        #[allow(clippy::match_like_matches_macro)]\n        match roots {\n            [] => true,\n            [BodyNode::Text(_text)] => true,\n            _ => false,\n        }\n    }\n\n    fn write_element(&mut self, el: &Element) -> Result {\n        let Element {\n            name,\n            raw_attributes: attributes,\n            children,\n            spreads,\n            brace,\n            ..\n        } = el;\n\n        write!(self.out, \"{name} \")?;\n        self.write_rsx_block(attributes, spreads, children, &brace.unwrap_or_default())?;\n\n        Ok(())\n    }\n\n    fn write_component(\n        &mut self,\n        Component {\n            name,\n            fields,\n            children,\n            generics,\n            spreads,\n            brace,\n            ..\n        }: &Component,\n    ) -> Result {\n        // Write the path by to_tokensing it and then removing all whitespace\n        let mut name = name.to_token_stream().to_string();\n        name.retain(|c| !c.is_whitespace());\n        write!(self.out, \"{name}\")?;\n\n        // Same idea with generics, write those via the to_tokens method and then remove all whitespace\n        if let Some(generics) = generics {\n            let mut written = generics.to_token_stream().to_string();\n            written.retain(|c| !c.is_whitespace());\n            write!(self.out, \"{written}\")?;\n        }\n\n        write!(self.out, \" \")?;\n        self.write_rsx_block(fields, spreads, &children.roots, &brace.unwrap_or_default())?;\n\n        Ok(())\n    }\n\n    fn write_text_node(&mut self, text: &TextNode) -> Result {\n        self.out.write_text(&text.input)\n    }\n\n    fn write_expr_node(&mut self, expr: &ExprNode) -> Result {\n        self.write_partial_expr(expr.expr.as_expr(), expr.span())\n    }\n\n    fn write_for_loop(&mut self, forloop: &ForLoop) -> std::fmt::Result {\n        write!(\n            self.out,\n            \"for {} in \",\n            forloop.pat.clone().into_token_stream(),\n        )?;\n\n        self.write_inline_expr(&forloop.expr)?;\n\n        if forloop.body.is_empty() {\n            write!(self.out, \"}}\")?;\n            return Ok(());\n        }\n\n        self.out.new_line()?;\n        self.write_body_indented(&forloop.body.roots)?;\n\n        self.out.tabbed_line()?;\n        write!(self.out, \"}}\")?;\n\n        Ok(())\n    }\n\n    fn write_if_chain(&mut self, ifchain: &IfChain) -> std::fmt::Result {\n        // Recurse in place by setting the next chain\n        let mut branch = Some(ifchain);\n\n        while let Some(chain) = branch {\n            let IfChain {\n                if_token,\n                cond,\n                then_branch,\n                else_if_branch,\n                else_branch,\n                ..\n            } = chain;\n\n            write!(self.out, \"{} \", if_token.to_token_stream(),)?;\n\n            self.write_inline_expr(cond)?;\n\n            self.out.new_line()?;\n            self.write_body_indented(&then_branch.roots)?;\n\n            if let Some(else_if_branch) = else_if_branch {\n                // write the closing bracket and else\n                self.out.tabbed_line()?;\n                write!(self.out, \"}} else \")?;\n\n                branch = Some(else_if_branch);\n            } else if let Some(else_branch) = else_branch {\n                self.out.tabbed_line()?;\n                write!(self.out, \"}} else {{\")?;\n\n                self.out.new_line()?;\n                self.write_body_indented(&else_branch.roots)?;\n                branch = None;\n            } else {\n                branch = None;\n            }\n        }\n\n        self.out.tabbed_line()?;\n        write!(self.out, \"}}\")?;\n\n        Ok(())\n    }\n\n    /// An expression within a for or if block that might need to be spread out across several lines\n    fn write_inline_expr(&mut self, expr: &Expr) -> std::fmt::Result {\n        let unparsed = self.unparse_expr(expr);\n        let mut lines = unparsed.lines();\n        let first_line = lines.next().ok_or(std::fmt::Error)?;\n\n        write!(self.out, \"{first_line}\")?;\n\n        let mut was_multiline = false;\n\n        for line in lines {\n            was_multiline = true;\n            self.out.tabbed_line()?;\n            write!(self.out, \"{line}\")?;\n        }\n\n        if was_multiline {\n            self.out.tabbed_line()?;\n            write!(self.out, \"{{\")?;\n        } else {\n            write!(self.out, \" {{\")?;\n        }\n\n        Ok(())\n    }\n\n    // Push out the indent level and write each component, line by line\n    fn write_body_indented(&mut self, children: &[BodyNode]) -> Result {\n        self.out.indent_level += 1;\n        self.write_body_nodes(children)?;\n        self.out.indent_level -= 1;\n        Ok(())\n    }\n\n    pub fn write_body_nodes(&mut self, children: &[BodyNode]) -> Result {\n        let mut iter = children.iter().peekable();\n\n        while let Some(child) = iter.next() {\n            if self.current_span_is_primary(child.span().start()) {\n                self.write_comments(child.span().start())?;\n            };\n            self.out.tab()?;\n            self.write_ident(child)?;\n            if iter.peek().is_some() {\n                self.out.new_line()?;\n            }\n        }\n\n        Ok(())\n    }\n\n    /// Basically elements and components are the same thing\n    ///\n    /// This writes the contents out for both in one function, centralizing the annoying logic like\n    /// key handling, breaks, closures, etc\n    fn write_rsx_block(\n        &mut self,\n        attributes: &[Attribute],\n        spreads: &[Spread],\n        children: &[BodyNode],\n        brace: &Brace,\n    ) -> Result {\n        #[derive(Debug)]\n        enum ShortOptimization {\n            /// Special because we want to print the closing bracket immediately\n            ///\n            /// IE\n            /// `div {}` instead of `div { }`\n            Empty,\n\n            /// Special optimization to put everything on the same line and add some buffer spaces\n            ///\n            /// IE\n            ///\n            /// `div { \"asdasd\" }` instead of a multiline variant\n            Oneliner,\n\n            /// Optimization where children flow but props remain fixed on top\n            PropsOnTop,\n\n            /// The noisiest optimization where everything flows\n            NoOpt,\n        }\n\n        // Write the opening brace\n        write!(self.out, \"{{\")?;\n\n        // decide if we have any special optimizations\n        // Default with none, opt the cases in one-by-one\n        let mut opt_level = ShortOptimization::NoOpt;\n\n        // check if we have a lot of attributes\n        let attr_len = self.is_short_attrs(brace, attributes, spreads);\n        let has_postbrace_comments = self.brace_has_trailing_comments(brace);\n        let is_short_attr_list =\n            ((attr_len + self.out.indent_level * 4) < 80) && !has_postbrace_comments;\n        let children_len = self\n            .is_short_children(children)\n            .map_err(|_| std::fmt::Error)?;\n        let has_trailing_comments = self.has_trailing_comments(children, brace);\n        let is_small_children = children_len.is_some() && !has_trailing_comments;\n\n        // if we have one long attribute and a lot of children, place the attrs on top\n        if is_short_attr_list && !is_small_children {\n            opt_level = ShortOptimization::PropsOnTop;\n        }\n\n        // even if the attr is long, it should be put on one line\n        // However if we have childrne we need to just spread them out for readability\n        if !is_short_attr_list\n            && attributes.len() <= 1\n            && spreads.is_empty()\n            && !has_trailing_comments\n            && !has_postbrace_comments\n        {\n            if children.is_empty() {\n                opt_level = ShortOptimization::Oneliner;\n            } else {\n                opt_level = ShortOptimization::PropsOnTop;\n            }\n        }\n\n        // if we have few children and few attributes, make it a one-liner\n        if is_short_attr_list && is_small_children {\n            if children_len.unwrap() + attr_len + self.out.indent_level * 4 < 100 {\n                opt_level = ShortOptimization::Oneliner;\n            } else {\n                opt_level = ShortOptimization::PropsOnTop;\n            }\n        }\n\n        // If there's nothing at all, empty optimization\n        if attributes.is_empty()\n            && children.is_empty()\n            && spreads.is_empty()\n            && !has_trailing_comments\n        {\n            opt_level = ShortOptimization::Empty;\n\n            // Write comments if they exist\n            self.write_inline_comments(brace.span.span().start(), 1)?;\n            self.write_todo_body(brace)?;\n        }\n\n        // multiline handlers bump everything down\n        if attr_len > 1000 || self.out.indent.split_line_attributes() {\n            opt_level = ShortOptimization::NoOpt;\n        }\n\n        let has_children = !children.is_empty();\n\n        match opt_level {\n            ShortOptimization::Empty => {}\n            ShortOptimization::Oneliner => {\n                write!(self.out, \" \")?;\n\n                self.write_attributes(attributes, spreads, true, brace, has_children)?;\n\n                if !children.is_empty() && !attributes.is_empty() {\n                    write!(self.out, \" \")?;\n                }\n\n                let mut children_iter = children.iter().peekable();\n                while let Some(child) = children_iter.next() {\n                    self.write_ident(child)?;\n                    if children_iter.peek().is_some() {\n                        write!(self.out, \" \")?;\n                    }\n                }\n\n                write!(self.out, \" \")?;\n            }\n\n            ShortOptimization::PropsOnTop => {\n                if !attributes.is_empty() {\n                    write!(self.out, \" \")?;\n                }\n\n                self.write_attributes(attributes, spreads, true, brace, has_children)?;\n\n                if !children.is_empty() {\n                    self.out.new_line()?;\n                    self.write_body_indented(children)?;\n                }\n\n                self.out.tabbed_line()?;\n            }\n\n            ShortOptimization::NoOpt => {\n                self.write_inline_comments(brace.span.span().start(), 1)?;\n                self.out.new_line()?;\n                self.write_attributes(attributes, spreads, false, brace, has_children)?;\n\n                if !children.is_empty() {\n                    self.out.new_line()?;\n                    self.write_body_indented(children)?;\n                }\n\n                self.out.tabbed_line()?;\n            }\n        }\n\n        // Write trailing comments\n        if matches!(\n            opt_level,\n            ShortOptimization::NoOpt | ShortOptimization::PropsOnTop\n        ) && self.leading_row_is_empty(brace.span.span().end())\n        {\n            let comments = self.accumulate_full_line_comments(brace.span.span().end());\n            if !comments.is_empty() {\n                self.apply_line_comments(comments)?;\n                self.out.tab()?;\n            }\n        }\n\n        write!(self.out, \"}}\")?;\n\n        Ok(())\n    }\n\n    fn write_attributes(\n        &mut self,\n        attributes: &[Attribute],\n        spreads: &[Spread],\n        props_same_line: bool,\n        brace: &Brace,\n        has_children: bool,\n    ) -> Result {\n        enum AttrType<'a> {\n            Attr(&'a Attribute),\n            Spread(&'a Spread),\n        }\n\n        let mut attr_iter = attributes\n            .iter()\n            .map(AttrType::Attr)\n            .chain(spreads.iter().map(AttrType::Spread))\n            .peekable();\n\n        let has_attributes = !attributes.is_empty() || !spreads.is_empty();\n\n        while let Some(attr) = attr_iter.next() {\n            self.out.indent_level += 1;\n\n            if !props_same_line {\n                self.write_attr_comments(\n                    brace,\n                    match attr {\n                        AttrType::Attr(attr) => attr.span(),\n                        AttrType::Spread(attr) => attr.expr.span(),\n                    },\n                )?;\n            }\n\n            self.out.indent_level -= 1;\n\n            if !props_same_line {\n                self.out.indented_tab()?;\n            }\n\n            match attr {\n                AttrType::Attr(attr) => self.write_attribute(attr)?,\n                AttrType::Spread(attr) => self.write_spread_attribute(&attr.expr)?,\n            }\n\n            let span = match attr {\n                AttrType::Attr(attr) => attr\n                    .comma\n                    .as_ref()\n                    .map(|c| c.span())\n                    .unwrap_or_else(|| self.total_span_of_attr(attr)),\n                AttrType::Spread(attr) => attr.span(),\n            };\n\n            let has_more = attr_iter.peek().is_some();\n            let should_finish_comma = has_attributes && has_children || !props_same_line;\n\n            if has_more || should_finish_comma {\n                write!(self.out, \",\")?;\n            }\n\n            if !props_same_line {\n                self.write_inline_comments(span.end(), 0)?;\n            }\n\n            if props_same_line && !has_more {\n                self.write_inline_comments(span.end(), 0)?;\n            }\n\n            if props_same_line && has_more {\n                write!(self.out, \" \")?;\n            }\n\n            if !props_same_line && has_more {\n                self.out.new_line()?;\n            }\n        }\n\n        Ok(())\n    }\n\n    fn write_attribute(&mut self, attr: &Attribute) -> Result {\n        self.write_attribute_name(&attr.name)?;\n\n        // if the attribute is a shorthand, we don't need to write the colon, just the name\n        if !attr.can_be_shorthand() {\n            write!(self.out, \": \")?;\n            self.write_attribute_value(&attr.value)?;\n        }\n\n        Ok(())\n    }\n\n    fn write_attribute_name(&mut self, attr: &AttributeName) -> Result {\n        match attr {\n            AttributeName::BuiltIn(name) => write!(self.out, \"{}\", name),\n            AttributeName::Custom(name) => write!(self.out, \"{}\", name.to_token_stream()),\n            AttributeName::Spread(_) => unreachable!(),\n        }\n    }\n\n    fn write_attribute_value(&mut self, value: &AttributeValue) -> Result {\n        match value {\n            AttributeValue::IfExpr(if_chain) => {\n                self.write_attribute_if_chain(if_chain)?;\n            }\n            AttributeValue::AttrLiteral(value) => {\n                write!(self.out, \"{value}\")?;\n            }\n            AttributeValue::Shorthand(value) => {\n                write!(self.out, \"{value}\")?;\n            }\n            AttributeValue::EventTokens(closure) => {\n                self.out.indent_level += 1;\n                self.write_partial_expr(closure.as_expr(), closure.span())?;\n                self.out.indent_level -= 1;\n            }\n            AttributeValue::AttrExpr(value) => {\n                self.out.indent_level += 1;\n                self.write_partial_expr(value.as_expr(), value.span())?;\n                self.out.indent_level -= 1;\n            }\n        }\n\n        Ok(())\n    }\n\n    fn write_attribute_if_chain(&mut self, if_chain: &IfAttributeValue) -> Result {\n        let cond = self.unparse_expr(&if_chain.if_expr.cond);\n        write!(self.out, \"if {cond} {{ \")?;\n        self.write_attribute_value(&if_chain.then_value)?;\n        write!(self.out, \" }}\")?;\n        match if_chain.else_value.as_deref() {\n            Some(AttributeValue::IfExpr(else_if_chain)) => {\n                write!(self.out, \" else \")?;\n                self.write_attribute_if_chain(else_if_chain)?;\n            }\n            Some(other) => {\n                write!(self.out, \" else {{ \")?;\n                self.write_attribute_value(other)?;\n                write!(self.out, \" }}\")?;\n            }\n            None => {}\n        }\n\n        Ok(())\n    }\n\n    fn write_attr_comments(&mut self, brace: &Brace, attr_span: Span) -> Result {\n        // There's a chance this line actually shares the same line as the previous\n        // Only write comments if the comments actually belong to this line\n        //\n        // to do this, we check if the attr span starts on the same line as the brace\n        // if it doesn't, we write the comments\n        let brace_line = brace.span.span().start().line;\n        let attr_line = attr_span.start().line;\n\n        if brace_line != attr_line {\n            // Get the raw line of the attribute\n            let line = self.src.get(attr_line - 1).unwrap_or(&\"\");\n\n            // Only write comments if the line is empty before the attribute start\n            let row_start = line.get(..attr_span.start().column - 1).unwrap_or(\"\");\n            if !row_start.trim().is_empty() {\n                return Ok(());\n            }\n\n            self.write_comments(attr_span.start())?;\n        }\n\n        Ok(())\n    }\n\n    fn write_inline_comments(&mut self, final_span: LineColumn, offset: usize) -> Result {\n        let line = final_span.line;\n        let column = final_span.column;\n        let Some(src_line) = self.src.get(line - 1) else {\n            return Ok(());\n        };\n\n        // the line might contain emoji or other unicode characters - this will cause issues\n        let Some(mut whitespace) = src_line.get(column..).map(|s| s.trim()) else {\n            return Ok(());\n        };\n\n        if whitespace.is_empty() {\n            return Ok(());\n        }\n\n        whitespace = whitespace[offset..].trim();\n\n        // don't emit whitespace if the span is messed up for some reason\n        if final_span.line == 1 && final_span.column == 0 {\n            return Ok(());\n        };\n\n        if whitespace.starts_with(\"//\") {\n            write!(self.out, \" {whitespace}\")?;\n        }\n\n        Ok(())\n    }\n\n    fn accumulate_full_line_comments(&mut self, loc: LineColumn) -> VecDeque<usize> {\n        // collect all comments upwards\n        // make sure we don't collect the comments of the node that we're currently under.\n        let start = loc;\n        let line_start = start.line - 1;\n\n        let mut comments = VecDeque::new();\n\n        // don't emit whitespace if the span is messed up for some reason\n        if loc.line == 1 && loc.column == 0 {\n            return comments;\n        };\n\n        let Some(lines) = self.src.get(..line_start) else {\n            return comments;\n        };\n\n        // We go backwards to collect comments and empty lines. We only want to keep one empty line,\n        // the rest should be `//` comments\n        let mut last_line_was_empty = false;\n        for (id, line) in lines.iter().enumerate().rev() {\n            let trimmed = line.trim();\n            if trimmed.starts_with(\"//\") {\n                comments.push_front(id);\n                last_line_was_empty = false;\n            } else if trimmed.is_empty() {\n                if !last_line_was_empty {\n                    comments.push_front(id);\n                    last_line_was_empty = true;\n                }\n\n                continue;\n            } else {\n                break;\n            }\n        }\n\n        // If there is more than 1 comment, make sure the first comment is not an empty line\n        if comments.len() > 1 {\n            if let Some(&first) = comments.back() {\n                if self.src[first].trim().is_empty() {\n                    comments.pop_back();\n                }\n            }\n        }\n\n        comments\n    }\n\n    fn apply_line_comments(&mut self, mut comments: VecDeque<usize>) -> Result {\n        while let Some(comment_line) = comments.pop_front() {\n            let Some(line) = self.src.get(comment_line) else {\n                continue;\n            };\n\n            let line = &line.trim();\n\n            if line.is_empty() {\n                self.out.new_line()?;\n            } else {\n                self.out.tab()?;\n                writeln!(self.out, \"{}\", line.trim())?;\n            }\n        }\n        Ok(())\n    }\n\n    fn write_comments(&mut self, loc: LineColumn) -> Result {\n        let comments = self.accumulate_full_line_comments(loc);\n        self.apply_line_comments(comments)?;\n        Ok(())\n    }\n\n    fn attr_value_len(&mut self, value: &AttributeValue) -> usize {\n        match value {\n            AttributeValue::IfExpr(if_chain) => {\n                let condition_len = self.retrieve_formatted_expr(&if_chain.if_expr.cond).len();\n                let value_len = self.attr_value_len(&if_chain.then_value);\n                let if_len = 2;\n                let brace_len = 2;\n                let space_len = 2;\n                let else_len = if_chain\n                    .else_value\n                    .as_ref()\n                    .map(|else_value| self.attr_value_len(else_value) + 1)\n                    .unwrap_or_default();\n                condition_len + value_len + if_len + brace_len + space_len + else_len\n            }\n            AttributeValue::AttrLiteral(lit) => lit.to_string().len(),\n            AttributeValue::Shorthand(expr) => {\n                let span = &expr.span();\n                span.end().line - span.start().line\n            }\n            AttributeValue::AttrExpr(expr) => expr\n                .as_expr()\n                .map(|expr| self.attr_expr_len(&expr))\n                .unwrap_or(100000),\n            AttributeValue::EventTokens(closure) => closure\n                .as_expr()\n                .map(|expr| self.attr_expr_len(&expr))\n                .unwrap_or(100000),\n        }\n    }\n\n    fn attr_expr_len(&mut self, expr: &Expr) -> usize {\n        let out = self.retrieve_formatted_expr(expr);\n        if out.contains('\\n') {\n            100000\n        } else {\n            out.len()\n        }\n    }\n\n    fn is_short_attrs(\n        &mut self,\n        _brace: &Brace,\n        attributes: &[Attribute],\n        spreads: &[Spread],\n    ) -> usize {\n        let mut total = 0;\n\n        // No more than 3 attributes before breaking the line\n        if attributes.len() > 3 {\n            return 100000;\n        }\n\n        for attr in attributes {\n            if self.current_span_is_primary(attr.span().start()) {\n                if let Some(lines) = self.src.get(..attr.span().start().line - 1) {\n                    'line: for line in lines.iter().rev() {\n                        match (line.trim().starts_with(\"//\"), line.is_empty()) {\n                            (true, _) => return 100000,\n                            (_, true) => continue 'line,\n                            _ => break 'line,\n                        }\n                    }\n                };\n            }\n\n            total += match &attr.name {\n                AttributeName::BuiltIn(name) => {\n                    let name = name.to_string();\n                    name.len()\n                }\n                AttributeName::Custom(name) => name.value().len() + 2,\n                AttributeName::Spread(_) => unreachable!(),\n            };\n\n            if attr.can_be_shorthand() {\n                total += 2;\n            } else {\n                total += self.attr_value_len(&attr.value);\n            }\n\n            total += 6;\n        }\n\n        for spread in spreads {\n            let expr_len = self.retrieve_formatted_expr(&spread.expr).len();\n            total += expr_len + 3;\n        }\n\n        total\n    }\n\n    fn write_todo_body(&mut self, brace: &Brace) -> std::fmt::Result {\n        let span = brace.span.span();\n        let start = span.start();\n        let end = span.end();\n\n        if start.line == end.line {\n            return Ok(());\n        }\n\n        writeln!(self.out)?;\n\n        for idx in start.line..end.line {\n            let Some(line) = self.src.get(idx) else {\n                continue;\n            };\n            if line.trim().starts_with(\"//\") {\n                for _ in 0..self.out.indent_level + 1 {\n                    write!(self.out, \"    \")?\n                }\n                writeln!(self.out, \"{}\", line.trim())?;\n            }\n        }\n\n        for _ in 0..self.out.indent_level {\n            write!(self.out, \"    \")?\n        }\n\n        Ok(())\n    }\n\n    fn write_partial_expr(&mut self, expr: syn::Result<Expr>, src_span: Span) -> Result {\n        let Ok(expr) = expr else {\n            self.invalid_exprs.push(src_span);\n            return Err(std::fmt::Error);\n        };\n\n        thread_local! {\n            static COMMENT_REGEX: Regex = Regex::new(\"\\\"[^\\\"]*\\\"|(//.*)\").unwrap();\n        }\n\n        let pretty = self.retrieve_formatted_expr(&expr).to_string();\n        let source = src_span.source_text().unwrap_or_default();\n        let mut src_lines = source.lines().peekable();\n\n        // Comments already in pretty output (from nested rsx!) - skip these from source\n        let pretty_comments: HashSet<_> = pretty\n            .lines()\n            .filter(|l| l.trim().starts_with(\"//\"))\n            .map(|l| l.trim())\n            .collect();\n\n        let mut out = String::new();\n\n        if src_lines.peek().is_none() {\n            out = pretty;\n        } else {\n            for line in pretty.lines() {\n                let trimmed = line.trim();\n                let compacted = line.replace(\" \", \"\").replace(\",\", \"\");\n\n                // Pretty comments: consume matching source lines, preserve preceding empty lines\n                if trimmed.starts_with(\"//\") {\n                    if !out.is_empty() {\n                        out.push('\\n');\n                    }\n                    let mut had_empty = false;\n                    while let Some(s) = src_lines.peek() {\n                        let t = s.trim();\n                        if t.is_empty() {\n                            had_empty = true;\n                            src_lines.next();\n                        } else if t == trimmed {\n                            src_lines.next();\n                            break;\n                        } else {\n                            break;\n                        }\n                    }\n                    if had_empty {\n                        out.push('\\n');\n                    }\n                    out.push_str(line);\n                    continue;\n                }\n\n                // Pretty empty lines: preserve and sync with source\n                if trimmed.is_empty() {\n                    if !out.is_empty() {\n                        out.push('\\n');\n                    }\n                    while src_lines\n                        .peek()\n                        .map(|s| s.trim().is_empty())\n                        .unwrap_or(false)\n                    {\n                        src_lines.next();\n                    }\n                    continue;\n                }\n\n                if !out.is_empty() {\n                    out.push('\\n');\n                }\n\n                // Scan source for comments/empty lines before the matching line\n                let mut pending_comments = Vec::new();\n                let mut had_empty = false;\n                let mut multiline: Option<Vec<&str>> = None;\n\n                while let Some(src) = src_lines.peek() {\n                    let src_trimmed = src.trim();\n\n                    if src_trimmed.is_empty() || src_trimmed.starts_with(\"//\") {\n                        if src_trimmed.is_empty() {\n                            if pending_comments.is_empty() {\n                                had_empty = true;\n                            }\n                        } else if !pretty_comments.contains(src_trimmed) {\n                            pending_comments.push(src_trimmed);\n                        }\n                        src_lines.next();\n                        continue;\n                    }\n\n                    let src_compacted = src.replace(\" \", \"\").replace(\",\", \"\");\n\n                    // Exact match\n                    if src_compacted.contains(&compacted) {\n                        break;\n                    }\n\n                    // Multi-line method chain (e.g., foo\\n  .bar()\\n  .baz())\n                    if !src_compacted.is_empty() && compacted.starts_with(&src_compacted) {\n                        let is_call = src_trimmed.ends_with('(')\n                            || src_trimmed.ends_with(',')\n                            || src_trimmed.ends_with('{');\n                        if !is_call {\n                            multiline = Some(vec![*src]);\n                            break;\n                        }\n                    }\n\n                    // Non-matching line - clear pending and skip\n                    pending_comments.clear();\n                    had_empty = false;\n                    src_lines.next();\n                    break;\n                }\n\n                // Output empty line if needed\n                if had_empty {\n                    out.push('\\n');\n                }\n\n                // Output pending comments\n                for comment in &pending_comments {\n                    for c in line.chars().take_while(|c| c.is_whitespace()) {\n                        out.push(c);\n                    }\n                    if matches!(trimmed.chars().next(), Some(')' | '}' | ']')) {\n                        out.push_str(self.out.indent.indent_str());\n                    }\n                    out.push_str(comment);\n                    out.push('\\n');\n                }\n\n                // Handle multi-line method chains\n                if let Some(mut ml) = multiline {\n                    src_lines.next();\n                    let mut acc = ml[0].replace(\" \", \"\").replace(\",\", \"\");\n\n                    while let Some(src) = src_lines.peek() {\n                        let t = src.trim();\n                        if t.starts_with(\"//\") {\n                            ml.push(src);\n                            src_lines.next();\n                            continue;\n                        }\n                        if t.is_empty() {\n                            src_lines.next();\n                            continue;\n                        }\n\n                        acc.push_str(&src.replace(\" \", \"\").replace(\",\", \"\"));\n                        ml.push(src);\n\n                        if acc.contains(&compacted) {\n                            src_lines.next();\n                            break;\n                        }\n\n                        let cont = t.starts_with('.')\n                            || t.starts_with(\"&&\")\n                            || t.starts_with(\"||\")\n                            || matches!(t.chars().next(), Some('+' | '-' | '*' | '/' | '?'));\n\n                        if cont || compacted.starts_with(&acc) {\n                            src_lines.next();\n                            continue;\n                        }\n                        break;\n                    }\n\n                    // Write multi-line with adjusted indentation\n                    let base_indent = ml[0].chars().take_while(|c| c.is_whitespace()).count();\n                    let target: String = line.chars().take_while(|c| c.is_whitespace()).collect();\n\n                    for (i, src_line) in ml.iter().enumerate() {\n                        let indent = src_line.chars().take_while(|c| c.is_whitespace()).count();\n                        out.push_str(&target);\n                        for _ in 0..indent.saturating_sub(base_indent) {\n                            out.push(' ');\n                        }\n                        out.push_str(src_line.trim());\n                        if i < ml.len() - 1 {\n                            out.push('\\n');\n                        }\n                    }\n                } else {\n                    // Single line - output pretty line and capture inline comments\n                    out.push_str(line);\n                    if let Some(src_line) = src_lines.next() {\n                        if let Some(cap) = COMMENT_REGEX.with(|r| r.captures(src_line)) {\n                            if let Some(c) = cap.get(1) {\n                                out.push_str(\" // \");\n                                out.push_str(c.as_str().replace(\"//\", \"\").trim());\n                            }\n                        }\n                    }\n                }\n            }\n        }\n\n        self.write_mulitiline_tokens(out)?;\n        Ok(())\n    }\n\n    fn write_mulitiline_tokens(&mut self, out: String) -> Result {\n        let mut lines = out.split('\\n').peekable();\n        let first = lines.next().unwrap();\n\n        // a one-liner for whatever reason\n        // Does not need a new line\n        if lines.peek().is_none() {\n            write!(self.out, \"{first}\")?;\n        } else {\n            writeln!(self.out, \"{first}\")?;\n\n            while let Some(line) = lines.next() {\n                if !line.trim().is_empty() {\n                    self.out.tab()?;\n                }\n\n                write!(self.out, \"{line}\")?;\n                if lines.peek().is_none() {\n                    write!(self.out, \"\")?;\n                } else {\n                    writeln!(self.out)?;\n                }\n            }\n        }\n\n        Ok(())\n    }\n\n    fn write_spread_attribute(&mut self, attr: &Expr) -> Result {\n        let formatted = self.unparse_expr(attr);\n\n        let mut lines = formatted.lines();\n\n        let first_line = lines.next().unwrap();\n\n        write!(self.out, \"..{first_line}\")?;\n        for line in lines {\n            self.out.indented_tabbed_line()?;\n            write!(self.out, \"{line}\")?;\n        }\n\n        Ok(())\n    }\n\n    // check if the children are short enough to be on the same line\n    // We don't have the notion of current line depth - each line tries to be < 80 total\n    // returns the total line length if it's short\n    // returns none if the length exceeds the limit\n    // I think this eventually becomes quadratic :(\n    fn is_short_children(&mut self, children: &[BodyNode]) -> syn::Result<Option<usize>> {\n        if children.is_empty() {\n            return Ok(Some(0));\n        }\n\n        // Any comments push us over the limit automatically\n        if self.children_have_comments(children) {\n            return Ok(None);\n        }\n\n        let res = match children {\n            [BodyNode::Text(ref text)] => Some(text.input.to_string_with_quotes().len()),\n\n            // TODO: let rawexprs to be inlined\n            [BodyNode::RawExpr(ref expr)] => {\n                let pretty = self.retrieve_formatted_expr(&expr.expr.as_expr()?);\n                if pretty.contains('\\n') {\n                    None\n                } else {\n                    Some(pretty.len() + 2)\n                }\n            }\n\n            // TODO: let rawexprs to be inlined\n            [BodyNode::Component(ref comp)]\n            // basically if the component is completely empty, we can inline it\n                if comp.fields.is_empty()\n                    && comp.children.is_empty()\n                    && comp.spreads.is_empty() =>\n            {\n                Some(\n                    comp.name\n                        .segments\n                        .iter()\n                        .map(|s| s.ident.to_string().len() + 2)\n                        .sum::<usize>(),\n                )\n            }\n\n            // Feedback on discord indicates folks don't like combining multiple children on the same line\n            // We used to do a lot of math to figure out if we should expand out the line, but folks just\n            // don't like it.\n            _ => None,\n        };\n\n        Ok(res)\n    }\n\n    fn children_have_comments(&self, children: &[BodyNode]) -> bool {\n        for child in children {\n            if self.current_span_is_primary(child.span().start()) {\n                'line: for line in self.src[..child.span().start().line - 1].iter().rev() {\n                    match (line.trim().starts_with(\"//\"), line.is_empty()) {\n                        (true, _) => return true,\n                        (_, true) => continue 'line,\n                        _ => break 'line,\n                    }\n                }\n            }\n        }\n\n        false\n    }\n\n    // make sure the comments are actually relevant to this element.\n    // test by making sure this element is the primary element on this line (nothing else before it)\n    fn current_span_is_primary(&self, location: LineColumn) -> bool {\n        self.leading_row_is_empty(LineColumn {\n            line: location.line,\n            column: location.column + 1,\n        })\n    }\n\n    fn leading_row_is_empty(&self, location: LineColumn) -> bool {\n        let Some(line) = self.src.get(location.line - 1) else {\n            return false;\n        };\n\n        let Some(sub) = line.get(..location.column - 1) else {\n            return false;\n        };\n\n        sub.trim().is_empty()\n    }\n\n    #[allow(clippy::map_entry)]\n    fn retrieve_formatted_expr(&mut self, expr: &Expr) -> Cow<'_, str> {\n        let loc = expr.span().start();\n\n        // never cache expressions that are spanless\n        if loc.line == 1 && loc.column == 0 {\n            return self.unparse_expr(expr).into();\n        }\n\n        if !self.cached_formats.contains_key(&loc) {\n            let formatted = self.unparse_expr(expr);\n            self.cached_formats.insert(loc, formatted);\n        }\n\n        self.cached_formats\n            .get(&loc)\n            .expect(\"Just inserted the parsed expr, so it should be in the cache\")\n            .as_str()\n            .into()\n    }\n\n    fn final_span_of_node(node: &BodyNode) -> Span {\n        // Get the ending span of the node\n        match node {\n            BodyNode::Element(el) => el\n                .brace\n                .as_ref()\n                .map(|b| b.span.span())\n                .unwrap_or_else(|| el.name.span()),\n            BodyNode::Component(el) => el\n                .brace\n                .as_ref()\n                .map(|b| b.span.span())\n                .unwrap_or_else(|| el.name.span()),\n            BodyNode::Text(txt) => txt.input.span(),\n            BodyNode::RawExpr(exp) => exp.span(),\n            BodyNode::ForLoop(f) => f.brace.span.span(),\n            BodyNode::IfChain(i) => match i.else_brace {\n                Some(b) => b.span.span(),\n                None => i.then_brace.span.span(),\n            },\n        }\n    }\n\n    fn total_span_of_attr(&self, attr: &Attribute) -> Span {\n        match &attr.value {\n            AttributeValue::Shorthand(s) => s.span(),\n            AttributeValue::AttrLiteral(l) => l.span(),\n            AttributeValue::EventTokens(closure) => closure.span(),\n            AttributeValue::AttrExpr(exp) => exp.span(),\n            AttributeValue::IfExpr(ex) => ex.span(),\n        }\n    }\n\n    fn brace_has_trailing_comments(&self, brace: &Brace) -> bool {\n        let span = brace.span.span();\n        let line = self.src.get(span.start().line - 1).unwrap_or(&\"\");\n        let after_brace = line.get(span.start().column + 1..).unwrap_or(\"\").trim();\n        after_brace.starts_with(\"//\")\n    }\n\n    fn has_trailing_comments(&self, children: &[BodyNode], brace: &Brace) -> bool {\n        let brace_span = brace.span.span();\n\n        let Some(last_node) = children.last() else {\n            return false;\n        };\n\n        // Check for any comments after the last node between the last brace\n        let final_span = Self::final_span_of_node(last_node);\n        let final_span = final_span.end();\n        let mut line = final_span.line;\n        let mut column = final_span.column;\n        loop {\n            let Some(src_line) = self.src.get(line - 1) else {\n                return false;\n            };\n\n            // the line might contain emoji or other unicode characters - this will cause issues\n            let Some(mut whitespace) = src_line.get(column..).map(|s| s.trim()) else {\n                return false;\n            };\n\n            let offset = 0;\n            whitespace = whitespace[offset..].trim();\n\n            if whitespace.starts_with(\"//\") {\n                return true;\n            }\n\n            if line == brace_span.end().line {\n                // If we reached the end of the brace span, stop\n                break;\n            }\n\n            line += 1;\n            column = 0; // reset column to the start of the next line\n        }\n\n        false\n    }\n}\n"
  },
  {
    "path": "packages/autofmt/tests/error_handling.rs",
    "content": "#[test]\nfn no_parse() {\n    let src = include_str!(\"./partials/no_parse.rsx\");\n    assert!(syn::parse_file(src).is_err());\n}\n\n#[test]\nfn parses_but_fmt_fails() {\n    let src = include_str!(\"./partials/wrong.rsx\");\n    let file = syn::parse_file(src).unwrap();\n    let formatted = dioxus_autofmt::try_fmt_file(src, &file, Default::default());\n    assert!(&formatted.is_err());\n}\n\n#[test]\nfn parses_and_is_okay() {\n    let src = include_str!(\"./partials/okay.rsx\");\n    let file = syn::parse_file(src).unwrap();\n    let formatted = dioxus_autofmt::try_fmt_file(src, &file, Default::default()).unwrap();\n    assert_ne!(formatted.len(), 0);\n}\n"
  },
  {
    "path": "packages/autofmt/tests/partials/no_parse.rsx",
    "content": "#[component]\nfn SidebarSection() -> Element {\n    rsx! {\n        div {\n            { .doesnt_work) }\n        }\n    }\n}\n"
  },
  {
    "path": "packages/autofmt/tests/partials/okay.rsx",
    "content": "#[component]\nfn SidebarSection() -> Element {\n    rsx! {\n        div {\n            onclick: move |_| {\n                works()\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "packages/autofmt/tests/partials/wrong.rsx",
    "content": "#[component]\nfn SidebarSection() -> Element {\n    rsx! {\n        div {\n            onclick: move |_| {\n                .doesnt_work()\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "packages/autofmt/tests/samples/asset.rsx",
    "content": "rsx! {\n    div { \"hi\" }\n    img {\n        // note: the line breaking here is weird but it's because of prettyplease, not dioxus-autofmt\n        // we might want to fix this in the future\n        src: asset!(\n            \"/assets/logo.png\".image().size(512, 512).format(ImageType::Jpg).url_encoded()\n            .image().size(512, 512).format(ImageType::Jpg).url_encoded()\n        ),\n        alt: \"logo\",\n    }\n}\n"
  },
  {
    "path": "packages/autofmt/tests/samples/attributes.rsx",
    "content": "rsx! {\n    div {\n        key: \"{ddd}\",\n        class: \"asd\",\n        class: \"asd\",\n        class: \"asd\",\n        class: \"asd\",\n        class: \"asd\",\n        class: \"asd\",\n        src: asset!(\"/123.png\"),\n        src: asset!(\"/456.png\"),\n        blah: 123,\n        onclick: move |_| {\n            let blah = 120;\n            true\n        },\n        div {\n            div { \"hi\" }\n            h2 { class: \"asd\" }\n        }\n        Component::<Generic> {}\n        Component {}\n    }\n\n    // Long attributes\n    div {\n        a: \"1234567891012345678910123456789101234567891012345678910123456789101234567891012345678910123456789101234567891012345678910\",\n        a: \"123\",\n        a: \"123\",\n        a: \"123\",\n        a: \"123\",\n        a: \"123\",\n        a: \"123\",\n        a: \"123\",\n        a: \"123\",\n    }\n\n    div {\n        a: \"123\",\n        a: \"123\",\n        a: \"123\",\n        a: \"123\",\n        a: \"123\",\n        a: \"123\",\n        a: \"123\",\n        a: \"123\",\n        a: \"123\",\n    }\n}\n"
  },
  {
    "path": "packages/autofmt/tests/samples/basic_expr.rsx",
    "content": "fn itworks() {\n    rsx! {\n        div {\n            \"hi\"\n            {children}\n        }\n    }\n}\n"
  },
  {
    "path": "packages/autofmt/tests/samples/blank_lines.rsx",
    "content": "rsx! {\n    div {\n        match true {\n            true => rsx! {\n                if true {\n                    span { \"a\" }\n                }\n                if true {\n                    span { \"b\" }\n                }\n                span { \"c\" }\n            },\n            false => rsx! {},\n        }\n    }\n}\n"
  },
  {
    "path": "packages/autofmt/tests/samples/blank_lines_preserved.rsx",
    "content": "rsx! {\n    div {\n        span { \"a\" }\n\n        span { \"b\" }\n\n        span { \"c\" }\n    }\n}\n"
  },
  {
    "path": "packages/autofmt/tests/samples/collapse.rsx",
    "content": "// nesting pushes out\nrsx! {\n    Fragment {\n        Fragment {\n            Fragment {\n                Fragment {\n                    Fragment {\n                        div { \"Finally have a real node!\" }\n                    }\n                }\n            }\n        }\n    }\n}\n\n// we don't make extra spaces\nrsx! {\n    Component { blah: rsx! {} }\n}\n\nrsx! {}\n"
  },
  {
    "path": "packages/autofmt/tests/samples/collapse_expr.rsx",
    "content": "fn itworks() {\n    rsx! {\n        \"{name}\"\n        \"{name}\"\n        \"{name}\"\n    }\n}\n"
  },
  {
    "path": "packages/autofmt/tests/samples/comments.rsx",
    "content": "rsx! {\n    div {\n        // Comments\n        class: \"asdasd\",\n        \"hello world\"\n    }\n    div {\n        // My comment here 1\n        // My comment here 2\n        // My comment here 3\n        // My comment here 4\n        class: \"asdasd\",\n\n        // Comment here\n        onclick: move |_| {\n            let a = 10;\n            let b = 40;\n            let c = 50;\n        },\n\n        // my comment\n\n        // This here\n        \"hi\"\n    }\n\n    // Comment head\n    div { class: \"asd\", \"Jon\" }\n\n    // Comment head\n    div {\n        // Collapse\n        class: \"asd\",\n        \"Jon\"\n    }\n\n    // comments inline\n    div { // inline\n        // Collapse\n        class: \"asd\", // super inline\n        class: \"asd\", // super inline\n        \"Jon\" // all the inline\n        // Comments at the end too\n    }\n\n    // please dont eat me 1\n    div { // please dont eat me 2\n        // please dont eat me 3\n    }\n\n    // please dont eat me 1\n    div { // please dont eat me 2\n        // please dont eat me 3\n        abc: 123,\n    }\n\n    // please dont eat me 1\n    div {\n        // please dont eat me 3\n        abc: 123,\n    }\n\n    div {\n        // I am just a comment\n    }\n\n    div {\n        \"text\"\n        // I am just a comment\n    }\n\n    div {\n        div {}\n        // I am just a comment\n    }\n\n    div {\n        {some_expr()}\n        // I am just a comment\n    }\n\n    div {\n        \"text\" // I am just a comment\n    }\n\n    div {\n        div {} // I am just a comment\n    }\n\n    div {\n        {some_expr()} // I am just a comment\n    }\n\n    div {\n        // Please dont eat me 1\n        div {\n            // Please dont eat me 2\n        }\n        // Please dont eat me 3\n    }\n\n    div {\n        \"hi\"\n        // Please dont eat me 1\n    }\n    div {\n        \"hi\" // Please dont eat me 1\n        // Please dont eat me 2\n    }\n\n    // Please dont eat me 2\n    Component {}\n\n    // Please dont eat me 1\n    Component {\n        // Please dont eat me 2\n    }\n\n    // Please dont eat me 1\n    Component {\n        // Please dont eat me 2\n    }\n\n    div {\n        {\n            // Please dont eat me 1\n            let millis = timer\n                .with(|t| {\n                    t.duration()\n                        .saturating_sub(\n                            t.started_at.map(|x| x.elapsed()).unwrap_or(Duration::ZERO),\n                        )\n                        .as_millis()\n                });\n\n            // Please dont eat me 2\n            format!(\n                \"{:02}:{:02}:{:02}.{:01}\",\n                millis / 1000 / 3600 % 3600, // Please dont eat me 3\n                millis / 1000 / 60 % 60,\n                millis / 1000 % 60,\n\n                // Please dont eat me 4\n                millis / 100 % 10,\n            );\n\n            // booo //\n            let b = { yay };\n\n            // boo // boo\n            let a = {\n                let a = \"123 // boo 123\";\n                // boo // boo\n                asdb\n            };\n\n            format!(\"{b} {a}\")\n            // ennd\n        }\n    }\n\n    div {\n        // booo //\n        {yay}\n\n        // boo // boo\n        {\n            let a = \"123 // boo 123\";\n            // boo // boo\n            rsx! { \"{a}\" }\n        }\n    }\n\n    div {\n        input {\n            r#type: \"number\",\n            min: 0,\n            max: 99,\n            value: format!(\"{:02}\", timer.read().hours),\n            oninput: move |e| {\n                // A comment inside an expression\n                timer.write().hours = e.value().parse().unwrap_or(0);\n            },\n        }\n\n        input {\n            r#type: \"number\",\n            min: 0,\n            max: 59,\n            value: format!(\"{:02}\", timer.read().minutes),\n            oninput: move |e| {\n                // A comment inside an expression\n                timer.write().minutes = e.value().parse().unwrap_or(0);\n\n                // A comment inside an expression\n            },\n        }\n\n        input {\n            r#type: \"number\",\n            min: 0,\n            max: 59,\n            value: format!(\"{:02}\", timer.read().seconds),\n            oninput: move |e| {\n                // A comment inside an expression\n                timer.write().seconds = e.value().parse().unwrap_or(0);\n                // A comment inside an expression\n            },\n        }\n    }\n\n    {\n        rsx! { \"{a}\" }\n    }\n\n    {\n        rsx! { \"one\" }\n    }\n\n    div {}\n\n    {\n        rsx! { \"one two three\" }\n    }\n\n    // Please dont eat me 1\n    //\n    // Please dont eat me 2\n}\n"
  },
  {
    "path": "packages/autofmt/tests/samples/commentshard.rsx",
    "content": "rsx! {\n    // Comments\n    div {\n        // Comments\n        class: \"asdasd\",\n\n        // Comments\n        \"hello world\"\n\n        // Comments\n        {expr1}\n\n        // Comments\n        {expr2}\n\n        // Comments\n        // Comments\n        // Comments\n        // Comments\n        // Comments\n        {expr3}\n\n        div {\n            // todo some work in here\n        }\n\n        div {\n            // todo some work in here\n            // todo some work in here\n            //\n            // todo some work in here\n        }\n\n        div {\n            // todo some work in here\n            class: \"hello world\",\n\n            // todo some work in here\n            class: \"hello world\",\n        }\n\n        div {\n            div {}\n        }\n    }\n}\n"
  },
  {
    "path": "packages/autofmt/tests/samples/complex.rsx",
    "content": "rsx! {\n    // Complex nesting with components\n    button {\n        class: \"flex items-center pl-3 py-3 pr-2 text-gray-500 hover:bg-indigo-50 rounded\",\n        onclick: move |evt| {\n            show_user_menu.set(!show_user_menu.get());\n            evt.cancel_bubble();\n        },\n        onmousedown: move |evt| show_user_menu.set(!show_user_menu.get()),\n        span { class: \"inline-block mr-4\", icons::icon_14 {} }\n        span { \"Settings\" }\n    }\n\n    // Complex nesting with handlers\n    li {\n        Link {\n            class: \"flex items-center pl-3 py-3 pr-4 {active_class} rounded\",\n            to: \"{to}\",\n            span { class: \"inline-block mr-3\", icons::icon_0 {} }\n            span { \"{name}\" }\n            {children.is_some().then(|| rsx! {\n                span {\n                    class: \"inline-block ml-auto hover:bg-gray-500\",\n                    onclick: move |evt| {\n                        evt.cancel_bubble();\n                    },\n                    icons::icon_8 {}\n                }\n            })}\n        }\n        div { class: \"px-4\",\n            {is_current.then(|| rsx! {\n                {children}\n            })}\n        }\n    }\n\n    // No nesting\n    Component {\n        adsasd: \"asd\",\n        onclick: move |_| {\n            let blah = 120;\n        },\n    }\n\n    // Component path\n    my::thing::Component {\n        adsasd: \"asd\",\n        onclick: move |_| {\n            let blah = 120;\n        },\n    }\n\n    for i in 0..10 {\n        Component { key: \"{i}\", blah: 120 }\n    }\n    for i in 0..10 {\n        Component { key: \"{i}\" }\n    }\n\n    for i in 0..10 {\n        div { key: \"{i}\", blah: 120 }\n    }\n\n    for i in 0..10 {\n        div { key: \"{i}\" }\n    }\n\n    div {\n        \"asdbascasdbasd\"\n        \"asbdasbdabsd\"\n        {asbdabsdbasdbas}\n    }\n}\n"
  },
  {
    "path": "packages/autofmt/tests/samples/docsite.rsx",
    "content": "pub(crate) fn Nav() -> Element {\n    rsx! {\n        SearchModal {}\n        header {\n            class: \"sticky top-0 z-30 bg-white dark:text-gray-200 dark:bg-ideblack border-b dark:border-stone-700 h-16 bg-opacity-80 backdrop-blur-sm\",\n            class: if HIGHLIGHT_NAV_LAYOUT() { \"border border-orange-600 rounded-md\" },\n            div { class: \"lg:py-2 px-2 max-w-screen-2xl mx-auto flex items-center justify-between text-sm leading-6 h-16\",\n                button {\n                    class: \"bg-gray-100 rounded-lg p-2 mr-4 lg:hidden my-3 h-10 flex items-center text-lg z-[100]\",\n                    class: if !SHOW_DOCS_NAV() { \"hidden\" },\n                    onclick: move |_| {\n                        let mut sidebar = SHOW_SIDEBAR.write();\n                        *sidebar = !*sidebar;\n                    },\n                    MaterialIcon {\n                        name: \"menu\",\n                        size: 24,\n                        color: MaterialIconColor::Dark,\n                    }\n                }\n                div { class: \"flex z-50 md:flex-1 px-2\", LinkList {} }\n\n                div { class: \"hidden md:flex h-full justify-end ml-2 flex-1\",\n                    div { class: \"hidden md:flex items-center\",\n                        Search {}\n                        div { class: \"hidden lg:flex items-center border-l border-gray-200 ml-4 pl-4 dark:border-gray-800\",\n                            label {\n                                class: \"sr-only\",\n                                id: \"headlessui-listbox-label-2\",\n                                \"Theme\"\n                            }\n                            Link {\n                                to: \"https://discord.gg/XgGxMSkvUM\",\n                                class: \"block text-gray-400 hover:text-gray-500 dark:hover:text-gray-300\",\n                                new_tab: true,\n                                span { class: \"sr-only\", \"Dioxus on Discord\" }\n                                crate::icons::DiscordLogo {}\n                            }\n                            Link {\n                                to: \"https://github.com/dioxuslabs/dioxus\",\n                                class: \"ml-4 block text-gray-400 hover:text-gray-500 dark:hover:text-gray-300\",\n                                new_tab: true,\n                                span { class: \"sr-only\", \"Dioxus on GitHub\" }\n                                crate::icons::Github2 {}\n                            }\n                        }\n                        div { class: \"hidden lg:flex items-center border-l border-gray-200 ml-4 pl-6 dark:border-gray-800\",\n                            label {\n                                class: \"sr-only\",\n                                id: \"headlessui-listbox-label-2\",\n                                \"Theme\"\n                            }\n                            Link {\n                                to: Route::Deploy {},\n                                class: \"md:ml-0 md:py-2 md:px-3 bg-blue-500 ml-4 text-lg md:text-sm text-white rounded font-semibold\",\n                                \"DEPLOY\"\n                            }\n                            if LOGGED_IN() {\n                                Link { to: Route::Homepage {},\n                                    img {\n                                        src: \"https://avatars.githubusercontent.com/u/10237910?s=40&v=4\",\n                                        class: \"ml-4 h-10 rounded-full w-auto\",\n                                    }\n                                }\n                            }\n                        }\n                    }\n                }\n            }\n        }\n    }\n}\n\n#[component]\nfn SidebarSection(chapter: &'static SummaryItem<BookRoute>) -> Element {\n    let link = chapter.maybe_link()?;\n\n    let sections = link.nested_items.iter().map(|chapter| {\n        rsx! {\n            SidebarChapter { chapter }\n        }\n    });\n\n    let _ = rsx! {\n        SidebarChapter { chapter }\n    };\n\n    rsx! {\n        SidebarChapter { chapter }\n    };\n\n    rsx! {\n        div {}\n    };\n\n    rsx! { \"hi\" }\n\n    rsx! {\n        div { class: \"full-chapter pb-4 mb-6\",\n            if let Some(url) = &link.location {\n                Link {\n                    onclick: move |_| *SHOW_SIDEBAR.write() = false,\n                    to: Route::Docs { child: *url },\n                    h3 { class: \"font-semibold mb-4\", \"{link.name}\" }\n                }\n            }\n            ul { class: \"ml-1\", {sections} }\n        }\n    }\n}\n"
  },
  {
    "path": "packages/autofmt/tests/samples/emoji.rsx",
    "content": "rsx! {\n    div { class: \"asdasdasd asdasdasd asdasdasd asdasdasd asdasdasd asdasdasd asdasdasd asdasdasd asdasdasd\",\n        section { \"🦀🦀🦀🦀🦀🦀🦀🦀🦀🦀\" }\n    }\n}\n"
  },
  {
    "path": "packages/autofmt/tests/samples/expr_on_conditional.rsx",
    "content": "//! A comment about this file\n\nuse dioxus::prelude::*;\n\n#[component]\npub fn Sample() -> Element {\n    let is_active = use_signal(|| false);\n\n    rsx! {\n        div { class: if is_active() { \"active\" } else { \"inactive\" },\n            div { class: if is_active2() { \"a\" } else { \"b\" } }\n        }\n    }\n}\n"
  },
  {
    "path": "packages/autofmt/tests/samples/fat_exprs.rsx",
    "content": "//! Exprs that are too long to fit on one line\n\nfn it_works() {\n    rsx! {\n        div {\n            if thing\n                .some_long_method_that_is_too_long_to_fit_on_one_line()\n                .some_long_method_that_is_too_long_to_fit_on_one_line()\n                .some_long_method_that_is_too_long_to_fit_on_one_line({\n                    chain()\n                        .some_long_method_that_is_too_long_to_fit_on_one_line()\n                        .some_long_method_that_is_too_long_to_fit_on_one_line()\n                })\n            {\n                \"hi\"\n            }\n\n            for item in thing\n                .some_long_method_that_is_too_long_to_fit_on_one_line()\n                .some_long_method_that_is_too_long_to_fit_on_one_line()\n                .some_long_method_that_is_too_long_to_fit_on_one_line({\n                    chain()\n                        .some_long_method_that_is_too_long_to_fit_on_one_line()\n                        .some_long_method_that_is_too_long_to_fit_on_one_line()\n                })\n            {\n                \"hi\"\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "packages/autofmt/tests/samples/ifchain_forloop.rsx",
    "content": "rsx! {\n    // Does this work?\n    for i in b {\n        // Hey it works?\n        div {}\n    }\n\n    // Some ifchain\n    if a > 10 {\n        //\n        div {}\n    } else if a > 20 {\n        h1 {}\n    } else if a > 20 {\n        h1 {}\n    } else if a > 20 {\n        h1 {}\n    } else if a > 20 {\n        h1 {}\n    } else if a > 20 {\n        h1 {}\n    } else if a > 20 {\n        h1 {}\n    } else {\n        h3 {}\n    }\n\n    div {\n        class: \"asdasd\",\n        class: if expr { \"asdasd\" } else { \"asdasd\" },\n        class: if expr { \"asdasd\" },\n        class: if expr { \"asdasd\" } else if expr { \"asdasd\" } else { \"asdasd\" },\n\n        // comments?\n        class: if expr { \"asdasd\" } else if expr { \"asdasd\" } else { \"asdasd\" }, // comments!!?\n        // comments?\n    }\n}\n"
  },
  {
    "path": "packages/autofmt/tests/samples/immediate_expr.rsx",
    "content": "fn it_works() {\n    rsx! {\n        {()}\n    }\n}\n"
  },
  {
    "path": "packages/autofmt/tests/samples/key.rsx",
    "content": "rsx! {\n    li { key: \"{link}\",\n        Link { class: \"py-1 px-2 {hover} {hover_bg}\", to: \"{link}\", \"{name}\" }\n    }\n\n    li { key: \"{link}\", asd: \"asd\",\n        Link { class: \"py-1 px-2 {hover} {hover_bg}\", to: \"{link}\", \"{name}\" }\n    }\n}\n"
  },
  {
    "path": "packages/autofmt/tests/samples/letsome.rsx",
    "content": "#[component]\nfn SidebarSection() -> Element {\n    rsx! {\n        if let Some(url) = &link.location {\n            \"hi {url}\"\n        }\n\n        if val.is_empty() {\n            \"No content\"\n        }\n    }\n}\n"
  },
  {
    "path": "packages/autofmt/tests/samples/long.rsx",
    "content": "use dioxus::prelude::*;\n\n#[component]\npub fn Explainer(invert: bool, title: &'static str, content: Element, flasher: Element) -> Element {\n    // pt-5 sm:pt-24 lg:pt-24\n\n    let mut right = rsx! {\n        div { class: \"relative w-1/2\", {flasher} }\n    };\n\n    let align = match invert {\n        true => \"mr-auto ml-16\",\n        false => \"ml-auto mr-16\",\n    };\n\n    let mut left = rsx! {\n        div { class: \"relative w-1/2 {align} max-w-md leading-8\",\n            h2 { class: \"mb-6 text-3xl leading-tight md:text-4xl md:leading-tight lg:text-3xl lg:leading-tight font-heading font-mono font-bold\",\n                \"{title}\"\n            }\n            {content}\n        }\n    };\n\n    if *invert {\n        std::mem::swap(&mut left, &mut right);\n    }\n\n    rsx! {\n        div { class: \"flex flex-wrap items-center dark:text-white py-16 border-t font-light\",\n            {left}\n            {right}\n        }\n    }\n}\n"
  },
  {
    "path": "packages/autofmt/tests/samples/long_exprs.rsx",
    "content": "rsx! {\n    div {\n        div {\n            div {\n                div {\n                    section { class: \"body-font overflow-hidden dark:bg-ideblack\",\n                        div { class: \"container px-6 mx-auto\",\n                            div { class: \"-my-8 divide-y-2 divide-gray-100\",\n                                {POSTS.iter().enumerate().map(|(id, post)| rsx! {\n                                    BlogPostItem { post, id }\n                                })}\n                            }\n                        }\n                    }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "packages/autofmt/tests/samples/manual_props.rsx",
    "content": "rsx! {\n    div {\n        Component {\n            adsasd: \"asd\",\n            onclick: move |_| {\n                let a = a;\n            },\n            div { \"thing\" }\n        }\n        Component {\n            asdasd: \"asdasd\",\n            asdasd: \"asdasdasdasdasdasdasdasdasdasd\",\n            ..Props { a: 10, b: 20 },\n        }\n        Component {\n            asdasd: \"asdasd\",\n            ..Props {\n                a: 10,\n                b: 20,\n                c: {\n                    fn main() {}\n                },\n            },\n            \"content\"\n        }\n    }\n}\n"
  },
  {
    "path": "packages/autofmt/tests/samples/many_exprs.rsx",
    "content": "#![allow(dead_code, unused)]\nuse dioxus::desktop::use_window;\nuse dioxus::prelude::*;\nuse std::{\n    process::exit,\n    time::{Duration, Instant},\n};\nuse tokio::time::sleep;\n\nfn main() {\n    dioxus::LaunchBuilder::desktop().launch(app);\n}\n\nstruct WindowPreferences {\n    always_on_top: bool,\n    with_decorations: bool,\n    exiting: Option<Instant>,\n}\n\nimpl Default for WindowPreferences {\n    fn default() -> Self {\n        Self {\n            with_decorations: true,\n            always_on_top: false,\n            exiting: None,\n        }\n    }\n}\n\nimpl WindowPreferences {\n    fn new() -> Self {\n        Self::default()\n    }\n}\n\n#[derive(Default)]\nstruct Timer {\n    hours: u8,\n    minutes: u8,\n    seconds: u8,\n    started_at: Option<Instant>,\n}\n\nimpl Timer {\n    fn new() -> Self {\n        Self::default()\n    }\n\n    fn duration(&self) -> Duration {\n        Duration::from_secs(\n            (self.hours as u64 * 60 + self.minutes as u64) * 60 + self.seconds as u64,\n        )\n    }\n}\n\nconst UPD_FREQ: Duration = Duration::from_millis(100);\n\nfn exit_button(\n    delay: Duration,\n    label: fn(Signal<Option<Instant>>, Duration) -> Option<VNode>,\n) -> Element {\n    let mut trigger: Signal<Option<Instant>> = use_signal(|| None);\n    use_future(move || async move {\n        loop {\n            sleep(UPD_FREQ).await;\n            if let Some(true) = trigger.read().map(|e| e.elapsed() > delay) {\n                exit(0);\n            }\n        }\n    });\n    let stuff: Option<VNode> = rsx! {\n        button {\n            onmouseup: move |_| {\n                trigger.set(None);\n            },\n            onmousedown: move |_| {\n                trigger.set(Some(Instant::now()));\n            },\n            width: 100,\n            {label(trigger, delay)}\n        }\n    };\n    stuff\n}\n\nfn app() -> Element {\n    let mut timer = use_signal(Timer::new);\n    let mut window_preferences = use_signal(WindowPreferences::new);\n\n    use_future(move || async move {\n        loop {\n            sleep(UPD_FREQ).await;\n            timer.with_mut(|t| {\n                if let Some(started_at) = t.started_at {\n                    if t.duration().saturating_sub(started_at.elapsed()) == Duration::ZERO {\n                        t.started_at = None;\n                    }\n                }\n            });\n        }\n    });\n\n    rsx! {\n        div {\n            {\n                let millis = timer\n                    .with(|t| {\n                        t.duration()\n                            .saturating_sub(\n                                t.started_at.map(|x| x.elapsed()).unwrap_or(Duration::ZERO),\n                            )\n                            .as_millis()\n                    });\n                format!(\n                    \"{:02}:{:02}:{:02}.{:01}\",\n                    millis / 1000 / 3600 % 3600,\n                    millis / 1000 / 60 % 60,\n                    millis / 1000 % 60,\n                    millis / 100 % 10,\n                )\n            }\n        }\n        div {\n            input {\n                r#type: \"number\",\n                min: 0,\n                max: 99,\n                value: format!(\"{:02}\", timer.read().hours),\n                oninput: move |e| {\n                    timer.write().hours = e.value().parse().unwrap_or(0);\n                },\n            }\n\n            input {\n                r#type: \"number\",\n                min: 0,\n                max: 59,\n                value: format!(\"{:02}\", timer.read().minutes),\n                oninput: move |e| {\n                    timer.write().minutes = e.value().parse().unwrap_or(0);\n                },\n            }\n\n            input {\n                r#type: \"number\",\n                min: 0,\n                max: 59,\n                value: format!(\"{:02}\", timer.read().seconds),\n                oninput: move |e| {\n                    timer.write().seconds = e.value().parse().unwrap_or(0);\n                    // A comment inside an expression\n                },\n            }\n        }\n\n        button {\n            id: \"start_stop\",\n            onclick: move |_| {\n                timer\n                    .with_mut(|t| {\n                        t.started_at = if t.started_at.is_none() {\n                            Some(Instant::now())\n                        } else {\n                            None\n                        }\n                    })\n            },\n            {timer.with(|t| if t.started_at.is_none() { \"Start\" } else { \"Stop\" })}\n        }\n        div { id: \"app\",\n            button {\n                onclick: move |_| {\n                    let decorations = window_preferences.read().with_decorations;\n                    use_window().set_decorations(!decorations);\n                    window_preferences.write().with_decorations = !decorations;\n                },\n                {\n                    format!(\n                        \"with decorations{}\",\n                        if window_preferences.read().with_decorations { \" ✓\" } else { \"\" },\n                    )\n                }\n            }\n            button {\n                onclick: move |_| {\n                    window_preferences\n                        .with_mut(|wp| {\n                            use_window().set_always_on_top(!wp.always_on_top);\n                            wp.always_on_top = !wp.always_on_top;\n                        })\n                },\n                width: 100,\n                {\n                    format!(\n                        \"always on top{}\",\n                        if window_preferences.read().always_on_top { \" ✓\" } else { \"\" },\n                    )\n                }\n            }\n        }\n        {\n            exit_button(\n                Duration::from_secs(3),\n                |trigger, delay| {\n                    rsx! {\n                        {\n                            format!(\n                                \"{:0.1?}\",\n                                trigger\n                                    .read()\n                                    .map(|inst| (delay.as_secs_f32() - inst.elapsed().as_secs_f32())),\n                            )\n                        }\n                    }\n                },\n            )\n        }\n    }\n}\n"
  },
  {
    "path": "packages/autofmt/tests/samples/messy_indent.rsx",
    "content": "fn SaveClipboard() -> Element {\n    rsx! {\n        div { class: \"relative w-1/2 {align} max-w-md leading-8\",\n            h2 { class: \"mb-6 text-3xl leading-tight md:text-4xl md:leading-tight lg:text-3xl lg:leading-tight font-heading font-mono font-bold\",\n                \"{title}\"\n            }\n        }\n    };\n\n    rsx! {\n        div {\n            \"hello world\"\n            \"hello world\"\n            \"hello world\"\n        }\n    }\n}\n"
  },
  {
    "path": "packages/autofmt/tests/samples/misplaced.rsx",
    "content": "pub(crate) fn Nav() -> Element {\n    rsx! {\n        SearchModal {}\n        header {\n            class: \"sticky top-0 z-30 bg-white dark:text-gray-200 dark:bg-ideblack border-b dark:border-stone-700 h-16 bg-opacity-80 backdrop-blur-sm\",\n            class: if HIGHLIGHT_NAV_LAYOUT() { \"border border-orange-600 rounded-md\" },\n            div { class: \"lg:py-2 px-2 max-w-screen-2xl mx-auto flex items-center justify-between text-sm leading-6 h-16\",\n                button {\n                    class: \"bg-gray-100 rounded-lg p-2 mr-4 lg:hidden my-3 h-10 flex items-center text-lg z-[100]\",\n                    class: if !SHOW_DOCS_NAV() { \"hidden\" },\n                    onclick: move |_| {\n                        let mut sidebar = SHOW_SIDEBAR.write();\n                        *sidebar = !*sidebar;\n                    },\n                    MaterialIcon {\n                        name: \"menu\",\n                        size: 24,\n                        color: MaterialIconColor::Dark,\n                    }\n                }\n                div { class: \"flex z-50 md:flex-1 px-2\", LinkList {} }\n            }\n        }\n    }\n}\n\n#[component]\nfn SidebarSection(chapter: &'static SummaryItem<BookRoute>) -> Element {\n    let link = chapter.maybe_link()?;\n    let sections = link.nested_items.iter().map(|chapter| {\n        rsx! {\n            SidebarChapter { chapter }\n        }\n    });\n}\n"
  },
  {
    "path": "packages/autofmt/tests/samples/multirsx.rsx",
    "content": "rsx! {\n\n    // hi\n    div {}\n\n    // hi\n    div {\n        \"abcd\"\n        \"ball\"\n        \"s\"\n    }\n\n    //\n    //\n    //\n    div {\n        \"abcd\"\n        \"ball\"\n        \"s\"\n    }\n\n    //\n    //\n    //\n    div {\n        \"abcd\"\n        \"ball\"\n        \"s\"\n\n        //\n        \"asdasd\"\n    }\n}\n"
  },
  {
    "path": "packages/autofmt/tests/samples/nested.rsx",
    "content": "//! some basic test cases with nested rsx!\n\nfn App() -> Element {\n    let mut count = use_signal(|| 0);\n    let mut text = use_signal(|| \"...\".to_string());\n\n    rsx! {\n        div {\n            div { \"hi\" }\n            div {\n                header: rsx! {\n                    div { class: \"max-w-lg lg:max-w-2xl mx-auto mb-16 text-center\",\n                        \"gomg\"\n                        \"hi!!\"\n                        \"womh\"\n                    }\n                },\n                header: rsx! {\n                    div { class: \"max-w-lg lg:max-w-2xl mx-auto mb-16 text-center\",\n                        \"gomg\"\n                        \"hi!!\"\n                        \"womh\"\n                    }\n                },\n                header: rsx! {\n                    div { class: \"max-w-lg lg:max-w-2xl mx-auto mb-16 text-center\",\n                        \"gomg\"\n                        // \"hi!!\"\n                        \"womh\"\n                    }\n                },\n                onclick: move |_| {\n                    rsx! {\n                        div { class: \"max-w-lg lg:max-w-2xl mx-auto mb-16 text-center\",\n                            \"gomg\"\n                            \"hi!!\"\n                            \"womh\"\n                        }\n                    }\n                    println!(\"hi\")\n                },\n                \"hi\"\n            }\n            ContentList { header, content: &BLOG_POSTS, readmore: true }\n        }\n        Component {\n            header: rsx! {\n                h1 { \"hi\" }\n                h1 { \"hi\" }\n            },\n            blah: rsx! {\n                h1 { \"hi\" }\n            },\n            blah: rsx! {\n                h1 { \"hi\" }\n            },\n            blah: rsx! {\n                h1 { \"hi\" }\n            },\n            blah: rsx! { \"hi\" },\n            blah: rsx! {\n                h1 { \"hi\" }\n                Component {\n                    header: rsx! {\n                        Component {\n                            header: rsx! {\n                                div { \"hi\" }\n                                h3 { \"hi\" }\n                                p { \"hi\" }\n                                Component {\n                                    onrender: move |_| {\n                                        count += 1;\n                                        let abc = rsx! {\n                                            div {\n                                                h1 { \"hi\" }\n                                                \"something nested?\"\n                                                Component {\n                                                    onrender: move |_| {\n                                                        count2 += 1;\n                                                        rsx! {\n                                                            div2 {\n                                                                h12 { \"hi\" }\n                                                                \"so22mething nested?\"\n                                                            }\n                                                        }\n                                                    },\n                                                }\n                                            }\n                                        };\n                                        rsx! {\n                                            div {\n                                                h1 { \"hi\" }\n                                                \"something nested?\"\n                                            }\n                                        }\n                                    },\n                                }\n                            },\n                        }\n                    },\n                }\n            },\n            onrender: move |_| {\n                count += 1;\n                rsx! {\n                    div {\n                        h1 { \"hi\" }\n                        \"something nested?\"\n                    }\n                    Component2 {\n                        header2: rsx! {\n                            h1 { \"hi1\" }\n                            h1 { \"hi2\" }\n                        },\n                        onrender2: move |_| {\n                            count2 += 1;\n                            rsx! {\n                                div2 {\n                                    h12 { \"hi\" }\n                                    \"so22mething nested?\"\n                                }\n                            }\n                        },\n                        {\n                            rsx! {\n                                div2 {\n                                    h12 { \"hi\" }\n                                    \"so22mething nested?\"\n                                }\n                            }\n                        }\n                    }\n                }\n            },\n            div {\n                onclick: move |_| {\n                    let val = rsx! {\n                        div {\n                            h1 { \"hi\" }\n                            \"something nested?\"\n                        }\n                    };\n                },\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "packages/autofmt/tests/samples/oneline.rsx",
    "content": "rsx! { \"hello world\" }\n"
  },
  {
    "path": "packages/autofmt/tests/samples/prop_rsx.rsx",
    "content": "fn AvailablePlatforms() -> Element {\n    rsx! {\n        section { class: \"w-full dark:bg-ideblack\",\n            div { class: \"container mx-auto max-w-screen-lg\",\n                div { class: \"relative overflow-x-hidden\",\n                    div { class: \"flex flex-col items-center justify-center text-center max-w-screen-lg mx-auto pb-4\",\n                        h1 { class: \"text-[3.3em] font-bold tracking-tight dark:text-white text-ghdarkmetal pb-4 mb-4 \",\n                            \"One codebase, every platform.\"\n                        }\n                        p { class: \"text-xl text-gray-600 dark:text-gray-400 pb-4 max-w-screen-sm\",\n                            \"Dioxus is a React-inspired library for Rust focused on developer experience. Build fast, beautiful, and fully-featured apps for every platform in less time.\"\n                        }\n                    }\n                    snippets::Snippets {}\n                }\n            }\n            div { class: \"max-w-screen-lg mx-auto pb-8 px-2 md:px-16 dark:text-white\",\n                // div { class: \"max-w-screen-xl mx-auto pb-64 px-16 dark:text-white\",\n                TriShow {\n                    left: None,\n                    center: None,\n                    right: rsx! { \"Build for the web using Rust and WebAssembly. As fast as SolidJS and more robust than React. Integrated hot reloading for instant iterations.\" },\n                    to: Route::Docs {\n                        child: BookRoute::GettingStartedIndex {},\n                    },\n                    title: \"Web with WASM\",\n                }\n                TriShow {\n                    left: None,\n                    center: None,\n                    right: rsx! { \"Lightweight (<2mb) desktop and mobile apps with zero configuration. Choose between WebView or WGPU-enabled renderers. Runs on macOS, Windows, Linux, iOS, and Android.\" },\n                    to: Route::Docs {\n                        child: BookRoute::GettingStartedIndex {},\n                    },\n                    title: \"Desktop and Mobile\",\n                }\n                TriShow {\n                    to: Route::Docs {\n                        child: BookRoute::GettingStartedIndex {},\n                    },\n                    title: \"Terminal User Interfaces\",\n                    right: rsx! { \"Quickly convert any CLI tool to a beautiful interactive user interface with just a few lines of code. Runs anywhere with a terminal.\" },\n                    left: None,\n                    center: None,\n                }\n                TriShow {\n                    to: Route::Docs {\n                        child: BookRoute::GettingStartedIndex {},\n                    },\n                    title: \"Fullstack Apps\",\n                    right: rsx! { \"Pre-render on the server, and hydrate on the client. Perfect lighthouse scores and performance over 1000x better than Node and Python. Perfect for static site generation or fullstack apps.\" },\n                    left: None,\n                    center: None,\n                }\n                TriShow {\n                    to: Route::Docs {\n                        child: BookRoute::GettingStartedIndex {},\n                    },\n                    title: \"LiveView\",\n                    right: rsx! { \"Render your app entirely on the server. Zero backend configuration capable of handling thousands of active clients.\" },\n                    left: None,\n                    center: None,\n                    last: true,\n                }\n            }\n        }\n    }\n}\n\n#[component]\nfn TriShow(\n    left: Element,\n    center: Element,\n    right: Element,\n    title: &'static str,\n    to: Route,\n    last: Option<bool>,\n) -> Element {\n    rsx! {\n        div { class: \"w-full flex flex-row justify-center max-w-screen-lg\",\n            // div { class: \"grow basis-0\", left }\n            TriPadding { last: last.unwrap_or_default(), {center} }\n            div { class: \"grow basis-0\",\n                Link { to: to.clone(),\n                    div { class: \"min-w-lg max-w-screen-md hover:shadow-pop rounded-lg p-8\",\n                        h2 { class: \"text-2xl text-gray-800 font-semibold pb-2 dark:text-gray-100 \",\n                            \"{title}\"\n                        }\n                        {right}\n                    }\n                }\n            }\n        }\n    }\n}\n\n#[component]\nfn TriPadding(children: Element, last: bool) -> Element {\n    rsx!(\n        div { class: \"flex flex-col items-center\",\n            div { class: \"w-0 h-10 border-dashed border border-[#444]\" }\n            IconSplit {}\n\n            if !last {\n                div { class: \"w-0 h-full border-dashed border border-[#444]\", {children} }\n            }\n        }\n    )\n}\n\n#[component]\nfn DeveloperExperience() -> Element {\n    rsx! (\n        section { class: \"pt-36 w-full dark:bg-ideblack dark:text-white\",\n            div { class: \"container mx-auto max-w-screen-2xl\",\n                div { class: \"relative\",\n                    div { class: \"flex flex-col max-w-screen-lg mx-auto pb-20\",\n                        h1 { class: \"text-[3.3em] font-bold tracking-tight items-center justify-center text-center dark:text-white text-ghdarkmetal pb-4 mb-4 \",\n                            \"Redefining developer experience.\"\n                        }\n                        div { class: \"flex flex-row\",\n                            p { class: \"text-xl text-gray-600 dark:text-gray-400 pb-4 max-w-screen-sm w-1/2\",\n                                \"Dioxus is a React-inspired library for Rust that empowers you to quickly build fast, beautiful, and fully-featured apps for every platform.\"\n                            }\n                            p { class: \"text-xl text-gray-600 dark:text-gray-400 pb-4 max-w-screen-sm w-1/2\",\n                                \"Dioxus is a React-inspired library for Rust that empowers you to quickly build fast, beautiful, and fully-featured apps for every platform.\"\n                            }\n                        }\n                    }\n                    div { class: \"max-w-screen-2xl mx-auto flex flex-row\",\n                        div { class: \"w-1/2\" }\n                        div { class: \"w-1/2\",\n                            ExperienceText {\n                                title: \"Integrated Devtools\",\n                                content: \"Hot reloading for instant iteration, automatic code formatting, convert HTML to RSX, and more.\",\n                            }\n                            ExperienceText {\n                                title: \"Minimal configuration\",\n                                content: \"Start projects with `cargo new`. No build scripts or configuration required for development.\",\n                            }\n                            ExperienceText {\n                                title: \"\",\n                                content: \"Strong typing with no runtime overhead. Automatically derive props, forms, API clients, and more.\",\n                            }\n                        }\n                    }\n                }\n            }\n        }\n    )\n}\n\n#[component]\nfn ExperienceText(title: &'static str, content: &'static str) -> Element {\n    rsx!(\n        div { class: \"pb-12\",\n            h3 { class: \"text-2xl text-gray-800 font-semibold pb-2 dark:text-gray-100 \",\n                \"{title}\"\n            }\n            p { \"{content}\" }\n        }\n    )\n}\n\nfn IconSplit() -> Element {\n    rsx! {\n        svg {\n            class: \"mx-auto fill-[#444] dark:fill-white\",\n            version: \"1.1\",\n            view_box: \"0 0 24 24\",\n            width: \"24\",\n            \"data-view-component\": \"true\",\n            \"aria-hidden\": \"true\",\n            height: \"24\",\n            path {\n                stroke_width: \"1.5\",\n                fill_rule: \"evenodd\",\n                d: \"M15.5 11.75a3.5 3.5 0 11-7 0 3.5 3.5 0 017 0zm1.444-.75a5.001 5.001 0 00-9.888 0H2.75a.75.75 0 100 1.5h4.306a5.001 5.001 0 009.888 0h4.306a.75.75 0 100-1.5h-4.306z\",\n            }\n        }\n    }\n}\n\nfn Stats() -> Element {\n    rsx! {\n        section { class: \"py-12 w-full dark:bg-ideblack\",\n            div { class: \"container mx-auto max-w-screen-lg\",\n                div { class: \"relative \",\n                    div { class: \"flex flex-col items-center justify-center text-center max-w-screen-lg mx-auto pb-4\",\n                        // span { class: \"text-xl text-blue-300\", \"Portable\" }\n                        h1 { class: \"text-[3.3em] font-bold tracking-tight dark:text-white text-ghdarkmetal pb-4 mb-4 \",\n                            \"A vibrant, active community.\"\n                        }\n                        p { class: \"text-xl text-gray-600 dark:text-gray-400 pb-4 max-w-screen-sm\",\n                            \"Driven by a large, active, and welcoming community.\"\n                        }\n                    }\n                }\n            }\n            div { class: \"max-w-screen-xl mx-auto py-12 px-2 md:px-16 dark:bg-[#111111] mb-12\",\n                div { class: \"grid grid-cols-2 grid-rows-2 sm:grid-cols-4 sm:grid-rows-1\",\n                    StatsItem { major: \"16k\", minor: \"Stars\" }\n                    StatsItem { major: \"140k\", minor: \"Downloads\" }\n                    StatsItem { major: \"206\", minor: \"Contributors\" }\n                    StatsItem { major: \"1500\", minor: \"Community Projects\" }\n                }\n            }\n\n            a { href: \"https://github.com/dioxuslabs/dioxus/graphs/contributors\",\n                img {\n                    src: \"https://contrib.rocks/image?repo=dioxuslabs/dioxus&max=52&columns=13\",\n                    class: \"mx-auto pb-12\",\n                    alt: \"Dioxus Contributors\",\n                }\n            }\n        }\n    }\n}\n\n#[component]\nfn StatsItem(major: &'static str, minor: &'static str) -> Element {\n    rsx! {\n        div { class: \"text-center shadow mx-2 rounded-lg py-6 border\",\n            div { class: \"text-5xl font-bold text-gray-800 dark:text-gray-100\", {major} }\n            div { class: \"text-xl text-gray-600 dark:text-gray-400\", {minor} }\n        }\n    }\n}\n"
  },
  {
    "path": "packages/autofmt/tests/samples/raw_strings.rsx",
    "content": "rsx! {\n    // Raw text strings\n    button {\n        r#\"Click me\"#\n        r##\"Click me\"##\n        r######\"Click me\"######\n        r#\"dynamic {1}\"#\n    }\n\n    // Raw attribute strings\n    div {\n        width: r#\"10px\"#,\n        height: r##\"{10}px\"##,\n        \"raw-attr\": r###\"raw-attr\"###,\n        \"raw-attr2\": r###\"{100}\"###,\n    }\n}\n"
  },
  {
    "path": "packages/autofmt/tests/samples/reallylong.rsx",
    "content": "pub static Icon3: Component<()> = |cx| {\n    rsx! {\n        svg {\n            class: \"w-6 h-6\",\n            stroke_linecap: \"round\",\n            fill: \"none\",\n            stroke_linejoin: \"round\",\n            stroke_width: \"2\",\n            stroke: \"currentColor\",\n            view_box: \"0 0 24 24\",\n            path { d: \"M20 21v-2a4 4 0 00-4-4H8a4 4 0 00-4 4v2\" }\n            circle { cx: \"12\", cy: \"7\", r: \"4\" }\n        }\n    }\n};\n"
  },
  {
    "path": "packages/autofmt/tests/samples/shorthand.rsx",
    "content": "#[component]\nfn SomePassthru(class: String, id: String, children: Element) -> Element {\n    rsx! {\n        div {\n            // Comments\n            class,\n            \"hello world\"\n        }\n        h1 { class, \"hello world\" }\n\n        h1 { class, {children} }\n\n        h1 { class, id, {children} }\n\n        h1 { class,\n            \"hello world\"\n            {children}\n        }\n\n        h1 { id,\n            \"hello world\"\n            {children}\n        }\n\n        Other { class, children }\n\n        Other { class,\n            \"hello world\"\n            {children}\n        }\n\n        div {\n            // My comment here 1\n            // My comment here 2\n            // My comment here 3\n            // My comment here 4\n            class: \"asdasd\",\n\n            // Comment here\n            onclick: move |_| {\n                let a = 10;\n                let b = 40;\n                let c = 50;\n            },\n\n            // my comment\n\n            // This here\n            \"hi\"\n        }\n\n        // Comment head\n        div { class: \"asd\", \"Jon\" }\n\n        // Comment head\n        div {\n            // Collapse\n            class: \"asd\",\n            \"Jon\"\n        }\n    }\n}\n"
  },
  {
    "path": "packages/autofmt/tests/samples/simple.rsx",
    "content": "rsx! {\n    div { \"hello world!\" }\n    div {\n        \"hello world!\"\n        \"goodbye world!\"\n    }\n\n    // Simple div\n    div { \"hello world!\" }\n\n    // Compression with attributes\n    div { key: \"{a}\", class: \"ban\", style: \"color: red\" }\n\n    // But not too many attributes (3 max)\n    div {\n        id: \"{a}\",\n        class: \"ban\",\n        style: \"color: red\",\n        value: \"{b}\",\n    }\n\n    // Nested one level\n    div {\n        div { \"nested\" }\n    }\n\n    // Nested two level\n    div {\n        div {\n            h1 { \"highly nested\" }\n        }\n    }\n\n    // Anti-Nested two level\n    div {\n        div {\n            div {\n                h1 { \"highly nested\" }\n            }\n        }\n    }\n\n    // Compression\n    h3 { class: \"mb-2 text-xl font-bold\", \"Invite Member\" }\n    a { class: \"text-white\", \"Send invitation\" }\n\n    // Props on tops\n    h3 { class: \"mb-2 text-xl font-bold mb-2 text-xl font-bold mb-2 text-xl font-bold mb-2 text-xl font-bold mb-2 text-xl font-bold\",\n        \"Invite Member\"\n    }\n\n    // No children, minimal props\n    img { class: \"mb-6 mx-auto h-24\", src: \"artemis-assets/images/friends.png\" }\n\n    // One level compression\n    div {\n        a {\n            class: \"py-2 px-3 bg-indigo-500 hover:bg-indigo-600 rounded text-xs text-white\",\n            href: \"#\",\n            \"Send invitation\"\n        }\n    }\n\n    // Components\n    Component { ..Props {} }\n\n    // multiline\n    div {\n        class: \"asdaskjdhaskjdjaslkdjlakdjaslkdjaslkd asdaskjdhaskjdjaslkdjlakdjaslkdjaslkdasdaskjdhaskjdjaslkdjlakdjaslkdjaslkd\",\n        multiple: \"asd\",\n        \"hi\"\n    }\n}\n"
  },
  {
    "path": "packages/autofmt/tests/samples/skip.rsx",
    "content": "/// dont format this component\n#[rustfmt::skip]\n#[component]\nfn SidebarSection() -> Element {\n    rsx! {\n        div {\n            \"hi\" div {} div {}\n        }\n    }\n}\n\n/// dont format this component\n#[component]\nfn SidebarSection() -> Element {\n    // format this\n    rsx! {\n        div { \"hi\" }\n    }\n\n    // and this\n    rsx! {\n        div {\n            \"hi\"\n            div {}\n            div {}\n        }\n    }\n\n    // but not this\n    #[rustfmt::skip]\n    rsx! {\n        div {\n            \"hi\" div {} div {}\n        }\n    }\n}\n"
  },
  {
    "path": "packages/autofmt/tests/samples/spaces.rsx",
    "content": "rsx! {\n    if let Some(Some(record)) = &*records.read_unchecked() {\n        {\n            let (label, value): (Vec<String>, Vec<f64>) = record\n                .iter()\n                .rev()\n                .map(|d| (d.model.clone().expect(\"work\"), d.row_total))\n                .collect();\n            rsx! {\n                BarChart { id: \"bar-plot\".to_string(), x: value, y: label }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "packages/autofmt/tests/samples/staged.rsx",
    "content": "rsx! {\n    div {}\n\n    div { \"hi\" }\n\n    div { class: \"hello\", \"hi\" }\n\n    div { class: \"hello\", glass: \"123\", \"hi\" }\n\n    div { {some_expr} }\n    div {\n        {POSTS.iter().enumerate().map(|(id, post)| rsx! {\n            BlogPostItem { post, id }\n        })}\n    }\n\n    div { class: \"123123123123123123123123123123123123\",\n        {\n            some_really_long_expr_some_really_long_expr_some_really_long_expr_some_really_long_expr_\n        }\n    }\n\n    div { class: \"-my-8 divide-y-2 divide-gray-100\",\n        {POSTS.iter().enumerate().map(|(id, post)| rsx! {\n            BlogPostItem { post, id }\n        })}\n    }\n}\n"
  },
  {
    "path": "packages/autofmt/tests/samples/t2.rsx",
    "content": "rsx! {\n    div {}\n    div {\n        // div {\n    }\n}\n\n"
  },
  {
    "path": "packages/autofmt/tests/samples/tiny.rsx",
    "content": "fn ItWorks() {\n    rsx! {\n        div {\n            div {\n                div {}\n                div {}\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "packages/autofmt/tests/samples/tinynoopt.rsx",
    "content": "rsx! {\n    div {\n        div {}\n        div {}\n    }\n}\n"
  },
  {
    "path": "packages/autofmt/tests/samples/trailing_expr.rsx",
    "content": "fn it_works() {\n    rsx! {\n        div {\n            span {\n                \"Description: \"\n                {package.description.as_deref().unwrap_or(\"❌❌❌❌ missing\")}\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "packages/autofmt/tests/samples.rs",
    "content": "#![allow(deprecated)]\n\nmacro_rules! twoway {\n    (\n        $(\n\n            // doc attrs\n            $( #[doc = $doc:expr] )*\n            $name:ident,\n        )*\n    ) => {\n        $(\n            $( #[doc = $doc] )*\n            #[test]\n            fn $name() {\n                let src = include_str!(concat!(\"./samples/\", stringify!($name), \".rsx\"));\n                let formatted = dioxus_autofmt::fmt_file(src, Default::default());\n                let out = dioxus_autofmt::apply_formats(src, formatted);\n                // normalize line endings\n                let out = out.replace(\"\\r\", \"\");\n                let src = src.replace(\"\\r\", \"\");\n                pretty_assertions::assert_eq!(&src, &out);\n            }\n        )*\n    };\n}\ntwoway![\n    attributes,\n    basic_expr,\n    collapse_expr,\n    comments,\n    commentshard,\n    complex,\n    docsite,\n    emoji,\n    fat_exprs,\n    ifchain_forloop,\n    immediate_expr,\n    key,\n    letsome,\n    long_exprs,\n    long,\n    manual_props,\n    many_exprs,\n    messy_indent,\n    misplaced,\n    multirsx,\n    nested,\n    raw_strings,\n    reallylong,\n    shorthand,\n    simple,\n    skip,\n    spaces,\n    staged,\n    t2,\n    tiny,\n    tinynoopt,\n    trailing_expr,\n    oneline,\n    prop_rsx,\n    asset,\n    collapse,\n    expr_on_conditional,\n    blank_lines,\n    blank_lines_preserved,\n];\n"
  },
  {
    "path": "packages/autofmt/tests/srcless/asset.rsx",
    "content": "parse_quote! {\n    img { src: asset!(\"/assets/logos/123.png\") }\n    img { src: asset!(\"/assets/logos/456.png\") }\n    img { src: asset!(\"/assets/logos/789.png\") }\n}\n"
  },
  {
    "path": "packages/autofmt/tests/srcless/basic_expr.rsx",
    "content": "parse_quote! {\n    div {\n        \"hi\"\n        {children}\n    }\n    Fragment {\n        Fragment {\n            Fragment {\n                Fragment {\n                    Fragment {\n                        div { \"Finally have a real node!\" }\n                    }\n                }\n            }\n        }\n    }\n    div { class, \"hello world\" }\n    h1 { class, \"hello world\" }\n    h1 { class, {children} }\n    h1 { class, id, {children} }\n    h1 { class,\n        \"hello world\"\n        {children}\n    }\n    h1 { id,\n        \"hello world\"\n        {children}\n    }\n    Other { class, children }\n    Other { class,\n        \"hello world\"\n        {children}\n    }\n    div {\n        class: \"asdasd\",\n        onclick: move |_| {\n            let a = 10;\n            let b = 40;\n            let c = 50;\n        },\n        src1: asset!(\"/123.png\"),\n        src2: asset!(\"/456.png\"),\n        src3: asset!(\"/789.png\"),\n        src4: asset!(\"/101112.png\", WithOptions),\n        \"hi\"\n    }\n    p {\n        img {\n            src: asset!(\"/example-book/assets1/logo.png\", AssetOptions::image().with_avif()),\n            alt: \"some_local1\",\n            title: \"\",\n        }\n        img {\n            src: asset!(\"/example-book/assets2/logo.png\", AssetOptions::image().with_avif()),\n            alt: \"some_local2\",\n            title: \"\",\n        }\n    }\n    div { class: \"asd\", \"Jon\" }\n}\n"
  },
  {
    "path": "packages/autofmt/tests/srcless.rs",
    "content": "use dioxus_rsx::CallBody;\nuse syn::parse_quote;\n\nmacro_rules! test_case {\n    (\n        $path:literal\n    ) => {\n        works(include!($path), include_str!($path))\n    };\n}\n\n/// Ensure we can write RSX blocks without a source file\n///\n/// Useful in code generation use cases where we still want formatted code.\n#[test]\nfn write_block_out() {\n    test_case!(\"./srcless/basic_expr.rsx\");\n    test_case!(\"./srcless/asset.rsx\");\n}\n\nfn works(parsed: CallBody, src: &str) {\n    let block = dioxus_autofmt::write_block_out(&parsed).unwrap();\n    let src = src\n        .trim()\n        .trim_start_matches(\"parse_quote! {\")\n        .trim_end_matches('}');\n\n    // normalize line endings for windows tests to pass\n    pretty_assertions::assert_eq!(\n        block.trim().lines().collect::<Vec<_>>().join(\"\\n\"),\n        src.trim().lines().collect::<Vec<_>>().join(\"\\n\")\n    );\n}\n"
  },
  {
    "path": "packages/autofmt/tests/wrong/comments-4sp.rsx",
    "content": "rsx! {\n    div {\n        // Comments\n        class: \"asdasd\",\n        \"hello world\"\n    }\n}\n"
  },
  {
    "path": "packages/autofmt/tests/wrong/comments-4sp.wrong.rsx",
    "content": "rsx! {\n    div {\n        // Comments\n        class: \"asdasd\", \"hello world\" }\n}\n"
  },
  {
    "path": "packages/autofmt/tests/wrong/comments-attributes-4sp.rsx",
    "content": "//! hello\n\n#[component]\nfn App() -> Element {\n    rsx! {\n        Stylesheet { href: MAIN_CSS }\n        div { id: \"hero\",\n            // ss\n            // s\n            // asd\n            img {\n                // blah\n                src: HEADER_SVG,\n                id: \"header\",\n            }\n            div { id: \"links\",\n                a { href: \"https://discord.gg/XgGxMSkvUM\", \"👋 Community Discord\" }\n                div {\n                    // blah\n                    src: HEADER_SVG,\n                    id: \"header\",\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "packages/autofmt/tests/wrong/comments-attributes-4sp.wrong.rsx",
    "content": "//! hello\n\n#[component]\nfn App() -> Element {\n    rsx! {\n        Stylesheet { href: MAIN_CSS }\n        div { id: \"hero\",\n            // ss\n            // s\n            // asd\n            img {\n                // blah\n                src: HEADER_SVG, id: \"header\" }\n            div { id: \"links\",\n                a { href: \"https://discord.gg/XgGxMSkvUM\", \"👋 Community Discord\" }\n                div {\n                    // blah\n\n\n\n                    src: HEADER_SVG, id: \"header\"\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "packages/autofmt/tests/wrong/comments-big.rsx",
    "content": "pub fn MediaPlayerControls() -> Element {\n    rsx! {\n        div {\n            // Control Panel Row\n            nav { class: \"h-12 border-b border-gray-200 dark:border-[#222] flex items-center justify-between px-6 shrink-0 bg-gray-50 dark:bg-[#000000]\",\n                // Right: Playback controls + Settings\n                div { class: \"flex items-center gap-2\",\n                    // Playback buttons - shown only when track is loaded\n                    if let Some(track_id_str) = &current_track_id {\n                        // Get current track from the tracks hashmap\n                        {\n                            let current_track = tracks().get(track_id_str).cloned();\n                            let track_id_owned = track_id_str.clone();\n\n                            rsx! {\n                                // Play and Download buttons\n                                if current_track.is_some() {\n                                    {\n                                        rsx! {\n                                            // Play button - streams audio to device\n                                            button {\n                                                class: \"px-2.5 py-1 hover:bg-blue-50 dark:hover:bg-blue-900/20 rounded transition-colors flex items-center gap-1.5 text-sm text-blue-600 dark:text-blue-400 hover:text-blue-700 dark:hover:text-blue-300 cursor-pointer disabled:opacity-50\",\n                                                title: \"Play track (streams audio)\",\n                                                disabled: is_buffering(),\n                                                onclick: {\n                                                    let tid = track_id_owned.clone();\n                                                    // Capture output device for streaming\n                                                    let output_device = current_track\n                                                        .as_ref()\n                                                        .and_then(|t| t.preferred_output.clone());\n                                                    move |_| {\n                                                        let tid = tid.clone();\n                                                        let output_device = output_device.clone();\n                                                        async move {\n                                                            playback_result.set(None);\n                                                            playback_error.set(None);\n                                                            is_buffering.set(true);\n\n                                                            let track_id: uuid::Uuid = tid.parse().unwrap_or_default();\n                                                            let request = StreamAudioRequest {};\n\n                                                            match start_audio_stream(track_id, request).await {\n                                                                Ok(stream) => {\n                                                                    // Use output device if available, otherwise fall back to default\n                                                                    let device = output_device\n                                                                        .map(|d| format!(\"{}/\", d))\n                                                                        .unwrap_or_else(|| \"/dev/audio/default\".to_string());\n\n                                                                    // Connect to the audio stream endpoint\n                                                                    let stream_url = format!(\n                                                                        \"audio://stream/{}@{}{}\",\n                                                                        stream.session_token,\n                                                                        stream.server_host,\n                                                                        device,\n                                                                    );\n                                                                    // Open the audio stream\n                                                                    #[cfg(feature = \"web\")]\n                                                                    if let Some(window) = web_sys::window() {\n                                                                        let _ = window.location().set_href(&stream_url);\n                                                                    }\n                                                                    #[cfg(not(feature = \"web\"))]\n                                                                    let _ = stream_url;\n                                                                }\n                                                                Err(e) => {\n                                                                    playback_error\n                                                                        .set(Some(format!(\"Failed to start stream: {}\", e)));\n                                                                    show_error_modal.set(true);\n                                                                }\n                                                            }\n                                                            is_buffering.set(false);\n                                                        }\n                                                    }\n                                                },\n                                                Icon {\n                                                    name: \"play_arrow\",\n                                                    size: 16,\n                                                    color: IconColor::Custom(\"#2563eb\".to_string()),\n                                                }\n                                                if is_buffering() {\n                                                    \"Buffering...\"\n                                                } else if SHOW_CONTROL_TEXT {\n                                                    \"Play\"\n                                                }\n                                            }\n\n                                            // Download button - downloads track locally\n                                            button {\n                                                class: \"px-2.5 py-1 hover:bg-gray-100 dark:hover:bg-[#252525] rounded transition-colors flex items-center gap-1.5 text-sm text-gray-600 dark:text-[#888] hover:text-gray-900 dark:hover:text-white cursor-pointer disabled:opacity-50\",\n                                                title: \"Download track for offline playback\",\n                                                disabled: is_buffering(),\n                                                onclick: {\n                                                    let tid = track_id_owned.clone();\n                                                    move |_| {\n                                                        let tid = tid.clone();\n                                                        async move {\n                                                            playback_result.set(None);\n                                                            playback_error.set(None);\n                                                            is_buffering.set(true);\n                                                            show_download_modal.set(true);\n\n                                                            let track_id: uuid::Uuid = tid.parse().unwrap_or_default();\n                                                            let request = DownloadTrackRequest {};\n\n                                                            match download_track(track_id, request).await {\n                                                                Ok(download) => {\n                                                                    playback_result.set(Some(download));\n                                                                }\n                                                                Err(e) => {\n                                                                    playback_error\n                                                                        .set(Some(format!(\"Failed to download: {}\", e)));\n                                                                }\n                                                            }\n                                                            is_buffering.set(false);\n                                                        }\n                                                    }\n                                                },\n                                                Icon {\n                                                    name: \"download\",\n                                                    size: 16,\n                                                    color: IconColor::Custom(\"#666\".to_string()),\n                                                }\n                                                if is_buffering() {\n                                                    \"Downloading...\"\n                                                } else if SHOW_CONTROL_TEXT {\n                                                    \"Download\"\n                                                }\n                                            }\n                                        }\n                                    }\n                                }\n\n                                // Fullscreen mode toggle\n                                button {\n                                    class: \"px-2.5 py-1 hover:bg-gray-100 dark:hover:bg-[#252525] rounded transition-colors flex items-center gap-1.5 text-sm text-gray-600 dark:text-[#888] hover:text-gray-900 dark:hover:text-white mr-2 pr-3 border-r border-gray-200 dark:border-[#333] cursor-pointer\",\n                                    title: if (ctx.fullscreen)() { \"Exit fullscreen\" } else { \"Enter fullscreen\" },\n                                    onclick: move |_| ctx.fullscreen.set(!(ctx.fullscreen)()),\n                                    Icon {\n                                        name: if (ctx.fullscreen)() { \"fullscreen_exit\" } else { \"fullscreen\" },\n                                        size: 16,\n                                        color: IconColor::Custom(\"#666\".to_string()),\n                                    }\n                                    if SHOW_CONTROL_TEXT {\n                                        if (ctx.fullscreen)() {\n                                            \"Exit Fullscreen\"\n                                        } else {\n                                            \"Fullscreen\"\n                                        }\n                                    }\n                                }\n\n                                div { class: \"flex items-center gap-1 mr-2 pr-3 border-r border-gray-200 dark:border-[#333]\",\n                                    button {\n                                        class: \"px-2 py-1 hover:bg-gray-100 dark:hover:bg-[#252525] rounded transition-colors flex items-center gap-1.5 text-sm text-gray-600 dark:text-[#888] hover:text-gray-900 dark:hover:text-white cursor-pointer\",\n                                        title: \"Add to playlist\",\n                                        onclick: move |_| show_playlist_modal.set(true),\n                                        Icon {\n                                            name: \"playlist_add\",\n                                            size: 16,\n                                            color: IconColor::Custom(\"#666\".to_string()),\n                                        }\n                                        if SHOW_CONTROL_TEXT {\n                                            \"Add to Playlist\"\n                                        }\n                                    }\n\n                                    if let Some(ref track) = current_track {\n                                        {\n                                            let track_id_for_action = track_id_owned.clone();\n                                            match track.status {\n                                                TrackStatus::Playing { .. } => rsx! {\n                                                    button {\n                                                        class: \"px-2 py-1 hover:bg-gray-100 dark:hover:bg-[#252525] rounded transition-colors flex items-center gap-1.5 text-sm text-gray-600 dark:text-[#888] hover:text-gray-900 dark:hover:text-white cursor-pointer\",\n                                                        title: \"Pause playback\",\n                                                        onclick: move |_| {\n                                                            let tid = track_id_for_action.clone();\n                                                            async move {\n                                                                let track_id: uuid::Uuid = tid.parse().unwrap_or_default();\n                                                                if let Err(e) = pause_track(track_id).await {\n                                                                    error!(\"Failed to pause: {:?}\", e);\n                                                                }\n                                                            }\n                                                        },\n                                                        Icon {\n                                                            name: \"pause\",\n                                                            size: 16,\n                                                            color: IconColor::Custom(\"#666\".to_string()),\n                                                        }\n                                                        if SHOW_CONTROL_TEXT {\n                                                            \"Pause\"\n                                                        }\n                                                    }\n                                                },\n                                                TrackStatus::Paused { .. } => rsx! {\n                                                    button {\n                                                        class: \"px-2 py-1 bg-green-600 hover:bg-green-500 text-white rounded transition-colors flex items-center gap-1.5 text-sm cursor-pointer\",\n                                                        title: \"Resume playback\",\n                                                        onclick: move |_| {\n                                                            let tid = track_id_for_action.clone();\n                                                            async move {\n                                                                let track_id: uuid::Uuid = tid.parse().unwrap_or_default();\n                                                                if let Err(e) = resume_track(track_id).await {\n                                                                    error!(\"Failed to resume: {:?}\", e);\n                                                                }\n                                                            }\n                                                        },\n                                                        Icon { name: \"play_arrow\", size: 16, color: IconColor::Light }\n                                                        if SHOW_CONTROL_TEXT {\n                                                            \"Resume\"\n                                                        }\n                                                    }\n                                                },\n                                                _ => rsx! {},\n                                            }\n                                        }\n                                    }\n\n                                    {\n                                        let track_id_for_stop = track_id_owned.clone();\n                                        rsx! {\n                                            button {\n                                                class: \"px-2 py-1 hover:bg-red-100 dark:hover:bg-red-900/30 rounded transition-colors flex items-center gap-1.5 text-sm text-red-600 dark:text-red-400 cursor-pointer\",\n                                                title: \"Stop playback\",\n                                                onclick: move |_| {\n                                                    let tid = track_id_for_stop.clone();\n                                                    async move {\n                                                        if let Err(e) = stop_track(tid).await {\n                                                            error!(\"Failed to stop: {:?}\", e);\n                                                        }\n                                                    }\n                                                },\n                                                Icon {\n                                                    name: \"stop\",\n                                                    size: 16,\n                                                    color: IconColor::Custom(\"#dc2626\".to_string()),\n                                                }\n                                                if SHOW_CONTROL_TEXT {\n                                                    \"Stop\"\n                                                }\n                                            }\n                                        }\n                                    }\n                                }\n                            }\n                        }\n                    }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "packages/autofmt/tests/wrong/comments-big.wrong.rsx",
    "content": "pub fn MediaPlayerControls() -> Element {\n    rsx! {\n        div {\n            // Control Panel Row\n            nav { class: \"h-12 border-b border-gray-200 dark:border-[#222] flex items-center justify-between px-6 shrink-0 bg-gray-50 dark:bg-[#000000]\",\n                // Right: Playback controls + Settings\n                div { class: \"flex items-center gap-2\",\n                    // Playback buttons - shown only when track is loaded\n                    if let Some(track_id_str) = &current_track_id {\n                        // Get current track from the tracks hashmap\n                        {\n                            let current_track = tracks().get(track_id_str).cloned();\n                            let track_id_owned = track_id_str.clone();\n\n                            rsx! {\n                                // Play and Download buttons\n                                if current_track.is_some() {\n                                    {\n                                        rsx! {\n                                            // Play button - streams audio to device\n                                            button {\n                                                class: \"px-2.5 py-1 hover:bg-blue-50 dark:hover:bg-blue-900/20 rounded transition-colors flex items-center gap-1.5 text-sm text-blue-600 dark:text-blue-400 hover:text-blue-700 dark:hover:text-blue-300 cursor-pointer disabled:opacity-50\",\n                                                title: \"Play track (streams audio)\",\n                                                disabled: is_buffering(),\n                                                onclick: {\n                                                    let tid = track_id_owned.clone();\n                                                    // Capture output device for streaming\n                                                    let output_device = current_track\n                                                        .as_ref()\n                                                        .and_then(|t| t.preferred_output.clone());\n                                                    move |_| {\n                                                        let tid = tid.clone();\n                                                        let output_device = output_device.clone();\n                                                        async move {\n                                                            playback_result.set(None);\n                                                            playback_error.set(None);\n                                                            is_buffering.set(true);\n\n                                                            let track_id: uuid::Uuid = tid.parse().unwrap_or_default();\n                                                            let request = StreamAudioRequest {};\n\n                                                            match start_audio_stream(track_id, request).await {\n                                                                Ok(stream) => {\n                                                                    // Use output device if available, otherwise fall back to default\n                                                                    let device = output_device\n                                                                        .map(|d| format!(\"{}/\", d))\n                                                                        .unwrap_or_else(|| \"/dev/audio/default\".to_string());\n\n                                                                    // Connect to the audio stream endpoint\n                                                                    let stream_url = format!(\n                                                                        \"audio://stream/{}@{}{}\",\n                                                                        stream.session_token,\n                                                                        stream.server_host,\n                                                                        device,\n                                                                    );\n                                                                    // Open the audio stream\n                                                                    #[cfg(feature = \"web\")]\n                                                                    if let Some(window) = web_sys::window() {\n                                                                        let _ = window.location().set_href(&stream_url);\n                                                                    }\n                                                                    #[cfg(not(feature = \"web\"))]\n                                                                    let _ = stream_url;\n                                                                }\n                                                                Err(e) => {\n                                                                    playback_error\n                                                                        .set(Some(format!(\"Failed to start stream: {}\", e)));\n                                                                    show_error_modal.set(true);\n                                                                }\n                                                            }\n                                                            is_buffering.set(false);\n                                                        }\n                                                    }\n                                                },\n                                                Icon {\n                                                    name: \"play_arrow\",\n                                                    size: 16,\n                                                    color: IconColor::Custom(\"#2563eb\".to_string()),\n                                                }\n                                                if is_buffering() {\n                                                    \"Buffering...\"\n                                                } else if SHOW_CONTROL_TEXT {\n                                                    \"Play\"\n                                                }\n                                            }\n\n                                            // Download button - downloads track locally\n                                            button {\n                                                class: \"px-2.5 py-1 hover:bg-gray-100 dark:hover:bg-[#252525] rounded transition-colors flex items-center gap-1.5 text-sm text-gray-600 dark:text-[#888] hover:text-gray-900 dark:hover:text-white cursor-pointer disabled:opacity-50\",\n                                                title: \"Download track for offline playback\",\n                                                disabled: is_buffering(),\n                                                onclick: {\n                                                    let tid = track_id_owned.clone();\n                                                    move |_| {\n                                                        let tid = tid.clone();\n                                                        async move {\n                                                            playback_result.set(None);\n                                                            playback_error.set(None);\n                                                            is_buffering.set(true);\n                                                            show_download_modal.set(true);\n\n                                                            let track_id: uuid::Uuid = tid.parse().unwrap_or_default();\n                                                            let request = DownloadTrackRequest {};\n\n                                                            match download_track(track_id, request).await {\n                                                                Ok(download) => {\n                                                                    playback_result.set(Some(download));\n                                                                }\n                                                                Err(e) => {\n                                                                    playback_error\n                                                                        .set(Some(format!(\"Failed to download: {}\", e)));\n                                                                }\n                                                            }\n                                                            is_buffering.set(false);\n                                                        }\n                                                    }\n                                                },\n                                                Icon {\n                                                    name: \"download\",\n                                                    size: 16,\n                                                    color: IconColor::Custom(\"#666\".to_string()),\n                                                }\n                                                if is_buffering() {\n                                                    \"Downloading...\"\n                                                } else if SHOW_CONTROL_TEXT {\n                                                    \"Download\"\n                                                }\n                                            }\n                                        }\n                                    }\n                                }\n\n                                // Fullscreen mode toggle\n                                button {\n                                    class: \"px-2.5 py-1 hover:bg-gray-100 dark:hover:bg-[#252525] rounded transition-colors flex items-center gap-1.5 text-sm text-gray-600 dark:text-[#888] hover:text-gray-900 dark:hover:text-white mr-2 pr-3 border-r border-gray-200 dark:border-[#333] cursor-pointer\",\n                                    title: if (ctx.fullscreen)() { \"Exit fullscreen\" } else { \"Enter fullscreen\" },\n                                    onclick: move |_| ctx.fullscreen.set(!(ctx.fullscreen)()),\n                                    Icon {\n                                        name: if (ctx.fullscreen)() { \"fullscreen_exit\" } else { \"fullscreen\" },\n                                        size: 16,\n                                        color: IconColor::Custom(\"#666\".to_string()),\n                                    }\n                                    if SHOW_CONTROL_TEXT {\n                                        if (ctx.fullscreen)() {\n                                            \"Exit Fullscreen\"\n                                        } else {\n                                            \"Fullscreen\"\n                                        }\n                                    }\n                                }\n\n                                div { class: \"flex items-center gap-1 mr-2 pr-3 border-r border-gray-200 dark:border-[#333]\",\n                                    button {\n                                        class: \"px-2 py-1 hover:bg-gray-100 dark:hover:bg-[#252525] rounded transition-colors flex items-center gap-1.5 text-sm text-gray-600 dark:text-[#888] hover:text-gray-900 dark:hover:text-white cursor-pointer\",\n                                        title: \"Add to playlist\",\n                                        onclick: move |_| show_playlist_modal.set(true),\n                                        Icon {\n                                            name: \"playlist_add\",\n                                            size: 16,\n                                            color: IconColor::Custom(\"#666\".to_string()),\n                                        }\n                                        if SHOW_CONTROL_TEXT {\n                                            \"Add to Playlist\"\n                                        }\n                                    }\n\n                                    if let Some(ref track) = current_track {\n                                        {\n                                            let track_id_for_action = track_id_owned.clone();\n                                            match track.status {\n                                                TrackStatus::Playing { .. } => rsx! {\n                                                    button {\n                                                        class: \"px-2 py-1 hover:bg-gray-100 dark:hover:bg-[#252525] rounded transition-colors flex items-center gap-1.5 text-sm text-gray-600 dark:text-[#888] hover:text-gray-900 dark:hover:text-white cursor-pointer\",\n                                                        title: \"Pause playback\",\n                                                        onclick: move |_| {\n                                                            let tid = track_id_for_action.clone();\n                                                            async move {\n                                                                let track_id: uuid::Uuid = tid.parse().unwrap_or_default();\n                                                                if let Err(e) = pause_track(track_id).await {\n                                                                    error!(\"Failed to pause: {:?}\", e);\n                                                                }\n                                                            }\n                                                        },\n                                                        Icon {\n                                                            name: \"pause\",\n                                                            size: 16,\n                                                            color: IconColor::Custom(\"#666\".to_string()),\n                                                        }\n                                                        if SHOW_CONTROL_TEXT {\n                                                            \"Pause\"\n                                                        }\n                                                    }\n                                                },\n                                                TrackStatus::Paused { .. } => rsx! {\n                                                    button {\n                                                        class: \"px-2 py-1 bg-green-600 hover:bg-green-500 text-white rounded transition-colors flex items-center gap-1.5 text-sm cursor-pointer\",\n                                                        title: \"Resume playback\",\n                                                        onclick: move |_| {\n                                                            let tid = track_id_for_action.clone();\n                                                            async move {\n                                                                let track_id: uuid::Uuid = tid.parse().unwrap_or_default();\n                                                                if let Err(e) = resume_track(track_id).await {\n                                                                    error!(\"Failed to resume: {:?}\", e);\n                                                                }\n                                                            }\n                                                        },\n                                                        Icon { name: \"play_arrow\", size: 16, color: IconColor::Light }\n                                                        if SHOW_CONTROL_TEXT {\n                                                            \"Resume\"\n                                                        }\n                                                    }\n                                                },\n                                                _ => rsx! {},\n                                            }\n                                        }\n                                    }\n\n                                    {\n                                        let track_id_for_stop = track_id_owned.clone();\n                                        rsx! {\n                                            button {\n                                                class: \"px-2 py-1 hover:bg-red-100 dark:hover:bg-red-900/30 rounded transition-colors flex items-center gap-1.5 text-sm text-red-600 dark:text-red-400 cursor-pointer\",\n                                                title: \"Stop playback\",\n                                                onclick: move |_| {\n                                                    let tid = track_id_for_stop.clone();\n                                                    async move {\n                                                        if let Err(e) = stop_track(tid).await {\n                                                            error!(\"Failed to stop: {:?}\", e);\n                                                        }\n                                                    }\n                                                },\n                                                Icon {\n                                                    name: \"stop\",\n                                                    size: 16,\n                                                    color: IconColor::Custom(\"#dc2626\".to_string()),\n                                                }\n                                                if SHOW_CONTROL_TEXT {\n                                                    \"Stop\"\n                                                }\n                                            }\n                                        }\n                                    }\n                                }\n                            }\n                        }\n                    }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "packages/autofmt/tests/wrong/comments-inline-4sp.rsx",
    "content": "rsx! {\n    div { // Comments\n        class: \"asdasd\",\n        \"hello world\"\n    }\n\n    // todo: fix this case\n    img { src: HEADER_SVG, id: \"header\" }\n}\n"
  },
  {
    "path": "packages/autofmt/tests/wrong/comments-inline-4sp.wrong.rsx",
    "content": "rsx! {\n    div { // Comments\n        class: \"asdasd\", \"hello world\" }\n\n    // todo: fix this case\n    img { src: HEADER_SVG, id: \"header\" }\n}\n"
  },
  {
    "path": "packages/autofmt/tests/wrong/comments-tab.rsx",
    "content": "rsx! {\n\tdiv {\n\t\t// Comments\n\t\tclass: \"asdasd\",\n\t\t\"hello world\"\n\t}\n}\n"
  },
  {
    "path": "packages/autofmt/tests/wrong/comments-tab.wrong.rsx",
    "content": "rsx! {\n\tdiv {\n\t\t// Comments\n\t\tclass: \"asdasd\", \"hello world\" }\n}\n"
  },
  {
    "path": "packages/autofmt/tests/wrong/multi-4sp.rsx",
    "content": "fn app() -> Element {\n    rsx! {\n        div { \"hello world\" }\n    }\n}\n"
  },
  {
    "path": "packages/autofmt/tests/wrong/multi-4sp.wrong.rsx",
    "content": "fn app() -> Element {\n    rsx! { div {\"hello world\" } }\n}\n"
  },
  {
    "path": "packages/autofmt/tests/wrong/multi-tab.rsx",
    "content": "fn app() -> Element {\n    rsx! {\n\t\tdiv { \"hello world\" }\n\t}\n}\n"
  },
  {
    "path": "packages/autofmt/tests/wrong/multi-tab.wrong.rsx",
    "content": "fn app() -> Element {\n    rsx! { div {\"hello world\" } }\n}\n"
  },
  {
    "path": "packages/autofmt/tests/wrong/multiexpr-4sp.rsx",
    "content": "fn ItWorks() {\n    rsx! {\n        div { class: \"flex flex-wrap items-center dark:text-white py-16 border-t font-light\",\n            {left}\n            {right}\n        }\n    }\n}\n"
  },
  {
    "path": "packages/autofmt/tests/wrong/multiexpr-4sp.wrong.rsx",
    "content": "fn ItWorks() {\n    rsx! {\n        div { class: \"flex flex-wrap items-center dark:text-white py-16 border-t font-light\", {left}, {right} }\n    }\n}\n"
  },
  {
    "path": "packages/autofmt/tests/wrong/multiexpr-many.rsx",
    "content": "#![allow(dead_code, unused)]\nuse dioxus::desktop::use_window;\nuse dioxus::prelude::*;\nuse std::{\n    process::exit,\n    time::{Duration, Instant},\n};\nuse tokio::time::sleep;\n\nfn main() {\n    dioxus::LaunchBuilder::desktop().launch(app);\n}\n\nstruct WindowPreferences {\n    always_on_top: bool,\n    with_decorations: bool,\n    exiting: Option<Instant>,\n}\n\nimpl Default for WindowPreferences {\n    fn default() -> Self {\n        Self {\n            with_decorations: true,\n            always_on_top: false,\n            exiting: None,\n        }\n    }\n}\n\nimpl WindowPreferences {\n    fn new() -> Self {\n        Self::default()\n    }\n}\n\n#[derive(Default)]\nstruct Timer {\n    hours: u8,\n    minutes: u8,\n    seconds: u8,\n    started_at: Option<Instant>,\n}\n\nimpl Timer {\n    fn new() -> Self {\n        Self::default()\n    }\n\n    fn duration(&self) -> Duration {\n        Duration::from_secs(\n            (self.hours as u64 * 60 + self.minutes as u64) * 60 + self.seconds as u64,\n        )\n    }\n}\n\nconst UPD_FREQ: Duration = Duration::from_millis(100);\n\nfn exit_button(\n    delay: Duration,\n    label: fn(Signal<Option<Instant>>, Duration) -> Option<VNode>,\n) -> Element {\n    let mut trigger: Signal<Option<Instant>> = use_signal(|| None);\n    use_future(move || async move {\n        loop {\n            sleep(UPD_FREQ).await;\n            if let Some(true) = trigger.read().map(|e| e.elapsed() > delay) {\n                exit(0);\n            }\n        }\n    });\n    let stuff: Option<VNode> = rsx! {\n        button {\n            onmouseup: move |_| {\n                trigger.set(None);\n            },\n            onmousedown: move |_| {\n                trigger.set(Some(Instant::now()));\n            },\n            width: 100,\n            {label(trigger, delay)}\n        }\n    };\n    stuff\n}\n\nfn app() -> Element {\n    let mut timer = use_signal(Timer::new);\n    let mut window_preferences = use_signal(WindowPreferences::new);\n\n    use_future(move || async move {\n        loop {\n            sleep(UPD_FREQ).await;\n            timer.with_mut(|t| {\n                if let Some(started_at) = t.started_at {\n                    if t.duration().saturating_sub(started_at.elapsed()) == Duration::ZERO {\n                        t.started_at = None;\n                    }\n                }\n            });\n        }\n    });\n\n    rsx! {\n        div {\n            {\n                let millis = timer\n                    .with(|t| {\n                        t\n                            .duration()\n                            .saturating_sub(\n                                t.started_at.map(|x| x.elapsed()).unwrap_or(Duration::ZERO),\n                            )\n                            .as_millis()\n                    });\n                format!(\n                    \"{:02}:{:02}:{:02}.{:01}\",\n                    millis / 1000 / 3600 % 3600,\n                    millis / 1000 / 60 % 60,\n                    millis / 1000 % 60,\n                    millis / 100 % 10,\n                )\n            }\n        }\n        div {\n            input {\n                r#type: \"number\",\n                min: 0,\n                max: 99,\n                value: format!(\"{:02}\", timer.read().hours),\n                oninput: move |e| {\n                    timer.write().hours = e.value().parse().unwrap_or(0);\n                },\n            }\n\n            input {\n                r#type: \"number\",\n                min: 0,\n                max: 59,\n                value: format!(\"{:02}\", timer.read().minutes),\n                oninput: move |e| {\n                    timer.write().minutes = e.value().parse().unwrap_or(0);\n                },\n            }\n\n            input {\n                r#type: \"number\",\n                min: 0,\n                max: 59,\n                value: format!(\"{:02}\", timer.read().seconds),\n                oninput: move |e| {\n                    timer.write().seconds = e.value().parse().unwrap_or(0);\n                },\n            }\n        }\n\n        button {\n            id: \"start_stop\",\n            onclick: move |_| {\n                timer\n                    .with_mut(|t| {\n                        t.started_at = if t.started_at.is_none() {\n                            Some(Instant::now())\n                        } else {\n                            None\n                        };\n                    })\n            },\n            {timer.with(|t| if t.started_at.is_none() { \"Start\" } else { \"Stop\" })}\n        }\n        div { id: \"app\",\n            button {\n                onclick: move |_| {\n                    let decorations = window_preferences.read().with_decorations;\n                    use_window().set_decorations(!decorations);\n                    window_preferences.write().with_decorations = !decorations;\n                },\n                {\n                    format!(\n                        \"with decorations{}\",\n                        if window_preferences.read().with_decorations { \" ✓\" } else { \"\" },\n                    )\n                        .to_string()\n                }\n            }\n            button {\n                onclick: move |_| {\n                    window_preferences\n                        .with_mut(|wp| {\n                            use_window().set_always_on_top(!wp.always_on_top);\n                            wp.always_on_top = !wp.always_on_top;\n                        })\n                },\n                width: 100,\n                {\n                    format!(\n                        \"always on top{}\",\n                        if window_preferences.read().always_on_top { \" ✓\" } else { \"\" },\n                    )\n                }\n            }\n        }\n        {exit_button(Duration::from_secs(3), |trigger, delay| rsx! {\n            {\n                format!(\n                    \"{:0.1?}\",\n                    trigger\n                        .read()\n                        .map(|inst| (delay.as_secs_f32() - inst.elapsed().as_secs_f32())),\n                )\n            }\n        })}\n    }\n}\n"
  },
  {
    "path": "packages/autofmt/tests/wrong/multiexpr-many.wrong.rsx",
    "content": "#![allow(dead_code, unused)]\nuse dioxus::desktop::use_window;\nuse dioxus::prelude::*;\nuse std::{\n    process::exit,\n    time::{Duration, Instant},\n};\nuse tokio::time::sleep;\n\nfn main() {\n    dioxus::LaunchBuilder::desktop().launch(app);\n}\n\nstruct WindowPreferences {\n    always_on_top: bool,\n    with_decorations: bool,\n    exiting: Option<Instant>,\n}\n\nimpl Default for WindowPreferences {\n    fn default() -> Self {\n        Self {\n            with_decorations: true,\n            always_on_top: false,\n            exiting: None,\n        }\n    }\n}\n\nimpl WindowPreferences {\n    fn new() -> Self {\n        Self::default()\n    }\n}\n\n#[derive(Default)]\nstruct Timer {\n    hours: u8,\n    minutes: u8,\n    seconds: u8,\n    started_at: Option<Instant>,\n}\n\nimpl Timer {\n    fn new() -> Self {\n        Self::default()\n    }\n\n    fn duration(&self) -> Duration {\n        Duration::from_secs(\n            (self.hours as u64 * 60 + self.minutes as u64) * 60 + self.seconds as u64,\n        )\n    }\n}\n\nconst UPD_FREQ: Duration = Duration::from_millis(100);\n\nfn exit_button(\n    delay: Duration,\n    label: fn(Signal<Option<Instant>>, Duration) -> Option<VNode>,\n) -> Element {\n    let mut trigger: Signal<Option<Instant>> = use_signal(|| None);\n    use_future(move || async move {\n        loop {\n            sleep(UPD_FREQ).await;\n            if let Some(true) = trigger.read().map(|e| e.elapsed() > delay) {\n                exit(0);\n            }\n        }\n    });\n    let stuff: Option<VNode> = rsx! {\n        button {\n            onmouseup: move |_| {\n                trigger.set(None);\n            },\n            onmousedown: move |_| {\n                trigger.set(Some(Instant::now()));\n            },\n            width: 100,\n            {label(trigger, delay)},\n        }\n    };\n    stuff\n}\n\nfn app() -> Element {\n    let mut timer = use_signal(Timer::new);\n    let mut window_preferences = use_signal(WindowPreferences::new);\n\n    use_future(move || async move {\n        loop {\n            sleep(UPD_FREQ).await;\n            timer.with_mut(|t| {\n                if let Some(started_at) = t.started_at {\n                    if t.duration().saturating_sub(started_at.elapsed()) == Duration::ZERO {\n                        t.started_at = None;\n                    }\n                }\n            });\n        }\n    });\n\n    rsx! {\n        div {{\n            let millis = timer.with(|t| t.duration().saturating_sub(t.started_at.map(|x| x.elapsed()).unwrap_or(Duration::ZERO)).as_millis());\n            format!(\"{:02}:{:02}:{:02}.{:01}\",\n                    millis / 1000 / 3600 % 3600,\n                    millis / 1000 / 60 % 60,\n                    millis / 1000 % 60,\n                    millis / 100 % 10)\n        }}\n        div {\n            input { r#type: \"number\", min: 0, max: 99, value: format!(\"{:02}\",timer.read().hours), oninput: move |e| {\n                timer.write().hours = e.value().parse().unwrap_or(0);\n                }\n            }\n\n            input { r#type: \"number\", min: 0, max: 59, value: format!(\"{:02}\",timer.read().minutes), oninput: move |e| {\n                timer.write().minutes = e.value().parse().unwrap_or(0);\n                }\n            }\n\n            input { r#type: \"number\", min: 0, max: 59, value: format!(\"{:02}\",timer.read().seconds), oninput: move |e| {\n                timer.write().seconds = e.value().parse().unwrap_or(0);\n                }\n            }\n        }\n\n        button {\n            id: \"start_stop\",\n            onclick: move |_| timer.with_mut(|t| t.started_at = if t.started_at.is_none() { Some(Instant::now()) } else { None } ),\n            { timer.with(|t| if t.started_at.is_none() { \"Start\" } else { \"Stop\" }) },\n        }\n        div { id: \"app\",\n            button { onclick: move |_| {\n                let decorations = window_preferences.read().with_decorations;\n                use_window().set_decorations(!decorations);\n                window_preferences.write().with_decorations = !decorations;\n                }, {\n                    format!(\"with decorations{}\", if window_preferences.read().with_decorations { \" ✓\" } else { \"\" }).to_string()\n                }\n            }\n            button {\n                onclick: move |_| {\n                    window_preferences.with_mut(|wp| {\n                        use_window().set_always_on_top(!wp.always_on_top);\n                        wp.always_on_top = !wp.always_on_top;\n                    })},\n                width: 100,\n                {\n                    format!(\"always on top{}\", if window_preferences.read().always_on_top { \" ✓\" } else { \"\" })\n                }\n            }\n        }\n        {\n            exit_button(\n                Duration::from_secs(3),\n                |trigger, delay| rsx! {\n                    {format!(\"{:0.1?}\", trigger.read().map(|inst| (delay.as_secs_f32() - inst.elapsed().as_secs_f32()))) }\n                }\n            )\n        }\n    }\n}\n"
  },
  {
    "path": "packages/autofmt/tests/wrong/multiexpr-tab.rsx",
    "content": "fn ItWorks() {\n\trsx! {\n\t\tdiv { class: \"flex flex-wrap items-center dark:text-white py-16 border-t font-light\",\n\t\t\t{left}\n\t\t\t{right}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "packages/autofmt/tests/wrong/multiexpr-tab.wrong.rsx",
    "content": "fn ItWorks() {\n\trsx! {\n\t\tdiv { class: \"flex flex-wrap items-center dark:text-white py-16 border-t font-light\", {left}, {right} }\n\t}\n}\n"
  },
  {
    "path": "packages/autofmt/tests/wrong/oneline-expand.rsx",
    "content": "fn main() {\n    rsx! {\n        button {\n            id: \"start_stop\",\n            onclick: move |_| {\n                timer\n                    .with_mut(|t| {\n                        t.started_at = if t.started_at.is_none() {\n                            Some(Instant::now())\n                        } else {\n                            None\n                        };\n                    })\n            },\n        }\n    }\n}\n"
  },
  {
    "path": "packages/autofmt/tests/wrong/oneline-expand.wrong.rsx",
    "content": "fn main() {\n    rsx! {\n        button {\n            id: \"start_stop\",\n            onclick: move |_| timer.with_mut(|t| t.started_at = if t.started_at.is_none() { Some(Instant::now()) } else { None } )\n        }\n    }\n}\n"
  },
  {
    "path": "packages/autofmt/tests/wrong/shortened.rsx",
    "content": "rsx! {\n    Chapter { chapter }\n\n    div { class }\n}\n"
  },
  {
    "path": "packages/autofmt/tests/wrong/shortened.wrong.rsx",
    "content": "rsx! {\n    Chapter { chapter: chapter }\n\n    div { class: class }\n}\n"
  },
  {
    "path": "packages/autofmt/tests/wrong/simple-combo-expr.rsx",
    "content": "fn main() {\n    rsx! {\n        div {\n            {\n                let millis = timer\n                    .with(|t| {\n                        t\n                            .duration()\n                            .saturating_sub(\n                                t.started_at.map(|x| x.elapsed()).unwrap_or(Duration::ZERO),\n                            )\n                            .as_millis()\n                    });\n                format!(\n                    \"{:02}:{:02}:{:02}.{:01}\",\n                    millis / 1000 / 3600 % 3600,\n                    millis / 1000 / 60 % 60,\n                    millis / 1000 % 60,\n                    millis / 100 % 10,\n                )\n            }\n        }\n        div {\n            input {\n                r#type: \"number\",\n                min: 0,\n                max: 99,\n                value: format!(\"{:02}\", timer.read().hours),\n                oninput: move |e| {\n                    timer.write().hours = e.value().parse().unwrap_or(0);\n                },\n            }\n            // some comment\n            input {\n                r#type: \"number\",\n                min: 0,\n                max: 99,\n                value: format!(\"{:02}\", timer.read().hours),\n                oninput: move |e| {\n                    timer.write().hours = e.value().parse().unwrap_or(0);\n                },\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "packages/autofmt/tests/wrong/simple-combo-expr.wrong.rsx",
    "content": "fn main() {\n    rsx! {\n        div {{\n            let millis = timer.with(|t| t.duration().saturating_sub(t.started_at.map(|x| x.elapsed()).unwrap_or(Duration::ZERO)).as_millis());\n            format!(\"{:02}:{:02}:{:02}.{:01}\",\n                    millis / 1000 / 3600 % 3600,\n                    millis / 1000 / 60 % 60,\n                    millis / 1000 % 60,\n                    millis / 100 % 10)\n        }}\n        div {\n            input { r#type: \"number\", min: 0, max: 99, value: format!(\"{:02}\",timer.read().hours), oninput: move |e| {\n                timer.write().hours = e.value().parse().unwrap_or(0);\n                }\n            }\n            // some comment\n            input { r#type: \"number\", min: 0, max: 99, value: format!(\"{:02}\",timer.read().hours), oninput: move |e| {\n                timer.write().hours = e.value().parse().unwrap_or(0);\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "packages/autofmt/tests/wrong/skipfail.rsx",
    "content": "/// dont format this component\n#[rustfmt::skip]\n#[component]\nfn SidebarSection() -> Element {\n    rsx! {\n        div {\n            \"hi\" div {} div {}\n        }\n    }\n}\n\n/// dont format this component\n#[component]\nfn SidebarSection() -> Element {\n    // format this\n    rsx! {\n        div { \"hi\" }\n    }\n\n    // and this\n    rsx! {\n        div {\n            \"hi\"\n            div {}\n            div {}\n        }\n    }\n\n    // but not this\n    #[rustfmt::skip]\n    rsx! {\n        div {\n            \"hi\" div {} div {}\n        }\n    }\n}\n"
  },
  {
    "path": "packages/autofmt/tests/wrong/skipfail.wrong.rsx",
    "content": "/// dont format this component\n#[rustfmt::skip]\n#[component]\nfn SidebarSection() -> Element {\n    rsx! {\n        div {\n            \"hi\" div {} div {}\n        }\n    }\n}\n\n/// dont format this component\n#[component]\nfn SidebarSection() -> Element {\n    // format this\n    rsx! {\n        div { \"hi\" }\n    }\n\n    // and this\n    rsx! {\n        div { \"hi\" div {} div {} }\n    }\n\n    // but not this\n    #[rustfmt::skip]\n    rsx! {\n        div {\n            \"hi\" div {} div {}\n        }\n    }\n}\n"
  },
  {
    "path": "packages/autofmt/tests/wrong/syntax_error.rsx",
    "content": "fn app() -> Element {\n    rsx! {\n        let x=\"hello world\";\n        div { \"hello world\" }\n    }\n}\n"
  },
  {
    "path": "packages/autofmt/tests/wrong/syntax_error.wrong.rsx",
    "content": "fn app() -> Element {\n    rsx! {\n        let x=\"hello world\";\n        div { \"hello world\" }\n    }\n}\n"
  },
  {
    "path": "packages/autofmt/tests/wrong.rs",
    "content": "#![allow(deprecated)]\n\nuse dioxus_autofmt::{IndentOptions, IndentType};\n\nmacro_rules! twoway {\n    ($val:literal => $name:ident ($indent:expr)) => {\n        #[test]\n        fn $name() {\n            let src_right = include_str!(concat!(\"./wrong/\", $val, \".rsx\"));\n            let src_wrong = include_str!(concat!(\"./wrong/\", $val, \".wrong.rsx\"));\n\n            let parsed = syn::parse_file(src_wrong)\n                .expect(\"fmt_file should only be called on valid syn::File files\");\n\n            let formatted =\n                dioxus_autofmt::try_fmt_file(src_wrong, &parsed, $indent).unwrap_or_default();\n            let out = dioxus_autofmt::apply_formats(src_wrong, formatted);\n\n            // normalize line endings\n            let out = out.replace(\"\\r\", \"\");\n            let src_right = src_right.replace(\"\\r\", \"\");\n\n            pretty_assertions::assert_eq!(&src_right, &out);\n        }\n    };\n}\n\ntwoway!(\"comments-4sp\" => comments_4sp (IndentOptions::new(IndentType::Spaces, 4, false)));\ntwoway!(\"comments-tab\" => comments_tab (IndentOptions::new(IndentType::Tabs, 4, false)));\n\ntwoway!(\"multi-4sp\" => multi_4sp (IndentOptions::new(IndentType::Spaces, 4, false)));\ntwoway!(\"multi-tab\" => multi_tab (IndentOptions::new(IndentType::Tabs, 4, false)));\n\ntwoway!(\"multiexpr-4sp\" => multiexpr_4sp (IndentOptions::new(IndentType::Spaces, 4, false)));\ntwoway!(\"multiexpr-tab\" => multiexpr_tab (IndentOptions::new(IndentType::Tabs, 4, false)));\ntwoway!(\"multiexpr-many\" => multiexpr_many (IndentOptions::new(IndentType::Spaces, 4, false)));\ntwoway!(\"simple-combo-expr\" => simple_combo_expr (IndentOptions::new(IndentType::Spaces, 4, false)));\ntwoway!(\"oneline-expand\" => online_expand (IndentOptions::new(IndentType::Spaces, 4, false)));\ntwoway!(\"shortened\" => shortened (IndentOptions::new(IndentType::Spaces, 4, false)));\ntwoway!(\"syntax_error\" => syntax_error (IndentOptions::new(IndentType::Spaces, 4, false)));\ntwoway!(\"skipfail\" => skipfail (IndentOptions::new(IndentType::Spaces, 4, false)));\ntwoway!(\"comments-inline-4sp\" => comments_inline_4sp (IndentOptions::new(IndentType::Spaces, 4, false)));\ntwoway!(\"comments-attributes-4sp\" => comments_attributes_4sp (IndentOptions::new(IndentType::Spaces, 4, false)));\ntwoway!(\"comments-big\" => comments_big (IndentOptions::new(IndentType::Spaces, 4, false)));\n"
  },
  {
    "path": "packages/check/Cargo.toml",
    "content": "[package]\nname = \"dioxus-check\"\nversion = { workspace = true }\nedition = \"2021\"\nauthors = [\"Dioxus Labs\"]\ndescription = \"Checks Dioxus RSX files for issues\"\nlicense = \"MIT/Apache-2.0\"\nrepository = \"https://github.com/DioxusLabs/dioxus/\"\nhomepage = \"https://dioxuslabs.com\"\nkeywords = [\"web\", \"desktop\", \"mobile\", \"gui\", \"wasm\"]\n\n[dependencies]\nproc-macro2 = { workspace = true, features = [\"span-locations\"] }\nquote = {workspace = true }\nsyn = { workspace = true, features = [\"full\", \"extra-traits\", \"visit\"] }\nowo-colors = { workspace = true, features = [\"supports-colors\"] }\n\n[dev-dependencies]\nindoc = \"2.0.6\"\npretty_assertions = { workspace = true }\n"
  },
  {
    "path": "packages/check/README.md",
    "content": "# dioxus-check\n\n[![Crates.io][crates-badge]][crates-url]\n[![MIT licensed][mit-badge]][mit-url]\n[![Build Status][actions-badge]][actions-url]\n[![Discord chat][discord-badge]][discord-url]\n\n[crates-badge]: https://img.shields.io/crates/v/dioxus-autofmt.svg\n[crates-url]: https://crates.io/crates/dioxus-check\n[mit-badge]: https://img.shields.io/badge/license-MIT-blue.svg\n[mit-url]: https://github.com/dioxuslabs/dioxus/blob/main/LICENSE-MIT\n[actions-badge]: https://github.com/dioxuslabs/dioxus/actions/workflows/main.yml/badge.svg\n[actions-url]: https://github.com/dioxuslabs/dioxus/actions?query=workflow%3ACI+branch%3Amaster\n[discord-badge]: https://img.shields.io/discord/899851952891002890.svg?logo=discord&style=flat-square\n[discord-url]: https://discord.gg/XgGxMSkvUM\n\n[Website](https://dioxuslabs.com) |\n[Guides](https://dioxuslabs.com/learn/0.7/) |\n[API Docs](https://docs.rs/dioxus-check) |\n[Chat](https://discord.gg/XgGxMSkvUM)\n\n## Overview\n\n`dioxus-check` analyzes Dioxus source code and reports errors and warnings. Primarily, it enforces the [Rules of Hooks](https://dioxuslabs.com/learn/0.7/essentials/basics/hooks#rules-of-hooks).\n\n## Contributing\n\n- Report issues on our [issue tracker](https://github.com/dioxuslabs/dioxus/issues).\n- Join the discord and ask questions!\n\n## License\n\nThis project is licensed under the [MIT license].\n\n[mit license]: https://github.com/dioxuslabs/dioxus/blob/main/LICENSE-MIT\n\nUnless you explicitly state otherwise, any contribution intentionally submitted\nfor inclusion in Dioxus by you shall be licensed as MIT without any additional\nterms or conditions.\n"
  },
  {
    "path": "packages/check/src/check.rs",
    "content": "use std::path::PathBuf;\n\nuse syn::{spanned::Spanned, visit::Visit, Pat};\n\nuse crate::{\n    issues::{Issue, IssueReport},\n    metadata::{\n        AnyLoopInfo, AsyncInfo, ClosureInfo, ComponentInfo, ConditionalInfo, FnInfo, ForInfo,\n        HookInfo, IfInfo, LoopInfo, MatchInfo, Span, WhileInfo,\n    },\n};\n\nstruct VisitHooks {\n    issues: Vec<Issue>,\n    context: Vec<Node>,\n}\n\nimpl VisitHooks {\n    const fn new() -> Self {\n        Self {\n            issues: vec![],\n            context: vec![],\n        }\n    }\n}\n\n/// Checks a Dioxus file for issues.\npub fn check_file(path: PathBuf, file_content: &str) -> IssueReport {\n    let file = syn::parse_file(file_content).unwrap();\n    let mut visit_hooks = VisitHooks::new();\n    visit_hooks.visit_file(&file);\n    IssueReport::new(\n        path,\n        std::env::current_dir().unwrap_or_default(),\n        file_content.to_string(),\n        visit_hooks.issues,\n    )\n}\n\n#[allow(unused)]\n#[derive(Debug, Clone)]\nenum Node {\n    If(IfInfo),\n    Match(MatchInfo),\n    For(ForInfo),\n    While(WhileInfo),\n    Loop(LoopInfo),\n    Closure(ClosureInfo),\n    Async(AsyncInfo),\n    ComponentFn(ComponentInfo),\n    HookFn(HookInfo),\n    OtherFn(FnInfo),\n}\n\nfn returns_element(ty: &syn::ReturnType) -> bool {\n    match ty {\n        syn::ReturnType::Default => false,\n        syn::ReturnType::Type(_, ref ty) => {\n            if let syn::Type::Path(ref path) = **ty {\n                if let Some(segment) = path.path.segments.last() {\n                    if segment.ident == \"Element\" {\n                        return true;\n                    }\n                }\n            }\n            false\n        }\n    }\n}\n\nfn is_hook_ident(ident: &syn::Ident) -> bool {\n    ident.to_string().starts_with(\"use_\")\n}\n\nfn is_component_fn(item_fn: &syn::ItemFn) -> bool {\n    returns_element(&item_fn.sig.output)\n}\n\nfn get_closure_hook_body(local: &syn::Local) -> Option<&syn::Expr> {\n    if let Pat::Ident(ident) = &local.pat {\n        if is_hook_ident(&ident.ident) {\n            if let Some(init) = &local.init {\n                if let syn::Expr::Closure(closure) = init.expr.as_ref() {\n                    return Some(&closure.body);\n                }\n            }\n        }\n    }\n\n    None\n}\n\nfn fn_name_and_name_span(item_fn: &syn::ItemFn) -> (String, Span) {\n    let name = item_fn.sig.ident.to_string();\n    let name_span = item_fn.sig.ident.span().into();\n    (name, name_span)\n}\n\nimpl<'ast> syn::visit::Visit<'ast> for VisitHooks {\n    fn visit_expr_call(&mut self, i: &'ast syn::ExprCall) {\n        if let syn::Expr::Path(ref path) = *i.func {\n            if let Some(segment) = path.path.segments.last() {\n                if is_hook_ident(&segment.ident) {\n                    let hook_info = HookInfo::new(\n                        i.span().into(),\n                        segment.ident.span().into(),\n                        segment.ident.to_string(),\n                    );\n                    let mut container_fn: Option<Node> = None;\n                    for node in self.context.iter().rev() {\n                        match &node {\n                            Node::If(if_info) => {\n                                let issue = Issue::HookInsideConditional(\n                                    hook_info.clone(),\n                                    ConditionalInfo::If(if_info.clone()),\n                                );\n                                self.issues.push(issue);\n                            }\n                            Node::Match(match_info) => {\n                                let issue = Issue::HookInsideConditional(\n                                    hook_info.clone(),\n                                    ConditionalInfo::Match(match_info.clone()),\n                                );\n                                self.issues.push(issue);\n                            }\n                            Node::For(for_info) => {\n                                let issue = Issue::HookInsideLoop(\n                                    hook_info.clone(),\n                                    AnyLoopInfo::For(for_info.clone()),\n                                );\n                                self.issues.push(issue);\n                            }\n                            Node::While(while_info) => {\n                                let issue = Issue::HookInsideLoop(\n                                    hook_info.clone(),\n                                    AnyLoopInfo::While(while_info.clone()),\n                                );\n                                self.issues.push(issue);\n                            }\n                            Node::Loop(loop_info) => {\n                                let issue = Issue::HookInsideLoop(\n                                    hook_info.clone(),\n                                    AnyLoopInfo::Loop(loop_info.clone()),\n                                );\n                                self.issues.push(issue);\n                            }\n                            Node::Closure(closure_info) => {\n                                let issue = Issue::HookInsideClosure(\n                                    hook_info.clone(),\n                                    closure_info.clone(),\n                                );\n                                self.issues.push(issue);\n                            }\n                            Node::Async(async_info) => {\n                                let issue =\n                                    Issue::HookInsideAsync(hook_info.clone(), async_info.clone());\n                                self.issues.push(issue);\n                            }\n                            Node::ComponentFn(_) | Node::HookFn(_) | Node::OtherFn(_) => {\n                                container_fn = Some(node.clone());\n                                break;\n                            }\n                        }\n                    }\n\n                    if let Some(Node::OtherFn(_)) = container_fn {\n                        let issue = Issue::HookOutsideComponent(hook_info);\n                        self.issues.push(issue);\n                    }\n                }\n            }\n        }\n        syn::visit::visit_expr_call(self, i);\n    }\n\n    fn visit_item_fn(&mut self, i: &'ast syn::ItemFn) {\n        let (name, name_span) = fn_name_and_name_span(i);\n        if is_component_fn(i) {\n            self.context.push(Node::ComponentFn(ComponentInfo::new(\n                i.span().into(),\n                name,\n                name_span,\n            )));\n        } else if is_hook_ident(&i.sig.ident) {\n            self.context.push(Node::HookFn(HookInfo::new(\n                i.span().into(),\n                i.sig.ident.span().into(),\n                name,\n            )));\n        } else {\n            self.context\n                .push(Node::OtherFn(FnInfo::new(i.span().into(), name, name_span)));\n        }\n        syn::visit::visit_item_fn(self, i);\n        self.context.pop();\n    }\n\n    fn visit_local(&mut self, i: &'ast syn::Local) {\n        if let Some(body) = get_closure_hook_body(i) {\n            // if the closure is a hook, we only visit the body of the closure.\n            // this prevents adding a ClosureInfo node to the context\n            syn::visit::visit_expr(self, body);\n        } else {\n            // otherwise visit the whole local\n            syn::visit::visit_local(self, i);\n        }\n    }\n\n    fn visit_expr_if(&mut self, i: &'ast syn::ExprIf) {\n        self.context.push(Node::If(IfInfo::new(\n            i.span().into(),\n            i.if_token\n                .span()\n                .join(i.cond.span())\n                .unwrap_or_else(|| i.span())\n                .into(),\n        )));\n        // only visit the body and else branch, calling hooks inside the expression is not conditional\n        self.visit_block(&i.then_branch);\n        if let Some(it) = &i.else_branch {\n            self.visit_expr(&(it).1);\n        }\n        self.context.pop();\n    }\n\n    fn visit_expr_match(&mut self, i: &'ast syn::ExprMatch) {\n        self.context.push(Node::Match(MatchInfo::new(\n            i.span().into(),\n            i.match_token\n                .span()\n                .join(i.expr.span())\n                .unwrap_or_else(|| i.span())\n                .into(),\n        )));\n        // only visit the arms, calling hooks inside the expression is not conditional\n        for it in &i.arms {\n            self.visit_arm(it);\n        }\n        self.context.pop();\n    }\n\n    fn visit_expr_for_loop(&mut self, i: &'ast syn::ExprForLoop) {\n        self.context.push(Node::For(ForInfo::new(\n            i.span().into(),\n            i.for_token\n                .span()\n                .join(i.expr.span())\n                .unwrap_or_else(|| i.span())\n                .into(),\n        )));\n        syn::visit::visit_expr_for_loop(self, i);\n        self.context.pop();\n    }\n\n    fn visit_expr_while(&mut self, i: &'ast syn::ExprWhile) {\n        self.context.push(Node::While(WhileInfo::new(\n            i.span().into(),\n            i.while_token\n                .span()\n                .join(i.cond.span())\n                .unwrap_or_else(|| i.span())\n                .into(),\n        )));\n        syn::visit::visit_expr_while(self, i);\n        self.context.pop();\n    }\n\n    fn visit_expr_loop(&mut self, i: &'ast syn::ExprLoop) {\n        self.context\n            .push(Node::Loop(LoopInfo::new(i.span().into())));\n        syn::visit::visit_expr_loop(self, i);\n        self.context.pop();\n    }\n\n    fn visit_expr_closure(&mut self, i: &'ast syn::ExprClosure) {\n        self.context\n            .push(Node::Closure(ClosureInfo::new(i.span().into())));\n        syn::visit::visit_expr_closure(self, i);\n        self.context.pop();\n    }\n\n    fn visit_expr_async(&mut self, i: &'ast syn::ExprAsync) {\n        self.context\n            .push(Node::Async(AsyncInfo::new(i.span().into())));\n        syn::visit::visit_expr_async(self, i);\n        self.context.pop();\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use crate::metadata::{\n        AnyLoopInfo, ClosureInfo, ConditionalInfo, ForInfo, HookInfo, IfInfo, LineColumn, LoopInfo,\n        MatchInfo, Span, WhileInfo,\n    };\n    use indoc::indoc;\n    use pretty_assertions::assert_eq;\n\n    use super::*;\n\n    #[test]\n    fn test_no_hooks() {\n        let contents = indoc! {r#\"\n            fn App() -> Element {\n                rsx! {\n                    p { \"Hello World\" }\n                }\n            }\n        \"#};\n\n        let report = check_file(\"app.rs\".into(), contents);\n\n        assert_eq!(report.issues, vec![]);\n    }\n\n    #[test]\n    fn test_hook_correctly_used_inside_component() {\n        let contents = indoc! {r#\"\n            fn App() -> Element {\n                let count = use_signal(|| 0);\n                rsx! {\n                    p { \"Hello World: {count}\" }\n                }\n            }\n        \"#};\n\n        let report = check_file(\"app.rs\".into(), contents);\n\n        assert_eq!(report.issues, vec![]);\n    }\n\n    #[test]\n    fn test_hook_correctly_used_inside_hook_fn() {\n        let contents = indoc! {r#\"\n            fn use_thing() -> UseState<i32> {\n                use_signal(|| 0)\n            }\n        \"#};\n\n        let report = check_file(\"use_thing.rs\".into(), contents);\n\n        assert_eq!(report.issues, vec![]);\n    }\n\n    #[test]\n    fn test_hook_correctly_used_inside_hook_closure() {\n        let contents = indoc! {r#\"\n            fn App() -> Element {\n                let use_thing = || {\n                    use_signal(|| 0)\n                };\n                let count = use_thing();\n                rsx! {\n                    p { \"Hello World: {count}\" }\n                }\n            }\n        \"#};\n\n        let report = check_file(\"app.rs\".into(), contents);\n\n        assert_eq!(report.issues, vec![]);\n    }\n\n    #[test]\n    fn test_conditional_hook_if() {\n        let contents = indoc! {r#\"\n            fn App() -> Element {\n                if you_are_happy && you_know_it {\n                    let something = use_signal(|| \"hands\");\n                    println!(\"clap your {something}\")\n                }\n            }\n        \"#};\n\n        let report = check_file(\"app.rs\".into(), contents);\n\n        assert_eq!(\n            report.issues,\n            vec![Issue::HookInsideConditional(\n                HookInfo::new(\n                    Span::new_from_str(\n                        r#\"use_signal(|| \"hands\")\"#,\n                        LineColumn { line: 3, column: 24 },\n                    ),\n                    Span::new_from_str(\n                        r#\"use_signal\"#,\n                        LineColumn { line: 3, column: 24 },\n                    ),\n                    \"use_signal\".to_string()\n                ),\n                ConditionalInfo::If(IfInfo::new(\n                    Span::new_from_str(\n                        \"if you_are_happy && you_know_it {\\n        let something = use_signal(|| \\\"hands\\\");\\n        println!(\\\"clap your {something}\\\")\\n    }\",\n                        LineColumn { line: 2, column: 4 },\n                    ),\n                    Span::new_from_str(\n                        \"if you_are_happy && you_know_it\",\n                        LineColumn { line: 2, column: 4 }\n                    )\n                ))\n            )],\n        );\n    }\n\n    #[test]\n    fn test_conditional_hook_match() {\n        let contents = indoc! {r#\"\n            fn App() -> Element {\n                match you_are_happy && you_know_it {\n                    true => {\n                        let something = use_signal(|| \"hands\");\n                        println!(\"clap your {something}\")\n                    }\n                    false => {}\n                }\n            }\n        \"#};\n\n        let report = check_file(\"app.rs\".into(), contents);\n\n        assert_eq!(\n            report.issues,\n            vec![Issue::HookInsideConditional(\n                HookInfo::new(\n                    Span::new_from_str(r#\"use_signal(|| \"hands\")\"#, LineColumn { line: 4, column: 28 }),\n                    Span::new_from_str(r#\"use_signal\"#, LineColumn { line: 4, column: 28 }),\n                    \"use_signal\".to_string()\n                ),\n                ConditionalInfo::Match(MatchInfo::new(\n                    Span::new_from_str(\n                        \"match you_are_happy && you_know_it {\\n        true => {\\n            let something = use_signal(|| \\\"hands\\\");\\n            println!(\\\"clap your {something}\\\")\\n        }\\n        false => {}\\n    }\",\n                        LineColumn { line: 2, column: 4 },\n                    ),\n                    Span::new_from_str(\"match you_are_happy && you_know_it\", LineColumn { line: 2, column: 4 })\n                ))\n            )]\n        );\n    }\n\n    #[test]\n    fn test_use_in_match_expr() {\n        let contents = indoc! {r#\"\n            fn use_thing() {\n                match use_resource(|| async {}) {\n                    Ok(_) => {}\n                    Err(_) => {}\n                }\n            }\n        \"#};\n\n        let report = check_file(\"app.rs\".into(), contents);\n\n        assert_eq!(report.issues, vec![]);\n    }\n\n    #[test]\n    fn test_for_loop_hook() {\n        let contents = indoc! {r#\"\n            fn App() -> Element {\n                for _name in &names {\n                    let is_selected = use_signal(|| false);\n                    println!(\"selected: {is_selected}\");\n                }\n            }\n        \"#};\n\n        let report = check_file(\"app.rs\".into(), contents);\n\n        assert_eq!(\n            report.issues,\n            vec![Issue::HookInsideLoop(\n                HookInfo::new(\n                    Span::new_from_str(\n                        \"use_signal(|| false)\",\n                        LineColumn { line: 3, column: 26 },\n                    ),\n                    Span::new_from_str(\n                        \"use_signal\",\n                        LineColumn { line: 3, column: 26 },\n                    ),\n                    \"use_signal\".to_string()\n                ),\n                AnyLoopInfo::For(ForInfo::new(\n                    Span::new_from_str(\n                        \"for _name in &names {\\n        let is_selected = use_signal(|| false);\\n        println!(\\\"selected: {is_selected}\\\");\\n    }\",\n                        LineColumn { line: 2, column: 4 },\n                    ),\n                    Span::new_from_str(\n                        \"for _name in &names\",\n                        LineColumn { line: 2, column: 4 },\n                    )\n                ))\n            )]\n        );\n    }\n\n    #[test]\n    fn test_while_loop_hook() {\n        let contents = indoc! {r#\"\n            fn App() -> Element {\n                while true {\n                    let something = use_signal(|| \"hands\");\n                    println!(\"clap your {something}\")\n                }\n            }\n        \"#};\n\n        let report = check_file(\"app.rs\".into(), contents);\n\n        assert_eq!(\n            report.issues,\n            vec![Issue::HookInsideLoop(\n                HookInfo::new(\n                    Span::new_from_str(\n                        r#\"use_signal(|| \"hands\")\"#,\n                        LineColumn { line: 3, column: 24 },\n                    ),\n                    Span::new_from_str(\n                        \"use_signal\",\n                        LineColumn { line: 3, column: 24 },\n                    ),\n                    \"use_signal\".to_string()\n                ),\n                AnyLoopInfo::While(WhileInfo::new(\n                    Span::new_from_str(\n                        \"while true {\\n        let something = use_signal(|| \\\"hands\\\");\\n        println!(\\\"clap your {something}\\\")\\n    }\",\n                        LineColumn { line: 2, column: 4 },\n                    ),\n                    Span::new_from_str(\n                        \"while true\",\n                        LineColumn { line: 2, column: 4 },\n                    )\n                ))\n            )],\n        );\n    }\n\n    #[test]\n    fn test_loop_hook() {\n        let contents = indoc! {r#\"\n            fn App() -> Element {\n                loop {\n                    let something = use_signal(|| \"hands\");\n                    println!(\"clap your {something}\")\n                }\n            }\n        \"#};\n\n        let report = check_file(\"app.rs\".into(), contents);\n\n        assert_eq!(\n            report.issues,\n            vec![Issue::HookInsideLoop(\n                HookInfo::new(\n                    Span::new_from_str(\n                        r#\"use_signal(|| \"hands\")\"#,\n                        LineColumn { line: 3, column: 24 },\n                    ),\n                    Span::new_from_str(\n                        \"use_signal\",\n                        LineColumn { line: 3, column: 24 },\n                    ),\n                    \"use_signal\".to_string()\n                ),\n                AnyLoopInfo::Loop(LoopInfo::new(Span::new_from_str(\n                    \"loop {\\n        let something = use_signal(|| \\\"hands\\\");\\n        println!(\\\"clap your {something}\\\")\\n    }\",\n                    LineColumn { line: 2, column: 4 },\n                )))\n            )],\n        );\n    }\n\n    #[test]\n    fn test_conditional_okay() {\n        let contents = indoc! {r#\"\n            fn App() -> Element {\n                let something = use_signal(|| \"hands\");\n                if you_are_happy && you_know_it {\n                    println!(\"clap your {something}\")\n                }\n            }\n        \"#};\n\n        let report = check_file(\"app.rs\".into(), contents);\n\n        assert_eq!(report.issues, vec![]);\n    }\n\n    #[test]\n    fn test_conditional_expr_okay() {\n        let contents = indoc! {r#\"\n            fn App() -> Element {\n                if use_signal(|| true) {\n                    println!(\"clap your {something}\")\n                }\n            }\n        \"#};\n\n        let report = check_file(\"app.rs\".into(), contents);\n\n        assert_eq!(report.issues, vec![]);\n    }\n\n    #[test]\n    fn test_closure_hook() {\n        let contents = indoc! {r#\"\n            fn App() -> Element {\n                let _a = || {\n                    let b = use_signal(|| 0);\n                    b.get()\n                };\n            }\n        \"#};\n\n        let report = check_file(\"app.rs\".into(), contents);\n\n        assert_eq!(\n            report.issues,\n            vec![Issue::HookInsideClosure(\n                HookInfo::new(\n                    Span::new_from_str(\n                        \"use_signal(|| 0)\",\n                        LineColumn {\n                            line: 3,\n                            column: 16\n                        },\n                    ),\n                    Span::new_from_str(\n                        \"use_signal\",\n                        LineColumn {\n                            line: 3,\n                            column: 16\n                        },\n                    ),\n                    \"use_signal\".to_string()\n                ),\n                ClosureInfo::new(Span::new_from_str(\n                    \"|| {\\n        let b = use_signal(|| 0);\\n        b.get()\\n    }\",\n                    LineColumn {\n                        line: 2,\n                        column: 13\n                    },\n                ))\n            )]\n        );\n    }\n\n    #[test]\n    fn test_hook_outside_component() {\n        let contents = indoc! {r#\"\n            fn not_component_or_hook() {\n                let _a = use_signal(|| 0);\n            }\n        \"#};\n\n        let report = check_file(\"app.rs\".into(), contents);\n\n        assert_eq!(\n            report.issues,\n            vec![Issue::HookOutsideComponent(HookInfo::new(\n                Span::new_from_str(\n                    \"use_signal(|| 0)\",\n                    LineColumn {\n                        line: 2,\n                        column: 13\n                    }\n                ),\n                Span::new_from_str(\n                    \"use_signal\",\n                    LineColumn {\n                        line: 2,\n                        column: 13\n                    },\n                ),\n                \"use_signal\".to_string()\n            ))]\n        );\n    }\n\n    #[test]\n    fn test_hook_inside_hook() {\n        let contents = indoc! {r#\"\n            fn use_thing() {\n                let _a = use_signal(|| 0);\n            }\n        \"#};\n\n        let report = check_file(\"app.rs\".into(), contents);\n\n        assert_eq!(report.issues, vec![]);\n    }\n\n    #[test]\n    fn test_hook_inside_hook_initialization() {\n        let contents = indoc! {r#\"\n            fn use_thing() {\n                let _a = use_signal(|| use_signal(|| 0));\n            }\n        \"#};\n\n        let report = check_file(\"app.rs\".into(), contents);\n\n        assert_eq!(\n            report.issues,\n            vec![Issue::HookInsideClosure(\n                HookInfo::new(\n                    Span::new_from_str(\n                        \"use_signal(|| 0)\",\n                        LineColumn {\n                            line: 2,\n                            column: 27,\n                        },\n                    ),\n                    Span::new_from_str(\n                        \"use_signal\",\n                        LineColumn {\n                            line: 2,\n                            column: 27,\n                        },\n                    ),\n                    \"use_signal\".to_string()\n                ),\n                ClosureInfo::new(Span::new_from_str(\n                    \"|| use_signal(|| 0)\",\n                    LineColumn {\n                        line: 2,\n                        column: 24,\n                    },\n                ))\n            ),]\n        );\n    }\n\n    #[test]\n    fn test_hook_inside_hook_async_initialization() {\n        let contents = indoc! {r#\"\n            fn use_thing() {\n                let _a = use_future(|| async move { use_signal(|| 0) });\n            }\n        \"#};\n\n        let report = check_file(\"app.rs\".into(), contents);\n\n        assert_eq!(\n            report.issues,\n            vec![\n                Issue::HookInsideAsync(\n                    HookInfo::new(\n                        Span::new_from_str(\n                            \"use_signal(|| 0)\",\n                            LineColumn {\n                                line: 2,\n                                column: 40,\n                            },\n                        ),\n                        Span::new_from_str(\n                            \"use_signal\",\n                            LineColumn {\n                                line: 2,\n                                column: 40,\n                            },\n                        ),\n                        \"use_signal\".to_string()\n                    ),\n                    AsyncInfo::new(Span::new_from_str(\n                        \"async move { use_signal(|| 0) }\",\n                        LineColumn {\n                            line: 2,\n                            column: 27,\n                        },\n                    ))\n                ),\n                Issue::HookInsideClosure(\n                    HookInfo::new(\n                        Span::new_from_str(\n                            \"use_signal(|| 0)\",\n                            LineColumn {\n                                line: 2,\n                                column: 40,\n                            },\n                        ),\n                        Span::new_from_str(\n                            \"use_signal\",\n                            LineColumn {\n                                line: 2,\n                                column: 40,\n                            },\n                        ),\n                        \"use_signal\".to_string()\n                    ),\n                    ClosureInfo::new(Span::new_from_str(\n                        \"|| async move { use_signal(|| 0) }\",\n                        LineColumn {\n                            line: 2,\n                            column: 24,\n                        },\n                    ))\n                ),\n            ]\n        );\n    }\n\n    #[test]\n    fn test_hook_inside_spawn() {\n        let contents = indoc! {r#\"\n            fn use_thing() {\n                let _a = spawn(async move { use_signal(|| 0) });\n            }\n        \"#};\n\n        let report = check_file(\"app.rs\".into(), contents);\n\n        assert_eq!(\n            report.issues,\n            vec![Issue::HookInsideAsync(\n                HookInfo::new(\n                    Span::new_from_str(\n                        \"use_signal(|| 0)\",\n                        LineColumn {\n                            line: 2,\n                            column: 32,\n                        },\n                    ),\n                    Span::new_from_str(\n                        \"use_signal\",\n                        LineColumn {\n                            line: 2,\n                            column: 32,\n                        },\n                    ),\n                    \"use_signal\".to_string()\n                ),\n                AsyncInfo::new(Span::new_from_str(\n                    \"async move { use_signal(|| 0) }\",\n                    LineColumn {\n                        line: 2,\n                        column: 19,\n                    },\n                ))\n            ),]\n        );\n    }\n}\n"
  },
  {
    "path": "packages/check/src/issues.rs",
    "content": "use owo_colors::{\n    colors::{css::LightBlue, BrightRed},\n    OwoColorize, Stream,\n};\nuse std::{\n    fmt::Display,\n    path::{Path, PathBuf},\n};\n\nuse crate::metadata::{\n    AnyLoopInfo, AsyncInfo, ClosureInfo, ConditionalInfo, ForInfo, HookInfo, IfInfo, MatchInfo,\n    WhileInfo,\n};\n\n/// The result of checking a Dioxus file for issues.\npub struct IssueReport {\n    pub path: PathBuf,\n    pub crate_root: PathBuf,\n    pub file_content: String,\n    pub issues: Vec<Issue>,\n}\n\nimpl IssueReport {\n    pub fn new<S: ToString>(\n        path: PathBuf,\n        crate_root: PathBuf,\n        file_content: S,\n        issues: Vec<Issue>,\n    ) -> Self {\n        Self {\n            path,\n            crate_root,\n            file_content: file_content.to_string(),\n            issues,\n        }\n    }\n}\n\nfn lightblue(text: &str) -> String {\n    text.if_supports_color(Stream::Stderr, |text| text.fg::<LightBlue>())\n        .to_string()\n}\n\nfn brightred(text: &str) -> String {\n    text.if_supports_color(Stream::Stderr, |text| text.fg::<BrightRed>())\n        .to_string()\n}\n\nfn bold(text: &str) -> String {\n    text.if_supports_color(Stream::Stderr, |text| text.bold())\n        .to_string()\n}\n\nimpl Display for IssueReport {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        let relative_file = Path::new(&self.path)\n            .strip_prefix(&self.crate_root)\n            .unwrap_or(Path::new(&self.path))\n            .display();\n\n        let pipe_char = lightblue(\"|\");\n\n        for (i, issue) in self.issues.iter().enumerate() {\n            let hook_info = issue.hook_info();\n            let hook_span = hook_info.span;\n            let hook_name_span = hook_info.name_span;\n            let error_line = format!(\"{}: {}\", brightred(\"error\"), issue);\n            writeln!(f, \"{}\", bold(&error_line))?;\n            writeln!(\n                f,\n                \"  {} {}:{}:{}\",\n                lightblue(\"-->\"),\n                relative_file,\n                hook_span.start.line,\n                hook_span.start.column + 1\n            )?;\n            let max_line_num_len = hook_span.end.line.to_string().len();\n            writeln!(f, \"{:>max_line_num_len$} {}\", \"\", pipe_char)?;\n            for (i, line) in self.file_content.lines().enumerate() {\n                let line_num = i + 1;\n                if line_num >= hook_span.start.line && line_num <= hook_span.end.line {\n                    writeln!(\n                        f,\n                        \"{:>max_line_num_len$} {} {}\",\n                        lightblue(&line_num.to_string()),\n                        pipe_char,\n                        line,\n                    )?;\n                    if line_num == hook_span.start.line {\n                        let mut caret = String::new();\n                        for _ in 0..hook_name_span.start.column {\n                            caret.push(' ');\n                        }\n                        for _ in hook_name_span.start.column..hook_name_span.end.column {\n                            caret.push('^');\n                        }\n                        writeln!(\n                            f,\n                            \"{:>max_line_num_len$} {} {}\",\n                            \"\",\n                            pipe_char,\n                            brightred(&caret),\n                        )?;\n                    }\n                }\n            }\n\n            let note_text_prefix = format!(\n                \"{:>max_line_num_len$} {}\\n{:>max_line_num_len$} {} note:\",\n                \"\",\n                pipe_char,\n                \"\",\n                lightblue(\"=\")\n            );\n\n            match issue {\n                Issue::HookInsideConditional(\n                    _,\n                    ConditionalInfo::If(IfInfo { span: _, head_span }),\n                )\n                | Issue::HookInsideConditional(\n                    _,\n                    ConditionalInfo::Match(MatchInfo { span: _, head_span }),\n                ) => {\n                    if let Some(source_text) = &head_span.source_text {\n                        writeln!(\n                            f,\n                            \"{} `{} {{ … }}` is the conditional\",\n                            note_text_prefix, source_text,\n                        )?;\n                    }\n                }\n                Issue::HookInsideLoop(_, AnyLoopInfo::For(ForInfo { span: _, head_span }))\n                | Issue::HookInsideLoop(_, AnyLoopInfo::While(WhileInfo { span: _, head_span })) => {\n                    if let Some(source_text) = &head_span.source_text {\n                        writeln!(\n                            f,\n                            \"{} `{} {{ … }}` is the loop\",\n                            note_text_prefix, source_text,\n                        )?;\n                    }\n                }\n                Issue::HookInsideLoop(_, AnyLoopInfo::Loop(_)) => {\n                    writeln!(f, \"{} `loop {{ … }}` is the loop\", note_text_prefix,)?;\n                }\n                Issue::HookOutsideComponent(_)\n                | Issue::HookInsideClosure(_, _)\n                | Issue::HookInsideAsync(_, _) => {}\n            }\n\n            if i < self.issues.len() - 1 {\n                writeln!(f)?;\n            }\n        }\n\n        Ok(())\n    }\n}\n\n#[derive(Debug, Clone, PartialEq, Eq)]\n#[non_exhaustive]\n#[allow(clippy::enum_variant_names)] // we'll add non-hook ones in the future\n/// Issues that might be found via static analysis of a Dioxus file.\npub enum Issue {\n    /// <https://dioxuslabs.com/learn/0.7/reference/hooks#no-hooks-in-conditionals>\n    HookInsideConditional(HookInfo, ConditionalInfo),\n    /// <https://dioxuslabs.com/learn/0.7/reference/hooks#no-hooks-in-loops>\n    HookInsideLoop(HookInfo, AnyLoopInfo),\n    /// <https://dioxuslabs.com/learn/0.7/reference/hooks#no-hooks-in-closures>\n    HookInsideClosure(HookInfo, ClosureInfo),\n    HookInsideAsync(HookInfo, AsyncInfo),\n    HookOutsideComponent(HookInfo),\n}\n\nimpl Issue {\n    pub fn hook_info(&self) -> HookInfo {\n        match self {\n            Issue::HookInsideConditional(hook_info, _)\n            | Issue::HookInsideLoop(hook_info, _)\n            | Issue::HookInsideClosure(hook_info, _)\n            | Issue::HookInsideAsync(hook_info, _)\n            | Issue::HookOutsideComponent(hook_info) => hook_info.clone(),\n        }\n    }\n}\n\nimpl std::fmt::Display for Issue {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        match self {\n            Issue::HookInsideConditional(hook_info, conditional_info) => {\n                write!(\n                    f,\n                    \"hook called conditionally: `{}` (inside `{}`)\",\n                    hook_info.name,\n                    match conditional_info {\n                        ConditionalInfo::If(_) => \"if\",\n                        ConditionalInfo::Match(_) => \"match\",\n                    }\n                )\n            }\n            Issue::HookInsideLoop(hook_info, loop_info) => {\n                write!(\n                    f,\n                    \"hook called in a loop: `{}` (inside {})\",\n                    hook_info.name,\n                    match loop_info {\n                        AnyLoopInfo::For(_) => \"`for` loop\",\n                        AnyLoopInfo::While(_) => \"`while` loop\",\n                        AnyLoopInfo::Loop(_) => \"`loop`\",\n                    }\n                )\n            }\n            Issue::HookInsideClosure(hook_info, _) => {\n                write!(f, \"hook called in a closure: `{}`\", hook_info.name)\n            }\n            Issue::HookInsideAsync(hook_info, _) => {\n                write!(f, \"hook called in an async block: `{}`\", hook_info.name)\n            }\n            Issue::HookOutsideComponent(hook_info) => {\n                write!(\n                    f,\n                    \"hook called outside component or hook: `{}`\",\n                    hook_info.name\n                )\n            }\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use crate::check_file;\n    use indoc::indoc;\n    use pretty_assertions::assert_eq;\n\n    #[test]\n    fn test_issue_report_display_conditional_if() {\n        owo_colors::set_override(false);\n        let issue_report = check_file(\n            \"src/main.rs\".into(),\n            indoc! {r#\"\n                fn App() -> Element {\n                    if you_are_happy && you_know_it {\n                        let something = use_signal(|| \"hands\");\n                        println!(\"clap your {something}\")\n                    }\n                }\n            \"#},\n        );\n\n        let expected = indoc! {r#\"\n            error: hook called conditionally: `use_signal` (inside `if`)\n              --> src/main.rs:3:25\n              |\n            3 |         let something = use_signal(|| \"hands\");\n              |                         ^^^^^^^^^^\n              |\n              = note: `if you_are_happy && you_know_it { … }` is the conditional\n        \"#};\n\n        assert_eq!(expected, issue_report.to_string());\n    }\n\n    #[test]\n    fn test_issue_report_display_conditional_match() {\n        owo_colors::set_override(false);\n        let issue_report = check_file(\n            \"src/main.rs\".into(),\n            indoc! {r#\"\n                fn App() -> Element {\n                    match you_are_happy && you_know_it {\n                        true => {\n                            let something = use_signal(|| \"hands\");\n                            println!(\"clap your {something}\")\n                        }\n                        _ => {}\n                    }\n                }\n            \"#},\n        );\n\n        let expected = indoc! {r#\"\n            error: hook called conditionally: `use_signal` (inside `match`)\n              --> src/main.rs:4:29\n              |\n            4 |             let something = use_signal(|| \"hands\");\n              |                             ^^^^^^^^^^\n              |\n              = note: `match you_are_happy && you_know_it { … }` is the conditional\n        \"#};\n\n        assert_eq!(expected, issue_report.to_string());\n    }\n\n    #[test]\n    fn test_issue_report_display_for_loop() {\n        owo_colors::set_override(false);\n        let issue_report = check_file(\n            \"src/main.rs\".into(),\n            indoc! {r#\"\n                fn App() -> Element {\n                    for i in 0..10 {\n                        let something = use_signal(|| \"hands\");\n                        println!(\"clap your {something}\")\n                    }\n                }\n            \"#},\n        );\n\n        let expected = indoc! {r#\"\n            error: hook called in a loop: `use_signal` (inside `for` loop)\n              --> src/main.rs:3:25\n              |\n            3 |         let something = use_signal(|| \"hands\");\n              |                         ^^^^^^^^^^\n              |\n              = note: `for i in 0..10 { … }` is the loop\n        \"#};\n\n        assert_eq!(expected, issue_report.to_string());\n    }\n\n    #[test]\n    fn test_issue_report_display_while_loop() {\n        owo_colors::set_override(false);\n        let issue_report = check_file(\n            \"src/main.rs\".into(),\n            indoc! {r#\"\n                fn App() -> Element {\n                    while check_thing() {\n                        let something = use_signal(|| \"hands\");\n                        println!(\"clap your {something}\")\n                    }\n                }\n            \"#},\n        );\n\n        let expected = indoc! {r#\"\n            error: hook called in a loop: `use_signal` (inside `while` loop)\n              --> src/main.rs:3:25\n              |\n            3 |         let something = use_signal(|| \"hands\");\n              |                         ^^^^^^^^^^\n              |\n              = note: `while check_thing() { … }` is the loop\n        \"#};\n\n        assert_eq!(expected, issue_report.to_string());\n    }\n\n    #[test]\n    fn test_issue_report_display_loop() {\n        owo_colors::set_override(false);\n        let issue_report = check_file(\n            \"src/main.rs\".into(),\n            indoc! {r#\"\n                fn App() -> Element {\n                    loop {\n                        let something = use_signal(|| \"hands\");\n                        println!(\"clap your {something}\")\n                    }\n                }\n            \"#},\n        );\n\n        let expected = indoc! {r#\"\n            error: hook called in a loop: `use_signal` (inside `loop`)\n              --> src/main.rs:3:25\n              |\n            3 |         let something = use_signal(|| \"hands\");\n              |                         ^^^^^^^^^^\n              |\n              = note: `loop { … }` is the loop\n        \"#};\n\n        assert_eq!(expected, issue_report.to_string());\n    }\n\n    #[test]\n    fn test_issue_report_display_closure() {\n        owo_colors::set_override(false);\n        let issue_report = check_file(\n            \"src/main.rs\".into(),\n            indoc! {r#\"\n                fn App() -> Element {\n                    let something = || {\n                        let something = use_signal(|| \"hands\");\n                        println!(\"clap your {something}\")\n                    };\n                }\n            \"#},\n        );\n\n        let expected = indoc! {r#\"\n            error: hook called in a closure: `use_signal`\n              --> src/main.rs:3:25\n              |\n            3 |         let something = use_signal(|| \"hands\");\n              |                         ^^^^^^^^^^\n        \"#};\n\n        assert_eq!(expected, issue_report.to_string());\n    }\n\n    #[test]\n    fn test_issue_report_display_multiline_hook() {\n        owo_colors::set_override(false);\n        let issue_report = check_file(\n            \"src/main.rs\".into(),\n            indoc! {r#\"\n                fn App() -> Element {\n                    if you_are_happy && you_know_it {\n                        let something = use_signal(|| {\n                            \"hands\"\n                        });\n                        println!(\"clap your {something}\")\n                    }\n                }\n            \"#},\n        );\n\n        let expected = indoc! {r#\"\n            error: hook called conditionally: `use_signal` (inside `if`)\n              --> src/main.rs:3:25\n              |\n            3 |         let something = use_signal(|| {\n              |                         ^^^^^^^^^^\n            4 |             \"hands\"\n            5 |         });\n              |\n              = note: `if you_are_happy && you_know_it { … }` is the conditional\n        \"#};\n\n        assert_eq!(expected, issue_report.to_string());\n    }\n}\n"
  },
  {
    "path": "packages/check/src/lib.rs",
    "content": "#![doc = include_str!(\"../README.md\")]\n#![doc(html_logo_url = \"https://avatars.githubusercontent.com/u/79236386\")]\n#![doc(html_favicon_url = \"https://avatars.githubusercontent.com/u/79236386\")]\n\nmod check;\nmod issues;\nmod metadata;\n\npub use check::check_file;\npub use issues::{Issue, IssueReport};\n"
  },
  {
    "path": "packages/check/src/metadata.rs",
    "content": "#[derive(Debug, Clone, PartialEq, Eq)]\n/// Information about a hook call or function.\npub struct HookInfo {\n    /// The name of the hook, e.g. `use_signal`.\n    pub name: String,\n    /// The span of the hook, e.g. `use_signal`.\n    pub span: Span,\n    /// The span of the name, e.g. `use_signal`.\n    pub name_span: Span,\n}\n\nimpl HookInfo {\n    pub const fn new(span: Span, name_span: Span, name: String) -> Self {\n        Self {\n            span,\n            name_span,\n            name,\n        }\n    }\n}\n\n#[derive(Debug, Clone, PartialEq, Eq)]\npub enum ConditionalInfo {\n    If(IfInfo),\n    Match(MatchInfo),\n}\n\n#[derive(Debug, Clone, PartialEq, Eq)]\npub struct IfInfo {\n    /// The span of the `if` statement, e.g. `if true { ... }`.\n    pub span: Span,\n    /// The span of the `if true` part only.\n    pub head_span: Span,\n}\n\nimpl IfInfo {\n    pub const fn new(span: Span, head_span: Span) -> Self {\n        Self { span, head_span }\n    }\n}\n\n#[derive(Debug, Clone, PartialEq, Eq)]\npub struct MatchInfo {\n    /// The span of the `match` statement, e.g. `match true { ... }`.\n    pub span: Span,\n    /// The span of the `match true` part only.\n    pub head_span: Span,\n}\n\nimpl MatchInfo {\n    pub const fn new(span: Span, head_span: Span) -> Self {\n        Self { span, head_span }\n    }\n}\n\n#[derive(Debug, Clone, PartialEq, Eq)]\n/// Information about one of the possible loop types.\npub enum AnyLoopInfo {\n    For(ForInfo),\n    While(WhileInfo),\n    Loop(LoopInfo),\n}\n\n#[derive(Debug, Clone, PartialEq, Eq)]\n/// Information about a `for` loop.\npub struct ForInfo {\n    pub span: Span,\n    pub head_span: Span,\n}\n\nimpl ForInfo {\n    pub const fn new(span: Span, head_span: Span) -> Self {\n        Self { span, head_span }\n    }\n}\n\n#[derive(Debug, Clone, PartialEq, Eq)]\n/// Information about a `while` loop.\npub struct WhileInfo {\n    pub span: Span,\n    pub head_span: Span,\n}\n\nimpl WhileInfo {\n    pub const fn new(span: Span, head_span: Span) -> Self {\n        Self { span, head_span }\n    }\n}\n\n#[derive(Debug, Clone, PartialEq, Eq)]\n/// Information about a `loop` loop.\npub struct LoopInfo {\n    pub span: Span,\n}\n\nimpl LoopInfo {\n    pub const fn new(span: Span) -> Self {\n        Self { span }\n    }\n}\n\n#[derive(Debug, Clone, PartialEq, Eq)]\n/// Information about a closure.\npub struct ClosureInfo {\n    pub span: Span,\n}\n\nimpl ClosureInfo {\n    pub const fn new(span: Span) -> Self {\n        Self { span }\n    }\n}\n\n#[derive(Debug, Clone, PartialEq, Eq)]\n/// Information about an async block.\npub struct AsyncInfo {\n    pub span: Span,\n}\n\nimpl AsyncInfo {\n    pub const fn new(span: Span) -> Self {\n        Self { span }\n    }\n}\n\n#[derive(Debug, Clone, PartialEq, Eq)]\n/// Information about a component function.\npub struct ComponentInfo {\n    pub span: Span,\n    pub name: String,\n    pub name_span: Span,\n}\n\nimpl ComponentInfo {\n    pub const fn new(span: Span, name: String, name_span: Span) -> Self {\n        Self {\n            span,\n            name,\n            name_span,\n        }\n    }\n}\n\n#[derive(Debug, Clone, PartialEq, Eq)]\n/// Information about a non-component, non-hook function.\npub struct FnInfo {\n    pub span: Span,\n    pub name: String,\n    pub name_span: Span,\n}\n\nimpl FnInfo {\n    pub const fn new(span: Span, name: String, name_span: Span) -> Self {\n        Self {\n            span,\n            name,\n            name_span,\n        }\n    }\n}\n\n#[derive(Debug, Clone, PartialEq, Eq)]\n/// A span of text in a source code file.\npub struct Span {\n    pub source_text: Option<String>,\n    pub start: LineColumn,\n    pub end: LineColumn,\n}\n\nimpl Span {\n    pub fn new_from_str(source_text: &str, start: LineColumn) -> Self {\n        let mut lines = source_text.lines();\n        let first_line = lines.next().unwrap_or_default();\n        let mut end = LineColumn {\n            line: start.line,\n            column: start.column + first_line.len(),\n        };\n        for line in lines {\n            end.line += 1;\n            end.column = line.len();\n        }\n        Self {\n            source_text: Some(source_text.to_string()),\n            start,\n            end,\n        }\n    }\n}\n\nimpl From<proc_macro2::Span> for Span {\n    fn from(span: proc_macro2::Span) -> Self {\n        Self {\n            source_text: span.source_text(),\n            start: span.start().into(),\n            end: span.end().into(),\n        }\n    }\n}\n\n#[derive(Debug, Clone, PartialEq, Eq)]\n/// A location in a source code file.\npub struct LineColumn {\n    pub line: usize,\n    pub column: usize,\n}\n\nimpl From<proc_macro2::LineColumn> for LineColumn {\n    fn from(lc: proc_macro2::LineColumn) -> Self {\n        Self {\n            line: lc.line,\n            column: lc.column,\n        }\n    }\n}\n"
  },
  {
    "path": "packages/cli/.gitignore",
    "content": "/target\nCargo.lock\n.DS_Store\n.idea/\n"
  },
  {
    "path": "packages/cli/Cargo.toml",
    "content": "[package]\nname = \"dioxus-cli\"\nversion = { workspace = true }\nauthors = [\"Jonathan Kelley\"]\nedition = \"2021\"\ndescription = \"CLI for building fullstack web, desktop, and mobile apps with a single codebase.\"\nrepository = \"https://github.com/DioxusLabs/dioxus/\"\nlicense = \"MIT OR Apache-2.0\"\nkeywords = [\"mobile\", \"gui\", \"cli\", \"dioxus\", \"wasm\"]\nrust-version = \"1.81.0\"\n\n[dependencies]\ndioxus-autofmt = { workspace = true }\ndioxus-check = { workspace = true }\ndioxus-rsx-rosetta = { workspace = true }\ndioxus-rsx = { workspace = true }\ndioxus-rsx-hotreload = { workspace = true }\ndioxus-html = { workspace = true, features = [\"hot-reload-context\"] }\ndioxus-core = { workspace = true, features = [\"serialize\"] }\ndioxus-core-types = { workspace = true }\ndioxus-devtools-types = { workspace = true }\ndioxus-cli-config = { workspace = true }\ndioxus-cli-opt = { workspace = true }\ndioxus-fullstack = { workspace = true }\ndioxus-dx-wire-format = { workspace = true }\nwasm-split-cli = { workspace = true }\ndepinfo = { workspace = true }\nsubsecond-types = { workspace = true }\ndioxus-cli-telemetry = { workspace =  true }\ndioxus-component-manifest = { workspace = true }\n\nbytes = { workspace = true }\nclap = { workspace = true, features = [\"derive\", \"cargo\"] }\nconvert_case = { workspace = true }\nthiserror = { workspace = true }\nuuid = { workspace = true, features = [\"v4\"] }\n# swc uses serde internals that break with newer versions of serde. We should move off of swc eventually,\n# but pinning serde fixes the issue in the meantime\nserde = { version = \"=1.0.221\", features = [\"derive\"] }\nserde_json = { workspace = true }\ntoml = { workspace = true }\ncargo_toml = { workspace = true, features = [\"features\"] }\nfutures-util = { workspace = true, features = [\"async-await-macro\"] }\nnotify = { workspace = true, features = [\"serde\"] }\nhtml_parser = { workspace = true }\ncargo_metadata = { workspace = true }\ntokio = { workspace = true, features = [\"full\"] }\ntokio-stream = { workspace = true }\ntokio-tungstenite = { workspace = true }\nchrono = { workspace = true }\nanyhow = { workspace = true }\nhyper = { workspace = true }\nhyper-util = { workspace = true }\nhyper-rustls = { workspace = true }\nrustls = { workspace = true }\nrayon = { workspace = true }\nfutures-channel = { workspace = true }\nkrates = { workspace = true }\nregex = \"1.11.1\"\nconsole = \"0.16.0\"\nctrlc = \"3.4.7\"\n\naxum = { workspace = true, default-features = true, features = [\"ws\"] }\naxum-server = { workspace = true, features = [\"tls-rustls\"] }\naxum-extra = { workspace = true, features = [\"typed-header\"] }\ntower-http = { workspace = true, features = [\"full\"] }\nproc-macro2 = { workspace = true, features = [\"span-locations\"] }\nsyn = { workspace = true, features = [\n    \"full\",\n    \"extra-traits\",\n    \"visit\",\n    \"visit-mut\",\n] }\n\nheaders = \"0.4.1\"\nwalkdir = \"2\"\ndunce = { workspace = true }\n\n# tools download\ndirs = { workspace = true }\nreqwest = { workspace = true, features = [\"rustls-tls\", \"trust-dns\", \"json\"] }\ntower = { workspace = true }\ngit2 = \"0.20.2\"\n\n# path lookup\nwhich = { version = \"8.0.0\" }\n\n# plugin packages\nopen = { workspace = true }\ncargo-generate = \"=0.23.3\"\ntoml_edit = \"0.22.27\"\n\n# formatting\n# syn = { workspace = true }\nprettyplease = { workspace = true }\n\n# Assets\nbrotli = \"8.0.1\"\nignore = \"0.4.23\"\nenv_logger = { workspace = true }\nconst-serialize = { workspace = true, features = [\"serde\"] }\nconst-serialize-07 = { package = \"const-serialize\", version = \"=0.7.2\", features = [\"serde\"] }\n\ntracing-subscriber = { version = \"0.3.19\", features = [\n    \"std\",\n    \"env-filter\",\n    \"json\",\n    \"registry\",\n    \"fmt\",\n] }\nconsole-subscriber = { version = \"0.4.1\", optional = true }\nschemars = \"0.8\"\ntracing = { workspace = true }\nansi-to-tui = { workspace = true }\nansi-to-html = { workspace = true }\npath-absolutize = { workspace = true }\ncrossterm = { workspace = true, features = [\"event-stream\"] }\nratatui = { workspace = true, features = [\"crossterm\", \"unstable\"] }\nshell-words = { workspace = true }\n\n# disable `log` entirely since `walrus` uses it and is *much* slower with it enableda\nlog = { version = \"0.4\", features = [\"max_level_off\", \"release_max_level_off\"] }\n\n# link intercept\ntempfile = \"3.19.1\"\nmanganis = { workspace = true }\nmanganis-core = { workspace = true }\nmanganis-core-07 = { workspace = true }\ntarget-lexicon = { version = \"0.13.2\", features = [\"serde\", \"serde_support\"] }\nwasm-encoder = \"0.235.0\"\n\n# Extracting data from an executable\nobject = { workspace = true, features = [\"all\"] }\ntokio-util = { workspace = true, features = [\"full\"] }\nitertools = { workspace = true }\nthrobber-widgets-tui = \"0.8.0\"\nunicode-segmentation = \"1.12.0\"\nhandlebars = \"6.3.2\"\nstrum = { version = \"0.27.1\", features = [\"derive\"] }\nmemmap = \"0.7.0\"\nwalrus = { workspace = true, features = [\"parallel\"] }\nwasmparser = { workspace = true }\n\ntauri-utils = { workspace = true }\ntauri-macos-sign = { workspace = true }\ntauri-bundler = { workspace = true }\ninclude_dir = \"0.7.4\"\nflate2 = \"1.1.2\"\ntar = \"0.4.44\"\nlocal-ip-address = \"0.6.5\"\ndircpy = \"0.3.19\"\nplist = \"1.7.4\"\nmemoize = \"0.5.1\"\nbacktrace = \"0.3.74\"\nar = \"0.9.0\"\nwasm-bindgen-externref-xform = \"0.2.100\"\npdb = \"0.8.0\"\nself_update = { version = \"0.42.0\", features = [\n    \"archive-tar\",\n    \"archive-zip\",\n    \"compression-flate2\",\n    \"compression-zip-deflate\",\n] }\nself-replace = \"1.5.0\"\ncargo-config2 = { workspace = true }\nposthog-rs = \"0.3.5\"\nserde_json5 = \"0.2.1\"\nfs2 = \"0.4.3\"\nsentry-backtrace = \"0.42.0\"\nnucleo = \"0.5.0\"\n\n# 0.5.12 rust-version is too high\nhome = \"=0.5.11\"\n\n[target.'cfg(target_env = \"musl\")'.dependencies]\ngit2 = { version = \"0.20.2\", features = [\"vendored-libgit2\"] }\n\n[build-dependencies]\nbuilt = { version = \"0.8.0\", features = [\"git2\"] }\n\n[features]\ndefault = []\ntokio-console = [\"dep:console-subscriber\", \"tokio/tracing\"]\nno-downloads = []\ndisable-telemetry = []\n\n[[bin]]\npath = \"src/main.rs\"\nname = \"dx\"\n\n[dev-dependencies]\nescargot = \"0.5\"\n\n[package.metadata.binstall]\npkg-url = \"{ repo }/releases/download/v{ version }/dx-{ target }{ archive-suffix }\"\npkg-fmt = \"zip\"\n\n[package.metadata.docs.rs]\nall-features = false\n"
  },
  {
    "path": "packages/cli/Dioxus.toml",
    "content": "[application]\n\n# App name\nname = \"project_name\"\n\n# `build` & `serve` output path\nout_dir = \"dist\"\n\n# Static files copied verbatim into the built app (set to enable, e.g. \"public\")\npublic_dir = \"public\"\n\n[web.app]\n\n# HTML title tag content\ntitle = \"project_name\"\n\n[web.watcher]\n\n# When watcher is triggered, regenerate the `index.html`\nreload_html = true\n\n# Which files or dirs will be monitored\nwatch_path = [\"src\", \"public\"]\n\n[[web.proxy]]\nbackend = \"http://localhost:8000/api/\"\n"
  },
  {
    "path": "packages/cli/README.md",
    "content": "<div>\n  <h1>📦✨ Dioxus CLI</h1>\n  <p><strong>Tooling to supercharge Dioxus projects</strong></p>\n</div>\n\nThe **dioxus-cli** (inspired by wasm-pack and webpack) is a tool for getting Dioxus projects up and running.\nIt handles building, bundling, development and publishing to simplify development.\n\n## Installation\n\n### Install the stable version (recommended)\n\n```shell\ncargo install dioxus-cli\n```\n\n### Install the latest development build through git\n\nTo get the latest bug fixes and features, you can install the development version from git.\nHowever, this is not fully tested.\nThat means you're probably going to have more bugs despite having the latest bug fixes.\n\n```shell\ncargo install --git https://github.com/DioxusLabs/dioxus dioxus-cli\n```\n\nThis will download the CLI from the master branch,\nand install it in Cargo's global binary directory (`~/.cargo/bin/` by default).\n\n### Install from local folder\nNote: The CLI will fail to build projects in debug profile. This is currently under investigation.\n```shell\ncargo install --path .\n```\n\n## Get started\n\nUse `dx new` to initialize a new Dioxus project.\nIt will be cloned from the [dioxus-template](https://github.com/DioxusLabs/dioxus-template) repository.\n\nAlternatively, you can specify the template path:\n\n```shell\ndx new --template gh:dioxuslabs/dioxus-template\n```\n\nRun `dx --help` for a list of all the available commands.\nFurthermore, you can run `dx <command> --help` to get help with a specific command.\n\n## Dioxus config file\n\nYou can use the `Dioxus.toml` file for further configuration.\nSome fields are mandatory, but the CLI tool will tell you which ones are missing.\nYou can create a `Dioxus.toml` with all fields already set using `dx config init project-name`,\nor you can use this bare-bones template (only mandatory fields) to get started:\n\n```toml\n[application]\nname = \"project-name\"\n# Currently supported platforms: web, desktop\ndefault_platform = \"web\"\n\n# Optional: enable copying from a static directory (e.g. \"public\")\npublic_dir = \"public\"\n\n[web.app]\ntitle = \"Hello\"\n\n[web.resource.dev]\n```\n"
  },
  {
    "path": "packages/cli/assets/android/MainActivity.kt.hbs",
    "content": "package dev.dioxus.main\n\ntypealias BuildConfig = {{application_id}}.BuildConfig\n\nclass MainActivity : WryActivity()\n"
  },
  {
    "path": "packages/cli/assets/android/gen/.gitignore",
    "content": "*.iml\n.gradle\n/.idea/caches\n/.idea/libraries\n/.idea/modules.xml\n/.idea/workspace.xml\n/.idea/navEditor.xml\n/.idea/assetWizardSettings.xml\n.DS_Store\nbuild\n/captures\n.externalNativeBuild\njniLibs\n.cxx\nlocal.properties\n"
  },
  {
    "path": "packages/cli/assets/android/gen/app/build.gradle.kts.hbs",
    "content": "plugins {\n    id(\"com.android.application\")\n    id(\"org.jetbrains.kotlin.android\")\n    {{#each gradle_plugins}}\n    id(\"{{ this }}\")\n    {{/each}}\n}\n\nandroid {\n    namespace=\"{{ application_id }}\"\n    compileSdk = {{ compile_sdk }}\n    defaultConfig {\n        applicationId = \"{{ application_id }}\"\n        minSdk = {{ min_sdk }}\n        targetSdk = {{ target_sdk }}\n        versionCode = 1\n        versionName = \"{{ version }}\"\n    }\n    {{#if android_bundle}}\n    signingConfigs {\n        create(\"release\") {\n            storeFile = file(\"../../../../../../../{{ android_bundle.jks_file }}\")\n            storePassword = \"{{ android_bundle.jks_password }}\"\n            keyAlias = \"{{ android_bundle.key_alias }}\"\n            keyPassword = \"{{ android_bundle.key_password }}\"\n        }\n    }\n    {{/if}}\n    buildTypes {\n        getByName(\"debug\") {\n            isDebuggable = true\n            isJniDebuggable = true\n            isMinifyEnabled = false\n            packaging {\n                jniLibs.keepDebugSymbols.add(\"*/arm64-v8a/*.so\")\n                jniLibs.keepDebugSymbols.add(\"*/armeabi-v7a/*.so\")\n                jniLibs.keepDebugSymbols.add(\"*/x86/*.so\")\n                jniLibs.keepDebugSymbols.add(\"*/x86_64/*.so\")\n            }\n        }\n        getByName(\"release\") {\n            isMinifyEnabled = true\n            {{#if android_bundle}}\n            signingConfig = signingConfigs.getByName(\"release\")\n            {{/if}}\n            proguardFiles(\n                *fileTree(\".\") { include(\"**/*.pro\") }\n                    .plus(getDefaultProguardFile(\"proguard-android-optimize.txt\"))\n                    .toList().toTypedArray()\n            )\n        }\n    }\n    kotlinOptions {\n        jvmTarget = \"17\"\n    }\n    compileOptions {\n        sourceCompatibility = JavaVersion.VERSION_17\n        targetCompatibility = JavaVersion.VERSION_17\n    }\n    buildFeatures {\n        buildConfig = true\n    }\n    sourceSets {\n        getByName(\"main\") {\n            java.srcDirs(\"src/main/kotlin\", \"src/main/java\")\n        }\n    }\n}\n\ndependencies {\n    implementation(\"androidx.webkit:webkit:1.6.1\")\n    implementation(\"androidx.appcompat:appcompat:1.6.1\")\n    implementation(\"com.google.android.material:material:1.8.0\")\n    {{#each gradle_dependencies}}\n    implementation(\"{{ this }}\")\n    {{/each}}\n}\n"
  },
  {
    "path": "packages/cli/assets/android/gen/app/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguardFiles setting in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original source file name.\n#-renamesourcefileattribute SourceFile"
  },
  {
    "path": "packages/cli/assets/android/gen/app/src/main/AndroidManifest.xml.hbs",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <!-- Default permissions -->\n    <uses-permission android:name=\"android.permission.INTERNET\" />\n\n    <!-- Permissions from Dioxus.toml [permissions] section -->\n    {{#each permissions}}\n    <uses-permission android:name=\"{{ this }}\" />\n    {{/each}}\n\n    <!-- Hardware features from Dioxus.toml [android] section -->\n    {{#each features}}\n    <uses-feature android:name=\"{{ this }}\" android:required=\"true\" />\n    {{/each}}\n\n    <!-- Raw manifest XML from Dioxus.toml [android.raw.manifest] -->\n    {{{ raw_manifest }}}\n\n    <application android:hasCode=\"true\" android:icon=\"@mipmap/ic_launcher\"\n        android:extractNativeLibs=\"true\"\n        android:allowNativeHeapPointerTagging=\"false\"\n        android:label=\"@string/app_name\"\n        android:supportsRtl=\"{{#if supports_rtl}}{{ supports_rtl }}{{else}}true{{/if}}\"\n        android:theme=\"{{#if app_theme}}{{ app_theme }}{{else}}@style/AppTheme{{/if}}\"\n        {{#if uses_cleartext_traffic}}android:usesCleartextTraffic=\"{{ uses_cleartext_traffic }}\"{{/if}}\n        {{#if large_heap}}android:largeHeap=\"{{ large_heap }}\"{{/if}}\n        android:networkSecurityConfig=\"@xml/network_security_config\">\n        <activity android:configChanges=\"orientation|screenLayout|screenSize|keyboardHidden\" android:exported=\"true\"\n            android:label=\"@string/app_name\" android:name=\"dev.dioxus.main.MainActivity\">\n            <meta-data android:name=\"android.app.lib_name\" android:value=\"dioxusmain\" />\n            <meta-data android:name=\"android.app.func_name\" android:value=\"ANativeActivity_onCreate\" />\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n\n            {{#each url_schemes}}\n            <!-- URL scheme deep link: {{ this }}:// -->\n            <intent-filter>\n                <action android:name=\"android.intent.action.VIEW\" />\n                <category android:name=\"android.intent.category.DEFAULT\" />\n                <category android:name=\"android.intent.category.BROWSABLE\" />\n                <data android:scheme=\"{{ this }}\" />\n            </intent-filter>\n            {{/each}}\n\n            {{#each app_link_hosts}}\n            <!-- App link: https://{{ this }} -->\n            <intent-filter android:autoVerify=\"true\">\n                <action android:name=\"android.intent.action.VIEW\" />\n                <category android:name=\"android.intent.category.DEFAULT\" />\n                <category android:name=\"android.intent.category.BROWSABLE\" />\n                <data android:scheme=\"https\" android:host=\"{{ this }}\" />\n            </intent-filter>\n            {{/each}}\n        </activity>\n\n        {{#if foreground_service_type}}\n        <service\n            android:name=\".DioxusForegroundService\"\n            android:foregroundServiceType=\"{{ foreground_service_type }}\"\n            android:exported=\"false\" />\n        {{/if}}\n    </application>\n</manifest>\n"
  },
  {
    "path": "packages/cli/assets/android/gen/app/src/main/assets/.gitignore",
    "content": ""
  },
  {
    "path": "packages/cli/assets/android/gen/app/src/main/kotlin/.gitignore",
    "content": ""
  },
  {
    "path": "packages/cli/assets/android/gen/app/src/main/res/drawable/ic_launcher_background.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"108dp\"\n    android:height=\"108dp\"\n    android:viewportWidth=\"108\"\n    android:viewportHeight=\"108\">\n    <path\n        android:fillColor=\"#3DDC84\"\n        android:pathData=\"M0,0h108v108h-108z\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M9,0L9,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,0L19,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M29,0L29,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M39,0L39,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M49,0L49,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M59,0L59,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M69,0L69,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M79,0L79,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M89,0L89,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M99,0L99,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,9L108,9\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,19L108,19\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,29L108,29\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,39L108,39\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,49L108,49\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,59L108,59\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,69L108,69\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,79L108,79\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,89L108,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,99L108,99\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,29L89,29\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,39L89,39\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,49L89,49\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,59L89,59\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,69L89,69\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,79L89,79\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M29,19L29,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M39,19L39,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M49,19L49,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M59,19L59,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M69,19L69,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M79,19L79,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n</vector>\n"
  },
  {
    "path": "packages/cli/assets/android/gen/app/src/main/res/drawable-v24/ic_launcher_foreground.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:aapt=\"http://schemas.android.com/aapt\"\n    android:width=\"108dp\"\n    android:height=\"108dp\"\n    android:viewportWidth=\"108\"\n    android:viewportHeight=\"108\">\n    <path android:pathData=\"M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z\">\n        <aapt:attr name=\"android:fillColor\">\n            <gradient\n                android:endX=\"85.84757\"\n                android:endY=\"92.4963\"\n                android:startX=\"42.9492\"\n                android:startY=\"49.59793\"\n                android:type=\"linear\">\n                <item\n                    android:color=\"#44000000\"\n                    android:offset=\"0.0\" />\n                <item\n                    android:color=\"#00000000\"\n                    android:offset=\"1.0\" />\n            </gradient>\n        </aapt:attr>\n    </path>\n    <path\n        android:fillColor=\"#FFFFFF\"\n        android:fillType=\"nonZero\"\n        android:pathData=\"M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z\"\n        android:strokeWidth=\"1\"\n        android:strokeColor=\"#00000000\" />\n</vector>"
  },
  {
    "path": "packages/cli/assets/android/gen/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <background android:drawable=\"@drawable/ic_launcher_background\" />\n    <foreground android:drawable=\"@drawable/ic_launcher_foreground\" />\n</adaptive-icon>"
  },
  {
    "path": "packages/cli/assets/android/gen/app/src/main/res/values/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"colorPrimary\">#008577</color>\n    <color name=\"colorPrimaryDark\">#00574B</color>\n    <color name=\"colorAccent\">#D81B60</color>\n</resources>"
  },
  {
    "path": "packages/cli/assets/android/gen/app/src/main/res/values/strings.xml.hbs",
    "content": "<resources>\n    <string name=\"app_name\">{{app_name}}</string>\n</resources>\n"
  },
  {
    "path": "packages/cli/assets/android/gen/app/src/main/res/values/styles.xml",
    "content": "<resources>\n\n    <!-- Base application theme. -->\n    <style name=\"AppTheme\" parent=\"@style/Theme.AppCompat.Light.NoActionBar\">\n        <!-- Customize your theme here. -->\n    </style>\n</resources>\n"
  },
  {
    "path": "packages/cli/assets/android/gen/app/src/main/res/xml/network_security_config.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<network-security-config>\n    <domain-config cleartextTrafficPermitted=\"true\">\n        <domain includeSubdomains=\"true\">127.0.0.1</domain>\n    </domain-config>\n</network-security-config>\n"
  },
  {
    "path": "packages/cli/assets/android/gen/build.gradle.kts",
    "content": "buildscript {\n    repositories {\n        google()\n        mavenCentral()\n    }\n    dependencies {\n        classpath(\"com.android.tools.build:gradle:8.7.0\")\n        classpath(\"org.jetbrains.kotlin:kotlin-gradle-plugin:2.0.20\")\n    }\n}\n\nallprojects {\n    repositories {\n        google()\n        mavenCentral()\n    }\n}\n\ntasks.register(\"clean\").configure {\n    delete(\"build\")\n}\n\n"
  },
  {
    "path": "packages/cli/assets/android/gen/gradle/wrapper/gradle-wrapper.properties",
    "content": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-9.1.0-bin.zip\nnetworkTimeout=10000\nvalidateDistributionUrl=true\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n"
  },
  {
    "path": "packages/cli/assets/android/gen/gradle.properties",
    "content": "# Project-wide Gradle settings.\n# IDE (e.g. Android Studio) users:\n# Gradle settings configured through the IDE *will override*\n# any settings specified in this file.\n# For more details on how to configure your build environment visit\n# http://www.gradle.org/docs/current/userguide/build_environment.html\n# Specifies the JVM arguments used for the daemon process.\n# The setting is particularly useful for tweaking memory settings.\norg.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8\n# When configured, Gradle will run in incubating parallel mode.\n# This option should only be used with decoupled projects. More details, visit\n# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects\n# org.gradle.parallel=true\n# AndroidX package structure to make it clearer which packages are bundled with the\n# Android operating system, and which are packaged with your app\"s APK\n# https://developer.android.com/topic/libraries/support-library/androidx-rn\nandroid.useAndroidX=true\n# Kotlin code style for this project: \"official\" or \"obsolete\":\nkotlin.code.style=official\n# Enables namespacing of each library's R class so that its R class includes only the\n# resources declared in the library itself and none from the library's dependencies,\n# thereby reducing the size of the R class for that library\nandroid.nonTransitiveRClass=true\nandroid.defaults.buildfeatures.buildconfig=true\nandroid.nonFinalResIds=false\n"
  },
  {
    "path": "packages/cli/assets/android/gen/gradlew",
    "content": "#!/bin/sh\n\n#\n# Copyright © 2015 the original authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n# SPDX-License-Identifier: Apache-2.0\n#\n\n##############################################################################\n#\n#   Gradle start up script for POSIX generated by Gradle.\n#\n#   Important for running:\n#\n#   (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is\n#       noncompliant, but you have some other compliant shell such as ksh or\n#       bash, then to run this script, type that shell name before the whole\n#       command line, like:\n#\n#           ksh Gradle\n#\n#       Busybox and similar reduced shells will NOT work, because this script\n#       requires all of these POSIX shell features:\n#         * functions;\n#         * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,\n#           «${var#prefix}», «${var%suffix}», and «$( cmd )»;\n#         * compound commands having a testable exit status, especially «case»;\n#         * various built-in commands including «command», «set», and «ulimit».\n#\n#   Important for patching:\n#\n#   (2) This script targets any POSIX shell, so it avoids extensions provided\n#       by Bash, Ksh, etc; in particular arrays are avoided.\n#\n#       The \"traditional\" practice of packing multiple parameters into a\n#       space-separated string is a well documented source of bugs and security\n#       problems, so this is (mostly) avoided, by progressively accumulating\n#       options in \"$@\", and eventually passing that to Java.\n#\n#       Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,\n#       and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;\n#       see the in-line comments for details.\n#\n#       There are tweaks for specific operating systems such as AIX, CygWin,\n#       Darwin, MinGW, and NonStop.\n#\n#   (3) This script is generated from the Groovy template\n#       https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt\n#       within the Gradle project.\n#\n#       You can find Gradle at https://github.com/gradle/gradle/.\n#\n##############################################################################\n\n# Attempt to set APP_HOME\n\n# Resolve links: $0 may be a link\napp_path=$0\n\n# Need this for daisy-chained symlinks.\nwhile\n    APP_HOME=${app_path%\"${app_path##*/}\"}  # leaves a trailing /; empty if no leading path\n    [ -h \"$app_path\" ]\ndo\n    ls=$( ls -ld \"$app_path\" )\n    link=${ls#*' -> '}\n    case $link in             #(\n      /*)   app_path=$link ;; #(\n      *)    app_path=$APP_HOME$link ;;\n    esac\ndone\n\n# This is normally unused\n# shellcheck disable=SC2034\nAPP_BASE_NAME=${0##*/}\n# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)\nAPP_HOME=$( cd -P \"${APP_HOME:-./}\" > /dev/null && printf '%s\\n' \"$PWD\" ) || exit\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=maximum\n\nwarn () {\n    echo \"$*\"\n} >&2\n\ndie () {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n} >&2\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"$( uname )\" in                #(\n  CYGWIN* )         cygwin=true  ;; #(\n  Darwin* )         darwin=true  ;; #(\n  MSYS* | MINGW* )  msys=true    ;; #(\n  NONSTOP* )        nonstop=true ;;\nesac\n\n\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=$JAVA_HOME/jre/sh/java\n    else\n        JAVACMD=$JAVA_HOME/bin/java\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=java\n    if ! command -v java >/dev/null 2>&1\n    then\n        die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nfi\n\n# Increase the maximum file descriptors if we can.\nif ! \"$cygwin\" && ! \"$darwin\" && ! \"$nonstop\" ; then\n    case $MAX_FD in #(\n      max*)\n        # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.\n        # shellcheck disable=SC2039,SC3045\n        MAX_FD=$( ulimit -H -n ) ||\n            warn \"Could not query maximum file descriptor limit\"\n    esac\n    case $MAX_FD in  #(\n      '' | soft) :;; #(\n      *)\n        # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.\n        # shellcheck disable=SC2039,SC3045\n        ulimit -n \"$MAX_FD\" ||\n            warn \"Could not set maximum file descriptor limit to $MAX_FD\"\n    esac\nfi\n\n# Collect all arguments for the java command, stacking in reverse order:\n#   * args from the command line\n#   * the main class name\n#   * -classpath\n#   * -D...appname settings\n#   * --module-path (only if needed)\n#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.\n\n# For Cygwin or MSYS, switch paths to Windows format before running java\nif \"$cygwin\" || \"$msys\" ; then\n    APP_HOME=$( cygpath --path --mixed \"$APP_HOME\" )\n\n    JAVACMD=$( cygpath --unix \"$JAVACMD\" )\n\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    for arg do\n        if\n            case $arg in                                #(\n              -*)   false ;;                            # don't mess with options #(\n              /?*)  t=${arg#/} t=/${t%%/*}              # looks like a POSIX filepath\n                    [ -e \"$t\" ] ;;                      #(\n              *)    false ;;\n            esac\n        then\n            arg=$( cygpath --path --ignore --mixed \"$arg\" )\n        fi\n        # Roll the args list around exactly as many times as the number of\n        # args, so each arg winds up back in the position where it started, but\n        # possibly modified.\n        #\n        # NB: a `for` loop captures its iteration list before it begins, so\n        # changing the positional parameters here affects neither the number of\n        # iterations, nor the values presented in `arg`.\n        shift                   # remove old arg\n        set -- \"$@\" \"$arg\"      # push replacement arg\n    done\nfi\n\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS='\"-Xmx64m\" \"-Xms64m\"'\n\n# Collect all arguments for the java command:\n#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,\n#     and any embedded shellness will be escaped.\n#   * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be\n#     treated as '${Hostname}' itself on the command line.\n\nset -- \\\n        \"-Dorg.gradle.appname=$APP_BASE_NAME\" \\\n        -jar \"$APP_HOME/gradle/wrapper/gradle-wrapper.jar\" \\\n        \"$@\"\n\n# Stop when \"xargs\" is not available.\nif ! command -v xargs >/dev/null 2>&1\nthen\n    die \"xargs is not available\"\nfi\n\n# Use \"xargs\" to parse quoted args.\n#\n# With -n1 it outputs one arg per line, with the quotes and backslashes removed.\n#\n# In Bash we could simply go:\n#\n#   readarray ARGS < <( xargs -n1 <<<\"$var\" ) &&\n#   set -- \"${ARGS[@]}\" \"$@\"\n#\n# but POSIX shell has neither arrays nor command substitution, so instead we\n# post-process each arg (as a line of input to sed) to backslash-escape any\n# character that might be a shell metacharacter, then use eval to reverse\n# that process (while maintaining the separation between arguments), and wrap\n# the whole thing up as a single \"set\" statement.\n#\n# This will of course break if any of these variables contains a newline or\n# an unmatched quote.\n#\n\neval \"set -- $(\n        printf '%s\\n' \"$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS\" |\n        xargs -n1 |\n        sed ' s~[^-[:alnum:]+,./:=@_]~\\\\&~g; ' |\n        tr '\\n' ' '\n    )\" '\"$@\"'\n\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "packages/cli/assets/android/gen/gradlew.bat",
    "content": "@rem\r\n@rem Copyright 2015 the original author or authors.\r\n@rem\r\n@rem Licensed under the Apache License, Version 2.0 (the \"License\");\r\n@rem you may not use this file except in compliance with the License.\r\n@rem You may obtain a copy of the License at\r\n@rem\r\n@rem      https://www.apache.org/licenses/LICENSE-2.0\r\n@rem\r\n@rem Unless required by applicable law or agreed to in writing, software\r\n@rem distributed under the License is distributed on an \"AS IS\" BASIS,\r\n@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n@rem See the License for the specific language governing permissions and\r\n@rem limitations under the License.\r\n@rem\r\n@rem SPDX-License-Identifier: Apache-2.0\r\n@rem\r\n\r\n@if \"%DEBUG%\"==\"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@rem  Gradle startup script for Windows\r\n@rem\r\n@rem ##########################################################################\r\n\r\n@rem Set local scope for the variables with windows NT shell\r\nif \"%OS%\"==\"Windows_NT\" setlocal\r\n\r\nset DIRNAME=%~dp0\r\nif \"%DIRNAME%\"==\"\" set DIRNAME=.\r\n@rem This is normally unused\r\nset APP_BASE_NAME=%~n0\r\nset APP_HOME=%DIRNAME%\r\n\r\n@rem Resolve any \".\" and \"..\" in APP_HOME to make it shorter.\r\nfor %%i in (\"%APP_HOME%\") do set APP_HOME=%%~fi\r\n\r\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\r\nset DEFAULT_JVM_OPTS=\"-Xmx64m\" \"-Xms64m\"\r\n\r\n@rem Find java.exe\r\nif defined JAVA_HOME goto findJavaFromJavaHome\r\n\r\nset JAVA_EXE=java.exe\r\n%JAVA_EXE% -version >NUL 2>&1\r\nif %ERRORLEVEL% equ 0 goto execute\r\n\r\necho. 1>&2\r\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2\r\necho. 1>&2\r\necho Please set the JAVA_HOME variable in your environment to match the 1>&2\r\necho location of your Java installation. 1>&2\r\n\r\ngoto fail\r\n\r\n:findJavaFromJavaHome\r\nset JAVA_HOME=%JAVA_HOME:\"=%\r\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\r\n\r\nif exist \"%JAVA_EXE%\" goto execute\r\n\r\necho. 1>&2\r\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2\r\necho. 1>&2\r\necho Please set the JAVA_HOME variable in your environment to match the 1>&2\r\necho location of your Java installation. 1>&2\r\n\r\ngoto fail\r\n\r\n:execute\r\n@rem Setup the command line\r\n\r\n\r\n\r\n@rem Execute Gradle\r\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -jar \"%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\" %*\r\n\r\n:end\r\n@rem End local scope for the variables with windows NT shell\r\nif %ERRORLEVEL% equ 0 goto mainEnd\r\n\r\n:fail\r\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\r\nrem the _cmd.exe /c_ return code!\r\nset EXIT_CODE=%ERRORLEVEL%\r\nif %EXIT_CODE% equ 0 set EXIT_CODE=1\r\nif not \"\"==\"%GRADLE_EXIT_CONSOLE%\" exit %EXIT_CODE%\r\nexit /b %EXIT_CODE%\r\n\r\n:mainEnd\r\nif \"%OS%\"==\"Windows_NT\" endlocal\r\n\r\n:omega\r\n"
  },
  {
    "path": "packages/cli/assets/android/gen/settings.gradle",
    "content": "include ':app'\n\n"
  },
  {
    "path": "packages/cli/assets/android/prebuilt/README.md",
    "content": "This folder contains prebuilt versions of android crates to make cross-compiling easier.\n\nWe use the official prebuilds distributed by google.\n\nYou can find the full set of prebuilt libraries that google distributes here:\n\nhttps://maven.google.com/web/index.html?q=com.android.ndk.thirdparty#com.android.ndk.thirdparty\n\nThe version included in `dx` is downloaded from here:\n\nhttps://maven.google.com/web/index.html?q=com.android.ndk.thirdparty#com.android.ndk.thirdparty:openssl:1.1.1q-beta-1\n\nThe SHA of the `.aar` file from google and `dx` are different. The Rust openssl-sys crate expects libcrypto and libssl to be in the same folder, but the `.aar` that google distributes splits the two libraries into two different folders. I (jon) have simply merged the folders together and then re-packed the folder as a `.tar.gz`.\n"
  },
  {
    "path": "packages/cli/assets/dioxus.toml",
    "content": "[application]\n\n# Web `build` & `serve` dist path\nout_dir = \"dist\"\n\n# resource (static) file folder\nasset_dir = \"public\"\n\n[web.wasm_opt]\n# The level wasm-opt should target. z is the smallest. 4 is the fastest.\nlevel = \"4\"\n\n[web.app]\n\n# HTML title tag content\ntitle = \"Dioxus | An elegant GUI library for Rust\"\n\n[web.watcher]\n\nindex_on_404 = true\n\nwatch_path = [\"src\", \"examples\"]\n\n[bundle]\n# Bundle identifier\nidentifier = \"io.github.{{project-name}}\"\n\n# Bundle publisher\npublisher = \"{{project-name}}\"\n\n# Bundle icon\nicon = [\"icons/icon.png\"]\n\n# Bundle resources\nresources = [\"public/*\"]\n\n# Bundle copyright\ncopyright = \"\"\n\n# Bundle category\ncategory = \"Utility\"\n\n# Bundle short description\nshort_description = \"An amazing dioxus application.\"\n\n# Bundle long description\nlong_description = \"\"\"\nAn amazing dioxus application.\n\"\"\"\n"
  },
  {
    "path": "packages/cli/assets/ios/ios.plist.hbs",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>CFBundleDisplayName</key>\n\t<string>{{ display_name }}</string>\n\n\t<key>CFBundleExecutable</key>\n\t<string>{{ executable_name }}</string>\n\n\t<key>CFBundleIdentifier</key>\n\t<string>{{ bundle_identifier }}</string>\n\n\t<key>CFBundleName</key>\n\t<string>{{ bundle_name }}</string>\n\n\t<key>CFBundleVersion</key>\n\t<string>{{ version }}</string>\n\t<key>CFBundleShortVersionString</key>\n\t<string>{{ version }}</string>\n\t<key>CFBundleDevelopmentRegion</key>\n\t<string>en_US</string>\n\t<key>UILaunchStoryboardName</key>\n\t<string>LaunchScreen</string>\n\t<key>LSRequiresIPhoneOS</key>\n\t<true/>\n\t<key>UISupportsTrueScreenSizeOnMac</key>\n\t<true/>\n\t<key>UIRequiredDeviceCapabilities</key>\n\t<array>\n\t\t<string>arm64</string>\n\t\t<string>metal</string>\n\t</array>\n\t<key>UIDeviceFamily</key>\n\t<array>\n\t\t<integer>1</integer>\n\t\t<integer>2</integer>\n\t</array>\n\t<key>CFBundleSupportedPlatforms</key>\n\t<array>\n\t\t<string>iPhoneOS</string>\n\t\t<string>iPadOS</string>\n\t</array>\n\t<key>UISupportedInterfaceOrientations</key>\n\t<array>\n\t\t<string>UIInterfaceOrientationPortrait</string>\n\t\t<string>UIInterfaceOrientationLandscapeLeft</string>\n\t\t<string>UIInterfaceOrientationLandscapeRight</string>\n\t</array>\n\t<key>UISupportedInterfaceOrientations~ipad</key>\n\t<array>\n\t\t<string>UIInterfaceOrientationPortrait</string>\n\t\t<string>UIInterfaceOrientationPortraitUpsideDown</string>\n\t\t<string>UIInterfaceOrientationLandscapeLeft</string>\n\t\t<string>UIInterfaceOrientationLandscapeRight</string>\n\t</array>\n\n\t<!-- Permission usage descriptions from Dioxus.toml [permissions] section -->\n\t{{#each permissions}}\n\t<key>{{ this.key }}</key>\n\t<string>{{ this.description }}</string>\n\t{{/each}}\n\n\t{{#if url_schemes}}\n\t<!-- URL schemes from Dioxus.toml [deep_links] and [ios] sections -->\n\t<key>CFBundleURLTypes</key>\n\t<array>\n\t\t<dict>\n\t\t\t<key>CFBundleURLSchemes</key>\n\t\t\t<array>\n\t\t\t\t{{#each url_schemes}}\n\t\t\t\t<string>{{ this }}</string>\n\t\t\t\t{{/each}}\n\t\t\t</array>\n\t\t</dict>\n\t</array>\n\t{{/if}}\n\n\t{{#if background_modes}}\n\t<!-- Background modes from Dioxus.toml [background] and [ios] sections -->\n\t<key>UIBackgroundModes</key>\n\t<array>\n\t\t{{#each background_modes}}\n\t\t<string>{{ this }}</string>\n\t\t{{/each}}\n\t</array>\n\t{{/if}}\n\n\t<!-- Additional plist entries from Dioxus.toml [ios.plist] section -->\n\t{{{ plist_entries }}}\n\n\t<!-- Raw plist XML from Dioxus.toml [ios.raw.info_plist] -->\n\t{{{ raw_plist }}}\n</dict>\n</plist>\n"
  },
  {
    "path": "packages/cli/assets/macos/mac.plist.hbs",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n\t<dict>\n\t\t<key>CFBundleDisplayName</key>\n\t\t<string>{{ display_name }}</string>\n\n\t\t<key>CFBundleExecutable</key>\n\t\t<string>{{ executable_name }}</string>\n\n\t\t<key>CFBundleIdentifier</key>\n\t\t<string>{{ bundle_identifier }}</string>\n\n\t\t<key>CFBundleName</key>\n\t\t<string>{{ bundle_name }}</string>\n\n\t\t<key>CFBundleIconFile</key>\n\t\t<string>icon.icns</string>\n\n\t\t<key>CFBundleInfoDictionaryVersion</key>\n\t\t<string>6.0</string>\n\n\t\t<key>CFBundlePackageType</key>\n\t\t<string>APPL</string>\n\n\t\t<key>CFBundleShortVersionString</key>\n\t\t<string>{{ version }}</string>\n\n\t\t<key>CFBundleVersion</key>\n\t\t<string>{{ version }}</string>\n\n\t\t<key>LSMinimumSystemVersion</key>\n\t\t<string>{{ minimum_system_version }}</string>\n\n\t\t<!-- Permission usage descriptions from Dioxus.toml [permissions] section -->\n\t\t{{#each permissions}}\n\t\t<key>{{ this.key }}</key>\n\t\t<string>{{ this.description }}</string>\n\t\t{{/each}}\n\n\t\t{{#if url_schemes}}\n\t\t<!-- URL schemes from Dioxus.toml [deep_links] and [macos] sections -->\n\t\t<key>CFBundleURLTypes</key>\n\t\t<array>\n\t\t\t<dict>\n\t\t\t\t<key>CFBundleURLSchemes</key>\n\t\t\t\t<array>\n\t\t\t\t\t{{#each url_schemes}}\n\t\t\t\t\t<string>{{ this }}</string>\n\t\t\t\t\t{{/each}}\n\t\t\t\t</array>\n\t\t\t</dict>\n\t\t</array>\n\t\t{{/if}}\n\n\t\t<!-- Additional plist entries from Dioxus.toml [macos.plist] section -->\n\t\t{{{ plist_entries }}}\n\n\t\t<!-- Raw plist XML from Dioxus.toml [macos.raw.info_plist] -->\n\t\t{{{ raw_plist }}}\n\t</dict>\n</plist>\n"
  },
  {
    "path": "packages/cli/assets/web/dev.index.html",
    "content": "<!DOCTYPE html>\n<html>\n    <head>\n        <title>{app_title}</title>\n        <meta content=\"text/html;charset=utf-8\" http-equiv=\"Content-Type\">\n        <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n        <meta charset=\"UTF-8\">\n        <style>\n            /* Inter Font */\n            @import url('https://fonts.googleapis.com/css2?family=Inter:wght@100..900&display=swap') layer;\n\n            #dx-toast-template {\n                display: none;\n                visibility: hidden;\n            }\n\n            .dx-toast {\n                position: absolute;\n                top: 10px;\n                right: 0;\n                padding-right: 10px;\n                user-select: none;\n                /* transition: transform 0.2s ease; */\n                z-index: 2147483647;\n            }\n\n            .dx-toast .dx-toast-inner {\n                /* transition: right 0.2s ease-out; */\n                position: fixed;\n\n                background-color: #181B20;\n                color: #ffffff;\n                font-family: \"Inter\", sans-serif;\n\n                display: grid;\n                grid-template-columns: auto auto;\n                max-width: 400px;\n                min-height: 56px;\n                border-radius: 5px;\n            }\n\n            .dx-toast .dx-toast-inner {\n                cursor: pointer;\n                margin-right: 10px;\n            }\n\n            .dx-toast .dx-toast-level-bar-container {\n                height: 100%;\n                width: 6px;\n            }\n\n            .dx-toast .dx-toast-level-bar-container .dx-toast-level-bar {\n                width: 100%;\n                height: 100%;\n                border-radius: 5px 0px 0px 5px;\n            }\n\n            .dx-toast .dx-toast-content {\n                padding: 8px;\n            }\n\n            .dx-toast .dx-toast-header {\n                display: flex;\n                flex-direction: row;\n                justify-content: start;\n                align-items: end;\n                margin-bottom: 10px;\n            }\n\n            .dx-toast .dx-toast-header>svg {\n                height: 18px;\n                margin-right: 5px;\n            }\n\n            .dx-toast .dx-toast-header .dx-toast-header-text {\n                font-size: 14px;\n                font-weight: 700;\n                padding: 0;\n                margin: 0;\n            }\n\n            .dx-toast .dx-toast-msg {\n                font-size: 11px;\n                font-weight: 400;\n                padding: 0;\n                margin: 0;\n            }\n\n            .dx-toast-level-bar.info {\n                background-color: #428EFF;\n            }\n\n            .dx-toast-level-bar.success {\n                background-color: #42FF65;\n            }\n\n            .dx-toast-level-bar.error {\n                background-color: #FF4242;\n            }\n        </style>\n        <script>\n            const STORAGE_KEY = \"SCHEDULED-DX-TOAST\";\n            let currentTimeout = null;\n            let currentToastId = 0;\n\n            // Show a toast, removing the previous one.\n            function showDXToast(headerText, message, progressLevel, durationMs) {\n                const decor = document.getElementById(\"__dx-toast-decor\");\n                const text = document.getElementById(\"__dx-toast-text\");\n                const msg = document.getElementById(\"__dx-toast-msg\");\n                const inner = document.getElementById(\"__dx-toast-inner\");\n                const toast = document.getElementById(\"__dx-toast\");\n\n                if (decor) decor.className = `dx-toast-level-bar ${progressLevel}`;\n                if (text) text.innerText = headerText;\n                if (msg) msg.innerText = message;\n                if (inner) inner.style.right = \"0\";\n                if (toast) {\n                    toast.removeAttribute(\"aria-hidden\");\n                    toast.addEventListener(\"click\", closeDXToast);\n                }\n\n                // Wait a bit of time so animation plays correctly.\n                setTimeout(\n                    () => {\n                        let ourToastId = currentToastId;\n                        currentTimeout = setTimeout(() => {\n                            if (ourToastId == currentToastId) {\n                                closeDXToast();\n                            }\n                        }, durationMs);\n                    },\n                    100\n                );\n\n                currentToastId += 1;\n            }\n\n            // Schedule a toast to be displayed after reload.\n            function scheduleDXToast(headerText, message, level, durationMs) {\n                let data = {\n                    headerText,\n                    message,\n                    level,\n                    durationMs,\n                };\n\n                let jsonData = JSON.stringify(data);\n                sessionStorage.setItem(STORAGE_KEY, jsonData);\n            }\n\n            // Close the current toast.\n            function closeDXToast() {\n                document.getElementById(\"__dx-toast-inner\").style.right = \"-1000px\";\n                document.getElementById(\"__dx-toast\").setAttribute(\"aria-hidden\", \"true\");\n                clearTimeout(currentTimeout);\n            }\n\n            // Handle any scheduled toasts after reload.\n            let potentialData = sessionStorage.getItem(STORAGE_KEY);\n            if (potentialData) {\n                sessionStorage.removeItem(STORAGE_KEY);\n                let data = JSON.parse(potentialData);\n                showDXToast(data.headerText, data.message, data.level, data.durationMs);\n            }\n\n            window.scheduleDXToast = scheduleDXToast;\n            window.showDXToast = showDXToast;\n            window.closeDXToast = closeDXToast;\n        </script>\n    </head>\n    <body>\n        <div id=\"__dx-toast\" class=\"dx-toast\" aria-hidden=\"true\">\n            <div id=\"__dx-toast-inner\" class=\"dx-toast-inner\" style=\"right:-1000px;\">\n                <div class=\"dx-toast-level-bar-container\">\n                    <div id=\"__dx-toast-decor\" class=\"dx-toast-level-bar __info\"></div>\n                </div>\n                <div class=\"dx-toast-content\">\n                    <div class=\"dx-toast-header\">\n                        <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 32 32\" preserveAspectRatio=\"none\">\n                            <path d=\"M22.158 1.783c0 3.077-.851 5.482-2.215 7.377s-3.32 3.557-5.447 5.33-4.425 3.657-6.252 6.195-3.102 5.515-3.102 9.532h4.699c0-3.077.853-5.377 2.217-7.272s3.32-3.557 5.447-5.33 4.425-3.657 6.252-6.195 3.102-5.62 3.102-9.637z\" fill=\"#e96020\"/>\n                            <path d=\"M9.531 25.927c-.635 0-1.021.515-1.02 1.15s.385 1.151 1.02 1.15H22.47a1.151 1.151 0 1 0 0-2.301zm1.361-4.076c-.608 0-.954.558-.953 1.166s.346 1.035.953 1.035h10.217a1.101 1.101 0 1 0 0-2.201zm0-13.594a1.101 1.101 0 1 0 0 2.201h10.217c.607 0 .953-.598.953-1.205s-.345-.996-.953-.996zM9.531 4.021A1.15 1.15 0 0 0 8.38 5.17a1.15 1.15 0 0 0 1.15 1.15h12.94c.635 0 1.021-.498 1.02-1.133s-.386-1.166-1.02-1.166z\" fill=\"#2d323b\"/>\n                            <path d=\"M5.142 1.783c0 4.016 1.275 7.099 3.102 9.637s4.125 4.422 6.252 6.195 4.083 3.656 5.447 5.551 2.215 3.974 2.215 7.051h4.701c0-4.016-1.275-7.038-3.102-9.576s-4.125-4.422-6.252-6.195-4.083-3.435-5.447-5.33S9.841 4.86 9.841 1.783z\" fill=\"#00a8d6\"/>\n                        </svg>\n                        <h3 id=\"__dx-toast-text\" class=\"dx-toast-header-text\">Your app is being rebuilt.</h3>\n                    </div>\n                    <p id=\"__dx-toast-msg\" class=\"dx-toast-msg\">A non-hot-reloadable change occurred and we must rebuild.</p>\n                </div>\n            </div>\n        </div>\n        <div id=\"main\"></div>\n    </body>\n</html>\n"
  },
  {
    "path": "packages/cli/assets/web/dev.loading.html",
    "content": "<!DOCTYPE html>\n<html>\n\n<head>\n  <title>Dioxus Build</title>\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n  <meta charset=\"UTF-8\" />\n  <link rel=\"icon\" type=\"image/png\" href=\"https://avatars.githubusercontent.com/u/79236386?s=200&v=4\">\n  <style>\n    /* Fira Mono Font */\n    @import url('https://fonts.googleapis.com/css2?family=Fira+Mono:wght@400;500;700&display=swap');\n\n    body {\n      margin: 0;\n      padding: 0;\n      overflow-x: hidden;\n      font-family: \"Fira Mono\", monospace;\n      color: #313131;\n      background-repeat: repeat;\n      background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAPoAAAD6CAMAAAC/MqoPAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAABgUExURdXZ39ba4Nfb4dHV29vf5dre5Njc4tnd49TY3uDk6t3h59zg5uHl697i6NPX3dLW3M/T2ePn7d/j6dDU2uTo7uLm7M7S2OXp783R18zQ1uru9O3x98vP1ebq8Ont88rO1EIB0XMAAAAJcEhZcwAADsMAAA7DAcdvqGQAAIpzSURBVHheLf2LgqNIErZpAhIIgUCZREd1z+zM7v3f5T4v9fehKjNCQu7mZt/B3EHDME7T4zkP8zKMw+s1r6/X8z295mV7bdP42vd5fr2Wedq2afGnfZ4+y2d67s99ePnJuEzL8RrnfVle4zk9jmNctnN+7dP2mo7X/hzncfss2zofr+X1Gqbl8Xou32U+9y56zJ9lWOblGCYXe0/LOL1e0ziN83Lur++2jl7mn4Y3H/P+PJ/z/B5f4zKu8/hcp206jm336j/D3/01ncf3O72WpZ9c749BPpf9+RrHeTLRYVjHa3o1k+m5LMNzPrZlPbfXPK/fr0lsx3Ls1+s69mPc9ufxem3j/LPO1/6aX+Prmqfnc3w9n9dwDdtr2SZ/fy3frUE+T4N+PWdzN/tt85Nt3x7DMo7b+D6W5/B8nsP8NLTXf1bjm4TmtRrEax5e3/3YRWAq1vu6mMJyToth+Ov0cv3lc74+Ju2TTq8yn2l4+pBNUOe3EZ7Gs2+L5dpn72nW2/Ac5ml6Ha5mYKO/jcfrHHzg/J28eXpNH6PdLmvurdNT7Nfjvb32ZXw+rf48rc99Gx7PY96e3+f2nK5xWoZj2ubn03Xkw97S/zyf/rCMPuq5Hq+3S7r277It7/25GvO6yY/5dbys2HPcZNe+foZSSn7s3203Y8MZvX0ejHcXXr8Zztl/1vF5vcxgHuTlsFgXv5tdz4Dm1+M5GMm0Gfw8b9cgkP732sfdoCz2a3i9p+10XSHZDwsyn+uwrZbOW57Df/b38no/53E8Fjm9vZ7jOEr16R+xuyZRmdfnuMoPwZPyy9MSzKt/vF7f43g+rfC8D/PxnCXItZn0KYktrBT1Iiv93iuk97Sbzn5/WKFeqp3Vn/aXcbT8YmrU27qOqyLZ1u21XrugL8t2bM9jOk75MIuEK05Vlze/J6P0kuHwSfPz5c+DWS3b+yUTjuf+VSwCf0yTIjrOP+OwjWL3UVbr+1y8aHhKInm5zl8D+VMSvsbJ5zyXz2a9G5q8Erp9XGVWlbGPTx8+P5fzcUzf17qv6m56XfJG4a/z3/f2WI36K8ijt1nU4Xks012s41YQZYwyNIefzUIKppLYwAQEsp6L8j+Oa1pfH5F/zePrO4rpbkjDtkxrhXVYwuXPXOIAl68P9ht4tlnM0diEZpY5+zUPj/l5uf7TAl5K2wIFXKuc8pcWRQLtr2tYhtf6OTZQo2ANZpaXAHGw4M9VmBWwxP1RdgY1ree6uv7yfg3D/h9JIDstvyoYj30ep91/jEcsAeWgeveQ561muti2zVYL/EwK93gevW03+0kaSMLpu2ygTiWLbsUGnk8zmE8/l6a7ejqq6Pc4mtdm2YaHSAnFOF27PKzgN8jqz+reS97L6irDugI+KaNqxktoYeQwP0xkP38NVkUO+z6Ahmt+AhOltM/nR1qBjR1ENqKoYhd3/y+pw/mnJd5lsF9YkadLe+V7GENR1VvRKm7rck2DepR57+G4PgA10jEzoRK/D5DdBxgwy7aQ5hhGPPAEOYFrWOatyucl81dFL+Tv61F5IrP3ckqq47d5DSEulP77X/kBY4CXstmHUHFRnXBsul7bd19B+HqoD8j9/vhsK6JonzMSlAT76zkdEnwdqsv5EP+v8rrnbh3f5o0dLpnbHMZTRg37sr69DSm+PyD3V8JO53hZqAtoV+bgWgIu1k2YWt+YVm0OcuF1PDeFe1342LUBkmUB5xU6BjqU7HDCDPM6vgK8jKcKKe0tisp8CqwiP/bxrxeDZrhqlSuKoRJcwcqq4qwiylV20gnJjQdAO7/iYISrjzIzWCNkgjlLYKSwlY0xySKENMD1AVPCiedAmembAJAPXp/rF29idX+zklIEt/u4RRQMfMIT3r/403cb1gPN7QTG61dyIYBjWt4/LwDsN4Hk+PbxEA6jfQfRekhfOQ0RaReIp7S34R7TuSAq9FjiqZnr8EHQX83gBcuvGl/PiwQw+xTFuisTeDK+n9/xmTCZHwpltkK7Cqk8oe+k8MYtaWSVzwc5ICQ+//rK1oF6GOOGv+OwnmKdkooDh2t7jj+QEqB735sUsaIuu8NlUdtTXYY1jcNbXldQ/7PafjSv8gBUwxoVuJR5MLia89vxPKX78nwILi4q2KSI2hbeJ2x+78uPhA5hR2PZ1xHUS/fTT3ZTE0QAUclKQPOYlSta869JeojcaWSAUb4Rhz4VkuBtNLAqPemHC49xkEYxz/OSHz7ZqwSE0HhJV+99HcBE/gLU5QrKfOr32NZfufrcj/f4JLmmx3ruwzpd7+Unidl0Vi+VkSIt9PTNchEiJqj4rihIMasJn5h6Umq0ndREDI1jWK+bE0C6Kc+7uE6rykME0zHNllqmrFvrbIY+EKSSjzIXQn1BIW4gWH6s/LYuMfi2CiX14aWIwep40XEASJ85bb/q0mesqEThbGSHWa9qABmRBtaxuvDpwcP8eH6eKmn6oj/TOc49joLPT3QSoQG2534SgdIaMsHf5SltjNdnv2TMNK2TJQa2p5jAju2kD5PX3ripM5cLa+EI8ScOrRJeBh5bUGXW6AdgYNIE/XLNSNYSvwU77JIe1/Og61DbNtOySBG1UoAuQLBbcym4UUtYHI2YHK6NK6CSZZ9RLUhIcDYqYl3V+kypSNkhOuCPMZ7TaSmBDeyb1t9pfDf1aybS/AhFIkAjuDUOCGum+1g2Iy+MBfMkGklheU1EBYZm23mMJ96QpFCNfPGGW0RvKFlKDS7+fj6Vlpwi841OlKDhsnEjhJ+KWJefF47yU9hsqJdsFMtJCZEkVlUeCAdx/HycNBnKO6HEDwDHHZIlwJVOYj7Q0gY4S3LF8XqEVcNCsxrgR6ryGs91+/Wx2/tCsxLGhA8UtktIZCjU6dvHMg3XKqwgBsLK+eFa3gojnWl21FxODoY1WHod01kTSoi3ei0/zX8FhMwLulQFEhXsES7fBc5sKUoafXr9wELQKx3QvSKYPxBZUrEUPBHg2V6vH+W7+TspMEUNJKS8hWHDfhm9MEwUu7B4zY5kH0COO8I+/A78lpDjiOYmoJ2ilFcK8fl3OFGRLIbZf2UsRxVqKnHkuhCQ6yrBWvpx/UcOPoYfojaknH6Ew4RRZiBnHJVRIbjGaxy+CCxClzrCeJuqBEZ4bexA0KeMBOj8+c0ILcf8+uE2p+GjsFQsPFd7WeB1/KWA/otTveYo/aL4xNCCdPzj9b9QL3tCs0AH5mR8qGMyd39zwSrmYGgkciUyC0Qjltg3fifeVPzzgSVCQsVH1OFbL5fPxD3k2BAlbrkO7F5dWHSFdp7LtZx4CeOqPWxYqewrpMmd03ivF5dIfWLyxO92DFIGyS/bJTzrnvQEgarMKsh28aAaLKzB01jpXALWoN9qWL7TEkLg4l9S7Z2CeAlUiM4sUsKU1loKyUNp/0/wm3PfKFKgI1cPNLOZ53OkTaXlM3kb05PEqJYRfu2YfvHCsT7B+1eIQ2KUGdrMk0jcboVifqqH2Gd9XSdyzU/A/4vyvuQaw4daLrnCnPl4SUoKf7bDsgCWIc5ROyeM8B/YrSDrQ7wIZwHeh2/xhO4HYjV0OBwJ4xfpc2cAqxI63mLerBmXWeYeyMYEiWKYysbKcj9b1MtICE8ncBBL2D4PGwBTPPEeH7ngSyMwReg1E2IgjQlREMiyuYq/5d1P/A8Z0tsmoT4ey+86fs8PLSSp2FOZl0/2Aa6/vK0zZSdHcKn6VShiQFqqFQPyykwageB/yfHPuS3fV1WdZSEAJcL1fauHEJk/ZqS/EuNFHIzL414XsNUPXuMl8V15fnOWqtRCEQfAJflFoQnGY/+GGl5PZweaiebnB09bIGRhYX2qWiezX9ubU8/U8/lW3uwnqWqk1gGNEvBVNlFkforHZ6MwQLBYG0kPJ2u5TPLvtCqylSk1aKQTLtT/QmIslfW2mLkNWfN6rgdyp8bI8Wm8RBX6Kau3v8qwtK1lEQvwgpEOLCEFqR2R2OGD94G5VPk5rCUPosc+0uTclT+F5JO4yOejkfC7PtIoDc3b1MmJwE+LjZMLlgKZ3mJqpGlOqb+VLoE+rscQt2WGr9M/QmR2q+rMH4/zFzxIYQAl+6SwgqMHD1RGepnmwnldkoZ1kssx+t/gYN5OqgMqHeMPIoBIZgb3D8MjWFYawqfJtS9aP0OJDUv20dt4QpFaKP2nZkp+IOtPeihfmGbBXmcYaNXH7/YIBHyUhDdDYE0XMoAGKhyXdYYqz/n8QUoLrylx19d7RlfBQAp7JSsknyhmnPAaVyg6jKO/knDiul7Xh2R1/V1qy+MKbbSiuJpykDroSEBZHOUx4azXISGC0+240KQAzj/riaRxErqgTJX5C8/KB2RMR0k/8M7bVHimgqhZC0ouSTO+E6Z8TYD5Gr+pD5kw7a83zzO84dnX+7M0frWd8w+uFK2kfXbXv38TaagEGSm312kgbA/0vu3qE6cKYC0Zos6cxfP1bSgQCQ+waohANFXArvJYRREus5VpnDsLDQS12s2v9iEotsp3Hj9Sma+P8dNBXkeSh4w1VK3w87OzXszRexxfBP44ffERM43/v1AEqEpzRLJbSBzLCn1Z0gc8UyfKJ5JJqIKim3EkIgwj3dJ/ZmiIaGf9PPYPaGTsrOjX7BEy4bK/n6p+NNz1HN9HUl/9G/C1AVQfLWegjX8DQ1cEPYMJH8qYGzaGS0EiVslugupDWb2JNdXMDoEb/3sPb7lE9qi+wIOkutl6qF2JZIyaapk+9D4EPB6Pbf1D5m3Z9z9KTJrIy9nbmBra1WcrBJVsMtGaucSK8xssRXCDhFNDaEdQXjTc+DwtsSB5i3p9geWGg+ygn7fVXAT1kuVQO28gQtsGZfP8R4JZ2I+kv/hhIjB4kMq5t2f9KIoXZ/s0tgSqjlQaN+S/1Pa0oyPDPF/vv5hA1Z43WY3LFw+hCfW4rVbau7/vXyty97O2GcviI5nYequ0fdh+Dbq0p104xOYbNz43dpQynb7HSrGGHQBgeraisuNcZy5CpVgk/2VSKBzFZMLbp6V+JZ3e87nDz1oeInDis28NeGn1b4SVOwriu6Tvg8Rlm5mhugJFW45PmyBu+6lO/xzbYTjTsobyXqE4Noow2f4a6UWrt1aswJ44NxeXqjenBK9dHcQxAPVdmzn1o1glKd76jNEsIHAtdSrmkH5QNVjTVffztrOy22xjP1ciZ2sXbRvnAm0CVjReo4pMHHxWTmv/Xt+ZgCNszg/Vacx1hL0mkmoTQ+hq1lipeEvKkhdyZlwbikJHF+BaeZv2gMUTN3zuDtUbabrIOPfp+hI/Gb/yF+DKJVhIj3yHegyW1ITLHjRHJ5IWliRqkxPDJ3b0QtQsM9s6UKxqjMGYzhpVQnC9axR6Cz7e5eykyAcvyQYddKshwng5pKjaB1q+LvQ8yW3idb+IN28aWRvifb8I3jrJEPQkx1IVxkMwPrLI5lVfpU/bpg8KYeFTLNXNt0Z3amNUtyDhYDLh6Ca/fpTOa0BQ4il283x9/aJGShwG+Yvv24qTegLjXxzBla3duHb5rtgQsNebj9T7EqsEKiOjqs3rYPbTCGz8k25TIQrKMjVlC7t/EytJzA+mn8Zf/0e9VlIKSeAG/pBscgsmja27d0MG+s/kOO1QAowp+w3UkjXAWP2Yr0KnOowO3iBR2vqzTf+RPob34mCUb4o1PdFFM24/EjLWptbTykVU7Tc78gOAjsO9/TOljmRZ4FfGSenXS/lu85sorBQsMBwkiWpgEBK1CYiLUJLxQYMwrKat924X93Az0QGxrEB4PgAqa46T2j5qFwpB1hKRvQTusZhQ2zPWTdbskCQIKlpUwUiz1ZtKxbta/pnge47vGq4iCTTU41PQzuefa4cH9Ckty1/h0vbdLIlw5QqMdqYIB9UM/Y5t4uH+7b3L3Vl5yrA6SzTYDCwbe217wiB43XvT+ECGcgcyqviYRj28wyxjeEtT9jmuluvL2YJVj+2FyuF5PFelIaim65Jrm4dp6Pm/qNRY21fbq03szFnkNslu9uKnlhWjT10aXongIv5iAUI4H4B+vA9ynmbywAGpc5p4XD77/vBujPYLP0aR5y/NuZYb2pn5dP5uqmtIFeOw9fiinv08Tnk9rzIObw4lDhBobK589yf84byUVibTi9Z6+fVVXu2WYm+LgQ4DLPlM1BFfSX65Bh6e8xckSamV2jsJ61B5qCPl3UOaEeNjjTYKJIK1YDYvaPUFrmm/bPjKpM6/y6fc335RG+ITBu4HnKF3GD3TcnRDH2WkVoK1bCcvbCmbTxh+vVckMP7sr1+VgoxrdiBTfJvbiMAjou27gbTaJuMGJwe84mNqST5/0XGkZbCKeh0/VIzoVWl01aNtnIPmKz0vVYVrFw4JWBETGxOcrmLyLZPFqCeMQWuZKKaHtKiz8Ra10h078BrzNxqSaed/a/7LWa7VkliwRD3utxQwhT+Ryn8V64CF/GJDWIwRSydDVuGI8smhOpg1H6UkMumNMLKAyar1ndj5hZPAx/pju+XHGrfvuiTFF7NDBO1yPBOXy/v7hK55Swm8fhc84eoWyXWZfkih8I2vbucmV2GJ31obmf1lqTZBVVnj8Mn5CFgNqiASKO5W+/3z/NdLgOlHTEMh/Ne6oHxXElVipfxz+fTNQNfNz0e9AQLUy28cpDlUwgoeZijI+/ufDGor9HD1TPq4rWzE696xzPYTNZHTrykDWaD2tDT1ICwzcrF+7XtZI8pCLBguqSOcV/thSVRh2Off45E4YRi+IX+7al+QpgDKelBGoIqc6YAYlPlcfk07EEL2ENJ0iKrbLKd0XoO/YKKTDDF1SyB9gfZi1esfJE/fTNFz+QgPeIYFy/awCGt+eVmHAxcvQPYb1IX/x/zdn99pfcglNpYTM0suJuhPlvmneig4sg0sy02lMX7Hx3KODMFG5avVNYla82jeh8exvIXlOw6nUf0Wqw9PgnU7qAD8sKllb9/k9GfpYax86SVnX+QUJBm/s+zfVytfJ3gcE1kWBOdKGRzQvvOM1wbRbimtyl/5JQxDCerTjAaA0tYRacrw/xreC8/UpPN1xmlFRVgu8AvGMcu8JX1okurxLkZCYOT4v7X+rCrkHLg3abRcrw61LM/xMUwfNEF00MBgnPC4EF9NC5S074M6fysgrLns0vK0BKQt4p6WP2nND9BK5XGOmbXrKFIbqJDQMjYzRZhCdyNv4zjBun5cbR4MB0cJUWoCMAku0VI/pjD7yFp+OWH43wkU0H+j+Vj//kB87f6Jo3V1TWmsUgVaSYnQbJn48M31o5f6bk8/UtXH780poNMcrkLDjGSqGSd4izX2gecgwF5r6Xivtkt3zGiG8nJbrhnGBaSeaXcDfiws+QdiwYBr2Z7HVv+rloGQQ/avqpAgxAthGW4yk5/VRxBOZnxNx7+NpedoZe+y4AVQyLzjOiuH5LYEyA2h+UzJuXyhaY19BoU+gDvL9PF3pUlRi3cqAnlYBHrXG17jz403ClAYqdiZlamiaeQcID6aEoyKtI0GbMmVY5UIQXVztTK3Vo9Uaw8kZIGpiQO/4Lxkn6K/Q/ripYGG/69rx16CGagq619/qP5X8nCvjCWwdx+bK9E+4/RIdRvrYaSYu9Yi4vBuVtmAto8qMaBl/W1drHlqi+VnAAXhPqIDYTKo7XPEznv0sayPtiHzfRXmMv2lwDnP/T+Ue6lzrYZUaIwrUYLMA6jleE/IKmo3oOH5v7prAPh5u+xdSMRC0VrJLMS0neP0Q79g13EfCBSxqynAZbZF9sBT3ofM5Be5LGj5cHC4MhTpWNc9XqidBhtALhcEpRSL4A6v3zw2+dkhgpeyltci0dYBXS8lwcWRdSvzn9t/rNHDn43ueCeNQMYDacwyXEIDb7ZnvAgayebnSBF8dwCinYs2dfD3WpuoHcA0q38+t693p+EGirl+53q3OGRdJ9nQgX+Pv1EdMpqebxk0CQfQrdNkNo1kPOla0rcUs7yS9bHUxLIUW7JDTojcTxAnLJID/YELYnI8DiiPvtGsQPjEXO1McCXt8WfdrJ2RS38QXulVZQX/rBGgQH/j9ZC6QIy+YAYGwXqT2NPyix3EiArGh3HHLyT9CTqVEz0iBM/5UTOkXGq1atwvbLpA3FISetQ9zaZ0aAtcobrv9sFtShY/KHtW/yud13F7P8WDplrO+e4EWUVOwBUfXMD6lgKzEG34m5BXlqIz7X/l4ZNvEILBS4AZg1hJK4qoBN582U0aEu+kYCwn59yu+d0reKQOOtjgP+13+8fwgSQCc6Z82iHOD7+2B6cr7xTC73cZf5Y/lOx7u6UEz8fk189AZ6QkY/CYrvGXYjF8KFxXbHh9Br+uKqSE4r+7c4SHbEcyavIXAYDC1r1Y/avo/H9dUuth5Q8X2/YfX76f8gZFdCiCSGXA4t/ty0YiL26iNlNilE1XdLfrTBEI1FchygaFkEUDiD/eH1ioppGoQI3zG4IbAZmrQqFj6u95cgTLF82qZsNOLVBkFitNIwzTVxV0OOqZqYt3gRttYtVP8udY2Q3Y+VuH5KKcCC4m6E30RCzb+UD5VlYpYNWPlB3x/WshtuWgDEkdZCLTfXD4wHvAj956d06CRJQSl0lnZa7oOzgxytpdVjBIq6lLIzyDydfv8Pr7ha80MIV/eyAJNK0yl8J8cenWYf+RZ7UD+k+oJN06iFZvLjf7Jomsq2XjIFP6udOXiXTEVIqRBF75f3yrP1ZNO7MiPZfvuSFOn0K2q1si7AcrBYkbAu4l1bdfkVydWsGqB4k3173at/03xPOB282Rht8OVYGvM6sMFHu7f52LYaZiuzYHWjyv8WnDo/a1QSE2ofK2PNenc4DSQ6240ihZLKi0h3HGIU4ILy9628s33UudR4x3NqJCjlGWvi/W5vMtSioaFH1eN/+W6WIIevNFXm3gOEHazmekqJJdNbdNtdECwOt4vec6RIDGL63g81HPvoZaLiwok9ZQ6bAwlJ/VUV3e+7BiodKL4wD067Ze5u6/P6RJmeJjR/OEW8uPOqF2wy45CEFFQK3k9VzeJFpdRi9b6udy4iNPVaGkNmV15LVt0ABVCT99fpbH9lYmInN+Ex5VYLUqPNNXdrz+dr7z+bdsqUdoAt561iJ7X/OTfAom2viQlCjYK6RalWZQay4s0d95YhUyDBe2ha732o683SEDtzZPJin0UrZIW2TXn0AJ1F0qQsmmSlxJ0YjQWWJfXnYf4iDuUTtBiHKPey+akquccAtvzcg/lHXnFBVyTotSTKS1yQKi/t0tNFaTfq8Yauuw17D/55EEAASUc+3ZyHH1N6LPDzAbLeF6+/Qz4W1ah+lWU0KCGa6aJYBBGX9Us4LNoWNPczyf2MhAKScE8KLO9/NvdneY10cyBnssp1wYW/0aRKsA7Z8k1bSzUrLIHymCv8ljiU3d1BInayirMyrMwB8GR1KukhVUc3GKyFButXRsA5RUN+1AWJSRwGZeOnXgt79qkn7vkEaF2vH1xp1jOlWFYT2JDIZzv+JZsEP2S7WxPWJpvC5f7nNBzgaGTTqhk/JW0gQo8YOvEVBp21lYEO9DTBBxQb35txMBYiBVuwYJcfc6tg4wWO4Xy5tmJez/kFfnQbSojbtZPf+fI7Kc5wshKZCUCy6SG4qBGn0SMNko6fQhiSQrRAWy49N1Lu/5MJYKRSLM1Fys4Ldlq8qDYHx7O1im0W7R9OuDpNLfx/Z6f+LBcGO+UhWrz6nLpHh8iCkwpo8x2UCYiKuk+DHztjXkIS497pOXnDreQ9IN1z/TToOFw/m3mb55C3ADRVcRkX8Wyb2epmaW53doD+hOs2WXW3D1rMtvBeto3EcAnuuwU8gz7BPeYwb7TMTX5YJvMee9jPH2FSXjRkK1rbD8TgeRB6IHNEyoLNIPtB4ZbfUNmDtjkkKUx9e8/N9c6Pj6U5fgPicHNpFIm37SCne95/XvZ1GrbGguqT/geleAbeVqbaGUQTBfprS7SOuQWHL7G+D9kQyMbK3iRcSPTXUeNNHSWSj5j37M+LvVylxF8PkFnGIPgarpzK0xFRdJkdr+Fm2l83qjNJQRcFUMogD0FQxdLFHryqWCfR7wSGQ2qbFmI+IlUVYyPEOApqCB/GqBKi8A0YkbGZ7uwS4+4ebfMBHKmIDPii3qE9QRGYFKBhMdf79A1BvfYDtHmTyS3vdeNWkHR3lTqvzowBkIivq9yppJrvWSYPwQN/BIoK1UKMrAwR2KjXTakfpK24kvxhuGpJ5c5fX4+4Q4UueYF7aEKJoYFx/luh1M/YsQaFGrJi86afo/kEy/0jg1WZ+1m6erLloIxDmydj8WZ6DbTPtf7yeZh7Yr27gOwviowRKAKKUnriTHuvzv9gidMRr2f2KzYeV1rMjrd7IqYQdRTtW0cGJnkfaRdv8/u/Aw9IlKK08B7jNw3wt155VY5+ErCbgfvkhSGca9RWbUiowxqTqQdPtXZj93945l36lL8DfNv8Uakvgja8+f9Rmo3Mcub2UHRNQCTJ/ajxj+WA2f2xqqE7VWl+R2QGDbzxM8S/ihWuQ6D9xJDAoK8EtYEhVEAp1v512X70b9f6z7QGDKcyiyt2fZ4ANHizys++/reH07BQLaNpSF7Une/4mDZBlPxctsj7xFfX05JV5eRNe7LHJu24t7M80aJc95XaEKSXSfAWorWfkkriaGhlGOZ7/vjG43zgBTSouqrfswbUw928TcA4w6CK6t0MdOD40MWcCnCmbDm4f3Q9Aqay6pbjXMdH1mMrUK8q2dC0NuFSs6eTopAv4t0r/6wgveCQK/Yfv9zfzeKq8dZ9p7Y1j/VITw3D/EQlbJLjxwEDnP4bq+d7vr/sd/fFIHB9QIk8OqgK+F/7iVn3Q99wR350xKN8DSLR5ydKe4Idnc1uFymKKPYRd2SP/FLPWLk8P5LwZ5jyvI4QG1o4VBST28fezUV7efoFPZihaMV/xxl0wzAlFPLvGsUaOCWS0eAqWWqpWaMAi7Y4XN0ariTWFbAEO9KsQBGMBPxzSmZ3BW3+z96uDXSC0AUBpg+SgzsscsOXps/jYg4GCd1BfgSa53dhLiErHgqC1kcW8/ytr4h+R/QSumG0EaOTgcvaPjUuv8FQwL/frjSibncipiq6WoKOF352wOZC23kZ4Ju9oy/5j6SEYuVBMtnu4yQRmyzud1HaqU8XWt71uh/aFLlEfNG+lf/kgPSyawr0sZH08iy1Aupr8KY5IVQ21raxFKqGMJt4w+ficN0fb2TW4TeDR7uv5Ovw4Sr28U+EOZ4NqnSjijuvwOpLqbOyRTyKMMj327RArydrBiU7rKiPv87ZyfudIp9b1Qi/xGXmcbOhJj++2OIVSBkta57oyK7PDg/Mbh/27UjmyWHDssGiNmESyk+OwnEUhwP7JVGOAb4w3T8EnXCXuyb/5Oz9/4/Udo28CpNiZ++6CL/bHb4LAjpIb4sg+WLEBTVW+LxSTJbtkkUCJTg6qON8Xs5Xm7kRPxinQuPQHj//t+tW+CjqjFb/dpZC6+CEhhvK5BCAiyTjWrsVxZMr5g4sqyXg3nZfzqtW4hm2sLeA1mTDpsZuVjOfvO1NTAJzjuzn4ez2eUM/NIp+ToPmw4lclfot/zy9X4ALksG+TwXdptPV4++vfohLN5QSOwI8Cz3+W93pDOp2CPEoPGxBev2g03de7jzQwPTlby+aT7uG1MA2PqfOLhVs/gkCLbsm9KU75TB4kHuL20O4yw7j48Z5x1qOirTIgdpUCqf09TY/0rwuyGvbED82CIsUxInIwSiPFq3AR5O0zS/nheBCMNIy6K0zKJtIySqUsn/OZ5PKPdbimr48IyLvI5DU4dSXCBwBaEzw+9382ARy23oXtT/51TnLe5VEbhtf3CPa5TfU+/MD/Hh3GFr02CnVGUgyw/oGgHq46a7EO7PtBiuTCzA7ygqVV4csORSkHJHNfBCvo7DnpFkWjUeh7nhwCX/QEsBBr4OhmjBlcg/jUekrqd3s5Qxt+vL4Uga6zYE1xGHf4aiPtV7TaIZo0Q2reugklk/esQJzkByvDuxqgsYnqlhDKa6ftsE+I1kg1BtrncTSLwZaXap8lkdUohCZsIna1Egn5qt1NurKvkvK4SLklQZbY//yjQq8LJdjBw9S6+X+ntg7fvkU7CWd0ttN8NUzqmccHFmentwNBA7bn0vcPbJouL0Dv+NnW2oPVFAUJyxRNQB2V4g1B2RrGNcwvVDeS3XJJGQtjZIwhyfCxyB5D+vT3PVGT9Zs3MWmrtL3ZgfFujVjOfQJdZacMYRJlH+g6gRW526PY8jLm9qVTCfitbmqddlKGbUGTSibeuiKLWBbS4OSSzpJD/3mZyWv6o9+XRgSrjBMtD8gcHSm+rJ90sg7gTGx3Rma/+qnKJQTkDOpaV71PGaKTEZY9jMJOZvyvDlM2XE6f4/ps23UXtfXLHyymVH3PfKiMT86lzu/3A4LFK2QU48ZsiAd3lgunO1zgoOWDavUdiEV9QToIgJRRZ7QjCdp7e78b/JlbqleDedmRTwxSr4cw1fTB3OuN1vF9S7hdHSkCrjXyLn091ISOXR0k1UpvW9rOalOsppVAWXb9vDEKu+67x4WFF21DsBoDleoM0WIVjXK9jz6a5rqqRd786wtjGzSQMNVzMfO9M0bnhbMPzbguKh0wNUHBaOAZMZ+qztv7AYvvL3RcOZYPuWa6BYmW8/NAV9f2pAByAbJ4dJdwuK6o+Di4ZIw2o8hRXI86Z5f87yBO/MkB5hGZmzW5PKIPPTw3eqJREXDdK86TMfUIVRE5TVusRvQPlioV1SeZDjPoA/gbn5zCIWilBsuP/tMXdrWJpOZbqhJ8gD08tHx5AAvkQ3iNFTwfIU/M2Mj4sHqFNz7dUeNLKw49fQWfiaRllZ8eZrMwPhwrOVduDk1Qb4EYAKzoAjDbmB2kgk41heT8GDsT1gqWUOtqZ9s/Cfm2iuwC752kNld/N9d8RODzeHQwTeuOVP4nCRnmf+ibN+JZz2I/3NFBKmdTEzPy/Tk1gg0I2wY/u9ZhZpLZtOqD5oUJGo/FixhseyEdiCUlZjy/Tr1plJxgMS6YWj4mTEFLqWQswUf22vKqP9Rfi9/KXNaDS1C2W71Z2Gjh3jxSOk8t2Vd52m784u+i+Xtf36taybnu52J9ueB3OjD4AoNBG+UxkStpwRNjUBi+nxl8WGZjTmF4f+M/Tj4EDNJmSB4u6O+8PezapmbrqwG7HGk2N/pFgEc3bWneCSGTm6XdQNlQgXLL01uYzVqR7d/YiNJkIFd9R1rKcLNiadasrjvBYdSguTb1+asOhUuVQC9DyevAGgxQ3ETFnFeIngzJIYrbDRx2dgJ0fclTg2jADPqGshSCpl/1xioXPTWrOFYeErxcpFrj8YCM6/nyM/+k5Ac2KkmG7chj0cfvukd5KCxsmYFrBK6bvbD6utR6UMX1HqCtPEswoXsMvrQsQOqSlDJiY2+CHu/J5XQDSQK4OZ7j/fKSyyOju3JGwSU9ry71Jya/Bu0C+UZA7dGQgAw3Q/Q6pAPKUw1ZwGyKFQw+sO49/qzxIpqAQRacPeVRMgYySNoHR3lXE2KXjgIgwQG4o80fJzw8pZrwC4AN4ZTpl4q/ZYh/zGn5AmrK4+2wjzX3W1CRXjptGRyrrxbpIsXDnp7Y1MlSEd5+WQrsLSAyt9tWthB2H2NnCC166oppNaX2JekN9q+zuGv8rOdNN1AGkeAdzKem2ZqzoasUOZHFvvxzeb2r+rDDj9buXBQeLLpn7h/IW1AEH1kPLSgDhttpe0x882IYEtvPTavbeai1HLW4HOb+MyforGvSVN2CEX1loJD0CwK9RnMTecl8ZH2VgoQlwl+UYy0/YWfOfMaeWPv4v88Fv3ubm+luGmKaJ4rKjHbB42urx2G0LTDAWZOw1g4eO5UTcVviAj9YwN31S9nVk2Vd5KS9qTSz72rMJtunrY+4efSjuQ42JXty+z6Obp63iOgCFS3i6cZn+JZT5CloDyiK21QBBJQs8XKi7oFtw1kCEva8y4pJe0hzFwvK/xOtKT0D0vPBr/0rE/clGJIba4ho4oOUN+6yXixjSbaoUBsA42swDOWnpP+RH7qLTYgVHVvrUj0B9I1KFth2MMTZ+r+xRRzsi7ltrlhBvQAzXv7QTwoK6GYymX+v6PiXC6rDXHY2hw9g6bx1HZqT7llxeDvHNe50XZVIGSQ1lYFLDbzJUdnsHtXrUHZYEx36+rot6a9/o33s7uzOAZHwAq2m93gD8L4xTbF7uLX5cnSrQcQNG3aMrhygTV63ZAmyPcf2bVaS+eJRmcD1HRar4nvXhAanPwaLws1tM2qUVYybED9tsY247+ju3dZvsoHMVEikoXxVa0/KW/WspmJ1Ndfv7fdpZUt2lyK6vEpm5brkTO6L+4N9dwlun+UF3ExNLuwP+JreMji6umzvV2JVuoyh6leV+knIj4AiLfOZO46LoQPVWCIMCci0+HZRYA+L1m2eAMYZeOsqe9twsuZHIbpXXkwREUw1gW/k2boTiLhcZBXlUy+G5dVJQbohW2CEhpPfGX+Ea6acWAH1H8SEZpnqb4IFHjaOugXnAEpl6bFnzkhc/s7Ay4rNKlWk9YzDDIVOaUo13ozPa73IbaUuNKDoVKVX8e33XEq5b3yhlV83o9CoHXX9FoZ7niUIwR73+hUaHo7KXNC0d7p4P0AM/Jlk25U7SQZBTCt2XBrWnKONqVcFICfXDBEiEb31mSXW1O0V+yET4MyzbDwctUzC09epUatb2tTKwFMEZWsqQ/EcHgR5WCsKKaVlqKJZaMOcvxQk3/1JTx1+SEHB3znXvuGi5Vrv9Sgb/ZhhMu50oDsIaqw6kwB5BIeMUFDm7g0e/f67YfWfvypPsY5pxOTdgzB5NUiR7lgAfLOgXvOEkxoJz94YadP+f+ZGD7xQVNKZ21h45Ig7AzWqzDtPwtz1fLg3bS0KJUCef/exmlokH/o3xiVNusZX6v+leJCyrBNS4YbmlOXFOx1fJXC+KbWgpaO/za+MLfv1UCzP2oBdLDPjuEwfwnKkMHE2dKKDykL0RDkNrKVXgWLsmPoxNfz/Ps0N+7VIqi7dVB+1KH551wDd9cxEv6tsK/a1Rb3HVDrhKW02XdLzUZ65tuu5b8V5ssYL59LoOSRXrV95//1gMCwazVDsKAAfpH+pkrClgqOfwJVf+lUJVvpoTARMOTh8yj9m459beTR0coFIS7z97rhPgYDf+WkRw8M9AHXvZ+YBC8G5/rJ2s51wqX9XemWjKhSv0IcS7HHg3VQXeubXwSygb4fz6R2Jee4fVu8+wfBTyT8pC3FdOkk0D7JlAGdJO8NMiJ5xfu5SkFIRCWsKZs/0YhrmNLWWQI7zpsWD1P0PYH8E0vvK2u3+TiTB22O/C3TMc/XZUDch1wquT3IinUxB1BdBl9cccKIhl/8NNCfX28atuS+/I6Kp4+a7uYzvIk+5uQIMP5npPUuecAos66TIxhdNprR+iDlfOr//4xHbdrFB+siWryW201nj/tPPV5lFNQ6ixEUZD971bZFlGdKkN1CtOXa9iwkgGfz4N18tFIbcz0X5y9fWL683O5If3cr0+ajtjMt5euGOrFKHJsPg5JhTAnMfJtDt7aUlOUxFcjqiMAvlqelA2FkyewQUJRgUhGvXFqxTzRp9fjWMR+tbB9aWd9qcrw80LQO9Tz8xIy5i2kqJPO34Kyr0gI9MDm7g1zgHjtNnB96XQJPgBTWl2Zay4P9ngY1IZBntJNYU9vZ+fOoTzf2UD1srGsRaqHxLCWCDDzbef1SFF4fADmUXgfCCYqT+H4SvbfaowJQzpyNRQyHH3jcJsAqcjnD+pDvBSs0mMMlnHU7qrTWOwcM+3FPx7789/nzz002/prLoNRVGYdlE3L/Q/7d9/N5XWvdOh+VaLML9ZyLW71gnuHSwTT2V9m/s+ugut87Ad2c9axteAMhgdmJsTlG1ELdxUFIG8BMBYIwomU98Ik0x7WLLlI5nADjkojHCXNzf+5jWuZ0KiFkKLE2pZCYsNiQSBzO0oCUz5ff2me378cPm+O5HrxcP06LF4wWkP9nq3410PsWz8ATSWDG5sNEco0O0eYHs8aYL31s6prHmeiKIOqw/GH2XO9xoV/zUwCTEbRGNZqKHkq8HwHtbRWgyZLCqLJjQA2MIWTctn5GzWXyxqfZMqIcdfscC0AkSGjZ0Nx8p3Qj7WnLnM7s7s2lVx0/++g+JoLUjt4y0RK/mhZ/lRgAv59Voe848LeE23xHUrE+CqyGpgQL7ywCJSkimDC/VZ+MhcpE6KfZVtgZOlrhP3XVaYeVv1J9pQapZFTPiZs0bw2OHoud4FKTIe24PGootfNUktIXJpWbFb56Hn9y/heRP6sl/gX24psB62tbzrzYMZUxZUuYFJrVluefQPWfLc38a6W58Si2pgfVQkJKtRxInLg2WVIbL+MrYOlyrJugD+MUhSUEHDKXC+39LeyvqVHe/INFCoeEtj4t6A1QmZ0RE83BLyrvWK6ir3ayrsBE6qnaJ+SzUVC5ywMOmTxxFAn1Ydy+xt+5l/iukFVehSjGYlXXV6q7zlPKCr8R+rOCqV3x5C6FOW4QOD+9S7J3WfUogpMnlS4DgvnNMxA0NE9ozNNJ0d6uu1pxApOSyp8qS0ujFDQVhkwWB8GKu9zLrfw/zfOq8Q8ZQolp71wjIZxZZbuala2lKKT3XcBVRZNF/RDPhc/csU3gDTESXRvebvPqh9dgkhxDc7bumQn8/t2Ze1sQ3GuIMUpS/YqTkwHVueHWEySyDV3lgvllw+V5aevQpcdMZIeWAERZQxqr2Duzv0YXlBZqogBG6b8nlimNf3hO8UWp0eOUhXWiOEBLNBoowg7EOrczyWHq0xbT2HJgSySF7bJt5PXeLX9L6N5fr820f9gh68KYtp1Lut9eJGuLBhVf4leulrtJh2+B6nBPBmHgZ2cEuK+dH4t6xUkrBmca19UWcdmbXz9zlyUt7ARnj57KUy0DyLo9HDzQ1YqfZpW9fpg+Tq1wEDDAJZBeI8SSpDqyZvILxhR8FFWNJqbWFWJgoWxVv3Qajagff+zHINeXfaLoQEEAM92lMl+IR3E5Qf3kQ/+IT35zz9gnky5vZbKHqlezdgDrxUV6Ty9OGAeBp+A+l+YGkt4ItIq1rnWusqQY0k5bY3OSCHp/mTb6F0j54U20NXunsqh/+8n8MAUPcvXxDi1V9IsBkyvfJVhetjrA0oXRB0oDo99j/dCzP3xKj1sVxIHJFkyt4H5UZ6rgoe2m9fMpA0j1xKquf/VFBHWw2zJwJF1N5iLvVzIG/3cNSTWfHWkcFZZOGROIxwIuP9zSzXW5oumuzB/Nfr7QEKfhQ5WO5aPNa6W6gNNwiZl1/LLhHvhyangFW45anZdft72UrmAN6koSV9PH/rZ9ZfkIsVbbtbDSeOQGq3jJVbsq4Npcd+dkBAkn0hGs0FIIyhGw/Gh4x4jf8puzo5Yzyi4QoH04icrVkblIjv6IEjtyzN+wPtqDMbdBjY8qZe6xfspvz68CriLa4m/kEAku561bztjkWFbHFbnxHhtamLIe+zPX6FuAAdSZ0HeXWbMhBMIHhvDMFWtSUCeCLSFloukLntBFcw436iQAV8fEcJFeZnMAT1TGqilZlG7Rz0s4NodVTlWDdTdE6hvZhgyAhc7Nb2wEtqLfRs5SU+rOY+97jQNkzl86NTzVKYfHr90Nn/WerwAboEDOVDTAdGbSt/ewNnNH3Hddt/muTys+y/bTd08z+zoMYMT5ln5cbz6LCiAAge4f2WrF5oTKVQelr4agl5SUNjVi3d9O11Qw/kUJffNIu1XH9KNJq55jdx8FBmRiM6ikA4hv2pnnit0KIa9zqqOz6MeGpaXacrdm/pPZ+RZDCkTnxTqmZKMa5vNHA9rNFDJaqh5+vzmb+1zGq1eI30Io//3Vaixc51bU/99kP1ajrs+vE+f+3WoN96C4h4P6AXNZznE8ES06yA0BdqXL+1S/x3+4bqf9rV7ILBI/HcYWei8mEulwRzMfKxhHBhmrODEPI7wr8fYs4TyyqD+/oJQsEluSqq4T7BFYcjmIhaWpTf3qoMftXgfWcicKu1olwPLOSdWZGBBUDRGzdVLwyodWWcAnwW/Pwx4tq1Svm3UwuGeDsVc+iBaPeHsFB+/jzYRhfLmpmDpPq8lm/vrtMse5CU2pPHkd4Tq85/uNrtfjjbV2KmOePl6+FVq7JRzV4QscKLW7BREeJECoEQyVzXZjrVF4mw9ISn8gOOZIz9e525kntKrwGPJiNZRaQWuE2DrFBFHQ7NmhAL/nb6pQ+usPbpOz87AndhgPaFoLMh+ZsLWvy/Sdpj2TpPjxFTS9KdEolu6CQOznJfJiqjBrWdy+gBTAz3vn1yOmIGKzr/lc8BDUKw7PfD8Z7jf6U1+AhC6zRFEvtxyljRXt6DYl6m+oaWMdJclMAbi0DWvI1hRDV52NDIXCPzjhFIEzhNChmnwn9YB7g2tJGEGymY2q8zh/fmv4f18btM3+f4XXFNp3PkX48W5GxKEQVKBFqo7CJauJ8IARpWuXihCnIlFvChMxV0tJ2KdWVr4AnYpcPYTWuApTsXpuFn6JG8AadcX6+vYIBecH2fzsc2GbYXFSkqbZ2F9Ba5hzhN1tUcTNK0LozVTaTWs3uhWREysW0PgwFyGxJtr7DHu0oYMvk5fmS3i9BluBcW0skdj20r19qSsyaI/Oejx03MPZxJAJ9E3kcWoTOL9cJEv6cJ8ifrlSg6O9QTQCOS5l/HMG8dKNKlMvdLz240VtN9QLa3nJHPbdIvZU/+gBeRYh9CqsNoYLIytiadR76fTfOWvELCoW+/48AjVYLdLvO2nNBN1Yni9vb7MOpb01fIAjuupV4LraAy2z82DYIsM0V6KoWDgV3JZqNSWgPHRPXVQcC1t/iy5uryXNRtqkkBTB8j8wspxkJ6QWrpB+r9dCggJvchL+Rvlc1MpsoeioUYCyPVf9tT6XDJJFWkKB2CFpTy+g6xVLh0MfC6Bf6HFLwN7O/n2G3et2mguS3U1xg79bdO/1fKY8Ewv4tsjtI3oAZsOh+AoAWgPTOmq41Vtf6VIULCA8V3iuQW6TVNu9pnPDEGBSDVjZAKux83rMylcqdoLNxXxouHSokrDfRntLJ1SefzrZA7xP1RO+mr6fUgaLorz5QgA3GRhEYTDxf5HGdbWTm6+wOQCscpdv90HPz1E8ZAAlM3gquHcDHr8g6YuMY6n/hqpCPkE6nAB65t8nV7Tr2Ln67tatVHx7oh4PD7xkxwIgg1beuq2KQGToVL1Lx/oA+IVodveMkvqH2z1+sEFqSIspZ63d6BABVoOPlkWuTs2/t7RltHzGb6dIEQszBLBTDrH21gXtsHdVb4HaZWyonNNsOlZ2f2IgUliN7ModXxPhXsn3eDtFP5eUHkjXjB9i3bhcAfzIiZgdXL/c0U0kmak57MR20dsTO99/LxDv9DxGQR7Pj0OCzlqnq8AzBL/ZqsNyB1BIKSw1YrM3F/GcU8/XI1lnYZO4vlcyFzzzCq9SNfpdu03e356ozlqU119rRQ4iwdbAkhyxTlS+jXfwwtWY9E1lnQveMX6QKGOsm1mYXrV3xVOIQSJ3Na5zfsbt+K5a2Y/vmB1MOanwRDbS9aHBGbN+rdenRSfbn+ty6PNjwS/bKX6ykZTuCVNksn10kdPpeAuZjJ+EzwzFea8bvm97HVbj4Rmuib1pWfe0oAGFOz7R1v9tRzoIAze9KEVRrAtzBau7C9Ex8+Lh9JTtSdN1cVU//Gz73+vVo5dlf0v2+CylJ21KSI4TSE3v7NQAJO+POaly/HvW8knCGmpTvzouzePR6LTZBZBXJsAyNpxcCY4fw6O0mJtkyb+v+CuJWmAHmwZgTPrtOEure9U7AG/TrBD6EEjuVnVt6MoLKoDWv28CLPZWjnfigGwQMaPQKBtKNFjCwpLWfW5/AxvXyDj+oZCnTV9Z0vqkAutWct67+I5zd5Nm7nUB/9uB/UKGWku7JHfT+sKapSThD1Gz5aLcqwNa0Z0JZ0+F8/YnMFXHoLuO8q9eRG/x+MlaJXSKVzT+E3HWHd1s9Lnu1ht7rf9zbU2viAo9Z/65zrbqy3iVdxX/NC/Upu/ByB+M9z76winyQDYc5RG8bUtsvLrHPn64iKWvJWLYMXJw1zt+DDNrAynS8k6m/yVigv761JUsWnDiTvaG3yRD5J5Vn+o+TmipVnzYRoGZZ5Seesa/J0AIUYeA2rVFRlxFWDFq/0p/Cqe3XWPVHrw7oIMFZDIzW12TAK7yBhLD1jSeNOdSb2O0eGodvexjOFVjPo7lRIvqOGtPewzD3wjogxTaXKBtVe5IFG1vHaIWQ+qV6GdxWNdsXb+0e9PXPHihuW5X9UMf8+5MLqIZXk2fP4GFxd83F4AwiOiTuXhGxQNy8bs9GyFig4YEPby899riVHAskRFFq8H28VhGb48iOQLEt9w1pH/2oHYPx9/AVEXk//7oLd0ZrxW/+h+7+lOz0VLE7nxXa/enjqxMOaE0uBWWQlrTntHYriUVgaEuAX/5SpbXhSGHnIOqD34VnEf00PscGXB0Fcs6QthFwMQ5DFjkrO8zs85K/XGdrwabsRwWcs3szR8tc8anADzrRomHulYb2OmKuHzv5kjmTSs7NuWEGC3IJxnN8t358aqyYkQe6NoCPl9c13KbH/9pzodTuu5ycL5Lo1m4eHSnap6W0uxqZWCX76Yqb2WGL5O/72cBbkyqUEA2BkGdh/SiCbzbQTMudtgbDONvYYNUm7n/DiyVflCaw/hhFICXaL1W5KYO6715SeJIpUjMUV/wRNmxI/7a1M275SJHhp/AK24z11qzRYNNGpu2HUKiWbsvPhz6+E6fhFSQr4uy1e+e9Sj6etY5hoq+9rNQVRdmSLmAAXgrLbVmcHlkgJrl46QENxv2/W3pWnhFbBnewYto/IuGiOOB8aZc3IwHv3dhlijwDQGvboVUVUl8E6wnly7knFmfSnloYIwA7J1UkJqKO+FBTyPcyHKEPEau5fQQKpTR3Mm/DYkw5Lh0jkAgLtn0oOADZuP0GnmVQu7KdSXmar1CP3UXkIx3x2FowKQwHgfgXGlEynWtsrSkv1cACo9unGhYfrnFRf6vHeho6se+wTTJXw/l+L+83Q/yrPHt0d360IlUoWW2D+GHCC+siJnBNT6AqSoVuW2uHsSNGXlt+lpdLxq3YosB0NVF/PBxAX23hu1/rj09HBq70Rn99BDgg5LCzu7RgFBB2by5cswC7zaUwAg8r5jm3RGDEslaHW3+DfCfT7RrZEZJ7DH8tyy2O5V/GaLv+s79J+9VozR1L1pVLDj1WcVBYYk9Wxxt1MsFDz+dccuTLiASPAKbmY7f8Zxx8k++1GVDlLEPW4k+fztozxuIWLcJYeop6uT4Txei1J+D3gtf1nREoGQbkK2PkGFWwF0hXI6d9d/0pKxKXTNBIELoreceEte9SZcpavtOJlVnV320wRqXFsx0/mHq88cAsl/tCdqicW+KmubIF4aIg/vuY/YT6Ix7JjjH/Ax64ESsyw3Zz3ez2+3UgnLuvr4kiMY3/+WTrAqnSSLriDjcL/NenEI1mWhZjCVqvmyhgYYD7EaCD06CkY3XMRJEI4L0acCHqn/TtNIyFidDQC0vuar6RHPpQ8T/5ZQ9YRXHxDQByNPfflu7/ht2h8IgPhvj8pZQJsfRZebK6qgDfFZe+1fKnH29LVHckyfvbM2nY/oeVbJtEd9VJRb1EHWaEpA8N+WUYTIJG68YqmElQ0cR37dI77+/nu9t7AKI3+eVG8p7zoOREGCcpvs32MK0n0iu3er9feDk2H0t8dkIMDPNdBeeLLj1D3TCuBMb/Wt8GtH+ouTKwW9nn9K7sVyvyfZX0QEsC4xy3ch4ClKDmcDO88gwVoeQ6kL7skFYA/+sSUnFJ6f9BqPbbn6yx9wc4tvS94B3jEqJMQVN72fFMgv/Jj7/RGLcBoySI9P7Vc1rgajCpRqUFRGL8P674ggqfj1CnZA2u6Qo5FsM72A8AyMi2CTLmlbt+/9nhxEhdz8mGXD2PV2xJsVwRf3K1IEJ4jIy47bj3Pj9qY0MaLTpglP9Ggi4k8SKHcXoL4XpRyhADJck5v1oQZbp0wQ1+6RlNJi/kavumU/Cx1h106S2PiPNojSXDf8cq0lqygBGi3rdbW2+XiEf0s+j+lj7DshEVdGQVE6rTdavDUsoBDjLow6QusJ8cJHST4m4VIQiXVKNX9AmwdQm7bse25rEvPKzlOHwb5Q7ASMUan2ub9x8hwPjgBYh1A7jHy5cTdT1MXFn5uTUxeoDcs/OluVHCSujFomBVuIcxuHeCpjm+EGAc+0ZuiGNvA68AMjMZTPqYW07F3lpfyVM4QmFOYV6s9dKN+1pyy9M+hrWBmRgJB3g78ZM+yCKVce4D+qjrIq/YrBfhLlQuTQbou42MyFmY8akcCXAOiWILUfWA+/WrrKJoIohZzTUfndbDX1cMrRPd7gRDaa35bpqiT1+hmwTapIcrjmN7D/umkg1LMykt80XnTxBTe43gMcq9Ut8xCX2+RtaqzcD+8JOyz/OMTIiZVT8tbkwiYwoLXb37yOT7m6XMzgHASV3LDzNaBBr1pTxJarmV5vDvlDRjqawi/gKhyZTriHkOT9UVgpFiVCpAGaZuk/chLwU/Pbtc1pqbMQo6BYMipDGZlf1E0UWMw1O8T2NjLH6BzvaXuuxjXdTyuzjj4qQR8Kn9sWWbXnuxpZG3GlE3jf2RSq9X5uWO+FLH5WPY7HafXQ5W9Olkh3Z7IwoDp4RUvfTtfI4rdudjGBUxObPsUQqZ4EWKC0jqMAFl+W512Go5/5MnKxP/iESXeg9BPM2gBgN4q/e5HWPXdYb+Dy9wgBXyAgwriHODAsKx50sFQ4O+bU2LGp+VHwbfZomyOdzcGAgZIEqWWeEMPhaJp63Ce3dEEgL+o9eo8nbBSO0ROEhI6GaPyus52eJef+rkZ25D4OyrQ/XkSla4kMErIa6CnDK/h0EGaXg5Rt59OLaLwweoPCEah1fpNI8nggJPTPc5aii1odyXX7qorf+8GkGv16Obf8e+MQvhWKxNU5Y8oSKvLHveNmj79+T2Utwt1Cdo+u0Qaq5Wgu0MCXgO51g429HBgclQitNyk8FIrn+w5t758iwCqk3C0w6HYIz0hnh4du4QHG6ywTOQBcpDoFm18PlpGfPm9VKML4PPEsGUF1oEnMRoUpMTJZ9AC0anC8eggYecBsiQxPGqHtko6Wc6kbZsoyYFaJ20+vH/NjJKLO4yatLwWAJHgt1T+1ck1ERgtncTf13Qhq5C8CGA39cnIUSSv5cFYuSxETPLh1MP6JiOVWIBkUl45HNwrrUdhCViTiTRiXz9ILVPAqX/l8/dBPquYdd0m8XMZ/glGIpXJrNr8ggz0B5L4X9H79wCNRV1fv8aIpefX4+rInSnvpT7VpdDryvfHlzIyR+NVpdGd4WU5/IFOBUN9mddaCR5jUFE45PMkYncVYn7riMMFrJ4SU+iT4jIv8BP/9JLumut5DfdhpeGM/gYSxRC66Xzg/8mEKgVwVukiW0/U6zvuljnDZ1yEvMH/NeqtF9znNMbt/x5rySnizidVbCOLKRNErmLv0LCxhuiq4H6E7ialN5w5kjuK56uI8aJ1PSihduG5q9Y/6xjBIHgChWt+DSvlHMCI/v477n9iWxotsChqytG/tlOWWPk6k23ZtGe1dz+s8liR93jeAXq7ikytFns40jT89lQGn9TNHp9b91Zu9Wja3YQ7xtF+MtTqaxKgwOt1HR9B82HsSwf98ULZTSTgk9an/cUMlALu8Oer5+bfLFlXCe5s4/p+wkCfch/cVh/Xd/58YQZpbjYVey1hHDM9ryvd8Vx+syOKGM7Fg2DTq6z1x6sYhqWu6bOnFKZ0lE1frYa2IBUxRwddMi5YyHTfTTlRVPvlk/jBT1gybn39jR+YOUUmC7mDfb8+Arbe+3X1nQtcDQ/Lwp5tn7eBiikVrM5Acdo1Uh3oRxezlh20K49khzzvh1KPdJUKw/JRgfIS1/ppDwjq5jyrN4GOGq9gR3CB0XpQhD0kVZ79wsWDRFCQ/v6wZOu0GmpNoe1Uc7JvksdHw7RGbR60ElbHYmcTzY3Qz169zEtY0hJcB7FlwKDUelru6FBpSq9zYJqp/HbslRwNEYMjEvAGHFWslEFcCztrLofc6Al6xZJO9Jmp1nbqSUJJbelewy+k8TuRa5S117rl84uY87YIx2AAqs+XOUfiQaWVRK7zeZ7eVR+eCUz/xwB1+vrE1/9z33DdzlRlVKSeDxeT7q8TavVooD5Jooi9KXV/v7C4thkpI2TEO7X7mM/C1MrN2h1PYp5npZrCAPmCZ1onv4Rj3dzXr1ISP/m4b5YatLKXtNBj/bPlnXpw6orURJHCxQti3dYgEPDuDyCoD+dz9pU07XSAcK/7k/lFM1aYDqMVylwLlnpCPSnbOwJ3JgK6cgKfRw0d/zKR7q2ans+fu/Wr3EhPYxUdnz9PUsQMP315fdKVCUXCsR3MJj7AWkcA0Pab8uXyahoh1K8UBoxygOrs7FUWUIGpx3bK1nhPJVKDjRNLzX27CB2Fsa5sZWeYseV9a5+CLIUlw9FTp5vn9HwMfYm2WqAzp3dHpWX/raXz8NN/XBJN8tXwwUIngV1NbNXlrtB8gIgTVeO7bTGcJbIQXbCin7tHIfzWGxyeOEf0DbFNPKb7yaDUTX+3U1EmI9+7OXDfPcU1rSaauLNANazuFbXIEhPb+o9B1nFVwcry7/gY5+ffp9TtIYTo40mI3mfk54clPa8vt9VjR6SW6pb4VsCwt6tSHB8G9K8dbexwO8JSokLeQdT6MmKFQpXBtbVE8lpJtqJ9WVdW5/6S9+VkzqX+3Ik2XJTClvPpDbmz8lTL85/p2CzvVcPG8sF/1Rr+3Rvx9xPGsHfnDG+H1cHxva/xxhl9y9OyTh01TE/3pKR6ZWneG4XEpk3UboWVFiijXsg/IeXMS/bgmDfFbkEVCQ71QTLhaxlr1XTgiDUev+SQCF31lqnMvjbmd/cuvwdL7cq7Wm4VzMx//0Xe7/f/XQibv7TWNLxT/e3b96XR/nW+e8TSNP71LlFXM8jko+YEjd03en4ckP4RZbFAlMvzTSb8ETHjz2NmLHqqbA0aWdA27XifQcDvgbnUgAdtw/VU03iNrqAoGzyImR/WQ95fvAdYQ833I3A65fCFc1EjantCYAXwfp53a77DVPj//R2EAvl8HhATNjxlltzGVjedQIOjuwi2WVpLMkUA/2eoG9Bd03H0LQq86/2chnp9jFx7PanT/dGlhSG7CMUqZkUP/r7Hl41uDy1SnX7rSz3MqlvCQUs97un0Gsv4Gq5fqIRbaUFZlbuxEMBZ1UiHT6oXGLU4Bk120ZU9woY/W+cEISmA+mkiAHOJrqii27qSQY8wk+j/S/wCrWjjJAHaiQAHabPtHc7l1U+fjZyT1sq4XmznvhBQW3+Y30/HxyaDh/s9dT1u8ChP6jrTdJ3r7gazWqLrQkge4PLvb9liLkpj/lmWN6n7AZeWzHj3r/QXYul7ijizTBL8/qv+KgTlMq/7oyLpfn9/qxFs2riF1iU12Wi+AkimqkVJnN7GA0lISc6g7b8Mn3JG2Vs7o1bR+2e58JCzJt2BZZIFZ0xlbe2rnkcj19t99dr7e0CYUWUEtuiSvBQUYn/rReLujn1vP68HWMlRtDAyrAVDODm19pA6GwAGxEyiRf14gm2x2gG1KX8sg3CnW+4qJCHV2K9V6+FDXobKJFExu2+jYfDOjXdjTSDV/PtMhDIzvynU746Ka6a61HzfIu5T2s00v7eI55mMBFTnUMVE0p09wAnY8jMC2QlvI7XyV0/4qiszEJUdiX0NH5kZmqKZAy43fhNTiO26nfNR41BBhcsdzjlQiEBIRbydo2IEINv436RZbPFs8/Hx9coR+hOkhhCOukLUhfbYC+GPgukHNtEAVWWn54AJV6ai1a0rGGKabvn6s1efND23gmPkIuqRTvSxl9Vxfb6/0myjCl49QINShxL3bCKwe6OMPL57lNWBcWAAMvT4doM1BpIDIWk54aWk1PW/Za0NFiVEDn5T9knDb+3ubHnREblz+6Z01+2f/c9X9A9hMfC17w9MHUsDCXnrrvdmVRILuEHBt/vbqZu+RM/s21nAGcn716MDNugVNiHi0JvOo//bGib4Xz+v+RfNTOv0/VViihe27W+ru149Ckw2XkzrsSobvwW6Lve4rQbkoNq8pOcnISsfbFYuJgHvzQ76z+J9xTDqb4t56UCgaZ0KSiCMr4am3zw3cuhg4npegqkIrsIC1qxqBy7ORxK8Ori4657MLg36dgLrzhFk563Z0cPIgjkfFQ2v1z64lo911YeE45ofbzaIal7Btzy4g5XvqZGLD2WBpYR78og6rOmiItSFaLss+9dWvb/7wY3dAJsvEyGz9HqYK6lqQs4/UKitEGN93bfmrvkKul8utIfF706fGkUgbPqyvslxH/SZ/YmCF5urp3lkI0RG5VHFSeAkuCFiWek/r+1ks3ydN4V/rZP8FEDAC16PzVIcnffehqmnAHZa5EUsSMO7E7mt6IxFGN+G2KMklovKo23r37ze0hi43ZUl+UgBIRWPJ1/7yLfUsEoX1/RSoUTZ+Rh5LKLy7hzMPiPD1Pdkju9H62RY3b3azZEJ9rzS3+d4dvLpM/9293dpbo7XR4zgLmM5ykI+tcMQZs0wwIKWqY0fVdTD40RKobRpvtX0MIlarGwftHkNb049nmtn+ej2Q8UF/EE1K/R8PYA/W5rrDghr3FMOiqk9i75AW1YN6p+YEorb30HGDex1l9jr0/MRBHOee3RaJ/GTwl+a3Di99xQbuvmqe+fC6A0avJs6AOyZQLk4qDsP1z99QzQ1erJiy/ox/0m9C6x0lGKdaoVgsv4Emj0HTgqhYeK12y3aL0fJ/lS3uiNoYtSJTQF7zx8Qtf7OSku1sTc4DsA812miue8ODN346iS8SPJxe4+cU3UdeOxkVrcgfvPv2MKQ7jK9fHpmH3e2Oqp6H0Ubt7djc76eH5PvkDd4Q+WSpqb0e79oYYw6QGaY84RAkEFxU63nc1dkPeeMWETp63T1WKovp0JmNbkaDjBKAAVGiUVX9aru9o04rd2xkDAHQ0QLugFuwMqaS0yAzxmBTcneTpdfBQdyxr/6Gg9Am0x4jp+7xGVVO8c4rxbMgQ7Hs/MK5vE8txnMcBQdpjbPjlt9SavpnSwXnxy2peu+fMUsDNKBeXn0gGv0N5Gmhwze7u5u8nXcu/0dPIfi47yuZtCNZrLUtdi2k4uWIMqKsL2bDLuA9ST6HqzWUwYVEAG8rIMPuzdsxh53xDaisJbJB6kpMrx2pYIK3ZmcKvY4f+QyiTatv8l48xGgavh+ZKMxqwqRCwCfCqv9nJoKQbLglt2ydLzWq1NTuLHDMIbWssskRUuyK5khMGkzBZp4W5nLj1lS6JuIxJ11NAqZj8ZS6g6UW8xl6LtiSItEODqv52dVXa9dtc0ywFkZXEkpdlXQMSpSRXS7xWkbx//QLlIWPNVcbKRee0I8ILD1jKKcYPunQmr5yNh8icEP1uTkRyWPtyiS6PRgflo5AuDOKsOiZs++h1/FuE6HSIbx9+sNhWB8desNMdkSkjs3Tv4gJ0ujujvAKyZyQyBaG2WRYqZHvVQWDGyPn9aTajXaQxegb9sb3EWbLZ2AfhhSp5pFybjHFePlVnJX9aQ7u1i5Gsu6/YB+WXSe6IM7MzIAn+ruCSfPlapsoG//l8A9wEUOrNKC3Lnvv5CXQLAjjImfAzrIWGkgryQivLYI65NW6o766PbhL5246syn93d2Fe+1vVsHSO2+ukNWrEH0vq99qc7KmNRzZCTgZTAaSchCKGCw1yP8Am0+GIiV1aI5HjBJMcn2uce6+Jf86XDcaoxTdUWJn2ffbCbcmXOTuejsWmsqnBaxGqGGQANmKKQyEj2qTxKROGHDyVcfdH9ClRJCORIGVlFriYvjlCrHvJJ1mcLv/vppv/HZoYEUONWypw6H5b7/3MxDCh8Lg8cP9d4KSxKu8QY0EzktsFDVAa5ZWPFtLLNrfk6/sN483Pn8fO7UN9msACPmlcpNwuIt2ew/sj4FQz2yyqqcCIWCAjFNV204n70tqKyuZ18bBL78aL3+ouFreW/dLkMEoFoSQADm4yv3RCkMNZnIxkcbLHarFWoKNS/lBsJmARf+ADjXn0nTeh+rQbNYY9y+9yXPYb4PRVrZTAxJlfhMrPHqqVeBEBTinZfrwWe6FKa3JMJYxwHfqxb6K8X54spMsLeoTTJJjbq8eCfd3p9PMkLdS6Dx+agB4PLB01sigUy8KbXaaCFmXpB3u4xcmH7azhHpu2OGva+jKkKxYyIwALKGJev4Oqe+0lSGSW1JMzxqbV99n4jaQtyCw8958wu9HGutDwFIx9WQahDT9D8gw9SLJD7uoCnWInZerz/+WbvkhfcsbSeFx49F/YU8PYIDzuyfLFhtGPiOiMJQoXXVjgv4p9w/nvv/1297llUSLIYStdRGxzW5TSAKj8hgFfu6RKETla3G8nmU2x2o8gMgiDDBOEJMJE/dX9M3AaicT+Xfl/qmHel6WkuimH5769bDQr2H97LuPeGH42tmonh/RsK8MhHTwn27G9imiE62o7KsbdHBGoN8HdvDB9aLM6aHgQwo6Ln9MOo93c51uz+wFvD8u19AxARvhW8iPRLx9UilNGcR7mYLDk/OYdLai6HN8qim69PK9u7pNyafqGr7zP1jsQkbgzKiQtfBGjy69yUTlSVp9483bPt7+pmvS9b8tk/YrdLVg2qgE85IZJ3f60kY+HgzPfuGvuRsOIB6c9hxBFQtH95miUW6s78tC4j9vZvR3wI9vK/5o+oKOc7EgELch3bQ1/jwsdWvoHo4ltS9xx5XmhKwRCgL5bFTIIm8lKVFu+V/jr8nfXVT27QY7AO93U/dbgcQPOyvTw36U1af6ihnuzw+bPETB6WMqHxo37066Gh8v3PRIAnOW3HLMM3/pK3jqa+0E0wgKBG7rVSN0YrY+UUjyCmCjE79WNMB4GAvdoY9o/Pvm8We3cEbDRey/QuLvfoL5cweMD3uczLGT6CafN9klAdyeX/w8cvRc4zX12NmM06LikQ6rX5MCoH27uzas16nOR0clIE+OlNhuYu6VOtpTFK5A/pGcP7HCAZcN3UwI7dFzg3vNtJpUCkaP3u5HNrqOdWPPpbvQJxzHguXyBKdew9l6x5mADzIMClE3sgvhYJvQUqkI+1bD+U8vR6kx/z6x8SiGyFYh9XaVEy1WYbHTF3NABxs1cI3slRepz4tyuP1H5kjxbAyiuWxaWJwrTClS2VdwZDYqZuRs2nSJ0yTrh16UCvyTLpBnD+dCTRB6GFxeszoKrTP5ft4dqrtnjXnEkxn/yBnvk2i3EMfVZ6VS/H8RH51CDuDvY2fq2/lBp/GDgddCe9bhU/PoJWqs/mF0T2x4f5TCqlmg8zA4Mtxbm3wSM1OcdwJMp6s3cC8IIblrex9Fk8u1J30XraOcT1f10qtveuowMNomjLy+9dyTn9FjrXt3P4+9VBGvCSQyshiKi1l9/hYP37+AEL+AxCSnMY/j33diXS/n7ACGPkhSmX48aomTn4Z2pigoSBv+9yTcCpxhAn2B47D9d68w3c/d2K41Jaos4G3PXdK5hc8tPiuhGK+dQEtVw2BDTJJ3xxR5VSuG+d5kOHtQvWYAKOt7z6tUjAnqwrSd0i4vbu6ZnhYhCGpi7+HKL1vrAVN+2yxnuOPKKnzVNLyWR5SboDHLTvntM2PC0tnLkzJCw3q4OM7se1fYCCtSwIAKh95QApg06H4cfwB2qus5GtwMGuR8qUh/U258f+GpgLJCDKt/mmtFoWSRVOxfZP3epAO8gT6GKGCREDqVzzaneiYC/DD5JIehsg29ZWU9y6UYXCIZHgSanQDoZEmFwzYYIUIJ5AfVvUNPm0B+ATgiG99kv+DooBDqinCDn2BZB4ZeB3oVNqmm7i82AErXPnLEPHKgMxDx4Lplp5vGipHMIIwbX/3ZfvcTZ9OvcxAU2piRsVxn8nkEYbnfzD3u6LuPmyDYaLmTaaDlukDJYjL49VjiKwuK6xUV4t5d2UQUtzTw/jWWsptg5ivkLIb+3+IMkxQfl5CAOsMzLBlU+kNe1Qdxp1WSu/TxqfhtZcyWnSpRUUT7R+LlP3M40qhNCsvhzZYHFyC4dNmpSBgMOThlnWhvhrkDepIPEl6JUiFy65l/iHwa7pZ7o2ZspiPL049E2cgyy96bDwZjqzVo0LvmasfsIpu6Y43u64wuj0q8kKkD6FbsLF0sxx147+d8ATeIKZOFcWE/7pz29K+rm+n6IVUzgaxfizZ4sE0XDvCGHnZvsBcaI/b0tSztv7mH35jt+OztFORj+ycdC1p5CWcoKCujeq6IRHk+6VomhFIODjgk7wDL/wWQCgpQFRbYvNfdm7omwckGHBTvC4TrByGfm//C1mGiSf4GPajkaNeKR5olAGWtx3d8riewP4gICQ4mG4H6lzJnvnN9KieanJ5j0cPuD5P1cif9H2saIb/eXQDGIhTka5OVBwZGjXY5/SoEJ7zpFJbH/p8PB9ff/vZL5F//udb/mTkerX55NAVUV+0c36tuYJ9/WMEncyEEfd/3kxq6vQ4bhtEKci9+7sdoNTC1d/t7Mta86/ns2ed3w+j2/uaMzVoBtCOu5mfv/MjMaHmWcQStTtFdu7x5IssSrc5/LEqVNVtfQFGxA2qjrVHkIkcar45d5nfwKETJ6Cc7tu+IodOuSsgN9fjvibOKntWG2aXBNLgWWXQsADvW0vzPQ/+3P3fCI9XuaUre+MPss6CS0wDTh8k1lVybUEf1a6nH2fVb6vXo/Xmd3UdfXHYrex1/I5fl7OOub2ecN13fH9JuFZLdkAzyU+RdWnqoMM+KlfFXx9xsZ6IH1gJsSu8r9L++f6RqGYGUre1I9S57A67Gi45ZMnYUBawLw8Ak/QZZRT9n0TpLItWFqon1wrxP7VegKIqAkY1vx/tg4KPfzsMtTZh9X1AN29sMjQqmSXrviC2rbN61YDdmtUZqHOVgMlJlc2MSk0/RiDAHS4Kc+ohf/LPaxaKQOCYDYudA2VZJYhppK/AqOIa2r173bt5V8fML/9BesNDZjzFJoRhO0K3Kk8Od7QnwpOUCT0TvXqEEHwbxirOor+hcJRrabHkcFll4orrZqWEv67t1I0/7zk/0c2QYHbNc4oCzQCoFOv06mEw4qZ42loSMaD5/HP+ZNCoo87Pvb1rNewwqLeZ9RPotgf7Sbz8iHRqZRIcuEn91LYE2GZDrdD7EkbK3x17CVrdi0dqcqz7IYPvOnYtFSUOSOS/Ex2IKWSYC9GP9HFL7L3zkUBLUnb0KV6pU9iCTbUIZrIFhSLy0LgWODAJrNQes6G+Lk4PBDJBHW+EK/jRH39FUsL2GoupTHoCO53UoX3mm4Q7LjGq0tpIgXoA9+EN/puuRoNPKSWmJrbfMvpMoqlq4mBDEBZunPr2df9pV6et2bzHwlCZC18vk3soPXy/PrdZe43vOtmoSml4NXYAgqIBDLAzgdXOq4xX13QLcg/+Xphx/eT4VC3FtM3tmpF3ipzIsrL3sZuer5wpI46f76+6c/ljnzjci/Ywr3H1wbhbLXnjOP/yXJILSCgs2N4xPjH3R5BTX9cKG2RHVs9tYA0FOWdCRVSejFmH4xIp+Wbamhjtlpw/3SxFAwi7y2EkzkKRgjxp6Ap5uPRDomH4tbR3/462Fbb623Jp+atK9lVkxV68JqG3BvUWucJ6G3JyPim42UBPtZInk3qpC8CiXi4xEhGF12H7/U9wkJv/9vBUcPcPhHfpb9W0gvYeHRPdqRKXvpIR1wE7za0eDqR09URR93pjtRr8FqRNDsleU2+8Cp8E4NiPoQc95EqUzVVLo6c7qxiDwQH7W20AW8GRRIPVv3mEXqptXMPRwAmd+1Qdh97Df5Rw0CovX+1me+NJaT6h0FEDmI7E/W1sopYTDG49PiaohRZRo1fEGvcXxIA0qwqnMHR2YK2pinE7VWXs+/ieRnIK5n8Q7NazExHM/vzLgJgFmu+r43CEyqF66wyMjaxbSmNSMmBs53j8vPffdYf0f0aOZwb7sIYY7YkPr05NgqyTULfKZmugtVHvEOR43gnR7CpIeHBoon1r6Hm777eRS8wkF2lGK9HLf5Cg479n7oFa7Q56pTNf2NQEpMR9J6eCkIOuTJolmKb3+N9BBRH07dZXCLUEP6/3n1KibfMNZyit6mOoY9XmrI8N/WCyheycOgaA7wkiWFflyCM5Au2l8783zHBcNIMYvttIXl1ObafZ0Aq8bnu952BXBp1tgSNSoqW77jtsB8vaSkCdd+l0l0vqsHRvpzK7BYOrU+ko757dOiRv522vSby2y4JHQqf7QWgS+25GZmXknEia6P/RYwKoJtssGC5AfFof0HCNp3XpezVVkhjGUvM3sqwPOX8NsqfQ4QIqzfTFKCei/H67kfm5kYbhueqGP1IA9TAAp5zx5x6FLhT+TpNR8Im6r7RpX+S9f4Q9odnhjwJUU2TeHvN1LzWI7wTuOmzKxpDSA6OP25ibFoTfuM+joOEOrotPm6T8kMu0z/v6zthc4Gi7nOj+FcMIR0ZSEe0QfYy2nip4XKLmV89VJUaP5Ws1JefYI7Ktonyh3rsdNGbJoiQzHmEnEafMTF1GJW4J6m9nPHq7CB1faEKYkDcue1eUJOMys3NqIVBuvTDF/wwqddDxgaR2PVA5ZrhSrBP+axjvo7sZxLvoNrIMGt/bVOgn1WcV/zuM7+5oolPaGj4gprofTn9FN30LTgitwhr49oCILESPR47VCf2P7FGNfVOqlLSuHZq5NbTyOs9r9FvWtBv6UdZz+vOUjAo4bZW/aZeDwZPWdVlVofHX22/A8vUhkHc3E6QoFPB1t9uuT8+xbnnlmZzx/5doKhfZ976Gfut/D3HvOQurT67NZQThEA+uUNPU/uMfB4NulFXw5aVHX3GThxFqnhTuufp4WtWRwjAqIrO9yO6L9Edpkl1uM4LqrH8A+5NCsl8VhkB8uUvUFQZkJG+htvB0AaBQqt/CvHHzPa7xOf+c911ug7o4husDvFt5U5/7Kj0zKYfqQkIbNc88mJgp1WHZqxa1nJxvT0LdKiYUISvDtu0+u2MYopjK+ij4NuXN4BgP/1wZHKF4jEdfctrRCUqmZ1FCwvZggIFL0hzwaRg4pvxv/+z7/KgQS/Vtc19x9hyRbf+OT6XQh1LudWUsybS+BfZhufw40fi+rSR8fO09OscP/7omhx6nxLoQeH5+OwIgQZ7zt8OJMGv9ARNpWYCjSKHb2hHcujTD1LOW8FitAowh+ZjAdi8xyXR9s5MWVLHDsb6yhMJKJ/AIVs7gM4ug7RfiizaRY0Wr+xpV31fPqjqCALl1K3W+Y8YPypGm799YzDKoUQQ1P4A/0j2lXUq8YkYa+aS7h6pEgxeKs6aV0hB56HQ9s8pS0MgEL6EorTtdV0PvPlBWOTCZ1iNgjcoNyzqixFvNLfP/1cN44al31ge8fEaPI9wWtLQqLsbRMNahu2dMmld7Dv+P8MUokxEmVtDTo5M3lmFjSIEyEXOP8jl85Hu7gbykyHmVrCtU67L+Q5QpLih6t19WOAlJvVBEfBqN9+pJlWmX2lEIlmATYsIFRj4fPSjuP9BUDlWJH3hYBtGd1FZb0jveRU735o4sL5fqVEkJ+rDN4ua5ftRkolLxNux6k9Kt7vXzffVUom5LEPWUdmaeXu1pbvvVzrg333eWo3njVsWE7nIel1Sb2jYXBKoCjViTQa2zVjL2YUDyBy53J1336nG8kc101PgMjYzsrbLqwhNBqMggjXn/MU5+FGqhZQKMbPrcvX1RQiDStXZv5qLDZdA9F8Zg++ROZfyY2cAtWcr7yXJ+IbNoHtd+fMmqOjsyW+HQmhtzv/ZdZLI0CO++7tYsgpdQpS5JFgI+e6pvj0P0R5pqIbcsESv+Kz+lg7F3bsP6sRv7ffStY07Up3xQhfOx/DXZch6wUyFE/LiNv7K4e8NJeMh0dSxE3gXWrwyn62+MuxSXU2VTC+TN3tuwPpZC7Imz596uinzqO/AocCpFElpgfrXnAp7AeLv3r+rCoY4lK2ZC19pjz0RFKgvc+l57fEzbEJkq2s6SGQ+7md7/o1YqTAuvFAxPQWGfffjBqp1L6la3u3lSc18M4Rewk5oWIlOARedfPzZb0gy3tM31Iorr7neQy1UVwHPfWX56tIc0LV9QnNnnr0LkgJ/xkCQ9qsjakRlrz8G1pLGqi4zbezg+u4VVDjhrAG7otce2+m3XjMMkUMJBalwAOZOwPbljEELKqPPu5PCvHnSKQKA4lfLqa4ARVowEyMEzu3TXfd0/a91dw/tVevTEaSYEOv0apJKapu9PT7beJD8ks5iK+dyoSqwIOdcDkW6nibRGhC691knXheVXMC1DCCJHa++8pvddZCI2xGPYoxw5p+MNgq1MROZT9l3lQVzDlmuPq00TqlZ+o/IelFFhJeusiYKl6rH6BTceYlxuKS3pCXKyedt3a9O+T1PYyp8d+4nTjO7VEwEFue9lIcTbh/iP1+VX6fbO1oVcvAjoFFbDbecImptbuvbg3iVv/Sq5kXTM7yEhc+z+AoCHRP5jUROT8qf9V2hWG+r9dx04RssIojvrMTJgYPCgK2Qi6P4kXPl7UaTNDGM1fuuS0tjrhphkvY3XX5LCyySKz6UG61992+4xuHC9xId1t7RW0eAOoBPA1qD+oRWUEwCxw+vdk8BBeMPTNUGftZV9Xmtt/gOESqfDdR89Ix5IHljVZ5J3FYwFxX7qKbfAQSWgD4OI/S9+jJzDJ5mzR0c3y4LUJDSyWp3o8r7uCVxp+9PlTPL3r2nL5N8eL3yz5LT/BaaZ/INarpeL4FwDX0+m/vpkVH9cspaL/0lFaNJxNlcHFA8WXzgl1Uaj+Dd745+E9i1/5cofBbI9LN+0v78YcB/W91RRLsdVL1lIQKpftO3CnPhYk2B7/t0fSNC4nmuOV3vRVl0+SQGU/iu7nh135ANMK6MQGbfXbKGoHuUSeUiXff12Fma4uvk3Nu+8R6RkFXPlbPwAciwINRFgWN93LaspNpY8QLKPpqG6zS9BanpzN5V0JkctQQAvN5QRQD2m99FJ7aCSylWN7Y9uoDk2Kbtzlus3x8koFuq2Ru69tWyQFUhVPb/Sfxn/Vp5W/27tvbohqlKSGW0WUac+mKyGKZ21TQD9PkeReddP+zav/IpBsyRtWs4059TX8BlheR6JJnaZORhjEYA2B9DjAxqHeR4djTD/7MIzjAWsUOHFxvd8/p61ASuZMWKGaKbEs9HRdvt6TaHDCvy/upEXSred0U6+MszUjEB1Ut2ILDAdFD3JfNWbv7ekkZBhn+bGTnVsNdvevhnmrgPsAq8/osEHxL7r2IhvnkfP6/NPRYjd0YzVNMhGQ1CZGx8HfaCggbUnu/o4lYrP4MJH2VDNFNyj3gxAReggpNc8X9IZvprRm5HFjDe617GSEvPoh4IkU8HGo0djZf2e84OPlZVvC3LvbxTIVI05WDDl0Fe7AQ1iHIJMA0A6M+BG8gaBV6GO2joyu5oji9GpHKrD2xrNfmy/0YvwYNie1JWGnxd1Km2uHvCjjsZ1OY7nb1yxMYJUe41NJbFM66e2sgv1/bFWXKgLpSKFClbnkjEWW+Js8/OnVlEb10alSJd2PyaTTx/7jC4zJnZvsOpE9f0dSMz/fL2lRV7MtUAYnHOlyILQDNxLzpoXy8pMCGjfIPM1570vKrUa7ZZSaM/1D8SvZdimRrZD6sBVf53Q3X1kra9c6pueI5WeDiWhuARBGcWl5xPQB5QfhWA0/4rk6TjLMfXnEj/76c0l5yMAzkQtfGz+/EHmWPPp9b8B08NdpkhapiO38UFD1Vl7Dl8m7bsMfeFdFNh3PLTH1F3sNEoNZRkDsmoh3P7KEtx9duv+nE+aw6J/pWSg4hLj8qj94HKMdH6WDfxccPPZHlo71GKCfVT5lAMzIfCX2P6yZ8PK1sqmqx2CjhNtP+vDalgdHwnqxr9WXFKBUJbo+e47H+X+OXcyfNh9xJrtnVpACHC8ExLL6/hrPbDTKfxEBKg3xR0yybn1NA7Z9hj/1HewBngZ/qqypEAnwhmk24J37AtiJ25qczJQ0udIcCeBVS+6ollIpGSnIcfNBiUdSHRQ84dsShkIRMo5aQiFk/T7+vdHFR2DxYgZIBAwePUg95p/t+aZjjWdkaDj0BCoDMZ2u1y1vIzDwxWVivz2jn3dVrqr8508v3++P9mUQTxRdfcQ1dOUY6YOzofu02eyoX2S4qo7mDQKTsEH2AVE59x5J+Syb+fWjrLaSLObkHR+fX4/WF0khLVbnV4fvC+JLNFxLI8UZo/0++OnQn9ZnK8F6TD59434JtMFg4lM2W9aNYvV0UqDq6uYTj7ybu/X1g5kV1v+cX3Qs93f23hu3ZktQQxWHrRZBjZIGPFj5DFR5ydGyrAvHbaymQWQmr4H2VJEpRKPeUp4yVKE9uTHPv4vyLSQJE825Ti5wKNpUhZL96T4qLpUtw2QGkhfzImJP9uKvo1jfJ9GTFVBovMW7Ups+nwpiB4Vh6wQSQbxeX+1zkYcscNmWfOxM4f1qB5vbJSBEfstFjTK0m06Hm1mQPwkvLJXs3OWArXIRBGn34qXZEEpJzER9y2n4pJatfa5LIBkQZqJn1xot7WM2piWxHXGaq17j3sudfLHFTJN03x/VxQskRUEqp+81hpTZEOn46HWd11+yG+CUxYIccY46UF5J116dLVg92WebV74eJ9x9lhUMD8ekukfk70tYC1CcZzub86S9WmTfzo7soz1OChLwsfI4QXemd/TX05imL+dWJxmvsnrEYAcNLBIrmMy3EL3u8LpbmntJu3K5nx9Acf+UeNTh9JglE/76Z6reu2u+BHtut0qjGTpiap58QdLB2beZOPrmpDDSZUljLlwAMKg12+gMTciMtXIac8iVqQsGYr3MqP4YvK+xEDutXlcV95Pu1tFfEQ2XeBz05pUa89AsdqjT5CI36vbEb6kvbe82vLHoyLgU2VXrYmd+sLy7cwj4FDrFhp+hpuFcnlIMdL0/Rhfv59ide/4Z0NHvJoxeIr7i2q7k3mrX8MhqMjn8CGRP+MBJw+ISOY19JmYImn/EV/Tpz9l7ksI6nfVivb56Y6OpEYebxrCJL12IynpTJYSCA/PDyHUPewu0Zacl0OJZfvUbRpVcA8lObptolbW/rN0dMdq4tG+FYVK41WTSvKybXPzI3jq+NZ8kTl+nwyXGN1O+tpPKgu6n5YnwYj3LdP5VdVyqlMeRyfcYBQ2kGqtKf1DICbppu2qicx/y6X5tT2U8qPqEy3ZmK6e/7a3zc0ObDumxm93KDj3vuwFe7ZtNghkeZuYNgVk9i3pmAzZB2U6ZCVSKF7W/z2QK0mstBjK9gyS4nhdnN6mUReKLLCcOD3zOnwotSaUEuhWhb5B59W9nGs3XvUkK2OkYTmv+kE5v84yZXm37lh7Atq+rEdsZdp/Brko7WvRpbbaBFEqG5TK/M7P9/gDaBI1VlyCL9j5p2cKsIIQZ8Pl/aZTrR3E4KMKy534NVv2iXHy56xmwPDdvnj12uce8Ap5TVgt+dOoangGOQR0Ok3w0zEVV/PLehw9BviTe4wDTHD8Q8udPz43vHzXhH1a3prQHF6gAKgpQzJPsKFfXQAUL7nxSWcPM+loNWFa5zp4pJ1OecIP3UT2Lr17yBSUu3EDqT9fw5veuYWdpeN62omD/2r1FnYd8CW7EPcqlRXUu1CIfEa/PGQh+17weJLsWOfjK3yEoTfGEkL8/Ur0FLYP2K8L3t1fraFEqDPAKV2FoO7vv49C2w9YRI/0fdwWCuLlszCfa/D002+NBm+xwpQ7slbPG6dgVjxadeQFeelmDQGhkxULSYKaRI1f0R3+ZCZWcvi7s8M13xSPgT+eMCbNmQFo//84+w6U7owGzTBXubfFapIxeI32fuZqlspa+DODtmb1Xp/tjbGNsm7VU7CPYTtPIPHuGP70uOgTIz8aZM+X+GHoxoNo9rf72XiQpTOWUw+iVgxCfpc6TlNbXGwHtSXJ73Nub5QGg/J96ctFl3Ws8ecc4QgcND8gI1Yyh6Uldor8juVrAyfTjb4P5s+2z27qg6LoZNlmyasF0SExz7e/bOCiO0e3S1mwdMQobMA5D/KP2qlM+YUNFSZv/R0uQCvwz/g85rjCT5cBoFozwkcxqKyyB1rLvB72PbQVFX2J6LSzZ9zGuv8M+/HIHKidra05gKUGOkLmOhaxHa7jelCbMJpaxSfTIZbT9Trez/3R1/y8li+uddkNc9Asx33r74lPVAJPplCfzwcQS8UI/Ul6wec3PqgpIO+krIKUQC4vlRSO15nv8vw8X9/2u3uOBZLokAUyoiTWHLkXlJw9AWp53L1ZJc967Oq62xDuZ4s8+kokCUXu+YS+gU2yg5cEesL/+bYYfuDHlj+wBxJ1aMlxPpGderGHM4EFOuraT9t0nZeVWrCaeoSGVCcYVWxM2br/cd3DNfdaOfuT4fuW3STuZz4l+ZPKQ7sFhC5WYfV+5Sq1w/5ZiraOsQHERoVhaWmRfTG5PdrDbyrvFo/zFVxJRER9dW59oDsp1rvF6I1eW9tBVASUVGk3mn3ymwu7SAxr5komUaMfphqKnMZ0alq+Mb7GIfcUJoQ5kl/tyK/PHtncKQZgjPXArN8YwXnxxydOajfh3+u20pkrMCY09YENavlTs2mefxXN80G/0S9i/Xo+OjUariobarmziSI7TWQ3S1K/6QOYQZBX1Npv9zz23QxDYQ2cpWFEHpnI9mFex59EbjUP5qA/0BG8C/KPH0Xs1QNP3XfrGmoEOhsPqMnm9PgjczgVrSqohSt6baEafM+1oaBqv7Yd2F0TdEqHi7+MvzkLj1W2RK41fd7b8g/VKIyn+dV7MYnq8T7S0O2MnNDYDjBoHq61p8AT0Ad2uP8HuckdojNY9Pk49f5aq74Y/s59nC1Dl5785wcuT8OLsoFIO8IF7O852I2ax3kQI1Mi7z+bzHfBDpF0QaJNASlhUwgyOxXts5VqH7s+z5uW8l8Efw9vMwng1sm5ug5dg5Qyw9o/FAgYCdQhLf5Iqg9kWj3wxtcGz/9Uoj/u/y/7cvt2y/42HZDQAeSZlIF7dYPC0ObVm9Wjqyj3X6tNaVFDYct1cPg1FDt/RwrLgpith0SP+3vvOMjcrdUAWiLNK5n7VYglq8t3n1BfL/gkvCF7jZJOoEyP5UGjj53s8IseyzqQGlYWbfn1+tt3AVmXULBEIFdG2LKVM5RWTZrOIGw9qA1CSuKHFLQcloKQ9omdyKUZ8HEqeQ1F08f7of6Hi/CW6l5tLi0GeD+hrYVJ8c4/fX8+qminRBLeHZJ2aUT/pF2V7ipt2crhiVhOUTO0u5VAih3QK4/UIczsilWbPzXsWiaD7mnON+N0FNEAn50V+AsWFA/9y3Bs5IF0Z/vLQEHC8OYMA86/66tDCeReMpRjBTjmuGGpUNVPhuv+oQUQmPY0oPbBt0oTY1MiHV/Pq9TG5w/avRyvqafL51E7M9XE1+bxGXDC+WYi8QnLmj1Wiq4rDU98v77X9R869T57KJSmZS1fn+/Oo72vOwvakOjUn1SUTTOjGg2vbfu17dzZNP99Pui18Dx/hIjqRn2/AmCudfrBowDVPvcDH+E1YGPaVXoHOzqsyabj/g5jDG9iM2RCwsL5WN4hjJ/1VKhjVWkWqoMofv36WlCQUAv13lWGnsnsb8dN4/MIRdqP+Q7ylGrv/B+oxhVQykSNG2PUxSxb555q2VNcaoQFAOqXcjVNuXQ/89HyA7JyezrOmW/Ig5nYjzFkAAdZ0NcB+JQjSQpvaZrO4HnZAkJTdQDTq9a/0sSFeAvJLeeTCsOA43veBSqOVwllxcGSRmL3zrCVppqMtA+hyyh3GkYlM5DdytnBzmbrgnIFilA7fCkL2iNiXt2JUPtPAikf0NX+5JWxViPp2KyQ3G4VVVzRRb2Hknl22gDoK7N/jC/wrQMoStIBRNbhuR+8pJRcAo52uifUof0vPHy3vhABJTN4s7XyHnayjY9xOYY/r+WN6K1ZhMx8sYZ5dTmU7/4FudlrEsoYf9TnYVgyzuenjchFnKWY6QVloDY722A9MU15Z1Q9vUdeZ5+2+6uyw/ErhSp52Y5xw8gd5zCpbR17ilPw+Bb+FavvPY0KEf45+8qurt0WSQPhrar67jE2EoE4/tP9gtKQmhiVjnkfXp1LffXQm/2kR9TxL1cCQSWKhaUzpaHsDrMM8rfg96Vp/z+5kdGYBYqYEIFw1wtfl/j1ZQgy4DiODscPHdiolL4+bZz7Kmtx+bn6oiUg1lAaIXwUqo5G8IQVZ2eNcFiTOruFIomJXdn8DowktSQu7AiRw/3p9W1rR8JIz4xsD1Lp0HrmIsjuPWoEcdQCxvSgVMkneqxoHVjY051mzZFuyJY8F46jJa3lFOa33MYiumKZRBS1TpxubyyghhXFPTaMTpCd7Hv6GCyI4KeTnij0+TnLN3IiAyO3umOWmehblNJsAo8uzalnbNR9Iwzfftao5mBp+KJVSBfY1GvI3LflA4PMHzzPDxhJgvXs0sxWHJHv7XC7KrBUWGd7PUyphmxZT4sOfVvrRYyZ5PHqwCFEm//87ftCxazEraQEz+eQXZaL3/uWrWENrdSRRcVVH5twVkJeePlAVlBy7LwcJIhGfzcoEldSGn+gl8lEWqQxfn+3vtf8/AN9IKgSo+2C/Kbh8m3ZfueHXIFkr0dJrSJUwJq8vzdd5Yw45XDTO/8kc6T/1PcC+IQwp35iq1V/UhXSsSfMaMNQordn/5HcOY7re779aO4uvCH34vJeJ1ms0BfSyCainRbbRv8iQ8wQ1kAFS+cPFOPyhb3sNIxXKVD36Jyc9SJ8IB1iTYbDr4hAKM7OjMi22LUBLwejpkwC9dr3nSYYf8AzZddjZn3kKVKg8Ct63M65nAQVZxtDb9wonQX3zXU4WYR2FQ1Ver/eCFFVry/ITaUHAkAlZpomtlqGHeM6WBjhhTCwiP7q/l4QOXdIGIyEAh31KnPAMIMaUEYCPasa91uq9TSdZf6B8j5wuLjV+oLKsX1BdRykbgbUUcj7tpW+MJZOCdSlrgWgmylPL6T3rCg/QDIK9psrR9l1aa1QftY69GUuRRSkEhs1/ofuAbsg7yHKPDvoJIvGx73tzO1Mw3v63ce/pjdfotXO6dlzNS0NcU4ny1cDbqdtg1/jX1MbnoRQTfXoNvjqG2Go9s6B1oHIz249rYf52TvmIbamZK5KrQDAtM/5wE78u+Lt65pivA6dytf0mAV09ZsqWMPvqzvPKqpOBqwuY8bCDHcfrI5abOvZpwuA9wYRd2OubY2+T0UZT2wdDqIhKl0/EJfOQ+4BkqzbQH8nDyTcyknFOrmD5bcHvHSSRJJacINhVw7E1eHznjRzg2TPzfPLy2SJkZUQ8oo6Ast1P6+5b/Pt20BbdYV6Dm/Y0P4sMNkGwhmAv61mzXeXFDILzSDLTGJcqouQWoAZ83f6htOhAsyRC4D8b3sx4uBt39un9EhS2NpdKJ1c4q6oq3plxt9NPN8s+Tp0IOm19WV2Sw/pMnoXDB+P+znux4rb88scSWCJ4BHfbyICNMP9mOX1n2QeHwvuSLC70dXdDjL2PqeXz2WmUnIUR092SKpJeckN/yy/FxuSjxFghE8EdSpwiu4H14RhPqUTyWmRh2HV0Vt+HuSadOu7D/r2EGl6CFMdKXbGTLe1/W9lATvb5flJ5coJeF6Loi1XMCaiOEPtlkSf/uQlitJIxV5GiBz5b41R8GZW6lVKdCiDfK9ZelKXeNr1sB/8B8AIA4qeG+05SH41BZSgz5cuZwe6+80rkjnd8qeopbm5ZDw678iD4i9/DTV+I9svsQokpX3HS16glQztIQxAentwKZxE4BbMtzOi4PBIXW4c2EILEbqZvvdJiD3PegpDHa+5Z9MUqKrKYEn8ehjL2JEB/FOK/s/Mw2zqA0VzZXNflvzPWX9Cnkyv4894nSFPz1YwqU+UhWLJGUktjY0q93n6FwP4r5+tPT12FrI7N6ah7/QViTa22iB4rQ85bknEZyQmLfTITfZcE3LWtF3y9aRASAPyaOspuKVYxRzS8xd9l0ambN+6fxDjuPhT1ET+9fWiWbD6agTTaecZKVDSsur18/CP1Coi/WQBlwdQo9iIPBByc4iUP+XSJkmN87ptSi5AjRAvAMUHq+j0U60H4jt1LvyNe2yLm+QtuuunpkL3NvRdoqZIgUkAv8UlXFrnbg2cEaD+ZFLdLUh7fBekdWsf2Zmg8WKRqO/bgQsfcXyks09FuRMFWvPgPKbzAW1SmzLv7gb1VEk0d3XvQJ78Ut3Lj5djgZa3GB4SatqUlB+8/zWGD1UOYD49gCb66XGnC8CURDDIkneqAzN3Q5zozqLlDycdMrRnDT/YLCX/GyWBFqoDUiBrANopM/+RQxRczbiTqhFYHjmyvhuZzwQlVEkk0GJCnFa5fq03w17EXAFLftqnm4/vAR54WAbzfR9HM6j7dmaD5eJo79fn/IPB7/t+QHf4S1tRgN5lNhS3AZV8iiLLnD3Dkp2bgmF1S5+o3o/9Wpnyc8nluu/eUVplkdZOj1zj62tAY2VmhdL5/uT/akSJ/rt5QnPF7/uMi3zml4WxOg0YhMtN5Sz112n+0/0kEB7rko7EKKF3GR7Mw4M1wtgtRTT2SPCrrY5t52DIb3x7rveJN1mKbOO2Zf2pGdaNkrQDgWNRBIe3oMEgYX6hqcr9mPe+kgJgqxCp36BKoTXmEuy4JZPLN6WklL/9kkYw1eRdwPR6mI76VPH/tlRqpy3Xg/Yn4FhMl6RI5fTxQ3EEToaHwXJrw7sb+IavquOlFCWBJeuU+mv43R/jDw6CW6jWsrTVyj4HpNZmu9Aa7PM52RMlZSxtLHFjiJr1FTOhxJRA/dr7giCf21O0MEMN9e+4//ZR0q4QZRLqrUa8U7dYS5IaqhfbTbpCDuJwwfxe0Jd+WO8vlhdOIPOllTa4+Ppc86NvVSK/GaJPX747vygIQOGSw7GY0i77XtOf9l9kCGRGtXc3loJRgR3GYyR5HYqBILotCXa8fCRqfNPznSOTK8v93VCvY/jtzBwgTF/eMw5Yijou/1FqFLlig/PdbwMK8oYGC0xAbaKNcwFakBU6QmuvHEhy5fF6UCmnOH76Xac16lbQGFBl7CGy0PJ5ruO5n52Up3/KFuVWKrDU8ZaI9zvO0OuOxEjFFrkN888LQxZHGTo//GwT73khg949SEj9UiWsIosazLRhLENM8SQ0VPBNEuHn/Uif4NxaKrD7Mb7xWU9vDdiPj1roIfFiH4st+8GDfK7uWK1pDok6C9z5M6LBDLetk4qjZY+V1I00ef7QHD0oEuAsFOp9MuvuSW/9WqBNA6t1g+OfHnRUM54xsSbT8R1dUiK1FZTmXBYSNIsKOb1/n3/qofnvLS8MQ7YEF7J6+enI59DXLABz87kPANULivYEcj5WSWfA69aGmB+mMMtB4JCO7cvdusGprfNMerg2bW8IKDdxtwyRju0pUkjRCx5EZ90oi4yFSiQt6hjbGopay+xxStacpEjp9Fw+l5XWlvozwRDDMAigIkfg252GzAWs8F/Rjjhr8K6SaR6pLbrxnw2nx1BgLenVXWOT2ZqF5YAZBtYdEiupdXXGURbIAfWYvHuu6+ktSbR4ar/105F5uj2J5YXRnTy6QeBtXp1s2j6d7HNllNgqI+OADO28rTcal88PynBOpbNVL4HuHjsYbA1UBw/R9/lm5dZ2A0jHHAAaKB1A5vjv98sd8pul/VShgr3+jdpJ9fvwD/GBR0ernmaS0yS5xAdO45VcBFGp+6ne2kcOuPDaQf5x3t43ZqzjwxxqTCldkJh7s6Cqlu64agmqdvKvXgqB8vwjUax0T/98oPpBAl8rKdqx3cSjNU8oo13ltXzSMt2G0PdNjD1hk5R6dMgM6kCR2j7NAq3fkUcHQkTp1dxpUcMbExHNC76tr3eN/skIAaN4mXw7jRRiWhku18qTFgSAhRuAw2v7yDbqp1N0KgDypXd+LpHb/txath7HRENO1zugqKXXnqFF744BS/6VGoaaLyaMz/v0YD1mKRY0+PSGjlJuR4OqTD2RJVemjq8okoo5/Kr75EUut37OY+hL2oCCQexyOzYezxER8WCpRpGYfiRpZ1DlW8bOZRa/7NhlTH9jt7wJBHNYDTuJvD6rzmBAQC6hYGgldMmYvkcbaPCG5NcvPhmHP8mFVaZi198Zjqbgs0rRffXqfcTa+HaFR/qgvt1GGyimaewr5VbARfUS98e6bjLjgmWJY2Ykx8uvHxnEOv3jUEp3iygwkhO8sxU0LC+rdFURkZ4Unqc3z56Iu2ULR3bAZWtwIzp8YKpVWtsKXzPqnPT8+q7egmV6ZIZaDt4sNHzzyS7dSRvw+vpz9qXgg/RXDaECnHt9fzJByE4+WUmMFcD+e16HoO1rCdd6qiruJvfpu27nOT1IHXUGbpiOWkSklrydpo9XGuARI+fRU9zS7Pkzf9mxzgjcvj03BFfD8HYPRL5va6nm4PV98OLulTIBbdmubInYAdZEC9ZVJ0vfMmbkRGNnKswEnLW7gF3l331nDIC/uVEeFJrzkSpUSmVJpwOVyP2tcouik7dnmJexUPmw4H3/IYEk7X28cVhzPDB+Q8Lr2j+v4mSkbXYJfCM8ghU4Um+GKVLBqlQxj7/vhvS793zzby1KbxxDR5ZfDFrI+Ue2eSmTa8wgANxIJuYDIC7Hv/kjE+oKyMbYkdmgM03xdZQ2qZ17svOTwdip0xZSSkX3wz/gNx1u4O0ti6yYLYBlrq3DLCAmaNx56EUwJIB6h7MNKFqxzilXP8dN8lbMWJcHEbeMbwyLZtbKT9F4W2dAfODaDo8hWnRVgIot4bdzXjJf1CVr7oolWtqIoJYAhNGBzCJSc6iN8vpDm/qq/gkcBZSfMXoJ/6qHiO2ERUKBgp5N7UKAUcLVSLvrTyrEWwYJpmCjzCKvlunTcyHKO5BBhop9X/iAIzDq+8u6hwZSM0mw/nWx4KmD8lDOCGQcXSua2Gocf9UXd4FGlhMh9WAOwbQSDJC6Ete6EmAIVmZPkNxyvIXFh3WvEpySxdPpTWopjWJhvGbq27L2z0XxtQuxjB95KlRpk2l5ZGDP9Rr2n5uXFffISwFfOqlv+KAW2nv80hMjD83AHejWDHxoh6Do5BEVyRI6V13730jGl1eKYCDVz+cfgZPLbSGCZGlGQH9ERjDl6ErSme+/N8TAKkXsBakgtEA3F9nbglIwBxOZ4m1N4I3M6LmUJS1b/uzeTj4ojy1r2p0Ej9JnQZD/MVsI0mnHlyy+IlwXNmkh3LbaUX/e4KCqSYob7J6d4jbi1Z5zQpZgfJGVLb/5pGU4rW4qUrQlxjRQdDWzLbihUUpS6IuqqnpyNsOiEpUiHj3Vozmcouba9/YekFw7RwURvP3WCKLeU9GqivcuU58/fZlCv8+tt+fQCYPOmtaSWT68wPMmmcRdouQbnlEo03G0nHgZ0hyouttqATRkpqUls8wzCvEwRAJx7Umz83Aa0skRYivLJjfr4WYR1AttCtJlQDstKe0cxfI9CN+rHTqYIzVeHbyzBsNISGFt+XL9+20r8/NvrFmT9w0oLNg+90So+U01iaQlX96WDFYHEc9blqw5N0YAm+w980DxyzEg0uEYibK+LVDtlhKN+0wg/MkSuRpz5w/YcFQB5MXKukdYPuq4H6ExHTMHcdXFqFqN/QaQwBz0z90njkTn+0l4BgTXan7imhfryjPXv9of81WTlrZOFMvc7G/P0Zooq2n/pf2r3rrxJItrWGtBoIlP8EGAWKSafBxn6LFt1x+0Qyk8btYz/77HRpLKzelbj1B2x/KPqVjdX9Tol+wyYoT0X5yf5J2ZtfPCvDXS5+n3/SL0B84nA2GM7Yq+vGbt5FMPSA3zp+5csXyXqkhm3d22HovZYZLHVzKRvRAVzKjfea3tKaWMcuyp90ffPET0K8iNUrGKbW6Nj3/yuSyX39QhVjNfWLwP724459xd70XQiIUs75lYS4+mAxrv529Irb5efNuznXpDcd3fykpN5PX/BK6Ios5Ax9+7y6CnwooYYkeeyue8v8++fMoGE1Y3U3S+FPRc8y/hQoCAAf9IqXbWWjZHsnKDEqS/LIbA3TeE1/L+rq82mJ+uOX1JvFwONSUzOvlzfyHmXTNQehCt7nj/P+fTWKWd5jHQOslUF+KQ0AIu0/2nCPqfXHvAWZgsxXi6dJoV6emn90M9tu3/ndZMcbe0lB71SgOn5wNBbX2bebdcdziWgRkgj5znbnp6L4iSNyy/tG5IMq2HOl4/a/t/3aL57D6HvqDPYso+OgGwhJgJLsVirJ/1vqPzM54/3EHPa78MMbchTc3pB5osr6tnsh19l+JILEUpSBjz0SzK7HmR1qyKRe3Rxnv1Tm1Zjr76cpUGYtbp0qnzstCdS8fcpv1TJcnGutp1lU6ZAxIUuqWTcKnUE8LNrpox8RdXOuSZtRQJlZk3BCk1eC7EnKHo0d51CoXy1QMtrSFFZAQPRCaQNSqifroVBfzHi5ftlOsyuTa3GPX+Aad3q32YdivRtzpVGh3yC9wfnN3yWx/+yrGoDrRiFZbPDBph4+vnJEmm/Su12vM0ONUZaXw62kyEw3N1MPeESgQqnf7mroh6gPeBAPcpap9WexasnSj7PsRPzL5+zy9ItrQ8sM9us7kO6+u/7esoiO7wEdyXbElPQ1mJHAuuPpfa/xKgA8KrlFvi/aiBo7b6cp2159m5dF0GWWSYnT+y0p8sDbxVKO0KDNPvaUrTcJQaDc8CPFkgaWFUquYmAatlMml0jnwTtL6ibBv4KKqGvX5MP5YGZMNQcNdqSuCOk/dElPe/cCYmXlOmS8vld6RKX4fh1Le+ZoJGdHscBESFULX0qjoA+7zpVfL3eB0lKIvaOMrPJRzfEy3fxr6UhkkdWyGxrBbiBGXLc5/+/3t3NEgFOiDnAAAAAElFTkSuQmCC')\n    }\n\n    .hidden {\n      display: none !important;\n      visibility: hidden;\n    }\n\n    /* Header */\n    #header {\n      display: flex;\n      align-items: center;\n      width: 80vw;\n      margin-left: auto;\n      margin-right: auto;\n      margin-top: 20px;\n    }\n\n    #header>#dioxus {\n      margin-left: 10px;\n      font-weight: 500;\n      font-size: 1.3rem;\n    }\n\n    #header>#project-info {\n      margin-left: auto;\n      font-weight: 500;\n      font-size: 1.1rem;\n    }\n\n    .content-container {\n      width: max-content;\n      margin-left: auto;\n      margin-right: auto;\n      margin-top: 10vh;\n      display: grid;\n      grid-template-columns: 1fr;\n    }\n\n    .content-header-text {\n      font-weight: 700;\n      font-size: 2rem;\n    }\n\n    /* Building proggres screen */\n    #building>h1 {\n      margin-bottom: 60px;\n      text-align: center;\n    }\n\n    #building>#progress-bar {\n      width: 100%;\n      height: 8px;\n      background-color: #BEC4D0;\n      border-radius: 5px;\n    }\n\n    #building>#progress-bar>div {\n      height: 100%;\n      background-color: #55A4FF;\n      border-radius: 10px;\n    }\n\n    #building>#text {\n      width: 100%;\n      display: flex;\n      flex-direction: row;\n      margin-top: 5px;\n    }\n\n    #building>#text>h4 {\n      margin-top: 0;\n    }\n\n    #building>#text>#build-message {\n      margin-right: auto;\n      font-weight: 500;\n      font-size: 1rem;\n    }\n\n    #building>#text>#build-progress-percent {\n      margin-left: auto;\n      font-weight: 700;\n      font-size: 1.1rem;\n    }\n\n    /* Error */\n    #error {\n      align-items: start;\n    }\n\n    #error>h1 {\n      margin-bottom: 20px;\n      text-align: left;\n    }\n\n    #error>#error-sub-text {\n      font-weight: 500;\n    }\n\n    #error>#error-block {\n      background-color: #BEC4D0;\n      border-radius: 5px;\n      padding: 20px;\n      font-weight: 500;\n      overflow-x: auto;\n      white-space: pre-wrap;\n      min-width: 640px;\n    }\n\n    /* Text coloring */\n    .err {\n      color: #d94545;\n    }\n\n    .info {\n      color: #3980d1;\n    }\n\n    .warn {\n      color: #CC7E00;\n    }\n\n    .text-theme {\n      color: #313131;\n    }\n\n\n    /* Footer */\n    #footer {\n      width: 100vw;\n      margin-top: 100px;\n    }\n\n    #footer>h4 {\n      text-align: center;\n      box-sizing: content-box;\n      font-size: 1.1rem;\n      font-weight: 500;\n    }\n\n    #footer>h4>a {\n      text-decoration: none;\n      color: #313131;\n    }\n\n    #footer>h4>a:hover {\n      text-decoration: underline;\n    }\n\n    @media (max-width: 725px) {\n      #header {\n        width: 95vw;\n      }\n\n      #building {\n        width: 95vw;\n      }\n\n      #building>h1 {\n        width: 100%;\n      }\n    }\n\n    @media (max-width: 780px) {\n      #header {\n        width: 95vw;\n      }\n\n      #error {\n        width: 95vw;\n      }\n\n      #error>h1 {\n        width: 100%;\n      }\n\n      #error>#error-block {\n        min-width: auto;\n      }\n    }\n\n    /* Dark Theme */\n    @media (prefers-color-scheme: dark) {\n      body {\n        color: #E2E5E9;\n        background-repeat: repeat;\n        background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAPoAAAD6CAMAAAC/MqoPAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAABdUExURRgbIBseIxcaHxodIhkcIRUYHRQXHBMWGxwfJB8iJxYZHh0gJSAjKB4hJhEUGRIVGg8SFxATGCEkKSIlKiMmKw4RFg0QFSUoLQwPFCQnLAsOEyotMiYpLgkMEScqL54l03gAAAAJcEhZcwAADsMAAA7DAcdvqGQAAIqdSURBVHheNf2HgqNIu65tChBGIEQqq6v/tfaY8z/MuW56z2e6q9JAxGseExGgx2MYH8P0HKZpXtbHtL3mcVv25zA+x/25PrZpmKdlGg9fWsbh4W/bsO3zPD3H5fmYp2Gf/fi+T/u6T+/lOT4fj2M8Jz//2Nbj8Zjmx+OxP6dt9nvj8PTH/blsTxc99+f4mMdpH+bn43i4wOkW8z4fgx+el+H5/MzX8xzXsXEY0eM5zds2b8/9Mbv2c3n+PL59YT6n1/Oxzts4j89rW8btM1zX6B7P8TM/j2NYHuu2PJ+P53M6BiP329OxntMynAZlmM/j+bgM8/V8Xq4yd7NpfEzjNk7P5+bm+zRNzWVd/WV7bIMYzPP6fJxm42eOwe38+xyEaR+W6zie02N8Les2jetjX859nIZrcePnY1q2fRvWyY2ux/Lc5880bc95H37NZBsHtz7MRXDd8rOYvTuP1/4y8IfMfIbHZHKPUbge4rrMk7mNz/lYhn0axmHePsfvc/XfyR39yGOcXvtzfk+HGB/jPj4WSdilfrt/45gmeVv3cd+3Yzs+67SOj/V5rdN3n9d5aO7++5mGx2lu4/B5rOf4uARIcZj4YObjvoyKY/7M+yjT4rwPn33a/jw++/kY1vl4Pcd5Pn9dyWSHbTk+4/IY9nVdRvNex9d2jRI3j0IsZuO2PvfpMV/78Dag2XXP8ZoruflSMM+KZ3osD5FSKrP7jLsCOXb/M5/p+VAgbmvsg6J8CuxD0J9m/Dzn5/xaPs9j3ZfhePqHOyjsfX6MiuJ4PvdRah8V+eR39IOGqcjlY1nGaZmF9TmYy6hUzOO5TevW2Mzy8bg2Q3985kd5KwnbKOeHa4z+OA5+Zj+P17QY8TrMhnw+xnP5VVp+bhsODSbHy/JVUY/leK4S/By20203nTNM2/4zd2FdpL3G8T1el0Bs8+d8agoN6arLrsa2x/V4vJ77TyWlvJ5mNSrp1b2EeHjsSnQ7lMVRE++DtGybdllOyV0/SmU8Rkldnmb2j1u64fnZhsex7cs8ntDh9d63aR7Wr3sPOn/16yY9PcIJ4CHpOvQ5yuc2nM/Hx/f0m043FKOcDUXfqUlFvleAKmE55HbY/OK2/t3UzAKq1Ly224vntl/ztc7zMr6un31XnO996srqpsIc3+fjPB73l65L9Y/X83qCPJeY9nMppiYqhSp4mqcfgdKyEro/1sGk9YcfVc3b9vK983c/XXf5StlyPUzbN8HBaAKrpMnN+nhsT7dbdNZjUHZ/he2xTMBl/0IIUAYpJsM6nq8z7P2Ztn8M2OBU2DXrg2kc3PcBugDocRX05bUMj/fzs+ymoIrD0+mzv576aFYQSsBMxLkbvJ9g58ddzeGhjQxRDx9qRo5qBLkdru3zAkp+1Q9MvqTkn8ui/HWwLLmHBKmFcfjrZoKyrMhhg9u+us8/6lA5+aVrXV+gG8xoc4juu9cjfIK8wb+mXrbxBC/6dQGrL3UNQ4bD3B5fRfENkqWgxtFQd3+oEV0GnY6l2Mi1OQ6AUBu4zmFGWrYwNXq/ua4gTqJ8fRFLraDK8Jemn/dPCdeb6/EWuxNS6Xg09tL03RmlHLPv44ES+Lp+1aIoGfJ2bkpyhIOvX7w0zG44Hu4v1sN1aAqT2Nfh2H9UwwnwNXMo4W5P9TSOyEkZXbIXc02mjDmiM202QQA1OazSbCIo2SiWEQGcqGkS81Ue8R6u2YfzAdTAVbg0bjhTpbrPPq7P18+16gu9dka9r4pcnEVleH7qCLVVsepIYHHOpxbfHuOhP17F4qP/x3V6G9lzHqAv7FYEy43yEn1NML3oj3pTq+JJ99r/XvN0wlvlOBkJJMeFu8i6ScyASDTRCcQPYuOYAO8p8VKrQBQCJTA/X5BDn7mLiR2P5zU8Xp8ZnuwQ0s+sahxQGkFoARRB1uMNPuJjpftY0RmYe+Ouet/Apnk81vmB2ZTccL5WgDSrATc+RzMXq0OW3B/7TPs8/F2MXNjn8DwYgjCSBaX6wrAu66/qWh6zPw0rKoi8NqQ5CMYw/CRr9jemA4JgYtc/upXaGcGvhBmguc/fZ1B6vcwgkFIAqYDr8RMmbRKlXOuFvicP/kl8wBGzJ15WBQJYhAZuK8Vr2QNKPT0ounWRX5VwLup2lg8I6BvXW+Ivc9XnHxhxXi/ixRB/3WTaf7rTsU7LBzSe168y3JCbaEA6Y3HztfK9dYHgrzpkgVyyMfxUDSq4Lz3+QITffmN+rh+AhtSn6ThQ9hEDKEb85HLj8P2eSOFzuM9T4Y9aVc9jDPIMx0I9YKqvxBto/O4q8zmmzD6VO1S6M1SboSfdrWKF4ADEfue5fUmukXDdXxJ7CMD68CWhnb8wcNKTxzgds/41/K1uIjPlSM9BOtLwcSgIHaKA9+MXS3wTwFW4YU5fuoBG0P3fdf8lBAC+wh/cvYp5qE7ydKAP0Uoyy5j9xkDg+sOMKZIRmsX/9Yc/ozM1BKUOekWg0IOYoj8/LMzbfkzz2088dsVYg6oRkLwLlC+mQnQs7fX8+sn13CgLOIzeCs0Ti88fGvmZRgUV2FDtJXJor2U8IaZ0ithHUZ2mZjQuEb7DWzUAAdZZoBsBCbgquo/CfYJWv012+fq5+9FHMk0HqiGS4nzs13O/RnJGT/hNk4bqYk1AmDRNgGOwE9jgNKi0sW4e52sDIRudSkYJ9F8BoQAlAN4Rvf0yFFQE8EokOJBpOD/g9T+Fd9MSvChbilBslv8g/K2YjlkxH1+IFyqh0SVIQg2LuxiHRvWz1JguAPr4Rd1CyH9ZnP+QHXihiJgAzvodQkougMhA7lEiiyGSp12z6UFMvRmxdS2/pCZTUO79ihzG1/m8pGk7zEUj7ktdq5Q0+4v23qRZAQGucSMKLwgDaJeL2PBNMJWmhX04kmAQbf36mJfzGt+cCQsXq1PDWDO1oxLnTezXlcV7mayR3qwR+4oRxaIw6YoUm9/ff8yFPCYrVkBILABYzahycZzpESzTinY5AlSN5SDfj7aLfMH18B70sPYBxmdISqDfQF7XmfZ4kE11Da06/V5aVZUPAfFz++y/EV63k1ltVTIQ84rHdqBgRj9Vk/kERWbitwRnIGCSPdVC9WbuI0A9msQWVpLamn2az+yq7+ohWGGUN6++D/Ega6QIVPsFkuyW/rTXR8Rk9fiLzsIaJkERv6RxPcbjEh7k4oZKQ+1xFoAYuGkIwLwsmjRMgXdEbgISE+ewKmW18SaldMx8XCMwU9TGIeTFBpURke7IRrqxQKgo4wnE9ucXBER7ONYXCA4VS8zLSlj5LfP8BLSnjIQXVKgcYMVkI/Umcl4ErX/dokcvG+CLaiNfqeAZdvm1/SOBNe5qjqvcGoN/iGuYcH3UDnIamQ1lKu33IsDx1qr504+RzgS/CgUvAeKmxGDHJh3HpmExrgaGSN1GOwv1X4iUzPCzAihd+LQlC0QnNOAp1AXXgtvFMPpL2gYdVcEzdCYt6N1/MtvEkPRkv8KT/JFpyDGRWT+fb3+h/Rc0pbHW+Uo460cRGXEoQHAF8YEuJj1sn/tCw1e0g05pmp/XNLwyi5nblGpQejPkTBAksIUoVF9OsR7oYJCgGffpexz+vc0xW1yCa5Gbf0/izdYPF0c/d+1thVUMJJJX4WSG6jNU9lXgOAuMBI6VRL1URarZjJ7ug38fpfyY8eh3AgNks7lOP3B0+o6BgfKfJSUsjJmW8WtCBdBFru0iZOdpXC/Qh49fy9+YR4MrdTCd8Ni/z4XQECumY/moeSOEVjE9AVcjbse3UlDl+fXaZf9IE8r5igehrEH42zigwn4+38/p97EvK+ADUkpl/nxeJLDixfUaSO/9P3I5bNJEOm2GqqfGIBbKmePzZbRaWUrB38x7KBIdc66TuNeTYK7VpsOoxZhjJdx/9FcAmKC4CUuzXi/fTNzqUVKeRjSsfXulDWZ3jhyzWjW5inmgV7r48A/gVKUGrexeUqlvwGjzaCUB1AE20GkGiWY1rhp9fffrqo1RaXx073RhzJBGAAeN9lqWFXipaigE90Ualozf6Tjnt0wQM8IBEwEBr8NkPccXBiDxDyWqS+YFUwOHlFV6j6PZ9dKYhIUCzL86QhLpGuCdh3PVmTZPGZnVRyWR7r59zb/jlfrQpL8DE7MHztPH0GMAFUtrEQ4JIiR/LVoevqyY9XGQYuNbjrJsyr40mUh2y8T254F2EFVEobsxom5WPTix5hZALjrVnFi5V43knnoCTdu+fWNZtIUO/OGgiG/LUXp+OQbpZ+Ne6P1cvnheEAVovPLGaGVHwDKVVKB3oNbCXzxbdpoX6H7QKdoKCaIfruBxUD5Ms7EAYcNPs30vmPjZgBkg00cipoweEeZ8to4KBIVT/ZPdfIoS1E3EZIuQt9+7Hn9RxmN5Sb6q3i9o9NLsyjN//dguUpWlkNHjpjdFa9Krjg0G0A60NgsgAKbH39Bv+gMRhiUoVlQKP3ZpzULExe9cH49f937sb1fSvcqbRiHv9/E9L/P++DR/tdFKJqh/o3A3+0jElPo63wCGL28dTC2ys/zl44NwzSnQ1yNfd5uO8W0AT17kUJ37rzYpTrJUa6YNsc8u1P+BzbG/s1gQUPYyZ1j+8amxUpnD8Y/uY2QVZmvKKgm3Q+xNr92V0PTiCzPQ3jfEJdeNIFHpxq95+27L81TsE82oacWZ5XmYjC+2FqcYlc/cr4jL/hbcrZS2+tMK0Xc+iBa53+AldtKCgdo04Uc44icMH19L2bNVj+bj+pVflHbz02MhXnUWNPcfFmCaWtZUNq2Y3zzDqJq3r9QSkusO2ux9pbaknIiv3eb0NymmNZSaEFTH40a7CTNkvvsi40wkDfMGo/22bk+qfI1TGxcoQlEekRAmlY4WrLLR8FX+s0AaZfoM4+eWSwspapDFG8ENmyssHKypijru3DjWelngtUFq0SgBHXudI/Yfwwjjj8d6LJDuoklmlvZwY/Ohs7q6PviTBxQgMXk/qvfLfT5JuIDMhfTD8DyPXzennlkyJQARl+lFwmcwni3vSBRkAST8RxxNq7WOj3XzhX3rRkkzu31IVmVQmS2KLMfJ+ED5sgaavy57hjOuPVDD0YgEasr53DmmqG8FaAvMufUalf8FTwoM6nQFGdYfE4FAaABoA2WT5vXCDyow0Tv8PIaPFlRlvoR5pvNDExFpogonxfyMXpCj+qMu1uUM/qb/kWkm84IEb5iGm/COTFJ+amwfVo4GJsDe6oixda+WukGHOf/7P5l0CTpDb6GUkKPl4O8vW+UHMCFXkn3kAwUyUIZLj+d1yBPslA+XU7x+driml2Y4dKTKGwmVdNMEOFqhWNZvdilACZQMFI3xO/vxWs/jmD/bAJugEtW0DRoCtcmu2yEs+GkS8VnRfyes9u8qXOFITNKG10fF844TaTmb1fURYzeUN71Prviz/+NL1minZp9tDXCgahf9nKbB/c2fXaWdh8b4VToM4GMG+aeurO1HxhF/vQjtqLqF2IUeEzw1pAV/Tj+W3jSyN/5q+eL4W0w2uSA2WtB9YgYFFXDMH8VHVYbKpoEExz/w9XNnY/8grnYxDrjW90AOC7AQCIpBSjUbefeuJfIPzMpOrLbHxh0uoCp+QsY5sAHEJSNM4nHEGFKhsrUL64VUoIQwosLliX79xDycokVfuaDfeXz5QVhm8JwGVJAE3YKywmAYkKdLYrNzqp8+kB25o/YIa15IkC6NZHCgRxW3XTUAPWV15QbYaPdr/fYUBfNRJtq4wLjECIoM8PGlJO4f1qgk3TFVBS1Or2sAoDaDzpMsVX3r8NovDch4SJuGfL3VC00QPCuW6clBki1bq75kz2Iaq5rLgiStn5OfQsWKmxwEYUeSl7E3qG2nO3lcdkECFawxinOrkdXWVumm2TWgVFyjajQRlSP8cvWcr5cQXMDK75Ac2vCrGI9BI+Dc274bBydHE63z19WQsnjjy43F2qh/002pojxWhL3EMCLJ8AAi1HQ7nigyiPcD1wYxhWsjftBVeN82CZzWoLpEaIB9HruFu6QpmXZinjd7R8ESVSrwdK99euloyFultwnCYS7TvixcTeRBYSDkhQ8jD2TvXgJ8jH+TVzphgZEt8BhZ3CPK+jAJmpTwRxnURZ+2R1H/qMWU5WP7Ec2CWjMsw0/RMilA8k3/to5wewfZbaXgVI/ytHyeL6IWBsHSlqdaWJrZfcpkoVMyKgK0iPyCJVuDgFx+9TCXlr8wyoXNSu7jt/h9F3G5g4PnpVc2iVQtnsE3Wk3X7ub0J1jMnPn7hwhb9+Xvgtsnwxk4lrbg7zX111volZVuwnN+YOERFr7y3TAYpfaUKU4h03hCcCmAl5QO7TWnA4QIcf/CBe4L9AEEPT0vGAGWnq1Jtfo188U8NHFKbx3Ku/LACWJurCZx7ms8aXIztXSkQW/lgOjRHOXZVmZE3ArvhzeJMUDRgY3wm1QLRKVr+uBdwV8jFawKomGMxGoFe5L5pJjl/q0jAydp4BlbHU/ub3Agj3G1W379ck0tGLoi+DFjQkIuUYv4Pbblmmcab7+GTyAeDNN6xXl7vrkYfTd9+MgPmyC+03RV/iut6YqsOQVSl0WBYK6kJj/Bo8EhW+OdCvcIejQqwXx82PqWWMDwYiLh/Cd/LQTrxWGzzcmo9dw/6lw1tDerkq4EC6yO1F15fJW+0FynkByQDlrqqc+1xzPoXE4oVLq+Dm3iX3FqxRMRXi3vYatZOPHXcQgtfdtyDpCEtIEBWNJO7ixlhrSv4/VXK/gRcX5cHIgCfU2P1/lYLpRohK2JAceUgqmj0vFUkP6mh9Tm54UF4dA5f477RgnyaVcBL/9FMyUgsR8sUaMI4fOcuQJ3EO2JrAR66+M4NpC4v06pYk9iwNRBDvAeBFRSlX7HJagzRACs+EeF7kqj9n50NXC2b1HeTAGpRa2rcwOY03THab3XVBdBBN2tArg4zbtpRW3BBfi7/wvjJvRK251UbKvUTPqWDlI3j9O//LrKhvXPf2Q/16HbU4n72mZT9g065dyltDUe9cY7q1N880VqUuKPLx30ON7DP/gx0FLc4JUSkuxsC1pY//JneHiege2qNn/Bp2ErmqW9DbSeqjb/XLtxKdn3p4Vtd2unCau+gCWZRLSthPmSOpz3U3FIKafHfX2WtVx2reSYUsUS8kX7+6e0XRBtWyn74a8eQIoX7TMc9BgSIixg0gCkYYCJa3yJTWO0cGUiRxsN87+SvH2m61jOdhD56QR6/4l9W45oHdLo/KrQIf/ooz5vbygC/XS377iJ0/y/mlYXsBMou8H+OdvMm37x87v6pAaFW+0NywuI6O70giHqAmh9VZ2yIYvl6vrJfa2SR7u24+9aty1p/WI4trllOmkBweM8fMfnG16/KXSeQiZbfcw1Ml9t/AdT2m5jtG5GmBOgSS4Wn0CFFnr10N86l6jABetvbhOs4VH0UPl9t/kXg2Us958HPzSsx1f1uV8UL9HkRKX2T6F7jB8od/0OpFdzjyNw1GaG8r6/HveSLytNuwBBqqM+qqM0DOhRCuHYp02H7f00g1tDkij67nUIXET9AG8fkEO4kA64eG5LksaZ1veTlI5yb0IHwNhfyckfVGzPtoM2K4eEkPfXtv3BHt1erYwtvk473/XNkVF0t3fdZlnxUx1nU02F1/faoXEJ7m0nRtaz1Wa96QL9KpaYoQAFN3we95qiQOEPfTuQCtvUv46fU+x16xFeqSzNSwVr6Vfnc+6tOL6oxSOxwyHtMKgSk5l+mfLh6mCNjKo1wOzPd7tNx7qeI4860HDa9zgRTqLKsB7zaVyL4dF4LXu3gdOxED0LUVUyLREJcZV5SbXpgqBeCy7n83N8pjb0ho/4yT+uUy0UAx+xHL87G6LBszGdFVM879ZhUnfsIdn7v5Mx0QvLNWyIA8w9szdCgxrS9Wl9laYu2da/mu/STRBUx7c/roiFFyLCaS1zDi++TvUK2WP8oS7XQ4wMSmG4+7p8znshH18SAUy7iYp4DCaR0CFAh3mq7zm/pv39PDi0lgeVZcLen/zCq447aYE2xVoWUieLn8jj4VyKVfQki7DZx4Nlk4qL43H942fNhWzKhNIBMtLtin74lK6JUoRL0rZCLS7d17BIux7odInS6CeITiaYstrX1i2fiHTQv4ZQKpn1krYZPyvlW/6TeblBFErogkqt4uvvS7xkkqtQRh1GolduG7OE1a+8CkNr1lxYLguIDB82RMuqXuJ1OyAP8nE3Re+OrcprSvRAT05kiQlSW0VK1SHIB9UGRYyT6Fj3x0lFQvt2pF5qR4dt+ytnzh8AJr0luq15i+y5fzFl2W0dat/8HHgLjPQm5dzOEECM1LQp5ED9oQWp1rqNf3xb2QHZbKqyFvYwqvM8h0ksqthXVMKHv+lSIllJ6EAdDps6nKX2p3/8nmlmysZvka9ISY95/328hv+8LTNWklW8DN8nG/gdhbOtZr3AhmV9/EygUJHH7vszFAjiL+F9IRfDuyKTZWDlwOhzQ5yfjKUBI85/cT4qui1z4V59f2NJY1gGEyXyfBRPuNvBHHpBptfTXwQMCRkF2oFlw1N1BW8pS/+Qbc6yJdxBA86vjCZhvn42gN3KABl1kC3btL3+7ZDMUVdujOhLZQ6GccHerBo1T9n/UL/UZtCauKPRw/fn8m4tvgLO0rA3YA9wYwTJeEJjQ+ZA0WZ9mvICoPd6z4vQuNr8fA8/+/QFwPOPrlFQ4RqdTDO8aES/Af9w9S3LoZ1E7c9XHbgtUBEKje8x75zXM632b1OMrd/lMsKI5wdPd40t59GSxwYXfK8Fvk2ui077S88TvLdXot00+0L+CEdCDLi+qDVD0223OL+mt3kDLHCumH2R6FkH6vEOP6pSLgbYIlfsKmrPx4unFvbrLZH6tLXkS4TxGNwwkI581BI/lzbT536soGftKGQwoNVo6iYjVeAOxCdl6/KHsXNv5JuMC5RITeiJDDZoqVaNxL/GuFcp2/t5kcBTIAaFFauC6gjIdqb2/iw6pI3Ave28s32YvYPVhFz+sfVqxEmKgak0eMv4Fx/hXhHJa2Efv/R77H/QhYi3WiSglbbcKc3WpSIV12+3cWk7j52+2ht2lxZPp+erE9HD2abOxFIamUsVTpep0kzSDHTYydluHU3CFqZrVOgdTRBWpKEchG2P6Z2goEldWcjpgMf8OtJQ69xd4w/yBraqjZyvdELqlSbonKnuCA7NNuCTU0CJlv79qkUDHb66MEvBcYVybvhq/RD2cPcnJkrrShWGf/zC0wtCicL4oiA+9+LEeEgPLWi+LSW5B9ewvR58bKcHqF/C4VLdSaewDakji9ZkKgqgs1VEmnU5tlfrRukagNUak6y/ZAcC5NyasXY9pi9nIpSCoow+S6UX//kmsToa57yqqmNlO0kJoL3/FeZTk4MSOYy7cc7FcAEfii8U1remATdF1XR0XicCSIzP0o7z8fOWO7QwUdrpe4GW5EMHDyNNojh0W0amb4kECmz5221oD41BmeAxNdISiK5DEYAM+v5rwt8ktMSFB7yu/02v9XlRFiFyqyhy4Mo6oY1Tla9s/VFduezz0KC3vtS+CVX6pPWAViXxGdibtImLP37kbMvyuBSlop2Px0ej3pV0tbuby5UvZZ6ywmuxO0JpHW0ghtYXabj9CtC4f9voMJ23iTMWkv36Q9YbSlXv6rDvA2HpMcmmhFwcmbUuIIHs497mmMvpTSRlolEwAESsX4UgKDwDXNDNB/h9XAzWK5QzMHoMcAjM3PJ4tLS2Z4VFsneQy1RwguzGu8znMP+leV6Tm9/yYNrn6RtuXgTw84tnNCRBgt4J3kuthlLPr5qKJmANpdoPhI+p9xblBV17IRgdWNx19MtkUq44hM9vnsM8f45IwuUEnFda+e88SDVUBcnVe/906AqGIPIOEW64v2XEjqsrS4oPD5O0oG/D8oRZHkSrFoNWW1rQi/d5aoae+9MUn3ldLy5B2WmBQkKX7DNJRBstL/D0Yos4pB95l+l5aCmjfQQ5x0/r+Pw/qt+EJlaw08gor/WIe1szVyltLZ0iq9/y2koEDJhehkfomCOmW8nsvMz8+FAi1AJcuU8eydUNY20Ec/dKsnvnU7i6naQGMesy/iVduQ2JPzMNDMX0/CRdZwyZfC6nkM59Azn0o7B5xBfcccW6vPUBQZzpH9FqVfr19GP1wRPUb5TD4HckCLaBQ/9qX3kb4e8CYEguskf7HEYPyLK8JFm8T+N23g+qETuP579642UkbhQx6RL4T5d2wo1CIyE6Y61SNcDySVSjqvO20DmaKanICupIdfJ1fRJUG5zGz/T94mxWWJMknggS+PcYvlcC9IkjYt08P32ZAAbkWlnRYpbOQtNnfqz1LC0NCVWyCShl0D+cIbbIdlroLdhVK5hL71WoDDBkPmZ6aD+x583Mq2m0kjlP2/d85SAoGIOZJB0ZMVwXcKxh5NM3ZVgnbi9ymUurUE3JjVgefubJwJEW/fhwSfbdRiXQVLieFtfkrFIie9UMUjZAwNxyARndmbBE2P46tl3k6zCCV1ELqmqEIMm7VlrivpFdV2dUk0lSVfqejGtRRDtHb6pEg4fcYkJP7u+8dzg++9r0Pv1bWdIXrZPsD0VH8LT5ROVwjfey5vpYIVWrBtPAe7RyQpONGht+SNZMXMqKUWhQNyWbGF2YwCjjvDCKKJWYFpqfD7ZUJa/G9ROIQwHg70r7/ruZJ1Zwu+pAcHzXeP3YL9wPKQ6QlH7F6uxqBhqQwjG9fW/66lWkqTDcSJUvyNUkSIPcf1b2ibHVKQQQKnc5P7q+fmWfewaClIJ0wAFUsiW6AnJe8x/ZMYPvJ7t2nx8HH4ebHModxhNbHzm98MYrSwVmqL0OZpjIFvjpAhf13++bbmzzXIOrXYhk9DW+vsWKnVzkuLXLsT8/z/Yfpz/ZF3AGMNTh9Pgc+1fHdxGBk86KXDBy3Xjg+fjV/ann1Q/fR2xlVDCHNxZrZRON9WjFJQR37fJIH5JKmJis/906CXmtb4kmonHAni9SEB/5ZTorCUlp7Tx+CldIpgN+tZzgehgM9aCOBRcMxpq+ZDphfuqVNeb5f6YmT17RHv8dspu373wt8/XUNRjQ79BRkFpds82PyqS9ku1vK1ITK7hUv8v4IYSNXcde+/IFd1idJjcUPdlKOoWnU3usrC2Pk/ltsx97rIry+desLybm6OtCqTiUWjhmlkpgeT1+W4cGEhfguhAlYDRgEnyPjCMU/3r84KfpVdYLQi0hB4sRA7sI5vo80Yph/bfAIyovNTkomqTz+YIWsPj+1a/ruSJRF8j42Z3ODjA+C08pw4CiaqEMOnI0MyHYbTdV0fxVfcv1YWiI6o6HHTuUnchJP6q7H8+31h0vuXgdMx2SdTX0DNdJgQ7yC70FF1r1vBc6ATFu2qEXs6rf2zfqX1CNuejcqvYj/afnb4fHgneuJYtYY+bfOlre+tU98mH6qyWTL0Sk2Kgn1AjSVOz2cY9Q1zyC2JXYPcbVJKkgjrV1laykQK+Pc2tPL5fLrp9rBySAfmkUVNdgHOAaSGjhj9oAFq+RqKnh9Jhx0Qzjopd8u9VNQL2ABDx5+Im2OWAaqG3Liwnyly8Ddv2s5ptWeX5PtSFDj+erKBB2suNqnfn/lEUl31EUdjMhVH1MbYspuZZEMBm229Sii5AJC7n1/GWqdLPfqE/VXbrg+SCKLgwhQAiNcMaqCLJDAXWA+RpEou82Bi3IJOP9w+/M7Ri23ojFKI8L7TIyqzpfad1dWyuJEdCalSq6ZoJfVR2dTHI3wV07l/7fyvCvS7eoIaBMSDczGCQEqPyM6OlFORj/jq2SybtIzxJ1QxQumMc1VZqe8Y0EVOtTNMT235NIqnr8FHhtfsAv2D38vc0+iutYjMprkWhoweufF91QT6hr+froE6JbkZGhbWJCt2yE1gbhnMRHc2jbkZaSJgrnhFA9t5wpaG+JU/m44Fd6RArE8LuyIlNPXrV2wd8bcZOmhDYfFMQMyWJSaCEJlZrfxQLtsijOmUwAEReFfiq2+XXeBwVaOm3LuM27jwJycYnqxOrF8Jg8PQJFzI3e/058+3+t3hPLPSUR8g+v1ciXlenZfmnV6Be5vZf1+SUqBIxYE15GzHdBVtuD8vibF/ANF2jBFKGBGn4fpcwv/s1Foezn1yz+SprSwCJmod/aiRWl3xtYOVx83ooajFuJ57C5AxowPEf5+O7bz++gWvWpBM7rZ7jeFSUUDAxWc8mv01xyxiuXm9a8EkprgCmHLYW2gESq6XySrUV7MjFNSUh15up8vjLyMFkUFzQLt2pHqN+zqYPGa6mo9N3N7qdwO5JNBW+dEfFNxPE4CJoZx+TaXxLeYUBavr0DxQCP/1wByXjKTw8ypZPbQnODUEsg1LCJq65OELxh3Pj8gj91riLkMz59Ia62LqIR8/4ok4xtO5Dkw7Acy1+tOv0ZUpQuemAy/l61UPNPEohM6IIpzi5bMTB/JzeLs1kmASPYyKk83vzdWtxOttRFypzk4W0h/yP3r3g/ZEyr/PBY0mUA1w7br17RQLOof6RXHfA6H+pU6bZ+ZpJJCaCwndM1XyEmanwh2oyQkpZAEHtmrMfv8hX/i6CSOwixgPRKQhKQzdrsUpaQuyPK7Rwij5Tg5zyuHQMMfImwGZ+xK3m/0alC9RZsHy0HsSmdzX3mFhEX8ND3rdhnnNpHGNYJgD7eq+9lFjOoBV7+4K+SqjyCZ9VSLgLy5H7PeX7ahDsfx3Z02i2BxLKWd6HN1aq4/xoFZ+m7TnBA82ynetV/4yXRPHQw6oosTfOkAyT4fbj7oZdIvM7N1e8f4XbV9q87knaxhC8aAZNvFxpbqCrRat9+eSxHzwfzLzoUYZ9QqzMIwGg7v8bFwBFWKoZ8os9h6D/Fr8p//GCt8c8nzGQzkl68Zw+qquZ5+l+/1VNq4Ga8FOyjFYfBbdqM/Xu9o3oWg2Fp9RN+iTV41nlwL9yMLaDWcs4onT6sbO+toYRge0HbCEJl1Fc2Q3l2d/WrHDUB0TIMLyl6Y2d0YzDigW4UcQDSyq6gnseCPKO5IspDwHe9nfUK+bb551yu73pvJ4LXsKeFSzDpSrCFTDApdQMCOhY7PP7VSS7GqzfmcaMZDXH7iQ31/msK/+AHh6SseCPpzCy92EK/od6WLYCRb0pjg8Rf+gOtaFhY1OAFqONMMgjWyevVDxWw6fj6By4Lc/BaKTlN39B1vfLZFOH29ndkh6rpyCGEN2KoqqZQEK3Xqa5Jaj/vcfjVsCecerUxJ6DETOXQiV01/DMBmHvBVFdMbSX0MgJVusyQQQZkJ5/M5QVrwiaBIgijycVJ3ktMe7FSn47+QCq5hmEnaOYam7oJgujOlikswb/jQDK59M+w/L+20hcLB5bxreod5os0OreeAJcUzdpThCBMnon5loWQkm8SY9Ig2WoFOaKhThKYEKDYxn9gAMAGum1CfwKW6Xu1XfMngFOxLso4A5cfdNHTmvqrleM24fic+V5TgqcEU2v+QShb15ZmWQiwPj2j3RICuiIPe3rUtR/KtTKc/+r476ejB8LTsZ58J3cBwh9/1+0DwWCNkKWmwI0UL1/lOsPgGKQQQ99WEPT8LVjxYCtV+bSHSlPDNGYKY6DWQC2QhgHKJ8HvP3C1sc7Xt4qHeTtrkO5R0Qj67orcC+fwGABGMJR2qTdFeyUgl7M1UezQ6RJKw/8SW0Faa6NuuKD4zgMkr1U966I6pFy/C6r0dbNpWWGY62s80zLpNUF5dAGi5f4hqrv9ojM4fH7ektMhCFD2ONstpf8ugf3RIkLqy/N7f61Ba0tDYIphN+pWOOafX8qqFdmmt4NVgf7ce3bGCBxAmlIv9pQwKLsr4gTviZEW8+dqM+cA1LR4/HCoR4UYqz6UQ945MXnA9/+5egvGYkbCwNEKGwUuL67HRsvWAw31ZECspJTH6VdLL1O7ZQZP61wKt6Pe8vpyLeZobPXlc5wCo2nIp+n1id/pEUxKVCwo7GGM15W8BMk50zvtPCR00xRGSg2pa0HkezISQMlEL/Lp6PCMwgFN0pR9uqlx/2zAHMSr+qlDygYMQlrFPzTGCnQTeufAoyJIZcCZ3GdGmKh2yTQTZEkXjd9kFs7B5oaBja92WLcDCIKvQwNVTkPmkz2+afC5aQ5fUdbJeZcjpPezB+J6oG2HJmEXBAVgyYdQhYoOnu+Y3wblmlLfKhFjvnJdfl9GT1M9yXHtAHdvda/rDXz4p1k1/11xaMkap6WotvyEtEhASxWp3shVlaiLijHSa+kAYsi5elcxCq9rhG3Ps7+A4xfHkfHZzXJh7PzlcN97H/XxAzX28XWfuZPc64/RSSyUqdS363yty3KNn1frEGCttnpf9E3f58m0hQ4EIzFYZfCuYD6QpiUHanA78S6ypYvXCTyQB7iLFFzXnmcVyjzM+pq+HYoF1+BSK36IiU6Kzd92IucfZalterrwwDSUaC2EzZVliwg5Up449MujxZRGzxyOokYfvqbhTar+eXW1af0DcHxjPwxruD6qYuRKgESYhgU4R3gBFiDr+EtSuXLnsI7Oo1y3M8xN3K5GE7YZxkxkjvyzpRpJ3i7yZAx88Ef7bfsHBW64+PG5ptdpEufDla6wimjpUmb0+bwy1p0mR44vkzMS+mp5MbYXpVFXtHB3s5W0d7gDnAL560AG7w61tLkBYVE8UcIwdqBT3XznTiVebQ/xZmm9jnbcDxy1QxJgLibIMJDwYqkMkl6nsKA6JAKQW1//PteVZm+WDP6jY4Vt+VR7+ZWW794oSN0vKua3hfP/E9AnhAByndsB3B+TCDso/N4x8Lzgt+yaYFi3AuYPJl5AoHmgHQJE3X1w8CD8sHw7HvN1rqjrvnFf53PUU0sFB73TS4GoPLZzWfyyK7D5SovbUvA9+GgkLc1J9u9LLR2qBgi2VBIU65HX7WDdnoSlKCKUaFWi/9Mx/IjbasX0tAqUoPJfp04xi8oUSiKZgvt5bGS1xh47fuEm9BKYetxvnKDWtltW6zPcrnj9RFuuJVwQbh2/rqueTP+QIS0I1u38QS86ancdbrQiCddVfnYfUV5aEAX/CDPRwBzz/1o3Fci4APSBZZw30uyvdBKdyr3JsC7Ql7Ixmf3xAY3MemgtEwbWCpkqx4JfbeY2NNifs7fx0HomSHtTSQpPdSmuFka2ztDGKIavAbcnVdPaYig6rQvNyppSA7RVz1ZqjjeQyZegN3MV58lXwuo29Hp12ac+4UhrAn7wdb/zjfiYV+qz81kt6elIwnV4Q3haUpqSi21ttgz+n20TQujCVkOZpv8BoS0lfVhRBIiYr227nxzuhUqadadXXb7LyRlU7NDOV3CgJ59DdogzoIfjHdyfTE5JdWcNRVcjQOZaYe2PNr605y3veB8/xzd1LD1hATA6KtE6hZsw1X6cUAtGiQLQpC61CXwSnA7/c6J7CrTyUOT3ko7YuYLK0pHi8CAsuSr/OTqaSUg36frm0YFbyhz1t887u8v6fKVHM/T7xkFHQyItr4rNlPxJjoIXXwQvndAEUucBdOKy8k3udupqaTGuzlGQAvvUEJgBPhHXwbdOnc5ONLNyaEUwLkUxf3urxiuxcDwu9twVlaRRHpCa1krPuVdd19ILox0uSsH4vcCgGx33k4bGtreyHJdL6y0dlZZQsiA4DIzdS31+dFp+mP7lpeOBVh2i9nhEvfjbbkjIprDCSt/VwxxvJ8ZiWunxf8qER2nrUyruFTDu5uZIOsA/Ecu9uqbyfFF9UqfJANOT5ql3NhkKsGyYvyu9szGQ1VbLlhybWiipQi4V27+w3kD/Usr/Cc2W8LTBj7SpLKbt1okouBXmlmelsqH1iIPSAWl9qX1P/RZ2jGNaoZWH7iEK0RO/a/CgqD0hV1xWiNKOSzY+ymi0dYmaGmZ3W3/3Xp/UU10FlzmRaDCusDlrKFWmnzNMb6kNQuSw1JeQYyuB2XruI8Cde1akM4q4gtKC/EXoWHs0tMABIRW+7L3Hx3RdJUuFx5ukYvHv+0GC60smvjTP/KaCK4mOZhg9Tn8SBm0cwJb5/9HgYCnDIBbcXNwOgmbAGIsuz/X16uHnDo3GPdsKme5noce3MoDRLQDRSD3tq4FIrlghCySD0dz8ogyGY/whUNpvRJSP4fv8dJSsHtr/ad9j+I7vW/v7Qs1FuWANV+x4besIbbsrSLdTo8QaT391AAlE+qYIA64PTdVpFSFHVRiYG0xNtcpZ0h4iAjlE8KME9KGcmrFqVCEiePU8+r3a8Xk//y7DDw/yWPRNGv//mb5rp21oZ6o4XDItPw4clIyMyHwe/vO6erZbgUvy1fPN/Hx7lXCm5x0pWf8OVh9vJQmv0MRdps9Ye86CBtmqQ4tztkTZAN7JEJ080FXjcPXA24EBjv3fdcaL84nfoxupvo9CHsm7/xZh7obt/oeBH39Ee95/Nz/5PSmoj7tNX7B7107rHHmNbVm6xf58tbfzGpDjdR7D5TdQ3X+volF+4OVeLFlEg9vo8QRlupIhrtxoP9P6BF2hJoQWkzawe0WgH+sFad18OquKNkUe4+/Rg5OKcW+swK3F3etX/7RBiS9gDmHwV/W+JbupaZ4nta5XQM/KTl5wQy2mxHrmDUy0gndDut4fROK4y8UI3GmFwzQRCb/phxcDr4tnN7i3dx7L5+K3NkY2FJs+asmI5Yo9aPW0EQbj+H96/h1e2KaoaOn1dKeNXWkh//dqL3BuOawj4sHP8YCV2uXUqe02f2QqIfJ8ZBk+0+c10FbHkpOTPJViPNDJHKUEPkoBGUe4UPZu7k9+MkKoGhXT8fjnfGyDAW9JfT1FpoHahzaAatubYXe9vvD4aKGGS8abzbnG/nQ6Fu9Zss7qGTgybgWr54uG/XVqIdmNzH9qEYWHvc6WvY9pPOsqP1B3+fvyVi0dUOvw0NLzIDD6XlIRt70Xfy0rC9sGAqoh1PrzhVNV2jX/tBX3/cQJZj78yeAnjxosVu9B357MVkDrl3r780wRo6BYk7ht9TNUao3J7ekTETmfLeOKq+H1qoiInjY68AMIBkP3mT7eaOcP9jdDjWtq6ta6OIP1gsQsqvSnsTskyPvQpXLE3elnNYyuAWmhMcasEMnIAGhycZNoZPYNlfHOVyTak6YhaLu0gd+Sy+t5vrf1M04MoK/omU6bsXE6UV+uZwdAz5eiZOXT+EGQ2ysHpeCnYNS8aCsxMxrApRXubWaNvN568N2CbU94DqsyhgGtUnVYeRivVufvFxA8vwf9tEbMmBa9Tm3VtWSLQP4LmDF2lIodb00UU8x+r7cxTEvLfZLYEnFIACNasrv8H+EDV42kkGGQ1jhuTNxM8fkrEHpUj6kyOE6krXn4519owhx/Lhcj9u6VBBVQn/y3YrLe+5Hwm2IBHiJX2M1Nnjf4ILIUcC4lWKVzQAPboCAVVE5EcqdXcuDei5UwdQMJRwUgRSs4Yhrl9YVaeza7Aw5mqzBHKX4iR78wTj/D/ntrObUOFM2iXUhkCVLw/Kg1/iYFFGTvUYDiU8+g7lzDnwzBoDHUWyHm9npbQg/J0v/XCxa6d9OhxzUauGuHjIs2i3mBW6GExsYHogRjzyQ0oaMmvuQF56dtp6vnOIlFv407obOItVITIaiazqfm/CVBAekjBArUQxUTa/+pdvMf+ZeyTqy25ikYLxmAuVIgG72TWRjR3/7zXXdz76QAojzUJX/dw97P5+9jjUE4NO3RjsU5mX8vakqGf3Zd8BYNvguo62jJ31geZQtUdaz7w5j7YPT+BdF64oDox0fdmZ303I+Y4aHMnFbpRaHYoh5Nz2posAAIK4dbtj9+8RHTnx6bn9v2bVeSvqCeVglLabSlznm5p/bTm0yjylAL47qJTLtiRptGNYtqYNrSED3zMRN+tAzPOT6/T4xA0q1ptflaHj/TIRMzP9OBOyF8sIoY2H/B8L7WMa6KD/1TUlAh04kjZRizvMwmVJ1PZSo/vX+IOL0evzTP9Pi+eg5EeikM/x7FUNqGn6EtF/1gAhw91pw+HatBtep/2T9fJXH6Fi1KWsD0nt3tGfTHH1nSNmpGB1XaW15UhAJU1Cn2SF/HKaXiHc1vg8agLgqXgbNYylWmttgzTv30diATXFv/MYRYUGFs08tN4ZkrtFya5F9M05dmjoTmfHzEYIRS2ExyPrMQ6ugbPbPe4gkjQ+a2KfNjYj0/O8mabG+pvNe6raLygIUuTb29XsRuJ37KST7uQZ88X5p5Ay1ZXHJEfahDdSB213VCF2Ip3/jq2r30lRYd6Vv5uMaPCVMJKjItJc2aQDtLlxJT4uyZNvx7AYXpeiHcg3eLzqcvdufmVeW38+j7+UPh9mLY3p0CcDYecG1NvkOXBPmSUV1xnTKl23nPHn1TalByeB2vHmwhc/CGWF/nMnXgIXKFJf4BH8iW+3lnFY3nlM0prQyAQgZdvjz0Bha+3x979Kg1mPs9kEaAGgDWef84//b5xJh4WzHGRVNCZWcWMA7412/b+pbF+Y92f6hf1vr22h9GN19xn/wBAjvx9fP8P+8FWbuBjngoomV55wGeWIGSILzkyv17TrK3OcEM2CkSw8uQwZmAt4g1/MkufNARZu+E/PV4B1nH6u+GebWl0PPrnShWnnqbpGzlonVDiqTY0mKnlP/VLeYkRS02MSktdhGLrg02373HPgrSOdTDNH5856A01/HtMsmv/RyYD3F5ttC5CrZaQzS9wqYtA0PhHI796n1eudZxv/4BtCQo/f/rx2p+BUA2AMc2HnWeVqRso3Epa11ge1K1ZCcRie5oGI5FJMg4Ob7fO/mQ5HM55x8AmvrVw1OvKW4VsX2GOvfPMQzr636DHYZJZqlal/34yTlob7/wUnC6TOeM4+uhjM2iLTv3Awvtnp6Z0LyRChxe7PLFLXSI62rrF4pSFxA8ZuHKV+mv0cLvx7cndLDimD3xO60l4nlpCptp+8TeAY+a0r7iKf2aaQLss7yccEzIpUEpPdUrxVv5QJ5o3BAlvsMyjMtPz5TkdygUUqv6CSqIJ2rzNR096P/beltrz20Dtoa9/IoyShqZKuFdlTH1uIw9K9HLSOTypW3ITUBDrfjpSeahKQRn89Pq29WbscugUTKIfQhBTEhH/rfQP66v7/GBnXezoczhUq7D/oZqPZehENUC9MqGtzbMDN/rQnvvA0QCxnZtpGwPEcZqgXqP5icmW+fvUxw6QHgv7ULm1qj4OeZY7Ibr5zxa3+gUJF2sFjQl1GilxlX+ZdnxUa2u7ubO/I6fVW09qIv7rEsvbhD9APN+x29QJEiJ9J9XB6/L++veqJBz+DS0YtwW0b0mB6grYw3WyhPunHoKArBM2qQH0V5iSuMHeShAqFNGPajbzvoY9d9G1V9ld5k73QKc3nuNsfx0QDkWJ6XkWOgen2/akwLiV5PrtVCLSedy+7TWwmA28Mtq5aiFkfgyQWBAghOm82s1yPW7/7aGIFn8M9YTpfsvyk+ZweX1+dYYPQaE6elDhCNULTsJlms/zw6SQaCYiLaqdhJNvtNyVAeVDCcw6rk6tu4+I9qrXL49BQg2MJpQQ0v567nGpaOVYM6woEaw5Pdd9ZPe6mAMAdR+8LyrIXW6de6EyH4ihAWMP75Sql5CLTrFlVSxuKKwDhPtf373zuamxsCgS8UzWGPQ5V26A5xH9AVNuvy/nEkCSgrWgwUZ/zfh2oPnLUL6F78ulcEy2wx6qfUvRm1vBfFwy4Dt6t1VCOvSfa60ndyBQZj7qoJa89r+ujOSv80StlF1xaanriPAzOfoCkIadKGEwXd94+aki0DHJGjq3l6r+qdXmdFGR0dm2nL2i7897CPHS8BfvYZlhyru+EeHQ0kMPyfFZJc5+9FPBNhlh+2HwCIvVVjkoaJQNxUni12JADXKxD3zNcPE/9mpnwfTrgCWq/cerPe5DBckz/Umo3+q6UiFoN3m67n+nWGN4tuWvJq8q63HfFJBb4iqiDiMQ/bZifWdFlS7QJHJB0UUqNICohDOHV5XZ9db3PCnQeNDVuG99JNE5zfQ6+NTs4JpolSxdOQE6bni83qCtOnsPHSy60UcizYTJEBu6173eiZaBDn+ZCTbytAahiEL2fzE8vr0PnZXI7KR0N2N2mJlJx5tDKPHffqXTD0J+PbbQo/5gyoUoNsMH2Km9bP6zi+2Mk1WImNgrOrNYZvZhzbHJdAF/X410DBW8NCrR0i3+VgXDSgGeKMHIIz+Nb8+ih5SK2XaQDXgkw5Mj493D5tEtYzC0S4K8ZbwyHCol+f5R7goDikRHWi4uwnqe/nhW0PC/WP/N6LK/KA3oTCisr8f57Ufh2ZEAsAU5v3zt6vAXRd0efgsANf808tp9TF20BDbfNIb6kJHkzvq3GQVBC+EVpcUWjwDhVJkPMax0FrBDJmmj9pBM+QJSeNgUSASSeUeSmw9eMK7y/I/PUSNVVfqgUgJdVG1nodzYkOxtd5wL/Qp9fkP4fES/k5KXfcps/nkLQB9ZVDJjDMZeR96+NwjD6LdwTDWuMw9VOjxmVsI6TAA5BR5bUpp8RvcDlfAmPb8br5qZxP7ZJA0XJ/Sk1NX6Gq2pQIUk0fUmztF//iMfc4JXGP+7hGJMoCBEiyB6eg8PovSQwgqkmgWxXdv8hhbPrqXMZLMxsyZXTCEVDi2X6xCl/0YZdT9RFfP3jPF+uwLj6nPb0AeMjBQQQ+RNu/lJE5erR3LFqIjOYkwMWn9ED+jvJbDBJFWmUWGSpZUyKbJzo4ZF89aYO0ZvRHMts0GytQ4qWmqfxhSVlKxLYwNjOtdbtvvwOFcYvY2+taC1SE1Jc7+AGdEMUWnSeUJMbTANu5vrDBdGvpgtU4iKUsgd1mLFjQVZiQxzRtxcD2vAgm/vj2E11u2/3vpxpQWz6cltV/EDFzuOfLlD85oIUkQm5bqEsj7BOj+92biBV1XskAMRmJPTdRPiMDjOxNZfrTjHx9Dv9mKyzsOPY84tZGiYfD43pXoaRvwha5ddNMQWBUcDf/nNi/qTTvuA1s4vbG4In92atn9H+DojUyH99ZTEmTRva0mxQF1hqyPC/JTbyNQkqQFnUdkY0I6ShUtj7fvPIRZvRkXnFDPKmrbdXsuHDmvLZRCh3Lrl16dYIfAo5Qf/ttSOK+JQHvtlH+Qw70d1LcSXLeP9aNv1TB9CJilxUejmj/T+qdFNFeoRzvwW8FcrSK/RZNU0SRqEYI8Z4GfzFu9qgR1V5vc6ze32uwshbuyKjBgPCHiPyiT71Wibf7Thf5Bdbv2sRFWGutjxGTKkW8Z4XP+F9zmQ8ZXJ6kJXTGA87d55yBOlaF2KJWePkxZpmt1wOPx6jSiP9QGfkB5Tc91dQehe8Hcm1pSpfcHlWV+ZDGmBqdwnJNo7UTr0ACJ45ZhmIdXNfhxDbG+01T9TK83Flk3FsPXQdu90bSpmVVrUgMAkLjRddew9wCS//wxeUEHFzI3427uwVgGxfVQ+z0+NA45FJV15N6Si6dkykO2PyGFuidsbrSQcOvIrbgw7unth7Lr+tt1yICibs2yjxlrKTeVHnbeezroRxLbgySvaJ3Wbki718bKDGCi92neRvN5Ht9zor0iSY4BVWX0MdXwBfsGJRw9N3vML/Lz2+H09m5ByOgChoTJa6c+rqZzgYIkpwREYHKff2kDm7aYtrYjDL58IBqaR9Tg9/G9uahn+ZbeaGew8EYhQsbsRR7mcfktvOZX51/RB5ugzgBVH13beQ8wlP2lx1vHjPX8NM7Jc2jUO91PJOBiyEXpPzDW9x2AgaHWi6CQm3ZUkgYSQYFSDQhiYsxex+vk3mgtuoekSBZ8h/GvgdARKiDBiq5oKoSPTQ8Q0qnM5dupD7Skan7MsXpW8FT6s4f8WkAmKyEX0GE1O+a/7l+xva0NCDHl+fnCqQB3E2C41Eoz9I3qc3UfdSa9qh/HAxn4Nn1+gYK6WLbHL2DDnCaq/YCWZK3j1WkMg34RA/0eVyczke29CgwzdF8WgAaLp67ps7DVhkMrzovg/gQLSkSUcV3rzaE4axDeq5JR/V0CgUHH+y3k+1ukS6ji9LOQXBOC3RLXyZyoRWfSRTqIX217Bxsg2tbDhuHfEq8qfIe/NQK/qqSE+Kl7j/F4/aMN1tsQcYQdH+5FET1zpNGl4lt39z3NsH5Jb94zfr+AatsJoESHJyFEUekSisu0/ypfRDz+9ugcmy0h7a+1FQgqocZ8cl/+BRjh1/Xv8KDl9s7lGba5KPRCAghogxHqVlc4s89mIvqASz7fTxwtq4q8InndD6sr2Z71CNvvtUnNKKti7aIL5TbgegbdJNt8qyf1/r5i6Q7U9UJmBdQG87G9wLkS0sfasodyO7lSH2nvcUspGecygNUW/j5tfyMUkuygtBXoPK/KoN057W3M/qk/eQSJXYbhZ/vcS0/7+2pxAs6aSGt7t+HZfg0aVcFHqf5vCtsfSvdUeXpYCAjBTsjM+7L+bigaG18oZ0T2IAfHbF8dTw4RNNv4J8Fi6OInVnBIExJIYrdu7bK3QFjs21Mi2KWF6thoPMV3SDOgbzG5A1gdHCL4QZ0gxXJtTWMwfwJW9OOIvaQyqve/gSjX3P4wLR1dff3SN/VpnmvfOhfVKi/ESygh/NehtkC1COsMP9VyuwAyduCh89BE47/gYVmXMycVavJx5/NVSEESANx3Y7s5q6fJCO8YyNVIuJ6lAVnr8AYAnWGiISmJebvwHFqRVhzfyaBlevxp9zX70taesEiIUlYRxpPdlFT/pnraIsjWttZFwjABfq9YqgTVJ4SGUO/DkFQ+2TwDG2jbWxjg3w5T130dl67fCqQBY5gPuhD31iqJxF68rgFwKkIkHo7WbMgFeqTHwA+CWn5neiN1OhQJ0xB5he13CvDWiVE2XFBoHSj0JFHaWAQLBM8GfIX9flvDjToP/ez+6lNrN6kTfCWliBRd2J4E1BsMDE6LAMT4arrqtJX786QA3sLehmBLuMPzFQzdW8267WoZNWTT+/AL1Ie4T46UMf7kFonKrNu+/7utMn62xbw+PqffueC/Gu8Ux7lI1YOySkp8qIq02k8br+3Jco3v4bcX3iMqDPZVnvsPzz6iSar4eFBRBtprxYZ1fi/X0drd9HOvsP1KYosP0gV62P29p0zfa2+aQpagROO3eCorrV9tWvfcvqF8EN/2Z2/Kd43f+eKmBHdovaiqEpH2cyMg4nWGUiIETVOFgp3O64NjOT3weO9THj2F1jEEdZzvfjzeSo6EDmKl683cwXHT1zCtSKo5IPwpfzgGN2CDL4exLtNvq+9tEk97Wzw90qO7gaoi65AUNsr+ajiVjs977TRNoqXMAWqQ53qzQ8pYUjUBvA6mDpIIH0deWkw0aHlfaaC8jy8rDeMGo1zvu2n7Wy9SYhtB4UvW08QGruPx076e7b4LhXqjD+8zcCKRCMBkvl6cMoH34l0v+25MJNVo1v6WbxYh3vSkl6VY4/ZCB8LOKG/50VHm3xZCChMlgzoYPnLyzwlNp/FXAN664WDGCka9HhIN69AbJAimlonvgzafTvfpSbAxX/CkdjL08e//XULYD8a3/aRV8bVl3RLMF2N2Vu49MlU9NEs09ezPf764db0ohAg21dIl41JDkfhDlwj63UfnAFYcq6UQeI97+nlx6rlHYlxrVqjE4K7Ce4CwvVFzJYdcGv4v+rNDFVrqcZHGy/nu7XhjH9W2wKH2VyNKQK3YXiYtrr1vapo+fkpQtrGQRxkGfm/bKcLm7ecpoB7N/Uu1uajot6BD0SXBBhJLPO5t7FWF7IdGVEtLn0+2zh9z0sBGgULXtl5bBCQl8JlRUR7HtQGoY+p9PbBI29/dA2gYOa27DOp7P6mQPmKpExrPZ0Tuf5Ur7OjkYMcAfOebiDkHSTHK2VDbT2h2+qfVRtgxj/eKYvczt06M9C46JJcSWZGO/u1BUEkJjpNSOtV09TrSvUWk1n6ux0sNRXiMLUa/VxnMv3PLyw3ljOzjOzzfOvDRUfzhXGjsx0L7AngVDMguGZTa9CE8gt0tCgk3qa8pvsf95szBnSHaPTs6tMkJvdpfKenW+oCUYPmXIv4GO0yZFCvl26DohRYoBH8251Plm0GrZzTx89dQuKIAAvnH561K8Qz3p8zQ9zeuBCAuZISqGH3TQ/MHP/Jfr04yPb/3YlILNS3kt/3nD9dvT4e9vjAzCbl09lVn6EV/aLNUccARN3y8ZF0yyXg/Sn/5x+JGHWeIuHuYktRTfr20C8pGHwmM8LYl1w4D3w9bjvP/+9o/S7ZCBa0v6ExKqe0oS20g29DqbA9AOBH6fimCS9dGW33+znnv4K+t/vcjndoW9h5ebQlEvTznjkryusKKE/HPeRO0kiYpWlSn2dpxUN9Gz5y0HG7ixqHj0l/koLL935Cr9yne1ApB41HjULfn3ofKtfYq/hBejpXNF8jJYqZSbdzFeIh267edGYA1bcz0Fm4phruG6W/f6FMjC967sIhTdPCZ9Pzz+bmNRzrh0ETT/qNkl6vNQWmWvjjwRcJhoEQQwBGQ9BXBTVlqMO7WRJgs8I9UoY2OpnDcaVFf+NV4JsSytn0ugBcX0hbDju0FaXh+SAhey51wz/j4WdrgrpkInFstzDLmgq1B0lsy2OrdU+UUZv9qa0DcjvFXC3PEEEGJEPrgQsn37CzBk5ZoWVC7t4IGwp7tSpk+g2s8OBfTb9ga7urHyvnAA/7knspdYQcq7fZoink4Pxzy6+rlMIDta+KdMeqzs2Gbkl/rJ5UaqmaP8d8hy+E2kGRd+3irIKGnXYeX6aHhFuR0T5K85WLKG5vizDs4ICYBd2hnVfmhvlCG+EAI9Up0mbuJungSdUaZyOiF75dhXXtrh+tdyJBqyiVer+1ffT3zGVlr2k5LwRdeNXkUAejPdDS0ZF9wvGjFZCbQSBTa4/xwWfrWVc7OBOBVCKqrBwVyhinL8Goxy4UImCubqtcERkckwlrWYio6mQnSDOybJgNgOmXpFVSdOOtw/ZZgagfYT+u/YelMc8xakuhWfcDxRfMdqlfVoD+8axelI3qSgQeHIRUQDvHhj/sF5K1fot9eiK7IFFKPdI2TObYz2liU1cftsTSoaj9P1XVuv0d9e7oq6V4Xy0grHLCsTVZ1IEjGq52VwMev62/9JSinG2i9R28TNjX8/NHYKDzNTUobPinch+SFyP56tJA2Xp1sI2PjViPcqToA1OB7oKJjmTlPnJTy+mWJ+4TFmcK75vsYpuJCFIQKVdHbIPRIzxPen5XeOcceA5n67GjFw5+7C8mhwUis+Vz3v7w6WXFN+hK4Qqrzt3dMwA+at/mgepXlRz4zCzJkNdTPPZ97EyKD3LmAjtkKrSh03KJTj4qrhVxMv0qRqBnuBqdihsGoMraxFDMoE18BRjnXa1WavXbhI0SdcZ2uPuftlqo1oqwI/7GjD78r/88f02XIpHV2a2XZGj692CIZUSWsIiptktcTMhrtve8q+H4ONXW4Lp1T6tnoNVuUWmjSJFpbs/zr5+vy7fsuzKVZQChpATKExE6Qi/0rJrpPEvUKEMO5X+5OzBnP/Vg4JdDndfmxjnWMx9kqw+fghZfxfSr2tQX46ul+AFlDAVKGEHHN4tJW577+ttWx/YQ26UMBER91bhgPhIo/pfDY6nVgp6U7YBQma2iq7FbBMX4fVY/Elj42QsNO619iyAiJHCqBrmnrSjN8HiscFBhtJPy+5N9PTir5e1cAKNeRFI/4wMPq9AjFFSMR1qtR4MCxXg+0KbwFRS3ouC5mPP/JkWret/xNuWE0gNNDi374BUjix//TFndLyO7WR8VGmnNvWn8p6UHFKf72BCs7U+vURb9QUQSNvWm7g1vrpmB7ewFjSB6D//bfOu91b9v78aW2VGgy3gJ3Edc7ubYOqIHAgdOVpl/qK4qbziQV6D6G10n61iuP9QPNCAskdy0/XNHzAGAIQan96amHP1GSrHEdAg+KW8BlTxboaJjvXbULWhKj51zn5RK7EBDSBgYt2yuS3iqw3Q9Qd8r4JSwGFHTgA+Z4uSWfqHy292ehA6mr17JACN3f8E3TVVJa+6c1iImp1tz7V8+oprHhPT49nqZ58DdTfVBDC5eQJiLOP0uuORMvfY/j5PyRItfhBzBvSZLVxKz43afqOkyMMLXxfP3s44vem9+G4UtipmnjNMBzdT5Dz2z//8WnmPM+mJQ/F4JO1nfM6jhqOLMAPR/V9d+8lHXLad9bxzz66JRUuTyN7xv99taH1vmgHZWZqBsW0u9gSd/opT9CGiqrI2TZql72U0l1NLTS790GvkdGmNuRjxZDrX5BDpUskgmN1mt78F0sYEsrrqm4D5ktLK6tFVym0xczHtLAGDIwaR28teKEsHCSQY8OhkKn54tiN6zaX7bb+eL4MhGPf1SMglaCr423SOA9eyHJ7ys3c/weM2F9xEWBTUlrv7EFyt7kAnoJoOOk49bxJ3l+KsNDa4/L27BJ1M/aqjvMvmXv1s7c881HQzOBuP70kJWU0KcdZ9v7jNhtFLesftM9h1d+wMA/r6m3DMLyixvmq+4fnV+68yKrVTKKKJnzB6bBw1G33pggteO39xL1HJcWpJbuhUbS7FblHZLTqPv2bwdSVpwAIXS67hg6+afLqTE3i6gpvQhRZWmaFwVzpZ/yK7jmH8M7X0ipgz4cCJkwsoI9GHHv2T7oaRd7XvuqSRQNNQ180/jzD52E8A92NenZLSEJA9zL3IFw2wd0mXFIoAZJU5JGHOqn56nUl2yTUJC0Iix4/mSafqpH4WG51ifQP1XBzUQR6/IG3Wk5dQXyq5XqOwJTZRTbvHcM3y2Dk2SDUr83Ip/7nwHpIhTo3qNdSXOa/c6sfHxpf43+/PukPUIVTdaTaAKVKWj9WHmnHjpJYfRo+SXu6ix+3b/T2nEVP9ELYMAYyXetrdbPhDxHfX6BlUpECR3+/WzrsHxaJtfY9FiLMx/90BJDO4jJ7n3QeEvvre0h1CDSwOq3vtsvpYPpk0gaZsDaPu0b+/3lT2CcaLTQ/T5EQKfphJc7dkKD+rpFh9IFOr0gM3/86KU5oUsZ8Z152X/Jl9e5zN+lDze55TWwYjt6G8DzzUuib6FAIPvwhQNMorxgMVXWy2RcEK/6wYFiM2VK6TWez88189o9w02AjnXQvSHMBymllgHkyBDwU/pbiygRmPxcQfDvCxjQosolRuCj87uqYtv/VaFpYeiY/Rr0CofOdD4ZFx5yNI0QAt62Itu+JuuoofG8Ahw/PbDVrurj+HQIeP350LgwWeGA7f3xZWfURCfeVVw2Sds+Y0tU1TYHiAFvOb+eCfIzLQL0MUkdIlz9bK+RgGv6rpOTmjHnrQQ+Sm5ELK1etopyn3P+OTQWoBPWX8DEzf5pOVIFE6xtLunSb++4Bh8bqFIYf4BVlFFsbpYj+Mdt+Gf66dxCDq/XU4isWCpU7bNAvl5PvCM4xqzuQIfmqP2ea+fGxoN3hnst/fJ573ulQ5t3LJjNGf5IZc716PVRd/vguZtEx+e3HRqZfn6wLCuyZyCvq/V3UROE79PFq+UWansmRvf0Wq95vRrUSoAWXdqo936REh0vSFQ9X0zRvZovE0+qYx073zr8pZ7DQQ3iei3P88sR+P2ERpoWzaUylG3BHP30vT96fzr1/u3ZaYkV0X3uVfB+MkcpxIlRrOHa8wASZ6lt23z80ivU8fFC9D1UYNqHyEAf6ZNKiA/jvytJNzJKfo0BeL57VO4XKhh2O9DKyj/vt7IoOUOqsO5o3rsKHUZ7Pk4deR/27ASxjvo5vgBA0pA3ndIT4QVJzBYOSDHLXstZ4YfL/RLNt+2FMBlLEe8MXY9I/F5EjRvoK5jMasVY/neLrgxNDRJvoylIyZvokt4nBj41nemyjOKRuIK/cP9e9mjlNUJVUa1+7DTdfch1XRkzV+bKAuEc4UzQE8Gd4A7rxD9wSPy6iCzA3kyqoqOf297GKOtYTM1N+UQy0+VX/ajGbhtxn35xaefipFF0P+v9RIaCWHrxSlsZg05XHx+G/P5c3F3JKlq1QbqsL14xOhAugUigtrwtnb1hbEXEHzP7coSm25plUMYtrs/1NXwlFGL+lcsOrRRSyLhg6e/wV22cmh2HQ/fbs5Jdw+MCB60rpF7GPupTrlZodm8Igp1jJE0yDenNrWyuPTS7H23Ng6c3W5RsU90x/3hqYkpvhsCc0L0V6W/jfQR9PhKzz/Ef3DxuzIauzbKQ5dOHe4JfafA5ww462/wExFml9gOn3ovcSgcBqhAJoBZrOvo4Us2aIY0BmZdjozLUeq+O0JBqsSO2J0ReFSZNaGIbbIBn+1eOWs6rZQ0VFepSlyl9J53RsRDlHK1AcWV/t3An2L7UaT4PtizqGPejxWs1Br+GrzsyRXIipx48Ju5CrnHruO1vR0MVVneeXhen0brWze0CptCoUcV1YQ4864uf4gcRWlkzAuWKTHoMCy5FOPntsweAJnLl02Ot/o2RMZL49rnoHTAaVKIryjQASrWIqoqF3fPce3S/AmRGwqtUoQhSgZntz0BDIFFZk1hPBai5KSlgKrsarPgxUbix/SeyDHi23FOVYbVe7Pql1HUPU9wjw64SLJeE1OF/rgSO3Cgw8lkUGB+g2JZ3vd7ab2UiyP6vTnHpdOmIHhZ7cYuKXWmGuVFV6/5CNO9XB8nQudl0WBSlPn/JZjf5GYS4xwbIJ5KKFNDeo1Zpsequ/YNwuCrK6s9EsNdOZ4E4c23qImUssquRVveRDbgZhAjLkmxL/5qVuY7fFpraVgeaq9nltfSRfwmJAfNJ7+mfnnC9P56gBTIzPqbrVFsPjfrAYZRyd6xD/E9P9Gi3+lISK235Gj//wAs3fqwH5QGIOIGe+1aevZAyTWAY73aIZeNXa5JXps1gMF0xby/7XrdWiV88EE7lnqo3AeplnvvruUkPwW/Yf++D3K/P+ApQWSTNfcDTW8g94JjkB4JtzlJYYEzCzSQSzIKZqQZWxROJRET32UxSWEACz5/jErkfiGbK2/8iphP0wok2IZL4oqGIg0FZEA5yLR8o7qbbdliVtuQr/5jwpF/B+TNzuN8fQ7hvZNgJQngQDSiWJxmGPHqjduTYUWek3YI1GaaOaFJF4xfXYz7JtHtjyWxiDV0PRKDjl2747F8CRTFBx7U9JLHIJ8SsSKkdvQnXv9aDhDKqP8h0NGSUBxT7LCc1u6wmNWrJ1D88EVboMx/rOOhusaDye06gtQrVc1BefUN5turTCrKE91Ye/yJ9IEYnnBTOR/BfqE+xTjNGzQWFK70rh/GAMcP+hq+tCOjuwAaRTXufJGFSHKtkr8dOUpeb3vrTRQ2932xxSWmDYYHTeG+B9V8AlEDtt7+fx69rj0srifexpKcoHbTNe2POkcLcliTfrc6w1VPJAUTczTiq8JUjqAP+Uavo98Lo7enQvIsA3Qf+EXWaEnzSTf9DZ8OIFqRkCU6JjOoQ7VeL9JqyReoceWcmKMFicx+5VSqP69xE3Dw0X+oOQSg9cNqesiSn/dV5Sw2+qgb+tur5Xt3ODCtodT5BEgntGdGMQ0WfDDGrqlbZBgQZHW34DTxX1oruhVDR+4lTJagqhBuEh6Khc0kphrm1W0RO5vYCaShtVBUBvK+i2TLEhq6hIVUNQTrAGGVDGCgoQOBdxL9nR+CXe3+8B1/c+Ukz1Rz6Z9VoomGOBFGGe/k9zO2LmTorQLEYP25S5W1wtMw0778wt0fUxrbhkjWsdp88re8rNdTQRPj6e3tRyqnQ7JkgdzRI2uIsg22jJsAejMgvo+A7LgYlJSAX3v5SMg1NOl6P4Z/H/EWZEqVadJ+2Wk/YUstGH/9+SS0/avDA7Ti4V1ds99uMw24V3Z86SzTeizkbIE5pXW+/xGsAsGn8CsTQ+HtQHgP10iBhwJ8Z/6z8ePzNX7pSFu2T4NUoMKpSGekS2q6FkEdtOfxQux1cMMJtfhBMuOiQjCRxd3dNGXarPuqlHmqF86HAdGIgRGp9QbxiVCcdRk1Ovqc+pY9fDGOm56fHU+YxW0kVolxdk/7IRH2UgU5elevYkyPg5YaHhDuhWDE/f4cXb768GI6vkbZd/CJORfcJ/qBNQlMumJRyI4pqobVVYlsmhrVP1+u4WRQHBZIjseH37V7b9HMQes/rL+8TWSiao1qaxje9rnT8ZGvdzMEfnj3rMmaxzOZWJILCPCsuyQ1b1cPe+zrUu/i4YSci0orczXAZYQuyoTor5w+Ft22ASg8HsKqk7/b86fpuHUM+WqzTPPCZHPiIab+N+mq+QBH/T+Pzd3mRC4K7D8dMZsF6hAO8OgXSTtF9X15N5JW0ZoeRj07u1E6diF71qkDxKYZPD9Uhht7JIoNAs2P6NWMjVV/5F3ERfkPZ3PUy/YU2nz4enJF5qNXXtG4veA5Vx5/UOcx3TZMKbWlmPua14aEcgozPw1nfwptjnr+c4qAik8Fapic5OneqtqS0ncfcRyESVk1PayP7e6zwoM0uBnH56bBKQslVfPm8V3d+KLhI1zTpvh6cBGxmOC0vjkHV6Ul8ZpydCOzFYo1Yf7RIdm+B5WPGFRgLeUfRQIvLBKkpUNEyV3/54eBxoFroYcoBf6L4mqBl7mj5aq0ilSEGm7ZIyN7bKK5xC670V5HhCffOJ2JMlHo7zZnGGI/vF17Mxp3YdK1Z/jiVntEnjMZDUfSRyiLsb+qRZI5kYADSg9gmlB+anzOcJLTdqYNQMbgSTAGRWi1X0zh5AN1v8HRM59Hg594iZc9R+ym/No8/GLaNRt3dq+6uGWTStOgDDvlx1EbMPyCE+qUOejWeATFFZt6OmdYmEU23PhNZ6euIw3LfTVH7puF9pr+9785NGepeU8ZUvNJ+ZnNF4Ouh8dNeLf0a0NxJyo2aU4/9VC/MEr8fSOBLw/GbiyJnurqix69E3+erkH6uZfhBmD2Er0fU7t/EPOxj1uCEJODdVpYwW29CBypEhHvcO333gKljVhenZA3Mj+4mJDrX0Xtm2toTrGUj8v0Ax0zA9sEt6VX6jxj6jMhHfbfuqFJdt70AoVFJvZHp86q0Q5//3sx1aNGWNR7Dn2dfUPY9OK3EKIyeyTxlndEJ0tXCvfS+3A557Bymb6io9W9S45blckfBtLKuIvSGrEKhe2OdCnH57X10VqvtNYPKULW0GHpRwON+8vM6TnBF7Rc2t8JacggciNhut7DhdAbzeLegI/x+ItkATNz97d59UCcQnnFs73mL0ZCOqhNAPUdNtH0DJ+O/4Q0vpCF4xdHoevxogh557YkUEk9dqMBmBF9zk3IGrTpzVH8GBaZIx6u9NvxavDwAS+yxX+Cx6kyF0ryRnDzJODndProfb9Fz41ZUK4lgFO073JIQrHfSi/nEl73fdukl8xIErY2jxzmEDtcYvt54Ad/ML+YyAiN5CS5R+mtu4/qfUlyuzpq5wcc9tL8aOuYWjd54vm3yNrvMmkBRWQ++WNBhhmyfikpyFLTa36i/7UnwLuMfmY2a6Mbx/CYq+RSxylU987J5pRLVmzJaNpm/IBVxGOv1+fhOz99+4FcHS57Pn1UFq8S4RfuQXH+BeTqbrBN4tEzO3AuPV/NVKwSQ72uqNDAyN0bK9nch9l/6KU75Qonfmw9vR/nfCTWNDDEoGrIqalXf5xdR+b2Oprzv830dqzIskVGE50vQKBA12OofcTrSz9lDudkkFQL6tuJOP7leax641fgp+GeWibpM3D/xzPR8j38Glaa9V5p4wInGWpUxWSwHuXHvsiuxntRQjR0VeS3HAozybCOINMqWdwgAVZk1ORPyfkNI8dGawiJx2Eda/16P9p0sYhsiMTYOb/FKk0MNPvQA4tO9LENtMor3X1OiWta9gUysToUpHiD7Y2oAS9l3Kt/dftvtuXeeGdq0mo5mLnK0nZNVWbQRwwzLEfRj7MVMtHdvfu0Bh+l+KcCNEOt77iFsesWFaTKk9qVC2yECKGrmFkpK79FrsAS8t+PcJ7KU1HKIB+By93F4y2KVAy01nXZHNwxhHpqE4FnNSw+396K+mYNNYif0eTz+II/17IWkorguvKs7KgwFDHzGx8+nza1gbfgjng86NWfkWmV94kam3o3Vx9+3POBXl3iOLpNEqPgDwBTpG5bWH8b5PA2jo1CIrmQJGx9sRJFSdTW+H0X+k29xyVrN1XWkO7ox4t5if7yLhDoWB0G/FRJX0hFbgr7HVyiPzuT0epSjh0f1WO/LH68WZMXSJeJ1Oauj2W7luULwni+Jo5/r395zu30REXg8xmP9wltSEqWDbj8EjxfOb+6IMDf1b95pIlq4PQpCK/qZCbaJKckTPKrL6CsWEwVQbX7yyA9RIu9eftZbCjt10IJpzNTyeVIqXNsU4fDplMYFoSRfZd4eLRfZZgcyE+bs3L120SNsgjZ9e4i1lVz0t2s7oHVRytCd6IKlkf78Jy8HDwfeW7DWZQ7HetSFP2oZ5eVeVKYoCeF1QLKLzyOQak790xszP/SHovNP2VVOEK33USiu+1Ns5pB2fvVhQMyU7k8y3YqBx/0ZJO/6oSA6UZC5z63L6NHnRo/QGqf9S0HhMvkij9UcvCKph8+zTaj/87zqeiWum3oZWy9uQzL+c3R6cd4hXKvNoy5Lk3fCbXp9apVftaxxDbjzJWsrDlIs1MOrh9mgVWumflAqaeyhRy7bImrh62gh7Yo90yPU5ue/sH3pmxbH8O30/Bdi/YVXVMHz37WDHYR+lfeZoYjrED1HKEdfIRqm4rm98rXyTb5M8PgyBC5jXdb7xCVQmu9XvTzQpznABHEn0AiweKfnr3u3yH/ejTb7s3SE59KciFz1dr4po8DBYME2RAzveHxe08/eGc1le1OvmFYd/roI7BEIGdjyge8Ubu84+LRaf6FRXFKq23Bi2dkohQmklZULdx4Ymr41itx/tocQddiWQQCdimXi7x/tQlNKKJdci43F+BVRNf7HvPGZ0oY5NYaGfbweR08Van6tS1FLiGKZe9Op+AzXcfbimPsZSmXjll10m8lWLvoFNVTavQahdZEb9KFKt+s83pqjwsZGc2ecMj5bx6x7GYYZ9NK2sXNQLgRihh5SWP6h6drh8/MCFz26YclVzc/ju3/MpY2EdTw3MCgk9xM6v1RKTzvsXN3aIzKxN65a+uL8U8f8X98UX6lESQY8B7vP7LbFH2/PPXNP0JoVuOqT0P396iB1NGKgj8ffzrGRU5J+GO8tLBX59vgLEig+t/ZNEe41KOP04n/WPkG699+o1emPPlUom+Qod9q87dH+BUdl/l5TFGy0Wr1pfwDtLp1jUWTam6/oPQKyYQqaxYzf8/9904KAUudt4LcG3nK1SJozy9Qx0M63c57BPRi8Oicifu+PmCgkLEb19VwyZbcsEijGBoTGehYEereBnTrvqAkh8UtrowUluzzfPGVr2JK0YoYk/htY3MeSBBUVXO+1V3jtSBHDcZotRvas/Hi/V6WokSzwPOPbkghR8FjfD63BN+XrgC1j2jYacFWhQJdG2vSjyli/6cgMn0t1Lhu9KmpBpCBaUbk3RVvsz4m4KSQ9e+wUP7UiNV/Psj+8JbBVtuv5Ms1Vfi+NUuCBIl7sQzGkCdld0xse+Uaf6qAIaDlaJd3T62xgs6v4xwaUmpP09OEnj/n933aukfUahK95PK5lxtUtDpBxjKQeFgLDQvfvtNzjb9tUevl1nNKZXDr+Kwzc5WoaEPMaww3enUz2XUrEtKNNArhHSdGXYleiDOvvu9e5aWeBhpeGsrFwTEurHgr++UuLLRKSzk8ZZBL2PrkmJk2emtmpueNnWvtWkGBxAXGSr9rkBCIltlWTLoOhuK3VforDxFtKbimsNzNUq2I+bf+m6HtQUdlViK3LmgOlg6U7t/JJCaihV6dz1J6unQ5SqweuMUQLXls2SJCyOjGReq93CD8ggzJaJn28qFISWlOr5KCRL9UdvytRkSiAIZnpjlDDesm6D59Qw5CCjW97Xt6ifSnueNSOLhTLR/wVOT3cX2OzewWaYpDSzKl05ACJOdBKmEf3cU7/0C9G2+7JMayvWyAlUEG/Gw3nTX318WODoL0wXyfP5Nnvp0MHVCdRfq0vPaaW199O+JtWZ9c0k051E3kZpP+xcVnUaE/quCqYmCF+yuVeZ3lcwpBZCCro8cytr+7TOn877kADVMBq5psJWnk3d1le28njzNP1ymyDQUkTvk484DRlKipkBUngXsouuoD2ecg2MeSok0qKvsd8NnBDftxvyKV8Zj/aOifPKOHD482R9OBEK44Hsdq+THswqdSkVG8Go+j6ZJUY59VS2fjTYkYu5lqXXvobNqSDI39RUAlJznM56QDtTr6ZAtyofPwoO92hF0opWKVLhjcUTtkLrRD0AINmdTVgnxpAnbXgFqt0CR1KXCN91h1FkwhUPk4zThDOvNBGzz+B4vM4kzmrxkhnU8x9YqO+H/v0QNhFaP01gTc3NPYh/8NfsxHkHuCCN7pZObkQW+ivUsWydPjiPuvnF5c9xnr+k2sxJJ2ILHooqEf67weuNrOg56RMK7zoqU5bSOQOmH/JwlYaI797reTTi1raRNHcLXZMa4vWGmt1S2kmKsDycvyuKwWGAmpnUVOKwjYrp2V6MdZR1X/Pg4HNjlPPn16v4qdH3ml+4VyI7j89Z08CBoXj8vZFN6J180U6G6NP/3uvNaUDSKwgHevMBnvey17T+KGr22xXoC1igKPefNxL8MjUxBXdQVUIC6NXn2My+fr+55je07q+/bioK63TPKX3P4R5tW2ri43bZVyeMpKRjmzXdNBMTnrHWMfTUkQfN2lBhG9ecmx+Wg2AXIpcK22LggQfymd50ksjhaC6NUm7HQB071wKCkB4MjtsUK/V13auFHCfkateO8gHCFpF0zN+sx1p0oVKFX55BLwGANyUwaga+jUmt1cnzV+ZzeDFgy29uV/wowDiRw7tai0j6FiBpNv1H80kADvIP3sCUvl1rlsvTGfYS+BFvy6SaFTjJtYn3/jLAzoZ/8ctdJlqaX8jwXr2CSmtZ3xh4H7NHxNWOo8WzMW3Q3g9c3s8TqPxpa/rhjhQCtp0rLR1pOfz93iryQYjRAGje1bM4hXVJB5+cfOng9Lr9uuX+bt6cblfw6k6fUE7gvuCsLIzWrB4G2a0nw3v0bqG9QzRMAiBuUx/W14gaLX+PL5fQEcTAwMZUh3Ta95OYpHZIlAgmX579FKh/yBKsHsu3t8BHNrdOT/JgzZMBDQJvZLlhhKrXD+RVt2zk/ZboutFBpVsgRXRvc/IgZQ7PBRDJR3WyIfB32svQXiGPCqWhgEz92YukI+Zqfx1ANKqu0UFMeF983MdndNUqIW4e3QMbRtYjZY8x8fP9mv0Yk/sfDKueuBC1IVx+qeOWtj2m373ywSHPlFZQKAhIzv8LsMyvhCAhk/nusfXLFvRbc1baG6y6iCo3FSM7kskhmN04KYgzUQPi4s7PYC06xkLuyGCqM5oJ069F59mHlxO611y3rIrw5XU04+H2xPlsVZCrQ9N1E1h/DrdguiD6klZWskoAIwaiyZvJRfG9WYkpRZIz9t87S1O574U2IdwyOKRLChR/u+tjRJjHucXtFZCSq3PbV2/AMb1AFbGnXSHFSQRw9V7vsCY9IdZ+x+46rJBEFDolAy+AYdSzBdszzernnZ9fkfUB4WBeMF/PF/vb5/MDZfeR+4NwPQ++uUDkXRch8moSLABgcytTWV/IvsRiGF1mg4/txhr+O1mUls9I5dcOHuFtRbZtzfQyAcTb6STdsHBT2mIY4GmCbMo5mV0KoyeFmu2W463oJESEsKZH3DLspK4kDz8q22/ULKPrQIy4/KrQMRyo5T7bCYd0G4s9exWfNjv+jOiPLlt7ULX5iWARXWtHZNtbqnugLHBYiG6/uiTNTOeMDflHb8puX2Zvj0yoZIyXx9IzmSpqBbF2QkShrJ4j6dKQWX4tIq9AYEvp25b1X5cFN4gihL6dvft9mnpfspAneHT2TR6pF90IcV7G4JoPECszcv6d1bmWlN5YTfFnyREBPP5WF982zyv5xtSshGu1dbkIZ7wdoBq6jBrcNe6aIJLVaQQUpPLmd7p5Mi9Rgf007DzqQEXASEBjN9023jcFRQ21yNBC7aXrXP49KLXGnEaicChHTtyuc2DPiNClMx5vC4QTAz0pkNuKakGDjqRZ6AnmJeZNuf0qJqWju9721eI1zPJYMgvP/+3o41X2DAt9Apt4u4qqQ2zzgSuyulc2zaXQVD/fOQ2Og5rdm2/k05tqbVOGFr7gdpSslwEzyqc/7ulmlg3JK3BNysJTJXBaC17eueefu4gHyjcr4uMsvIfTWIMv6AvtSLm03LuP9dwfKl8imcVQlb1t9smR2DsfnzNKWaim3HmMv6VoN5+06telM35eJ0sRztaj+HoYwBTHvnE+BkW0Cmo4/hS+60ad7yV/yRtOm10UPOMyXOVnv163cfZ/FJQEP3Hm/yGdv3l5dxGirtCSwwKWYQ3NdyS6H82CTAnCjpBpkj8OJFBR8SSQE6EzrmovMYUfxu6+7nNPcCUcVSj95KvqvuAwePz+H6ajaqR51/XbVmnlzOlQ/IVLVxt27uOgD0uT/DQ6Seh4R8bBSmr1xd48budNrvVp3bLYqq8kTTXXLQU0a+Dt9c2fBOM+mxgr5fxG0mevepeTla8jRl57dKRd6ov/W9awYU80W1X+43rwccy0EnMurWVDymtfSnP4Tucc6TceTjTVeJtEoUMfQyMEmyMvQ0fyKkDygJWyDY4N+7zL9qjqePvWXmlSMi5L0G/9VkU+EU3pGgnzDreL4sWxk3Tjtf3nFvFMiJREEzo+aIWXX9+fKtP4RCz83+U/nr1WqpWquHdFPjB5uGF6jh/UyKsxrvbUdGbY8VA6cq008g3mq+/iu3rNNp2T9+9e/N64qaOR8s2TG77blYQHI6ktEYAWOHw+elYNO08drYwgT0w3+PKlvuvzlZQwPlWyIDyAwhIidS6IoJMle5j3S42M1733xcSzLMeLz6bfsw7TlIp8PqifVzTSHFIqKQK0jy9HteLFu8Yw12wFL167+lE8AXvP/eBTIXQ4pqqNILlv7OQLgEw6whcBu1dpHRnasOar/vIf6qBg7ibDxCmJB6fzvxk2upy0VbNRF2LFHh23t61nYsqxlfXx7/tIqlx33DBTnzNKqVzHuP+Qul62G8OL0Ro/AUhXKHwwGyHZYImc+5AepSAhhpRvrl90ZavzXR6U2itKPQSom+f2T+/O0WeWMpKE6Yu/Hgty/jTAKf5H3Vmen73uckVAbenk2eidN/fCftO2ik8BQjlQDpCI3XVmp653zJO36iL3nT/XMc/ycvsVk8r4v/7QVVlO4qlCltyovtBUvoXnQGXeZpeUAcWW0g3o853gKs3NupdDn36N3UIhvcgdHyn6gS1ZTN128d7nvN5gDO//h2+vxzF9z91GTHyWcuFseWzk4N9VOzC8GX/QwGOLfeXMEjmg8ELvwSELoEm3PByFVqso3oo/1A2htJxk1Ovo9qDQz06mZuE0E29ohw6AIMUyX/vbncXc4U5LjsdPVF0YZ6OjH5nQI6h/i9BZlYUBGy9y04stZl6T4iSIe8vmWCe9MWsUHo8GWq4JI2qFFsF6pxah3cUST8HEnukKVmg2u41rJeZtcplnhT9byT2oV3N/l709QtKRgNUghJJNMy9VbtzHr16uNWq1z72WglgRQb3yOP48aMEc0hEZFydNXlp+se3Q7ZXokeI/eNeDhcczbls/zA6LqWt184xPjlOrdeKVh9hCEv9wtzTpb4oMO388mqsuSDIjCkIWx7q2ROVBmzY/vL8P5NK6x0eW4s+00mj6milQEx2NMkkZxXYulLX/D7P3oYENVYIjbAuhPttzTIZvnP/eHw7YmPDFtUE6PcgnTonwuRlaxUALNDNajhapjlxtyCYMP+sOs59ZSy1MboRsksYcONZA+wbACG7qKbPD3LUsFJhiqZTyT3+j6D++PLRu96YglYGOlzbk2XwSKHoWi2uudIA+ar7WUdyFCvSkahJhEh8uFzQCJ6empcwujqszoghQNri+aJakv1axw9eF/wwGrKhqW/TjwZz5/DsXN+S1E4YNaYjWmG5WC/VMGyfeboOxLovbw2sgvd3uwW/BgQ4p3bsqPX24snq1+PrEqEz/aGgt3XbOyg6jf9uC/koEIxLUBXA1cxrb3AQIRKNAlH2epH3cg3gCM71Ukxy57Cnmaj2iymbiVbaoV3DW2Bgyp5fkYsW8eGGtLfYck2N8fH6/bRVRdUpcqUt6lpf3//3LqNXVlPpw+Y248EHpe0KCnp6nw3/6KSCEGcIlX8kIPEQ5F6dHOhaeq1FZci692nXz+0XtD57xFPd6SejUFZtISQBgoPI7+7Tak8y4VkXI3F6jY8hGseQxkNuIJjPI7aX42RotXMP9VWF3brfLY+5WDF+9cEKQqM9l4TRLakUlF/b+9T1VHhPmE7XH4BxREok5TF+fkQJJ5J4+1dZ9VqODHY/0oqky89jmy5aJP7EFVMffq9UwWnHexnC4On5Mi0jR/3YmFgdIHH8FeeIWILK1OOk3z49vCNEGi0X0DuJCQ1VwVMZN0ngG4qWaexs9WJ0hmve7Ym1JtjBAJN3X9i0EgX53bW4wNrjUt1CsKmnxzBffwH167v9gC1EdxAVlZYKYwHVjAp9vhiMdvR7E03mv/XOdu+UNDOy4b1vcsrVNED6vRVIyN/ewIOpurbve123H0RACyyksJbIKo33U2n3Nl2vxlXD4lEf3E6hNUx8G2z01hCUPW8DzfyGo1vvz25/O8fZKu1Ep/BD9bOfBJgZJj7y3oev+0n9eplkQjstf61tffWsHnWpDn1RiZJXLmZWn03k+sEWtUthLztpsRYCEyfm99KK6qKXUZFlbts57wxf/8XF1FobtoYiOLOeVCB0JlXTwWe3TVZX7X2I2Ph4q+SeA8cBKqNVg8QgGaLqOjlWpA1jvVPygv+CbKbXqVXccf1kEWsRCWt5e4WlwWWYJE4ioyZbQdUuOkopgjes29NWva7v8YEx9yZ/K1IyFCT4xeR+/ubR+ar6CWID3ngxv36Zeh9i0U9Kn8HpkIK+pkVrQxg9JCPiR0XSRycZC9O+G4NrNkVUyIh0BMbsW7IiPZ5/SyJ+9z31RFu0jGMghJQ7PbdPrYzpeqhLIreVHPiVbFqWh6K4nr+STQrtP6zr77MXnfLVU81MiUEyHWYy9agf6NAzLJYynPNnwZ0MV486+WpvWdA7rxaeBFCIOJw2yf02KEx961sD/x8ISNU/mCgAJIzp72CRRnSrVLMycDtu2+9TIDl/kzvlkfHVCPdigbv+/LZzLwV0RAwl+hIiiveRjSgA8PLmrY/wtfctotYQLvELfPIkmUHXUPp0gV/Qwscv8+JrONAgpacP/lJrw/X7MWfRTGbSfrLTu2g1P5Hki4qLWGFK8CJogyrC5Lqf+1SO2idm0m2D4sSIK5tgVr0n+j4//9YL1778uXVDG4MPYZ7/90v1TT0iWQC49+VSh/d2pnm22pqWBmnnxAREtilKoJIGRjo/BGfyDL6rKgNWBu1FceQdpIaUT4SnwjeCblCTxwFrYRBHzJn6XhaYsV4f6wvcc3i9lgpUQzQzVRcFRcj68JGdFd2UVKqLxHapv9P+8iNZyOXXfD6uc28xEBXDRzm2GJOfkR4EJqwm8VWuV4vWGCwv3tKq7LgiUEOXK23Gvk29DE25Mkp67H8TdUjggWfoFWzw6YPqOgvwOPWhpnhDBUAPD9qndTsFdg0TGab1tqjUZCk/9TfPdEi3nU8XIHkhlHsrZ2D5XVUcnAjkw4EFsXwgI1kD6+dKMFjJ9og9ZL73iG6NK/Rm5QfcaFm/38f05bLqrpVKOMR7rfiRw0mZuFR/wIl93lhEFnnSjJl6UyUztQ+2pfLPQcg6VM4hz8MXCPwry1nX8dkLXI+hUvsKunmYaw8edol7g3t5EbF0Sp/YNL1b2Hmcl6m01OZfBt6SZx+bBDeTOirel0hF8Krcnq0S4kHShXujHJhQF0c6w/J2zVmddcjWP/B/kNzT/GTos7NzVawkZ4tWAUhBdKZp7M54qTeho6IKQTloyP8PhprPPiD5/eq8as8mQ3bdOE6/QHuNXEStRabP/ubIWio+21gMd2AmjIH62uwYv2pKwaRyVeFHN6GABzBrs6g1M9OFuoqtNZHUCDBnb06RR1yGEow9h/ehn/uosUcbJQbayCGwtlWZt2xqz0xrFsnx+nntZzn/3Ol21ePRedS5JQAZ3fff8/P+/elBMUa0xZ8Az1WS703l+OWByZHpjaxZcf0OIyiDQAKk3UrFgG+Zzofg7ljZNGP9+1kbYpfFn3pdx8iSdX6fQWgPWFbjf/Tz2P6IvADrK4XrPwhr+js9roVSqhOELD/76DU8yq/PHi1qyAVgMT8tmkJjV+hUTeRVAKb96+8JaFU6pCf+6YRR68K6pUcoOtjHmB/L23SB0EvY4cbE5P50PxRVDlTf8viFMEmPbZE2JJKUcEfiH8R/LvXfx5a0Pw/YtM6fDqHQeeciI+15EEQQAdZtGImnnc+XVE51XZyry80vZQNdWobJljwvEg1w1FFHG3jx7f1cTtiqNyt/mgVoEsiE1HOC8AB4ew2/EGf9Li7cI3ugAxAnT7/KpNqJfAT2ZVJ+zZ1fN93Ri9fz1ernPfyWMVdELJudNBSV3iVxuTa5edNISmjfPwjx/8n8wvMbjcax94ttoEqdw/xLpWztUbVW/Kz7NHe7TR3B+CwXa67sjty2IVMUQEjWjXChmf3ld5Vm6tJvv97Hsv/2ZtBkRCW1wQZELdZd4j7/eWKxHoXs3HYl1wdxKGO9oB6F995Fxd4w4U5Km+Y5b8ohFd5zkG9knNdLt4FzidICAP4jnhPd3GpvAgnQvHEnZPkq9AQoqSbXV6e+YtHMibqW1eNzb+Kwu3lMfH32vN58b/LE571EBP1TDTA5EnC7loWHR4sk4AnYDrPsBokC9FIVvdvw/pDOe0/q+XOvQtxnXz/z8GNys/Th7fEl/jLPt5PyoEfHFZeWvXTtDzOkl1Vgzv/6sK+wb5M8eEK2DO95ey2HauxFPh0pwNDKHcUq6K1HiqGR6Y4YmeDftu+167doTUgYM3Xsez9M20Nra67Ga27UnWbQQPj+pTh7J8OwqodosaNo9GxPkP504gxzGbxS/cxEf8zM5HIkrbjJld+smVR19Nny9syBaR8S+3itHxZm+vnz+KOk5Ce2FBgoeZsbvTsp+/3KpCtZCWYrtt4o7uIa4d43Sd08ftQhXlnJ6az3u88S6Ew4Q9ZhLtJS1IzlIqYzQps2RMiQf/BbUtAiCNCjJw1eaaf04wPaRO321gOGS/f0USv7tblHOkQeJEWRQhM5oMtM/zV9eqYS8Pac/EqLtGvqb4/e18gk/Qk1D6YdkJJk27sNkwFLvyCf9m0pVZb287j+W7vno8UdrWC5ZCRlsH4JurF1BTEtdUNrNVWAUOqh+ZjfhrX2EUSdVwQrem+41FPbiqKqYQjz7UO1RI+tp9A7elbu2kLKrwcX1Sguh6KubMZ6ZJ/+Luf4fh5/6ZjE94yQPtqZ3h8eb109ssfck7TvbwKAp2sNbZ7U/ACKAeUIC4rao+e4Z/WnRshS3+7jF1SSQUA+Abu4a0FWpq+zM1z5CwxFOlIAhJGrn3QCrBFk2hcuUDqkRkJG07XaWr8Td++O1CqVPiCtgei17YNuWx6QIH30Hf4sfWSsQoNNyCb5wBD2vM3Uax338zPfp2Hud322ZKs4BTixz31BLmCrqEI9CohWBALLqOE7llQXGQ9im2TlfkBx3F69zc3fNXoH7nqEbV8AISXSAW/j1F9tLe2Pb7tKv644v1t8N5xPuk0UM4e0hVFEG5Qs9K+pb61DpPYy67lP1F02/TO/BExmJIAESXQcPQN0F+r1uBio30Msnn8OpSyovUePCj87EhgIQk+FML97UQTw3Y7X/Q4MdUR7tYuoElQulm3nqgGWlXn4GNSgsJ/DH82VC3qi4lHk3eXW9Y+fQAO3kSiqvIr7CNv0j+4EsJixtT6khldEk/D4bDPp+/i5d2fl24iqm0vLu3II2PL+uPbWDPe419GTV9r+1V5t4SXv7pf8ltql0sHSq/Yatg5dwcupVTsaR2Nqttro+ONyvSEb78OZ+1kDI5/peYDTieX2zllK8J4MzDwlRl/bfuNAy1IYACAqV5XaiwgSYjIZhKOMWF5Zan8OoE1zkuWx9KCe+vrvYoKcaYIksNLt+GMx0WFCTIS2CbR/scB9imPcr1VhGHJvOifOlhcx3ysQ37nj5yYLHdycSOQfmZx6TxMKXdoM6sjJ3ToaGZC3Thx46RSMuxMMRtrb4M27J494OyiZVxk/7WXRinc2cAa+76EhmYsl/qknbmY2jcrXjz6pVB30Jstl3twEjyBoJTw321GNx+vxUU8vsMTiNXSKwgWmjUBPBdySqGcDNlGF1Sf/HqiB0HufrFUwfStk8PXJyiVU9+kXeLWZeS3oRKXAhkIk7qyVdpR6v/zZt2MxjPlXZDQhlGCJFHov4Do79fxWX5qgFVjQDjvhYW9B8LW3okKS2/i7JcdvsyWLy4rMb7lVBwNUlYsMW4baJKqPPTYvxQfhd3DUoZLkm5KmHnA87hr6GPUeVBSAPtweNNCV+Kkrd3SuhZySoN4utCTWxJahukxGVrr+VzcjoT8AgY7os1sUFHIhsGbXB0CdMsPGWvGbsTOy6/MSazygfUFIz7G1IFPk+68RL1jhPN5qT6tzKzJ4LzyYEr1/cCMX+64BW26bhtNl8p0tMejpHlHZ6Gx9sg6vjFV78sU2dTbDUWODvCmLVSFox/bJDJER3WTkElQyAG5OBvHsU834JY6mssedKjkaVXy0yvjjTvPzffSMA0xS2Z3HhD8EaUuYCjJFyGugoh/i4GOKauDPn613w4lLKx3zk0zqHEPrMjTm5eI6FjO2KCSGSCDq7LkuE+v44/EhIm7zBaKuljjb09yHDwezgMveeaGXyqlBIBdjf6P2Vqu3iZIl1uY/JjfTp0LCv37f+LDXMuCZtlWQD09x8EbPHjTi90X5XhN4tUylJEyknUmM7FcSN4x0mIlPOvC4ZuBE7+yEzfnqlXuXWB45LaVMKvTR98QORcFv6By1PUP21puzqdKpn4BpJZdCNMOO6zx6FQvyNzuA0H7GwBss19Haor7lWnkxPQG0pGpL5Pjtnk0DhsuzHZ9x+iMlnWHHD4w61wGDVv9v7Qr3MTLTR2mWdYUOr3rVBmuGlemTpNNd9mpJ+Yio1GtOAJ6NbU2oXUq8Ad17r+avSb9rJSAmBHrlQc3cR3U5WlPLgbfZnBlv/xrWqILEqEHep9mWl/h1Nu73vK0RZdw7QuEA0FH/slfogM8tepA/XErR+6LeFnBgPLt7r51RS9VUPCTOtd7jvG441Jc9ZeBHsAnSlwIqUjzu9675YzvHnT4SpOq+02nyIqF0YMYDR5lWB0bCLrprWuVcWvwOb+Z6bgatkov9CYr2YBcmZ6OEfH+cK6MEuzDBHEaKpmj1URiLLjeOAKJdLKTMhf4qPFhzr+O2QcFftMcwPV5to5iTXunTU3DCfWhLGfNyJ7Jq/eab/EMxVxgB+PChSfXK+HtNJb43kg5nAaHjF1q6Nkxh6T9LO340rQAe8Km1406PmTWXJqi91aCd3CRyK0ejDq0j2vkK7/yjZ039VKtQz+GnLWQ9881aouuV+HmO3/k434G4RtaqIs8VKHD/58eM9mN+aqZDCJ+5PZgbG2QvRlREnWhQjx0wKqmZ4Hef1DIrsfu1M/GWWC8Tf5XeH4ZZkapfNeVf3HJbkTma1Pa9nKOllH0SrM9Yo8f9wkt39/Ce+b00ku8dvU+PGQe9bcgMw794r10A7NmJgelIgPX+3xjLUIW4d5trQVC7b2/eS0u0tt7pBTU+niy2pu4BOFHVP0oEoLfmKcb7QiR3ru9erJ6JkHH4KoWuCFTqJpI6pdUpoLmVrYeC1yTf4ecIKSBN+1DJgh5SRnsZqHbHFV6yt+/1YjTQJFQtmlyP9XO1gPKjs1qO//u9oL35QxLYSsq3gNCeRsfKOwyzfygtI6nM6yRAD4ghWjONgnTDMW2/nYW+AFUq639US1bs7gbKDaDT8kCvd/cz4jABjtEPh6hG2mUagqlNGHCL4T52Hawmv7f9D0BGPUbA68gSbv60j32cc4u1gqHGdQT4JbzlqaO6aLSRKyz9Rh7QQqsGgTVAqegIS2fHpZJUO1S3UaipCrmP1KG38j+/Qvrk7Dq+YfYIp2XnPrtQm2pEPBp3TbGDElVcnYfcenEjPQOoRWgev233RpdJtdm0BS7dcX6Iz29NoNHydeqcyAAkSA5Cv8feK1j9tceRPkC2FM7ySsJILqjK/E0arqNK1/TXEEkNM25Zmy5QdFLB+pmpEZgEgEiH6VMSH9yoRV6iGnlc7dSjrXvRR6KF9dnnZyu13oz+vcwm93m8lBHNgM0Ue0cW44b1+a+QECpUmv89507xnt9FgarFTiG3deXC6dUemO4JIwmce4EF8+C7pfDsTftJ3iBX39ex7nkCq1ePAms44paMTm7DBMpLq6lfWNeBVHCpKnmaVU7PTkdATVCU+qTcyPhHryFQd5Hp46uKtWmsc/TArtaWZN0FRGDdn4b1WNfPjvGnxxtAqES/DLpzYXB1kwAoRQqqJQERc03fdg5uk9D6OlPhplRuD0ymutQ25aqez40YBQTLOegIme3zggFBiwD71EY9Rysw7/SnMWOX1iLwemiFZd0Yw02w4r523dWblvwBuN186htslDuR3iNL36OUvihDcEUzDDoX8F09yApr4SLlufb+keF33P6Vi6EFysGFP8DP9/6/LuwGOOo+eSr6bLj0cR/iTNSff1qm0gy6tA2Mlv33Y1cJfF7So8Mmq+puULVK8l7QbiTP07cUul2drMhEyJyL3ZuFbROq071PjLuUZksJHV3rAATw/xcSpof4GxhwnZxcB+RaJjNXpmDfP71Mt+UqYzpD26DHSA5d2I55s9CUbWu9OBUAsr/wgzCp185RdyR6o7uTiRi64xd36RkYIwXgjD0TiG7uLtpeoUHIPuWSzbfdG7o0+SUqbTtAb4Yec6gdk+uo8fxP720gOnSAYhYbwOsXOiYWRC3jW9drzvTO+fiE0r2CQEO9ycrtPfVh8a+WIMFEr87P3Jjr8rw/0uCAO/dJGHILCY8/Rt9phZYau7q2GQ83l7PzzUNK/CyQTSukLF2tAOG04X2uIFbttKq5hMK68PKFzzi/114MSb4BO7EwayWOck07725INeZB8K19llOeK6fUanFh235b4g7kYQV4RSOrioAQ0/PSV53kOqbl53GsrTyPPfrjEjVAp5PUDazv03JMssHCfmioVdq2aXdw29/6q1l8SVJzb/uAx+UyrvuTzLWrGqbc3j1JUgaF/+eXUF4qZJgFKNm41gDUGqptVaoZuZ+U6/Sf3jRPgrJGLbuY43NhBFqxaN1f0pXddS9YtgntHkPK3PSXTtAMbcIMUzfs4/L39UcrPHRY8vHxEW1OHBZtZKUGy3K3WfY2j/1+j68I/kzzqhPrNvHuI8790Pwn9dVGqCbAdYDBj9OARMHzf8vl49Ub2M3Y7xJ/4TxRceGJ3kyeqNykpDWsNiayk9Pzq3bebcY1GrJx7XzOLYD6BBmzEhj51Xrz8W5z/Tn18dBgPQJ6UrIrOY2hgDFM6s2tqPj7I7Z6Y4OP94vQ3NAdk8G91kbu8omUNbWQprkdXuLi9Ds5DNC09txEZxd7nd3ZLgNn1as4sBLVnRh15cff+2RISxIsXCKqw6ut0ZOY8rL4kfyDoT6fn3MgQ/K4UBItwk+/S5lCJniQE79rAOX2GewoTKgJuWq34PhZPzFreUa/Q+V9JBRk+1D70xvbfR4vX60hzKhTGxX1pixUHPlDEL9bVJrO3n3b01LPqbd46B+V+llOSuc1CydM010tSSBNGIJD9l85aCkc9vXuJBXW23nM96sqEvCGOGtTzeBXzcUYs475NC7I7Di6Uks0iZArp3PEcyJKlR3m7CT1/0n/tF8gUEIFLIZv++jw6EPN4FjKUHam/a9MqrajThS9ll33l1/i70A8kr9Obu9cenWTJBMalODyR2OaTolqZafz9VFEm9PH1DuEdWkCWc2eedmKpaU57XR/FlFyGlBo6/Z79FFbnx1+/nRKm0Ukqo/fY8OSwognJHl4cD2/0LZYH/oS+UhMKuP5jz/2SQNkcBsx2Cj/MvUK4PHnsX9BRh/oQMiqxFexwAp9kpnE3LPY77eGGlb0f5GbJtCn6GuSYXHDx6cNoU8vK7x0mvaQe9nYPh1D+qkysdTtjUzMPDVVHqOzz8akTjeYSv4ot2H+9PT6Rm3u8FsE3po7vjLAVoF7aqp1U9AdVle+qqnDR4pRHPRFj9HAWaB7KzNzPjuLeN3nsljBYFC++H3Y8kcjKoT3e+rQF9TxG27csnN7v1CBVWHLc9qfVs1dg7WjPSmd1o3Ik+E+y03vAMcedceU1dyGJ776DC8CRzzeCcO/vfY3Q0XtdsBV7l7zN//ykFqtrADdaWdP+7Hj5yID+LcOgeiuxLOpg3OzVIURS684p1k7j5uOTMdBDrTlf4ycQMqZxsH42e3pMPIQeiYuewz/+yB9DB4XqcM3NcYWt9fCfRpxb2jY361t9WwW8ok2W6QH9PHiE3+dHfHWx30o9P38D13+q1uQxchXSIVipJix9H1KIy7oiLPshKmvHt3Ui4K/XOeLoOH9leqPRpF3+K34PiYBUPTZBwz26T9pz2n8I5hg5NU7EF2w9PJ2+ERfbMcH69XGUL+ugrB++jP14o/7WQqy/CVCeTv1JwmTvgx1EORnvFpGFZbc96BjFHbLwLVphQ+uM0+P5dtOBAUFAVCAAalS9r9UKNr0MTKMCA/cvC04nndJJW70udTKrNjj0iSzngBmT2iV9+pBi5N47h2gKyxtr+ka3uUR3NFS7kg+1VnHL07Y/7lFtO8LD9Idf9bLNUT7vT6OwT39BsSiwJtCn4s1Hy0nbCPL0TkbDdzn9Q/KFUZqRIZD3vDnDLdqCSG+UDCG7aFSePB/dShA0+LMzuNNubRqCu8Td0IDpOPYZx8tFfDuZw+cERNUwP6jiO+6Ul9tgSpGdXy9/LmVWvaaPuxZRwxBLAGbFv8+HZlpw7U3XLQgqKpPRAKZzB87fY7tOf//AHxjM8PShEB1AAAAAElFTkSuQmCC');\n\n      }\n\n      /* Text coloring */\n      .err {\n        color: #F04C4C;\n      }\n\n      .info {\n        color: #55A4FF;\n      }\n\n      .warn {\n        color: #FFCB51;\n      }\n\n      .text-theme {\n        color: #E2E5E9;\n      }\n\n      #building>#progress-bar,\n      #error>#error-block {\n        background-color: #111318;\n      }\n\n      #footer>h4>a {\n        color: #E2E5E9;\n      }\n    }\n  </style>\n</head>\n\n<body>\n  <!-- Header -->\n  <div id=\"header\">\n    <!-- Dioxus Logo -->\n    <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"32\" height=\"32\" xmlns:v=\"https://vecta.io/nano\">\n      <path\n        d=\"M22.158 1.783c0 3.077-.851 5.482-2.215 7.377s-3.32 3.557-5.447 5.33-4.425 3.657-6.252 6.195-3.102 5.515-3.102 9.532h4.699c0-3.077.853-5.377 2.217-7.272s3.32-3.557 5.447-5.33 4.425-3.657 6.252-6.195 3.102-5.62 3.102-9.637z\"\n        fill=\"#e96020\" />\n      <path\n        d=\"M9.531 25.927c-.635 0-1.021.515-1.02 1.15s.385 1.151 1.02 1.15H22.47a1.151 1.151 0 1 0 0-2.301zm1.361-4.076c-.608 0-.954.558-.953 1.166s.346 1.035.953 1.035h10.217a1.101 1.101 0 1 0 0-2.201zm0-13.594a1.101 1.101 0 1 0 0 2.201h10.217c.607 0 .953-.598.953-1.205s-.345-.996-.953-.996zM9.531 4.021A1.15 1.15 0 0 0 8.38 5.17a1.15 1.15 0 0 0 1.15 1.15h12.94c.635 0 1.021-.498 1.02-1.133s-.386-1.166-1.02-1.166z\"\n        fill=\"#2d323b\" />\n      <path\n        d=\"M5.142 1.783c0 4.016 1.275 7.099 3.102 9.637s4.125 4.422 6.252 6.195 4.083 3.656 5.447 5.551 2.215 3.974 2.215 7.051h4.701c0-4.016-1.275-7.038-3.102-9.576s-4.125-4.422-6.252-6.195-4.083-3.435-5.447-5.33S9.841 4.86 9.841 1.783z\"\n        fill=\"#00a8d6\" />\n    </svg>\n\n    <h2 id=\"dioxus\">Dioxus</h2>\n\n    <!-- Project platform and name -->\n    <h2 id=\"project-info\">N/A | N/A</h2>\n  </div>\n\n  <!-- Building progress screen -->\n  <div class=\"content-container\" id=\"building\">\n    <h1 class=\"content-header-text\">One sec! <br />We're building your app now.</h1>\n\n    <div id=\"progress-bar\">\n      <div id=\"progress-bar-inner\" style=\"width: 1%;\"></div>\n    </div>\n    <div id=\"text\">\n      <!-- Funny loading messages or something -->\n      <h4 id=\"build-message\">Starting the build...</h4>\n\n      <!-- Progress % -->\n      <h4 id=\"build-progress-percent\">0%</h4>\n    </div>\n  </div>\n\n  <!-- Building Error -->\n  <div class=\"content-container hidden\" id=\"error\">\n    <h1 class=\"content-header-text\">Oops! The build failed.</h1>\n    <p id=\"error-sub-text\"><span id=\"error-inner-sub-text\" class=\"warn\">compiling my-cool-project</span> encountered an\n      error:</p>\n    <div id=\"error-block\">\n      <span class=\"err\">error</span><span>: expected `;`, found `rsx`</span> <br />\n      <span class=\"info\"> --></span><span> src/main.rs:21:6</span><br />\n      <span class=\"info\"> |</span><br />\n      <span class=\"info\">21 |</span><span> a</span><br />\n      <span class=\"info\"> |</span><span class=\"err\"> ^ help: add `;` here</span><br />\n      <span class=\"info\">22 |</span><span> rsx! {</span><br />\n      <span class=\"info\"> |</span><span class=\"info\"> --- unexpected token</span><br />\n\n      <span class=\"err\">error</span><span>: could not compile `my-cool-app` (bin \"my-cool-app\") due to 1 previous\n        error</span>\n    </div>\n\n  </div>\n\n  <!-- Footer Of Links -->\n  <div id=\"footer\">\n    <h4>\n      <a href=\"https://docs.rs/dioxus/latest/dioxus/\">docs</a>\n      |\n      <a href=\"https://dioxuslabs.com/learn\">guides</a>\n      |\n      <a href=\"https://discord.gg/KrGKhBbU6s\">help</a>\n    </h4>\n  </div>\n\n  <!-- Logic -->\n  <script>\n    let APPLICATION_NAME = \"N/A\";\n\n    // Wait for a \"Ready\" message from the server on the websocket served at /_dioxus/build_status\n    let protocol;\n    if (window.location.protocol === \"https:\") {\n      protocol = \"wss:\";\n    } else {\n      protocol = \"ws:\";\n    }\n    let url =\n      protocol + \"//\" + window.location.host + \"/_dioxus/build_status\";\n    let ws = new WebSocket(url);\n\n    // WS Message Handling\n    ws.onmessage = (event) => {\n      // Parse the message as json\n      let data = JSON.parse(event.data);\n\n      // If the message is \"Ready\", reload the page\n      if (data.type === \"Ready\") {\n        setTimeout(() => {\n          // Once we get a \"Ready\" message, reload the page\n          window.location.reload();\n        }, 500);\n      } else if (data.type === \"ClientInit\") {\n        // Get project info\n        APPLICATION_NAME = data.data.application_name;\n        const platform = data.data.bundle;\n\n        // Set project info\n        let projectInfo = document.getElementById(\"project-info\");\n        projectInfo.innerText = `${platform} | ${APPLICATION_NAME}`;\n\n      } else if (data.type === \"BuildError\") {\n        // If the message is \"BuildError\", display an error message\n        // Show correct view for message.\n        let errorContainer = document.getElementById(\"error\");\n        errorContainer.className = \"content-container\";\n\n        let buildingContainer = document.getElementById(\"building\");\n        buildingContainer.className = \"content-container hidden\";\n\n        let errorInnerSubText = document.getElementById(\"error-inner-sub-text\");\n        errorInnerSubText.innerText = `compiling ${APPLICATION_NAME}`;\n\n        // Replace with our styling.\n        let color1 = data.data.error.replaceAll(\"style='color:#f55'\", \"class='err'\");\n        let color2 = color1.replaceAll(\"style='color:#5ff'\", \"class='info'\");\n        let color3 = color2.replaceAll(\"style='color:#fff'\", \"class='text-theme'\");\n        let formatting1 = color3.replaceAll(\"<b>\", \"\");\n        let formatting2 = formatting1.replaceAll(\"</b>\", \"\");\n\n        let errorBlock = document.getElementById(\"error-block\");\n        errorBlock.innerHTML = formatting2;\n\n      } else if (data.type === \"Building\") {\n        // Show correct view for message.\n        let errorContainer = document.getElementById(\"error\");\n        errorContainer.className = \"content-container hidden\";\n\n        let buildingContainer = document.getElementById(\"building\");\n        buildingContainer.className = \"content-container\";\n\n        // Get the current progress\n        const progress = data.data.progress;\n        const roundedProgress = Math.round(progress * 100);\n        // Update the loading indicator\n        let loadingPercent = document.getElementById(\"build-progress-percent\");\n        let loadingBar = document.getElementById(\"progress-bar-inner\");\n\n        const formattedProgress = `${roundedProgress}%`;\n        loadingPercent.innerHTML = formattedProgress;\n        loadingBar.style.width = formattedProgress;\n\n        // Show progress message\n        const message = data.data.build_message;\n        let buildMessage = document.getElementById(\"build-message\");\n        buildMessage.innerText = message;\n      }\n    };\n\n\n    // WS Reconnect Logic\n    const POLL_INTERVAL_MIN = 250;\n    const POLL_INTERVAL_MAX = 4000;\n    const POLL_INTERVAL_SCALE_FACTOR = 2;\n\n    const reload_upon_connect = (event, poll_interval) => {\n      // Firefox will send a 1001 code when the connection is closed because the page is reloaded\n      // Only firefox will trigger the onclose event when the page is reloaded manually: https://stackoverflow.com/questions/10965720/should-websocket-onclose-be-triggered-by-user-navigation-or-refresh\n      // We should not reload the page in this case\n      if (event.code === 1001) {\n        return;\n      }\n      window.setTimeout(() => {\n        var ws = new WebSocket(url);\n        ws.onopen = () => window.location.reload();\n        ws.onclose = (event) => {\n          reload_upon_connect(\n            event,\n            Math.min(\n              POLL_INTERVAL_MAX,\n              poll_interval * POLL_INTERVAL_SCALE_FACTOR\n            )\n          );\n        };\n      }, poll_interval);\n    };\n\n    ws.onclose = (event) => reload_upon_connect(event, POLL_INTERVAL_MIN);\n  </script>\n\n\n</body>\n\n</html>"
  },
  {
    "path": "packages/cli/assets/web/prod.index.html",
    "content": "<!DOCTYPE html>\n<html>\n    <head>\n        <title>{app_title}</title>\n        <meta content=\"text/html;charset=utf-8\" http-equiv=\"Content-Type\">\n        <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n        <meta charset=\"UTF-8\">\n    </head>\n    <body>\n        <div id=\"main\"></div>\n    </body>\n</html>\n"
  },
  {
    "path": "packages/cli/build.rs",
    "content": "fn main() {\n    built::write_built_file().expect(\"Failed to acquire build-time information\");\n}\n"
  },
  {
    "path": "packages/cli/schema.json",
    "content": "{\n  \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n  \"title\": \"DioxusConfig\",\n  \"type\": \"object\",\n  \"properties\": {\n    \"android\": {\n      \"description\": \"Android-specific configuration.\",\n      \"$ref\": \"#/definitions/AndroidConfig\"\n    },\n    \"application\": {\n      \"$ref\": \"#/definitions/ApplicationConfig\"\n    },\n    \"background\": {\n      \"description\": \"Unified background mode configuration. Background capabilities declared here are mapped to platform-specific configurations. Use `[ios]`, `[android]` sections for overrides.\",\n      \"$ref\": \"#/definitions/BackgroundConfig\"\n    },\n    \"bundle\": {\n      \"$ref\": \"#/definitions/BundleConfig\"\n    },\n    \"components\": {\n      \"$ref\": \"#/definitions/ComponentConfig\"\n    },\n    \"deep_links\": {\n      \"description\": \"Unified deep linking configuration. URL schemes and universal links declared here are mapped to platform-specific configurations. Use `[ios]`, `[android]`, `[macos]` sections for overrides.\",\n      \"$ref\": \"#/definitions/DeepLinkConfig\"\n    },\n    \"ios\": {\n      \"description\": \"iOS-specific configuration.\",\n      \"$ref\": \"#/definitions/IosConfig\"\n    },\n    \"linux\": {\n      \"description\": \"Linux-specific configuration.\",\n      \"$ref\": \"#/definitions/LinuxConfig\"\n    },\n    \"macos\": {\n      \"description\": \"macOS-specific configuration.\",\n      \"$ref\": \"#/definitions/MacosConfig\"\n    },\n    \"permissions\": {\n      \"description\": \"Unified permissions configuration. Permissions declared here are automatically mapped to platform-specific identifiers (AndroidManifest.xml, Info.plist, etc.)\",\n      \"$ref\": \"#/definitions/PermissionsConfig\"\n    },\n    \"web\": {\n      \"$ref\": \"#/definitions/WebConfig\"\n    },\n    \"windows\": {\n      \"description\": \"Windows-specific configuration.\",\n      \"$ref\": \"#/definitions/WindowsConfig\"\n    }\n  },\n  \"definitions\": {\n    \"AndroidApplicationConfig\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"large_heap\": {\n          \"description\": \"Enable large heap.\",\n          \"type\": [\n            \"boolean\",\n            \"null\"\n          ]\n        },\n        \"supports_rtl\": {\n          \"description\": \"RTL layout support.\",\n          \"type\": [\n            \"boolean\",\n            \"null\"\n          ]\n        },\n        \"theme\": {\n          \"description\": \"Application theme.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"uses_cleartext_traffic\": {\n          \"description\": \"Enable cleartext (HTTP) traffic.\",\n          \"type\": [\n            \"boolean\",\n            \"null\"\n          ]\n        }\n      }\n    },\n    \"AndroidConfig\": {\n      \"description\": \"Android-specific configuration.\\n\\nExample: ```toml [android] min_sdk = 24 target_sdk = 34 identifier = \\\"com.example.myapp.android\\\"  # Override bundle.identifier for Android features = [\\\"android.hardware.location.gps\\\"]\\n\\n# Android signing configuration (previously in [bundle.android]) [android.signing] jks_file = \\\"keystore.jks\\\" jks_password = \\\"password\\\" key_alias = \\\"mykey\\\" key_password = \\\"keypassword\\\"\\n\\n[android.permissions] \\\"android.permission.FOREGROUND_SERVICE\\\" = { description = \\\"Background service\\\" } ```\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"application\": {\n          \"description\": \"Application-level config.\",\n          \"$ref\": \"#/definitions/AndroidApplicationConfig\"\n        },\n        \"category\": {\n          \"description\": \"App category. Overrides `bundle.category` for Android builds.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"compile_sdk\": {\n          \"description\": \"Compile SDK version.\",\n          \"type\": [\n            \"integer\",\n            \"null\"\n          ],\n          \"format\": \"uint32\",\n          \"minimum\": 0.0\n        },\n        \"copyright\": {\n          \"description\": \"Copyright notice. Overrides `bundle.copyright` for Android builds.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"features\": {\n          \"description\": \"Hardware/software features required.\",\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"foreground_service_types\": {\n          \"description\": \"Foreground service types for background operations. Valid values: \\\"camera\\\", \\\"connectedDevice\\\", \\\"dataSync\\\", \\\"health\\\", \\\"location\\\", \\\"mediaPlayback\\\", \\\"mediaProjection\\\", \\\"microphone\\\", \\\"phoneCall\\\", \\\"remoteMessaging\\\", \\\"shortService\\\", \\\"specialUse\\\", \\\"systemExempted\\\"\",\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"gradle_dependencies\": {\n          \"description\": \"Gradle dependencies to add.\",\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"gradle_plugins\": {\n          \"description\": \"Gradle plugins to apply.\",\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"icon\": {\n          \"description\": \"Icons for the app. Overrides `bundle.icon` for Android builds.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"identifier\": {\n          \"description\": \"The app's identifier (e.g., \\\"com.example.myapp\\\"). Overrides `bundle.identifier` for Android builds.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"intent_filters\": {\n          \"description\": \"Intent filters for deep linking. These extend the unified `[deep_links]` configuration with Android-specific options.\",\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/AndroidIntentFilter\"\n          }\n        },\n        \"long_description\": {\n          \"description\": \"Long description. Overrides `bundle.long_description` for Android builds.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"manifest\": {\n          \"description\": \"Path to custom AndroidManifest.xml to merge.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"min_sdk\": {\n          \"description\": \"Minimum SDK version.\",\n          \"type\": [\n            \"integer\",\n            \"null\"\n          ],\n          \"format\": \"uint32\",\n          \"minimum\": 0.0\n        },\n        \"permissions\": {\n          \"description\": \"Additional Android permissions not in unified config.\",\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"$ref\": \"#/definitions/RawPermission\"\n          }\n        },\n        \"proguard_rules\": {\n          \"description\": \"ProGuard rule files.\",\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"publisher\": {\n          \"description\": \"The app's publisher. Overrides `bundle.publisher` for Android builds.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"queries\": {\n          \"description\": \"Queries for package visibility (required for Android 11+). Specify packages or intents your app needs to query.\",\n          \"$ref\": \"#/definitions/AndroidQueries\"\n        },\n        \"raw\": {\n          \"description\": \"Raw XML injection points.\",\n          \"$ref\": \"#/definitions/AndroidRawConfig\"\n        },\n        \"resources\": {\n          \"description\": \"Additional resources to bundle. Overrides `bundle.resources` for Android builds.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"short_description\": {\n          \"description\": \"Short description. Overrides `bundle.short_description` for Android builds.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"signing\": {\n          \"description\": \"Android signing configuration for release builds. This replaces the deprecated `[bundle.android]` section.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/AndroidSigningConfig\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"target_sdk\": {\n          \"description\": \"Target SDK version.\",\n          \"type\": [\n            \"integer\",\n            \"null\"\n          ],\n          \"format\": \"uint32\",\n          \"minimum\": 0.0\n        },\n        \"url_schemes\": {\n          \"description\": \"Additional URL schemes beyond unified `[deep_links]`.schemes. These are merged with the unified schemes.\",\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        }\n      }\n    },\n    \"AndroidIntentData\": {\n      \"description\": \"Android intent data specification.\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"host\": {\n          \"description\": \"Host (e.g., \\\"example.com\\\").\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"mime_type\": {\n          \"description\": \"MIME type.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"path\": {\n          \"description\": \"Path (exact match).\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"path_pattern\": {\n          \"description\": \"Path pattern (with wildcards).\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"path_prefix\": {\n          \"description\": \"Path prefix.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"port\": {\n          \"description\": \"Port number.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"scheme\": {\n          \"description\": \"URL scheme (e.g., \\\"https\\\", \\\"myapp\\\").\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        }\n      }\n    },\n    \"AndroidIntentFilter\": {\n      \"description\": \"Android intent filter for deep linking.\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"actions\": {\n          \"description\": \"Actions (e.g., \\\"android.intent.action.VIEW\\\").\",\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"auto_verify\": {\n          \"description\": \"Auto-verify for App Links (requires HTTPS and assetlinks.json).\",\n          \"type\": \"boolean\"\n        },\n        \"categories\": {\n          \"description\": \"Categories (e.g., \\\"android.intent.category.DEFAULT\\\", \\\"android.intent.category.BROWSABLE\\\").\",\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"data\": {\n          \"description\": \"Data specifications.\",\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/AndroidIntentData\"\n          }\n        }\n      }\n    },\n    \"AndroidQueries\": {\n      \"description\": \"Android package visibility queries.\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"intents\": {\n          \"description\": \"Intent actions to query.\",\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/AndroidQueryIntent\"\n          }\n        },\n        \"packages\": {\n          \"description\": \"Package names to query.\",\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        }\n      }\n    },\n    \"AndroidQueryIntent\": {\n      \"description\": \"Android query intent specification.\",\n      \"type\": \"object\",\n      \"required\": [\n        \"action\"\n      ],\n      \"properties\": {\n        \"action\": {\n          \"description\": \"Action (e.g., \\\"android.intent.action.SEND\\\").\",\n          \"type\": \"string\"\n        },\n        \"mime_type\": {\n          \"description\": \"MIME type (e.g., \\\"text/plain\\\").\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"scheme\": {\n          \"description\": \"Data scheme (e.g., \\\"mailto\\\").\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        }\n      }\n    },\n    \"AndroidRawConfig\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"application\": {\n          \"description\": \"Raw XML inside `<application>` element.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"application_attrs\": {\n          \"description\": \"Raw attributes for `<application>` element.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"manifest\": {\n          \"description\": \"Raw XML to inject into manifest (after permissions).\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        }\n      }\n    },\n    \"AndroidSettings\": {\n      \"type\": \"object\",\n      \"required\": [\n        \"jks_file\",\n        \"jks_password\",\n        \"key_alias\",\n        \"key_password\"\n      ],\n      \"properties\": {\n        \"jks_file\": {\n          \"type\": \"string\"\n        },\n        \"jks_password\": {\n          \"type\": \"string\"\n        },\n        \"key_alias\": {\n          \"type\": \"string\"\n        },\n        \"key_password\": {\n          \"type\": \"string\"\n        }\n      }\n    },\n    \"AndroidSigningConfig\": {\n      \"description\": \"Android signing configuration for release builds.\",\n      \"type\": \"object\",\n      \"required\": [\n        \"jks_file\",\n        \"jks_password\",\n        \"key_alias\",\n        \"key_password\"\n      ],\n      \"properties\": {\n        \"jks_file\": {\n          \"description\": \"Path to the Java keystore file.\",\n          \"type\": \"string\"\n        },\n        \"jks_password\": {\n          \"description\": \"Password for the keystore.\",\n          \"type\": \"string\"\n        },\n        \"key_alias\": {\n          \"description\": \"Alias of the key in the keystore.\",\n          \"type\": \"string\"\n        },\n        \"key_password\": {\n          \"description\": \"Password for the key.\",\n          \"type\": \"string\"\n        }\n      }\n    },\n    \"ApplicationConfig\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"android_main_activity\": {\n          \"description\": \"Use this file for the MainActivity.kt associated with the Android app.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"android_manifest\": {\n          \"description\": \"Use this file for the AndroidManifest.xml associated with the Android app. `dx` will merge any required settings into this file required to build the app\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"android_min_sdk_version\": {\n          \"description\": \"Specified minimum sdk version for gradle to build the app with.\",\n          \"type\": [\n            \"integer\",\n            \"null\"\n          ],\n          \"format\": \"uint32\",\n          \"minimum\": 0.0\n        },\n        \"asset_dir\": {\n          \"description\": \"The path where global assets will be added when components are added with `dx components add`\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"ios_entitlements\": {\n          \"description\": \"Use this file for the entitlements.plist associated with the iOS app.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"ios_info_plist\": {\n          \"description\": \"Use this file for the info.plist associated with the iOS app. `dx` will merge any required settings into this file required to build the app\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"macos_entitlements\": {\n          \"description\": \"Use this file for the entitlements.plist associated with the macOS app.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"macos_info_plist\": {\n          \"description\": \"Use this file for the info.plist associated with the macOS app. `dx` will merge any required settings into this file required to build the app\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"out_dir\": {\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"public_dir\": {\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"tailwind_input\": {\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"tailwind_output\": {\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        }\n      }\n    },\n    \"BackgroundConfig\": {\n      \"description\": \"Unified background execution configuration.\\n\\nThis provides a cross-platform interface for background capabilities. Platform-specific overrides can be configured in `[ios]` and `[android]` sections.\\n\\nExample: ```toml [background] location = true audio = true fetch = true ```\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"audio\": {\n          \"description\": \"Background audio playback. iOS: UIBackgroundModes \\\"audio\\\" Android: FOREGROUND_SERVICE_MEDIA_PLAYBACK\",\n          \"type\": \"boolean\"\n        },\n        \"bluetooth\": {\n          \"description\": \"Bluetooth LE accessories. iOS: UIBackgroundModes \\\"bluetooth-central\\\" and \\\"bluetooth-peripheral\\\" Android: FOREGROUND_SERVICE_CONNECTED_DEVICE\",\n          \"type\": \"boolean\"\n        },\n        \"external-accessory\": {\n          \"description\": \"External accessory communication. iOS: UIBackgroundModes \\\"external-accessory\\\"\",\n          \"type\": \"boolean\"\n        },\n        \"fetch\": {\n          \"description\": \"Background data fetch. iOS: UIBackgroundModes \\\"fetch\\\" Android: WorkManager or foreground service\",\n          \"type\": \"boolean\"\n        },\n        \"location\": {\n          \"description\": \"Background location updates. iOS: UIBackgroundModes \\\"location\\\" Android: ACCESS_BACKGROUND_LOCATION permission\",\n          \"type\": \"boolean\"\n        },\n        \"processing\": {\n          \"description\": \"Background processing tasks. iOS: UIBackgroundModes \\\"processing\\\" Android: WorkManager\",\n          \"type\": \"boolean\"\n        },\n        \"remote-notifications\": {\n          \"description\": \"Remote push notifications. iOS: UIBackgroundModes \\\"remote-notification\\\" Android: Firebase Cloud Messaging\",\n          \"type\": \"boolean\"\n        },\n        \"voip\": {\n          \"description\": \"VoIP calls. iOS: UIBackgroundModes \\\"voip\\\" Android: FOREGROUND_SERVICE_PHONE_CALL\",\n          \"type\": \"boolean\"\n        }\n      }\n    },\n    \"BundleConfig\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"android\": {\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/AndroidSettings\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"category\": {\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"copyright\": {\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"deb\": {\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/DebianSettings\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"external_bin\": {\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"icon\": {\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"identifier\": {\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"long_description\": {\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"macos\": {\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/MacOsSettings\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"publisher\": {\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"resources\": {\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"short_description\": {\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"windows\": {\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/WindowsSettings\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        }\n      }\n    },\n    \"ComponentConfig\": {\n      \"description\": \"Configuration for the `dioxus component` commands\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"components_dir\": {\n          \"description\": \"The path where components are stored when adding or removing components\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"registry\": {\n          \"description\": \"The component registry to default to when adding components\",\n          \"$ref\": \"#/definitions/ComponentRegistry\"\n        }\n      }\n    },\n    \"ComponentRegistry\": {\n      \"description\": \"Arguments for a component registry Either a path to a local directory or a remote git repo (with optional rev)\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"git\": {\n          \"description\": \"The url of the component registry\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"path\": {\n          \"description\": \"The path to the components directory\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"rev\": {\n          \"description\": \"The revision of the component registry\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        }\n      }\n    },\n    \"CustomSignCommandSettings\": {\n      \"type\": \"object\",\n      \"required\": [\n        \"args\",\n        \"cmd\"\n      ],\n      \"properties\": {\n        \"args\": {\n          \"description\": \"The arguments to pass to the command.\\n\\n\\\"%1\\\" will be replaced with the path to the binary to be signed.\",\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"cmd\": {\n          \"description\": \"The command to run to sign the binary.\",\n          \"type\": \"string\"\n        }\n      }\n    },\n    \"DebianSettings\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"changelog\": {\n          \"description\": \"Path of the uncompressed Changelog file, to be stored at /usr/share/doc/package-name/changelog.gz. See <https://www.debian.org/doc/debian-policy/ch-docs.html#changelog-files-and-release-notes>\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"conflicts\": {\n          \"description\": \"the list of package conflicts.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"depends\": {\n          \"description\": \"the list of debian dependencies.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"desktop_template\": {\n          \"description\": \"Path to a custom desktop file Handlebars template.\\n\\nAvailable variables: `categories`, `comment` (optional), `exec`, `icon` and `name`.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"files\": {\n          \"description\": \"List of custom files to add to the deb package. Maps the path on the debian package to the path of the file to include (relative to the current working directory).\",\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          }\n        },\n        \"post_install_script\": {\n          \"description\": \"Path to script that will be executed after the package is unpacked. See <https://www.debian.org/doc/debian-policy/ch-maintainerscripts.html>\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"post_remove_script\": {\n          \"description\": \"Path to script that will be executed after the package is removed. See <https://www.debian.org/doc/debian-policy/ch-maintainerscripts.html>\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"pre_install_script\": {\n          \"description\": \"Path to script that will be executed before the package is unpacked. See <https://www.debian.org/doc/debian-policy/ch-maintainerscripts.html>\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"pre_remove_script\": {\n          \"description\": \"Path to script that will be executed before the package is removed. See <https://www.debian.org/doc/debian-policy/ch-maintainerscripts.html>\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"priority\": {\n          \"description\": \"Change the priority of the Debian Package. By default, it is set to `optional`. Recognized Priorities as of now are :  `required`, `important`, `standard`, `optional`, `extra`\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"provides\": {\n          \"description\": \"the list of dependencies the package provides.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"recommends\": {\n          \"description\": \"the list of recommended debian dependencies.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"replaces\": {\n          \"description\": \"the list of package replaces.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"section\": {\n          \"description\": \"Define the section in Debian Control file. See : <https://www.debian.org/doc/debian-policy/ch-archive.html#s-subsections>\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        }\n      }\n    },\n    \"DeepLinkConfig\": {\n      \"description\": \"Unified deep linking configuration.\\n\\nThis provides a cross-platform interface for URL schemes and universal/app links. Platform-specific overrides can be configured in `[ios]` and `[android]` sections.\\n\\nExample: ```toml [deep_links] schemes = [\\\"myapp\\\", \\\"com.example.myapp\\\"] hosts = [\\\"example.com\\\", \\\"*.example.com\\\"] ```\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"hosts\": {\n          \"description\": \"Universal link / App link hosts (e.g., \\\"example.com\\\"). Maps to Associated Domains on iOS and App Links on Android. Supports wildcards like \\\"*.example.com\\\".\",\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"paths\": {\n          \"description\": \"Path patterns for universal/app links (e.g., \\\"/app/*\\\", \\\"/share/*\\\"). If empty, all paths are matched.\",\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"schemes\": {\n          \"description\": \"Custom URL schemes (e.g., \\\"myapp\\\" for myapp://path). Maps to CFBundleURLSchemes on iOS/macOS and intent-filter on Android.\",\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        }\n      }\n    },\n    \"IosConfig\": {\n      \"description\": \"iOS-specific configuration.\\n\\nExample: ```toml [ios] deployment_target = \\\"15.0\\\" identifier = \\\"com.example.myapp.ios\\\"  # Override bundle.identifier for iOS\\n\\n[ios.entitlements] app-groups = [\\\"group.com.example.app\\\"]\\n\\n[ios.plist] UIBackgroundModes = [\\\"location\\\", \\\"fetch\\\"] ```\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"background_modes\": {\n          \"description\": \"Additional background modes beyond unified `[background]`. Valid values: \\\"audio\\\", \\\"location\\\", \\\"voip\\\", \\\"fetch\\\", \\\"remote-notification\\\", \\\"newsstand-content\\\", \\\"external-accessory\\\", \\\"bluetooth-central\\\", \\\"bluetooth-peripheral\\\", \\\"processing\\\"\",\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"category\": {\n          \"description\": \"App category. Overrides `bundle.category` for iOS builds.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"copyright\": {\n          \"description\": \"Copyright notice. Overrides `bundle.copyright` for iOS builds.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"deployment_target\": {\n          \"description\": \"Minimum iOS deployment target (e.g., \\\"15.0\\\").\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"document_types\": {\n          \"description\": \"Document types the app can open.\",\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/IosDocumentType\"\n          }\n        },\n        \"entitlements\": {\n          \"description\": \"iOS entitlements configuration.\",\n          \"$ref\": \"#/definitions/IosEntitlements\"\n        },\n        \"exported_type_identifiers\": {\n          \"description\": \"Exported type identifiers (custom UTIs).\",\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/IosTypeIdentifier\"\n          }\n        },\n        \"icon\": {\n          \"description\": \"Icons for the app. Overrides `bundle.icon` for iOS builds.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"identifier\": {\n          \"description\": \"The app's identifier (e.g., \\\"com.example.myapp\\\"). Overrides `bundle.identifier` for iOS builds.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"imported_type_identifiers\": {\n          \"description\": \"Imported type identifiers.\",\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/IosTypeIdentifier\"\n          }\n        },\n        \"info_plist\": {\n          \"description\": \"Path to custom Info.plist to merge with generated.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"long_description\": {\n          \"description\": \"Long description. Overrides `bundle.long_description` for iOS builds.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"plist\": {\n          \"description\": \"Additional Info.plist keys to merge.\",\n          \"type\": \"object\",\n          \"additionalProperties\": true\n        },\n        \"publisher\": {\n          \"description\": \"The app's publisher. Overrides `bundle.publisher` for iOS builds.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"raw\": {\n          \"description\": \"Raw XML injection points.\",\n          \"$ref\": \"#/definitions/IosRawConfig\"\n        },\n        \"resources\": {\n          \"description\": \"Additional resources to bundle. Overrides `bundle.resources` for iOS builds.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"short_description\": {\n          \"description\": \"Short description. Overrides `bundle.short_description` for iOS builds.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"url_schemes\": {\n          \"description\": \"Additional URL schemes beyond unified `[deep_links]`.schemes. These are merged with the unified schemes.\",\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"widget_extensions\": {\n          \"description\": \"Widget extensions to compile and bundle. Each entry defines a Swift-based widget extension (.appex) that will be compiled and installed into the app's PlugIns folder.\",\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/WidgetExtensionConfig\"\n          }\n        }\n      }\n    },\n    \"IosDocumentType\": {\n      \"description\": \"iOS document type declaration.\",\n      \"type\": \"object\",\n      \"required\": [\n        \"name\"\n      ],\n      \"properties\": {\n        \"extensions\": {\n          \"description\": \"File extensions (e.g., [\\\"txt\\\", \\\"md\\\"]).\",\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"icon\": {\n          \"description\": \"Icon file name.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"mime_types\": {\n          \"description\": \"MIME types.\",\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"name\": {\n          \"description\": \"Document type name.\",\n          \"type\": \"string\"\n        },\n        \"role\": {\n          \"description\": \"Role: \\\"Editor\\\", \\\"Viewer\\\", \\\"Shell\\\", or \\\"None\\\".\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"types\": {\n          \"description\": \"UTI types.\",\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        }\n      }\n    },\n    \"IosEntitlements\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"app-groups\": {\n          \"description\": \"App groups for shared data.\",\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"apple-pay\": {\n          \"description\": \"Enable Apple Pay.\",\n          \"type\": \"boolean\"\n        },\n        \"aps-environment\": {\n          \"description\": \"Push notification environment: \\\"development\\\" or \\\"production\\\".\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"associated-domains\": {\n          \"description\": \"Associated domains for universal links.\",\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"healthkit\": {\n          \"description\": \"Enable HealthKit.\",\n          \"type\": \"boolean\"\n        },\n        \"homekit\": {\n          \"description\": \"Enable HomeKit.\",\n          \"type\": \"boolean\"\n        },\n        \"icloud\": {\n          \"description\": \"Enable iCloud container support.\",\n          \"type\": \"boolean\"\n        },\n        \"keychain-access-groups\": {\n          \"description\": \"Keychain access groups.\",\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        }\n      },\n      \"additionalProperties\": true\n    },\n    \"IosRawConfig\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"entitlements\": {\n          \"description\": \"Raw XML to inject into entitlements.plist.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"info_plist\": {\n          \"description\": \"Raw XML to inject into Info.plist.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        }\n      }\n    },\n    \"IosTypeIdentifier\": {\n      \"description\": \"iOS Uniform Type Identifier declaration.\",\n      \"type\": \"object\",\n      \"required\": [\n        \"identifier\"\n      ],\n      \"properties\": {\n        \"conforms_to\": {\n          \"description\": \"Conforms to these UTIs.\",\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"description\": {\n          \"description\": \"Human-readable description.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"extensions\": {\n          \"description\": \"File extensions.\",\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"identifier\": {\n          \"description\": \"UTI identifier (e.g., \\\"com.example.myformat\\\").\",\n          \"type\": \"string\"\n        },\n        \"mime_types\": {\n          \"description\": \"MIME types.\",\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        }\n      }\n    },\n    \"LinuxConfig\": {\n      \"description\": \"Linux-specific configuration.\\n\\nExample: ```toml [linux] identifier = \\\"com.example.myapp.linux\\\"  # Override bundle.identifier for Linux categories = [\\\"Utility\\\"]\\n\\n# Debian package settings (previously in [bundle.deb]) [linux.deb] depends = [\\\"libwebkit2gtk-4.0-37\\\"] section = \\\"utils\\\" ```\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"categories\": {\n          \"description\": \"Desktop entry categories.\",\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"category\": {\n          \"description\": \"App category. Overrides `bundle.category` for Linux builds.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"copyright\": {\n          \"description\": \"Copyright notice. Overrides `bundle.copyright` for Linux builds.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"dbus_access\": {\n          \"description\": \"D-Bus interfaces to access.\",\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"deb\": {\n          \"description\": \"Debian-specific package settings.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/LinuxDebSettings\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"flatpak_permissions\": {\n          \"description\": \"Flatpak sandbox permissions.\",\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"icon\": {\n          \"description\": \"Icons for the app. Overrides `bundle.icon` for Linux builds.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"identifier\": {\n          \"description\": \"The app's identifier (e.g., \\\"com.example.myapp\\\"). Overrides `bundle.identifier` for Linux builds.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"keywords\": {\n          \"description\": \"Desktop entry keywords.\",\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"long_description\": {\n          \"description\": \"Long description. Overrides `bundle.long_description` for Linux builds.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"mime_types\": {\n          \"description\": \"MIME types the app can handle.\",\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"publisher\": {\n          \"description\": \"The app's publisher. Overrides `bundle.publisher` for Linux builds.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"resources\": {\n          \"description\": \"Additional resources to bundle. Overrides `bundle.resources` for Linux builds.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"short_description\": {\n          \"description\": \"Short description. Overrides `bundle.short_description` for Linux builds.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        }\n      }\n    },\n    \"LinuxDebSettings\": {\n      \"description\": \"Debian package settings.\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"changelog\": {\n          \"description\": \"Path to changelog file.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"conflicts\": {\n          \"description\": \"Package conflicts.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"depends\": {\n          \"description\": \"Package dependencies.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"desktop_template\": {\n          \"description\": \"Path to custom desktop template.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"files\": {\n          \"description\": \"Additional files to include. Maps package path to source path.\",\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          }\n        },\n        \"post_install_script\": {\n          \"description\": \"Post-install script path.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"post_remove_script\": {\n          \"description\": \"Post-remove script path.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"pre_install_script\": {\n          \"description\": \"Pre-install script path.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"pre_remove_script\": {\n          \"description\": \"Pre-remove script path.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"priority\": {\n          \"description\": \"Package priority (\\\"required\\\", \\\"important\\\", \\\"standard\\\", \\\"optional\\\", \\\"extra\\\").\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"provides\": {\n          \"description\": \"Packages this provides.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"recommends\": {\n          \"description\": \"Recommended packages.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"replaces\": {\n          \"description\": \"Packages this replaces.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"section\": {\n          \"description\": \"Debian section (e.g., \\\"utils\\\", \\\"web\\\").\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        }\n      }\n    },\n    \"LocationPermission\": {\n      \"description\": \"Location permission with precision control.\",\n      \"type\": \"object\",\n      \"required\": [\n        \"description\"\n      ],\n      \"properties\": {\n        \"description\": {\n          \"description\": \"User-facing description shown in permission dialogs.\",\n          \"type\": \"string\"\n        },\n        \"precision\": {\n          \"description\": \"Precision level: \\\"fine\\\" (GPS) or \\\"coarse\\\" (network-based).\",\n          \"$ref\": \"#/definitions/LocationPrecision\"\n        }\n      }\n    },\n    \"LocationPrecision\": {\n      \"type\": \"string\",\n      \"enum\": [\n        \"fine\",\n        \"coarse\"\n      ]\n    },\n    \"MacOsSettings\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"bundle_name\": {\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"bundle_version\": {\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"entitlements\": {\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"exception_domain\": {\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"files\": {\n          \"description\": \"List of custom files to add to the application bundle. Maps the path in the Contents directory in the app to the path of the file to include (relative to the current working directory).\",\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          }\n        },\n        \"frameworks\": {\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"hardened_runtime\": {\n          \"description\": \"Preserve the hardened runtime version flag, see <https://developer.apple.com/documentation/security/hardened_runtime>\\n\\nSettings this to `false` is useful when using an ad-hoc signature, making it less strict.\",\n          \"type\": \"boolean\"\n        },\n        \"info_plist_path\": {\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"license\": {\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"minimum_system_version\": {\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"provider_short_name\": {\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"signing_identity\": {\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        }\n      }\n    },\n    \"MacosConfig\": {\n      \"description\": \"macOS-specific configuration.\\n\\nExample: ```toml [macos] minimum_system_version = \\\"11.0\\\" identifier = \\\"com.example.myapp.macos\\\"  # Override bundle.identifier for macOS\\n\\n# macOS signing (previously in [bundle.macos]) signing_identity = \\\"Developer ID Application: My Company\\\" provider_short_name = \\\"MYCOMPANY\\\" ```\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"bundle_name\": {\n          \"description\": \"The bundle short version string (CFBundleShortVersionString).\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"bundle_version\": {\n          \"description\": \"The bundle version string (CFBundleVersion).\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"category\": {\n          \"description\": \"App category for the Mac App Store. E.g., \\\"public.app-category.productivity\\\"\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"copyright\": {\n          \"description\": \"Copyright notice. Overrides `bundle.copyright` for macOS builds.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"document_types\": {\n          \"description\": \"Document types the app can open (uses same format as iOS).\",\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/IosDocumentType\"\n          }\n        },\n        \"entitlements\": {\n          \"description\": \"macOS entitlements.\",\n          \"$ref\": \"#/definitions/MacosEntitlements\"\n        },\n        \"entitlements_file\": {\n          \"description\": \"Path to custom entitlements file for code signing. This overrides the generated entitlements.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"exception_domain\": {\n          \"description\": \"Exception domain for App Transport Security.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"exported_type_identifiers\": {\n          \"description\": \"Exported type identifiers (custom UTIs).\",\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/IosTypeIdentifier\"\n          }\n        },\n        \"files\": {\n          \"description\": \"Additional files to include in the app bundle. Maps the path in the Contents directory to the source file path.\",\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          }\n        },\n        \"frameworks\": {\n          \"description\": \"Frameworks to embed.\",\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"hardened_runtime\": {\n          \"description\": \"Preserve the hardened runtime version flag. Setting this to false is useful when using an ad-hoc signature.\",\n          \"type\": [\n            \"boolean\",\n            \"null\"\n          ]\n        },\n        \"icon\": {\n          \"description\": \"Icons for the app. Overrides `bundle.icon` for macOS builds.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"identifier\": {\n          \"description\": \"The app's identifier (e.g., \\\"com.example.myapp\\\"). Overrides `bundle.identifier` for macOS builds.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"imported_type_identifiers\": {\n          \"description\": \"Imported type identifiers.\",\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/IosTypeIdentifier\"\n          }\n        },\n        \"info_plist\": {\n          \"description\": \"Path to custom Info.plist.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"license\": {\n          \"description\": \"License file to include in DMG.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"long_description\": {\n          \"description\": \"Long description. Overrides `bundle.long_description` for macOS builds.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"minimum_system_version\": {\n          \"description\": \"Minimum macOS version (e.g., \\\"11.0\\\").\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"plist\": {\n          \"description\": \"Additional Info.plist keys.\",\n          \"type\": \"object\",\n          \"additionalProperties\": true\n        },\n        \"provider_short_name\": {\n          \"description\": \"The provider short name for notarization.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"publisher\": {\n          \"description\": \"The app's publisher. Overrides `bundle.publisher` for macOS builds.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"raw\": {\n          \"description\": \"Raw injection points.\",\n          \"$ref\": \"#/definitions/MacosRawConfig\"\n        },\n        \"resources\": {\n          \"description\": \"Additional resources to bundle. Overrides `bundle.resources` for macOS builds.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"short_description\": {\n          \"description\": \"Short description. Overrides `bundle.short_description` for macOS builds.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"signing_identity\": {\n          \"description\": \"The signing identity to use for code signing. E.g., \\\"Developer ID Application: My Company (TEAMID)\\\"\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"url_schemes\": {\n          \"description\": \"Additional URL schemes beyond unified `[deep_links]`.schemes. These are merged with the unified schemes.\",\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        }\n      }\n    },\n    \"MacosEntitlements\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"addressbook\": {\n          \"description\": \"Address book access.\",\n          \"type\": [\n            \"boolean\",\n            \"null\"\n          ]\n        },\n        \"allow-jit\": {\n          \"description\": \"Allow JIT.\",\n          \"type\": [\n            \"boolean\",\n            \"null\"\n          ]\n        },\n        \"allow-unsigned-executable-memory\": {\n          \"description\": \"Allow unsigned executable memory.\",\n          \"type\": [\n            \"boolean\",\n            \"null\"\n          ]\n        },\n        \"app-sandbox\": {\n          \"description\": \"Enable App Sandbox.\",\n          \"type\": [\n            \"boolean\",\n            \"null\"\n          ]\n        },\n        \"bluetooth\": {\n          \"description\": \"Bluetooth access.\",\n          \"type\": [\n            \"boolean\",\n            \"null\"\n          ]\n        },\n        \"calendars\": {\n          \"description\": \"Calendars access.\",\n          \"type\": [\n            \"boolean\",\n            \"null\"\n          ]\n        },\n        \"camera\": {\n          \"description\": \"Camera access.\",\n          \"type\": [\n            \"boolean\",\n            \"null\"\n          ]\n        },\n        \"disable-library-validation\": {\n          \"description\": \"Disable library validation.\",\n          \"type\": [\n            \"boolean\",\n            \"null\"\n          ]\n        },\n        \"files-user-selected\": {\n          \"description\": \"User-selected file access (read-write).\",\n          \"type\": [\n            \"boolean\",\n            \"null\"\n          ]\n        },\n        \"files-user-selected-readonly\": {\n          \"description\": \"User-selected file access (read-only).\",\n          \"type\": [\n            \"boolean\",\n            \"null\"\n          ]\n        },\n        \"location\": {\n          \"description\": \"Location services.\",\n          \"type\": [\n            \"boolean\",\n            \"null\"\n          ]\n        },\n        \"microphone\": {\n          \"description\": \"Microphone access.\",\n          \"type\": [\n            \"boolean\",\n            \"null\"\n          ]\n        },\n        \"network-client\": {\n          \"description\": \"Outgoing network connections.\",\n          \"type\": [\n            \"boolean\",\n            \"null\"\n          ]\n        },\n        \"network-server\": {\n          \"description\": \"Incoming network connections.\",\n          \"type\": [\n            \"boolean\",\n            \"null\"\n          ]\n        },\n        \"print\": {\n          \"description\": \"Printing.\",\n          \"type\": [\n            \"boolean\",\n            \"null\"\n          ]\n        },\n        \"usb\": {\n          \"description\": \"USB access.\",\n          \"type\": [\n            \"boolean\",\n            \"null\"\n          ]\n        }\n      },\n      \"additionalProperties\": true\n    },\n    \"MacosRawConfig\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"entitlements\": {\n          \"description\": \"Raw XML to inject into entitlements.plist.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"info_plist\": {\n          \"description\": \"Raw XML to inject into Info.plist.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        }\n      }\n    },\n    \"NSISInstallerMode\": {\n      \"type\": \"string\",\n      \"enum\": [\n        \"CurrentUser\",\n        \"PerMachine\",\n        \"Both\"\n      ]\n    },\n    \"NsisSettings\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"custom_language_files\": {\n          \"type\": [\n            \"object\",\n            \"null\"\n          ],\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          }\n        },\n        \"display_language_selector\": {\n          \"type\": \"boolean\"\n        },\n        \"header_image\": {\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"install_mode\": {\n          \"$ref\": \"#/definitions/NSISInstallerMode\"\n        },\n        \"installer_hooks\": {\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"installer_icon\": {\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"languages\": {\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"license\": {\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"minimum_webview2_version\": {\n          \"description\": \"Try to ensure that the WebView2 version is equal to or newer than this version, if the user's WebView2 is older than this version, the installer will try to trigger a WebView2 update.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"sidebar_image\": {\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"start_menu_folder\": {\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"template\": {\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        }\n      }\n    },\n    \"PermissionsConfig\": {\n      \"description\": \"Unified permission configuration that maps to platform-specific identifiers.\\n\\nExample: ```toml [permissions] location = { precision = \\\"fine\\\", description = \\\"Track your runs\\\" } camera = { description = \\\"Take photos for your profile\\\" } ```\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"background-location\": {\n          \"description\": \"Background location updates.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/SimplePermission\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"biometrics\": {\n          \"description\": \"Biometric authentication (Face ID, fingerprint).\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/SimplePermission\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"bluetooth\": {\n          \"description\": \"Bluetooth connectivity.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/SimplePermission\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"calendar\": {\n          \"description\": \"Calendar access.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/StoragePermission\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"camera\": {\n          \"description\": \"Camera access permission.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/SimplePermission\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"contacts\": {\n          \"description\": \"Contacts access.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/StoragePermission\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"health\": {\n          \"description\": \"Health data access.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/StoragePermission\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"homekit\": {\n          \"description\": \"HomeKit integration (iOS only).\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/SimplePermission\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"local-network\": {\n          \"description\": \"Local network access.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/SimplePermission\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"location\": {\n          \"description\": \"Location permission with precision level. Maps to ACCESS_FINE_LOCATION/ACCESS_COARSE_LOCATION on Android, NSLocationWhenInUseUsageDescription on iOS/macOS.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/LocationPermission\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"media-library\": {\n          \"description\": \"Media library access.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/SimplePermission\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"microphone\": {\n          \"description\": \"Microphone access permission.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/SimplePermission\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"motion\": {\n          \"description\": \"Motion and fitness data.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/SimplePermission\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"nearby-wifi\": {\n          \"description\": \"Nearby Wi-Fi devices (Android).\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/SimplePermission\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"nfc\": {\n          \"description\": \"NFC access.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/SimplePermission\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"notifications\": {\n          \"description\": \"Push notifications permission.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/SimplePermission\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"photos\": {\n          \"description\": \"Photo library access.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/StoragePermission\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"siri\": {\n          \"description\": \"Siri integration (iOS only).\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/SimplePermission\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"speech\": {\n          \"description\": \"Speech recognition.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/SimplePermission\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        }\n      }\n    },\n    \"RawPermission\": {\n      \"description\": \"Raw platform permission entry.\",\n      \"type\": \"object\",\n      \"required\": [\n        \"description\"\n      ],\n      \"properties\": {\n        \"description\": {\n          \"type\": \"string\"\n        }\n      }\n    },\n    \"SimplePermission\": {\n      \"description\": \"Simple permission with just a description.\",\n      \"type\": \"object\",\n      \"required\": [\n        \"description\"\n      ],\n      \"properties\": {\n        \"description\": {\n          \"description\": \"User-facing description shown in permission dialogs.\",\n          \"type\": \"string\"\n        }\n      }\n    },\n    \"StorageAccess\": {\n      \"type\": \"string\",\n      \"enum\": [\n        \"read\",\n        \"write\",\n        \"read-write\"\n      ]\n    },\n    \"StoragePermission\": {\n      \"description\": \"Storage permission with access level control.\",\n      \"type\": \"object\",\n      \"required\": [\n        \"description\"\n      ],\n      \"properties\": {\n        \"access\": {\n          \"description\": \"Access level: \\\"read\\\", \\\"write\\\", or \\\"read-write\\\".\",\n          \"$ref\": \"#/definitions/StorageAccess\"\n        },\n        \"description\": {\n          \"description\": \"User-facing description shown in permission dialogs.\",\n          \"type\": \"string\"\n        }\n      }\n    },\n    \"WasmOptConfig\": {\n      \"description\": \"The wasm-opt configuration\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"debug\": {\n          \"description\": \"Keep debug symbols in the wasm file\",\n          \"type\": \"boolean\"\n        },\n        \"extra_features\": {\n          \"description\": \"Extra arguments to pass to wasm-opt\\n\\nFor example, to enable simd, you can set this to `[\\\"--enable-simd\\\"]`.\\n\\nYou can also disable features by prefixing them with `--disable-`, e.g. `[\\\"--disable-bulk-memory\\\"]`.\\n\\nCurrently only --enable and --disable flags are supported.\",\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"keep_names\": {\n          \"description\": \"Keep the wasm name section, useful for profiling and debugging\\n\\nUnlike `debug` which preserves DWARF debug symbols (requiring a browser extension to read), the name section allows tools like `console_error_panic_hook` to print backtraces with human-readable function names without any browser extension.\",\n          \"type\": \"boolean\"\n        },\n        \"level\": {\n          \"description\": \"The wasm-opt level to use for release builds [default: s] Options: - z: optimize aggressively for size - s: optimize for size - 1: optimize for speed - 2: optimize for more for speed - 3: optimize for even more for speed - 4: optimize aggressively for speed\",\n          \"$ref\": \"#/definitions/WasmOptLevel\"\n        },\n        \"memory_packing\": {\n          \"description\": \"Enable memory packing\",\n          \"type\": \"boolean\"\n        }\n      }\n    },\n    \"WasmOptLevel\": {\n      \"description\": \"The wasm-opt level to use for release web builds [default: Z]\",\n      \"oneOf\": [\n        {\n          \"description\": \"Optimize aggressively for size\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"z\"\n          ]\n        },\n        {\n          \"description\": \"Optimize for size\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"s\"\n          ]\n        },\n        {\n          \"description\": \"Don't optimize\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"0\"\n          ]\n        },\n        {\n          \"description\": \"Optimize for speed\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"1\"\n          ]\n        },\n        {\n          \"description\": \"Optimize for more for speed\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"2\"\n          ]\n        },\n        {\n          \"description\": \"Optimize for even more for speed\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"3\"\n          ]\n        },\n        {\n          \"description\": \"Optimize aggressively for speed\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"4\"\n          ]\n        }\n      ]\n    },\n    \"WebAppConfig\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"base_path\": {\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"title\": {\n          \"type\": \"string\"\n        }\n      }\n    },\n    \"WebConfig\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"app\": {\n          \"$ref\": \"#/definitions/WebAppConfig\"\n        },\n        \"https\": {\n          \"$ref\": \"#/definitions/WebHttpsConfig\"\n        },\n        \"pre_compress\": {\n          \"description\": \"Whether to enable pre-compression of assets and wasm during a web build in release mode\",\n          \"type\": \"boolean\"\n        },\n        \"proxy\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/WebProxyConfig\"\n          }\n        },\n        \"resource\": {\n          \"$ref\": \"#/definitions/WebResourceConfig\"\n        },\n        \"wasm_opt\": {\n          \"description\": \"The wasm-opt configuration\",\n          \"$ref\": \"#/definitions/WasmOptConfig\"\n        },\n        \"watcher\": {\n          \"$ref\": \"#/definitions/WebWatcherConfig\"\n        }\n      }\n    },\n    \"WebDevResourceConfig\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"script\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"style\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        }\n      }\n    },\n    \"WebHttpsConfig\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"cert_path\": {\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"enabled\": {\n          \"type\": [\n            \"boolean\",\n            \"null\"\n          ]\n        },\n        \"key_path\": {\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"mkcert\": {\n          \"type\": [\n            \"boolean\",\n            \"null\"\n          ]\n        }\n      }\n    },\n    \"WebProxyConfig\": {\n      \"type\": \"object\",\n      \"required\": [\n        \"backend\"\n      ],\n      \"properties\": {\n        \"backend\": {\n          \"type\": \"string\"\n        }\n      }\n    },\n    \"WebResourceConfig\": {\n      \"type\": \"object\",\n      \"required\": [\n        \"dev\"\n      ],\n      \"properties\": {\n        \"dev\": {\n          \"$ref\": \"#/definitions/WebDevResourceConfig\"\n        },\n        \"script\": {\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"style\": {\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        }\n      }\n    },\n    \"WebWatcherConfig\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"index_on_404\": {\n          \"type\": \"boolean\"\n        },\n        \"reload_html\": {\n          \"type\": \"boolean\"\n        },\n        \"watch_path\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        }\n      }\n    },\n    \"WebviewInstallMode\": {\n      \"oneOf\": [\n        {\n          \"type\": \"string\",\n          \"enum\": [\n            \"Skip\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"required\": [\n            \"DownloadBootstrapper\"\n          ],\n          \"properties\": {\n            \"DownloadBootstrapper\": {\n              \"type\": \"object\",\n              \"required\": [\n                \"silent\"\n              ],\n              \"properties\": {\n                \"silent\": {\n                  \"type\": \"boolean\"\n                }\n              }\n            }\n          },\n          \"additionalProperties\": false\n        },\n        {\n          \"type\": \"object\",\n          \"required\": [\n            \"EmbedBootstrapper\"\n          ],\n          \"properties\": {\n            \"EmbedBootstrapper\": {\n              \"type\": \"object\",\n              \"required\": [\n                \"silent\"\n              ],\n              \"properties\": {\n                \"silent\": {\n                  \"type\": \"boolean\"\n                }\n              }\n            }\n          },\n          \"additionalProperties\": false\n        },\n        {\n          \"type\": \"object\",\n          \"required\": [\n            \"OfflineInstaller\"\n          ],\n          \"properties\": {\n            \"OfflineInstaller\": {\n              \"type\": \"object\",\n              \"required\": [\n                \"silent\"\n              ],\n              \"properties\": {\n                \"silent\": {\n                  \"type\": \"boolean\"\n                }\n              }\n            }\n          },\n          \"additionalProperties\": false\n        },\n        {\n          \"type\": \"object\",\n          \"required\": [\n            \"FixedRuntime\"\n          ],\n          \"properties\": {\n            \"FixedRuntime\": {\n              \"type\": \"object\",\n              \"required\": [\n                \"path\"\n              ],\n              \"properties\": {\n                \"path\": {\n                  \"type\": \"string\"\n                }\n              }\n            }\n          },\n          \"additionalProperties\": false\n        }\n      ]\n    },\n    \"WidgetExtensionConfig\": {\n      \"description\": \"Configuration for an iOS Widget Extension.\\n\\nWidget extensions are compiled as Swift executables and bundled as .appex bundles in the app's PlugIns folder.\\n\\nExample in Dioxus.toml: ```toml [[ios.widget_extensions]] source = \\\"src/ios/widget\\\" display_name = \\\"Location Widget\\\" bundle_id_suffix = \\\"location-widget\\\" deployment_target = \\\"16.2\\\" module_name = \\\"GeolocationPlugin\\\" ```\",\n      \"type\": \"object\",\n      \"required\": [\n        \"bundle_id_suffix\",\n        \"display_name\",\n        \"module_name\",\n        \"source\"\n      ],\n      \"properties\": {\n        \"bundle_id_suffix\": {\n          \"description\": \"Bundle ID suffix appended to the app's bundle identifier. For example, if the app is \\\"com.example.app\\\" and suffix is \\\"location-widget\\\", the widget bundle ID will be \\\"com.example.app.location-widget\\\".\",\n          \"type\": \"string\"\n        },\n        \"deployment_target\": {\n          \"description\": \"Minimum deployment target (e.g., \\\"16.2\\\"). Defaults to the app's iOS deployment target if not specified.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"display_name\": {\n          \"description\": \"Display name for the widget (shown in system UI).\",\n          \"type\": \"string\"\n        },\n        \"module_name\": {\n          \"description\": \"Swift module name for the widget. This MUST match the module name used by the main app's Swift plugin for ActivityKit type matching to work.\",\n          \"type\": \"string\"\n        },\n        \"source\": {\n          \"description\": \"Path to the Swift package source directory (relative to project root).\",\n          \"type\": \"string\"\n        }\n      }\n    },\n    \"WindowsConfig\": {\n      \"description\": \"Windows-specific configuration.\\n\\nExample: ```toml [windows] identifier = \\\"com.example.myapp.windows\\\"  # Override bundle.identifier for Windows\\n\\n# Windows installer settings (previously in [bundle.windows]) [windows.nsis] install_mode = \\\"PerMachine\\\"\\n\\n[windows.wix] language = [[\\\"en-US\\\", null]] ```\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"allow_downgrades\": {\n          \"description\": \"Allow downgrades when installing.\",\n          \"type\": [\n            \"boolean\",\n            \"null\"\n          ]\n        },\n        \"capabilities\": {\n          \"description\": \"UWP/MSIX capabilities.\",\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"category\": {\n          \"description\": \"App category. Overrides `bundle.category` for Windows builds.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"certificate_thumbprint\": {\n          \"description\": \"Certificate thumbprint for code signing.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"copyright\": {\n          \"description\": \"Copyright notice. Overrides `bundle.copyright` for Windows builds.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"device_capabilities\": {\n          \"description\": \"Device capabilities.\",\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"digest_algorithm\": {\n          \"description\": \"Digest algorithm for code signing.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"icon\": {\n          \"description\": \"Icons for the app. Overrides `bundle.icon` for Windows builds.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"icon_path\": {\n          \"description\": \"Path to custom Windows icon.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"identifier\": {\n          \"description\": \"The app's identifier (e.g., \\\"com.example.myapp\\\"). Overrides `bundle.identifier` for Windows builds.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"long_description\": {\n          \"description\": \"Long description. Overrides `bundle.long_description` for Windows builds.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"nsis\": {\n          \"description\": \"NSIS installer settings.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/WindowsNsisSettings\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"publisher\": {\n          \"description\": \"The app's publisher. Overrides `bundle.publisher` for Windows builds.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"resources\": {\n          \"description\": \"Additional resources to bundle. Overrides `bundle.resources` for Windows builds.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"restricted_capabilities\": {\n          \"description\": \"Restricted capabilities.\",\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"short_description\": {\n          \"description\": \"Short description. Overrides `bundle.short_description` for Windows builds.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"sign_command\": {\n          \"description\": \"Custom sign command.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/WindowsSignCommand\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"timestamp_url\": {\n          \"description\": \"Timestamp server URL for code signing.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"tsp\": {\n          \"description\": \"Use TSP (RFC 3161) timestamp.\",\n          \"type\": [\n            \"boolean\",\n            \"null\"\n          ]\n        },\n        \"webview_install_mode\": {\n          \"description\": \"WebView2 installation mode.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/WindowsWebviewInstallMode\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"wix\": {\n          \"description\": \"WiX installer settings.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/WindowsWixSettings\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        }\n      }\n    },\n    \"WindowsNsisSettings\": {\n      \"description\": \"NSIS installer settings.\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"custom_language_files\": {\n          \"description\": \"Custom language files.\",\n          \"type\": [\n            \"object\",\n            \"null\"\n          ],\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          }\n        },\n        \"display_language_selector\": {\n          \"description\": \"Display language selector.\",\n          \"type\": [\n            \"boolean\",\n            \"null\"\n          ]\n        },\n        \"header_image\": {\n          \"description\": \"Header image path.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"install_mode\": {\n          \"description\": \"Installation mode: \\\"CurrentUser\\\", \\\"PerMachine\\\", or \\\"Both\\\".\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"installer_hooks\": {\n          \"description\": \"Installer hooks script path.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"installer_icon\": {\n          \"description\": \"Installer icon path.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"languages\": {\n          \"description\": \"Languages to include.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"license\": {\n          \"description\": \"License file path.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"minimum_webview2_version\": {\n          \"description\": \"Minimum WebView2 version required.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"sidebar_image\": {\n          \"description\": \"Sidebar image path.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"start_menu_folder\": {\n          \"description\": \"Start menu folder name.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"template\": {\n          \"description\": \"Path to custom NSIS template.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        }\n      }\n    },\n    \"WindowsSettings\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"allow_downgrades\": {\n          \"type\": \"boolean\"\n        },\n        \"certificate_thumbprint\": {\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"digest_algorithm\": {\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"icon_path\": {\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"nsis\": {\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/NsisSettings\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"sign_command\": {\n          \"description\": \"Specify a custom command to sign the binaries. This command needs to have a `%1` in it which is just a placeholder for the binary path, which we will detect and replace before calling the command.\\n\\nExample: ```text sign-cli --arg1 --arg2 %1 ```\\n\\nBy Default we use `signtool.exe` which can be found only on Windows so if you are on another platform and want to cross-compile and sign you will need to use another tool like `osslsigncode`.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/CustomSignCommandSettings\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"timestamp_url\": {\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"tsp\": {\n          \"type\": \"boolean\"\n        },\n        \"webview_fixed_runtime_path\": {\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"webview_install_mode\": {\n          \"$ref\": \"#/definitions/WebviewInstallMode\"\n        },\n        \"wix\": {\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/WixSettings\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        }\n      }\n    },\n    \"WindowsSignCommand\": {\n      \"description\": \"Custom sign command for Windows code signing.\",\n      \"type\": \"object\",\n      \"required\": [\n        \"args\",\n        \"cmd\"\n      ],\n      \"properties\": {\n        \"args\": {\n          \"description\": \"Command arguments. Use \\\"%1\\\" as placeholder for binary path.\",\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"cmd\": {\n          \"description\": \"The command to run.\",\n          \"type\": \"string\"\n        }\n      }\n    },\n    \"WindowsWebviewInstallMode\": {\n      \"description\": \"WebView2 installation mode.\",\n      \"oneOf\": [\n        {\n          \"description\": \"Skip WebView2 installation.\",\n          \"type\": \"object\",\n          \"required\": [\n            \"type\"\n          ],\n          \"properties\": {\n            \"type\": {\n              \"type\": \"string\",\n              \"enum\": [\n                \"Skip\"\n              ]\n            }\n          }\n        },\n        {\n          \"description\": \"Download bootstrapper.\",\n          \"type\": \"object\",\n          \"required\": [\n            \"type\"\n          ],\n          \"properties\": {\n            \"silent\": {\n              \"type\": \"boolean\"\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"enum\": [\n                \"DownloadBootstrapper\"\n              ]\n            }\n          }\n        },\n        {\n          \"description\": \"Embed bootstrapper.\",\n          \"type\": \"object\",\n          \"required\": [\n            \"type\"\n          ],\n          \"properties\": {\n            \"silent\": {\n              \"type\": \"boolean\"\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"enum\": [\n                \"EmbedBootstrapper\"\n              ]\n            }\n          }\n        },\n        {\n          \"description\": \"Use offline installer.\",\n          \"type\": \"object\",\n          \"required\": [\n            \"type\"\n          ],\n          \"properties\": {\n            \"silent\": {\n              \"type\": \"boolean\"\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"enum\": [\n                \"OfflineInstaller\"\n              ]\n            }\n          }\n        },\n        {\n          \"description\": \"Use fixed runtime from path.\",\n          \"type\": \"object\",\n          \"required\": [\n            \"path\",\n            \"type\"\n          ],\n          \"properties\": {\n            \"path\": {\n              \"type\": \"string\"\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"enum\": [\n                \"FixedRuntime\"\n              ]\n            }\n          }\n        }\n      ]\n    },\n    \"WindowsWixSettings\": {\n      \"description\": \"WiX installer settings.\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"banner_path\": {\n          \"description\": \"Banner image path.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"component_group_refs\": {\n          \"description\": \"Component group references.\",\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"component_refs\": {\n          \"description\": \"Component references.\",\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"dialog_image_path\": {\n          \"description\": \"Dialog image path.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"enable_elevated_update_task\": {\n          \"description\": \"Enable elevated update task.\",\n          \"type\": [\n            \"boolean\",\n            \"null\"\n          ]\n        },\n        \"feature_group_refs\": {\n          \"description\": \"Feature group references.\",\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"feature_refs\": {\n          \"description\": \"Feature references.\",\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"fips_compliant\": {\n          \"description\": \"FIPS compliant mode.\",\n          \"type\": [\n            \"boolean\",\n            \"null\"\n          ]\n        },\n        \"fragment_paths\": {\n          \"description\": \"WiX fragment files to include.\",\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"language\": {\n          \"description\": \"Languages and their locale paths.\",\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"array\",\n            \"items\": [\n              {\n                \"type\": \"string\"\n              },\n              {\n                \"type\": [\n                  \"string\",\n                  \"null\"\n                ]\n              }\n            ],\n            \"maxItems\": 2,\n            \"minItems\": 2\n          }\n        },\n        \"license\": {\n          \"description\": \"License file path.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"merge_refs\": {\n          \"description\": \"Merge module references.\",\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"skip_webview_install\": {\n          \"description\": \"Skip WebView2 installation.\",\n          \"type\": [\n            \"boolean\",\n            \"null\"\n          ]\n        },\n        \"template\": {\n          \"description\": \"Path to custom WiX template.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"upgrade_code\": {\n          \"description\": \"MSI upgrade code (GUID).\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"version\": {\n          \"description\": \"MSI version string.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        }\n      }\n    },\n    \"WixSettings\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"banner_path\": {\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"component_group_refs\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"component_refs\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"dialog_image_path\": {\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"enable_elevated_update_task\": {\n          \"type\": \"boolean\"\n        },\n        \"feature_group_refs\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"feature_refs\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"fips_compliant\": {\n          \"type\": \"boolean\"\n        },\n        \"fragment_paths\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"language\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"array\",\n            \"items\": [\n              {\n                \"type\": \"string\"\n              },\n              {\n                \"type\": [\n                  \"string\",\n                  \"null\"\n                ]\n              }\n            ],\n            \"maxItems\": 2,\n            \"minItems\": 2\n          }\n        },\n        \"license\": {\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"merge_refs\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"skip_webview_install\": {\n          \"type\": \"boolean\"\n        },\n        \"template\": {\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"upgrade_code\": {\n          \"description\": \"A GUID upgrade code for MSI installer. This code **_must stay the same across all of your updates_**, otherwise, Windows will treat your update as a different app and your users will have duplicate versions of your app.\\n\\nBy default, tauri generates this code by generating a Uuid v5 using the string `<productName>.exe.app.x64` in the DNS namespace. You can use Tauri's CLI to generate and print this code for you by running `tauri inspect wix-upgrade-code`.\\n\\nIt is recommended that you set this value in your tauri config file to avoid accidental changes in your upgrade code whenever you want to change your product name.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"version\": {\n          \"description\": \"MSI installer version in the format `major.minor.patch.build` (build is optional).\\n\\nBecause a valid version is required for MSI installer, it will be derived from [`tauri_bundler::PackageSettings::version`] if this field is not set.\\n\\nThe first field is the major version and has a maximum value of 255. The second field is the minor version and has a maximum value of 255. The third and fourth fields have a maximum value of 65,535.\\n\\nSee <https://learn.microsoft.com/en-us/windows/win32/msi/productversion> for more info.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "packages/cli/src/build/assets.rs",
    "content": "//! The dioxus asset system.\n//!\n//! This module provides functionality for extracting assets from a binary file and then writing back\n//! their asset hashes directly into the binary file. Previously, we performed asset hashing in the\n//! `asset!()` macro. The new system, implemented here, instead performs the hashing at build time,\n//! which provides more flexibility in the asset processing pipeline.\n//!\n//! We chose to implement this approach since assets might reference each other which means we minimally\n//! need to parse the asset to create a unique hash for each asset before they are used in the application.\n//! The hashes are used both for cache busting the asset in the browser and to cache the asset optimization\n//! process in the build system.\n//!\n//! We use the same lessons learned from the hot-patching engine which parses the binary file and its\n//! symbol table to find symbols that match the `__ASSETS__` prefix. These symbols are ideally data\n//! symbols and contain the BundledAsset data type which implements ConstSerialize and ConstDeserialize.\n//!\n//! When the binary is built, the `dioxus asset!()` macro will emit its metadata into the __ASSETS__\n//! symbols, which we process here. After reading the metadata directly from the executable, we then\n//! hash it and write the hash directly into the binary file.\n//!\n//! During development, we can skip this step for most platforms since local paths are sufficient\n//! for asset loading. However, for WASM and for production builds, we need to ensure that assets\n//! can be found relative to the current exe. Unfortunately, on android, the `current_exe` path is wrong,\n//! so the assets are resolved against the \"asset root\" - which is covered by the asset loader crate.\n//!\n//! Finding the __ASSETS__ symbols is not quite straightforward when hotpatching, especially on WASM\n//! since we build and link the module as relocatable, which is not a stable WASM proposal. In this\n//! implementation, we handle both the non-PIE *and* PIC cases which are rather bespoke to our whole\n//! build system.\n\nuse std::{\n    io::{Cursor, Read, Seek, Write},\n    path::{Path, PathBuf},\n};\n\nuse crate::Result;\nuse anyhow::{bail, Context};\nuse const_serialize::{deserialize_const, serialize_const, ConstVec};\nuse dioxus_cli_opt::AssetManifest;\nuse manganis::{AssetOptions, AssetVariant, BundledAsset, ImageFormat, ImageSize};\nuse manganis_core::{AndroidArtifactMetadata, SwiftPackageMetadata, SymbolData};\nuse object::{File, Object, ObjectSection, ObjectSymbol, ReadCache, ReadRef, Section, Symbol};\nuse pdb::FallibleIterator;\nuse rayon::iter::{IntoParallelRefMutIterator, ParallelIterator};\n\n/// Extract all manganis symbols and their sections from the given object file.\nfn manganis_symbols<'a, 'b, R: ReadRef<'a>>(\n    file: &'b File<'a, R>,\n) -> impl Iterator<Item = (ManganisVersion, Symbol<'a, 'b, R>, Section<'a, 'b, R>)> + 'b {\n    file.symbols().filter_map(move |symbol| {\n        let name = symbol.name().ok()?;\n        let version = looks_like_manganis_symbol(name)?;\n        let section_index = symbol.section_index()?;\n        let section = file.section_by_index(section_index).ok()?;\n        Some((version, symbol, section))\n    })\n}\n\n#[derive(Copy, Clone)]\nenum ManganisVersion {\n    /// The legacy version of the manganis format published with 0.7.0 and 0.7.1\n    Legacy,\n    /// The new version of the manganis format 0.7.2 onward\n    /// This now includes both assets (old BundledAsset format) and permissions (SymbolData format)\n    New,\n}\n\nimpl ManganisVersion {\n    fn size(&self) -> usize {\n        match self {\n            ManganisVersion::Legacy => {\n                <manganis_core_07::BundledAsset as const_serialize_07::SerializeConst>::MEMORY_LAYOUT.size()\n            }\n            // For new format, we use a larger buffer size to accommodate variable-length CBOR\n            // The actual size will be determined by CBOR deserialization\n            ManganisVersion::New => 4096,\n        }\n    }\n\n    /// Deserialize data, trying multiple formats for backward compatibility\n    ///\n    /// Tries in order:\n    /// 1. SymbolData (new unified format) - can contain Asset or Permission\n    /// 2. BundledAsset (old asset format) - for backward compatibility\n    fn deserialize(&self, data: &[u8]) -> Option<SymbolDataOrAsset> {\n        match self {\n            ManganisVersion::Legacy => {\n                let buffer = const_serialize_07::ConstReadBuffer::new(data);\n\n                let (_, legacy_asset) =\n                    const_serialize_07::deserialize_const!(manganis_core_07::BundledAsset, buffer)?;\n\n                Some(SymbolDataOrAsset::Asset(legacy_asset_to_modern_asset(\n                    &legacy_asset,\n                )))\n            }\n            ManganisVersion::New => {\n                // First try SymbolData (new format with enum variant)\n                // const-serialize deserialization returns (remaining_bytes, value)\n                // We accept if remaining is empty or contains only padding (zeros)\n                if let Some((remaining, symbol_data)) = deserialize_const!(SymbolData, data) {\n                    // Check if remaining bytes are all zeros (padding) or empty\n                    // This handles the case where the linker section is larger than the actual data\n                    // Be very lenient with padding - as long as we successfully deserialized, accept it\n                    // The padding is just zeros added to fill the buffer size\n                    let is_valid = remaining.is_empty()\n                        || remaining.iter().all(|&b| b == 0)\n                        || remaining.len() <= data.len(); // Allow any amount of padding as long as it's not larger than data\n\n                    if is_valid {\n                        return Some(SymbolDataOrAsset::SymbolData(Box::new(symbol_data)));\n                    } else {\n                        tracing::debug!(\n                            \"SymbolData deserialized but invalid padding: {} remaining bytes out of {} total (first few bytes: {:?})\",\n                            remaining.len(),\n                            data.len(),\n                            &data[..data.len().min(32)]\n                        );\n                    }\n                } else {\n                    tracing::debug!(\n                        \"Failed to deserialize as SymbolData. Data length: {}, first few bytes: {:?}\",\n                        data.len(),\n                        &data[..data.len().min(32)]\n                    );\n                }\n\n                // Fallback: try BundledAsset (direct format - assets are now serialized this way)\n                // This handles assets that were serialized directly as BundledAsset (not wrapped in SymbolData)\n                if let Some((remaining, asset)) = deserialize_const!(BundledAsset, data) {\n                    // Check if remaining bytes are all zeros (padding) or empty\n                    // Accept any amount of padding as long as it's all zeros (which is what we pad with)\n                    let is_valid = remaining.is_empty() || remaining.iter().all(|&b| b == 0);\n\n                    if is_valid {\n                        tracing::debug!(\n                            \"Successfully deserialized BundledAsset, remaining padding: {} bytes\",\n                            remaining.len()\n                        );\n                        return Some(SymbolDataOrAsset::Asset(asset));\n                    } else {\n                        tracing::warn!(\n                            \"BundledAsset deserialized but remaining bytes are not all zeros: {} remaining bytes, first few: {:?}\",\n                            remaining.len(),\n                            &remaining[..remaining.len().min(16)]\n                        );\n                    }\n                } else {\n                    tracing::warn!(\n                        \"Failed to deserialize as BundledAsset. Data length: {}, first 32 bytes: {:?}\",\n                        data.len(),\n                        &data[..data.len().min(32)]\n                    );\n                }\n\n                None\n            }\n        }\n    }\n\n    fn serialize_asset(&self, asset: &BundledAsset) -> Vec<u8> {\n        match self {\n            ManganisVersion::Legacy => {\n                let legacy_asset = modern_asset_to_legacy_asset(asset);\n                let buffer = const_serialize_07::serialize_const(\n                    &legacy_asset,\n                    const_serialize_07::ConstVec::new(),\n                );\n                buffer.as_ref().to_vec()\n            }\n            ManganisVersion::New => {\n                // New format: serialize as BundledAsset directly (backward compatible)\n                // Pad to 4096 bytes to match the linker output size\n                let buffer = serialize_const(asset, ConstVec::new());\n                let mut data = buffer.as_ref().to_vec();\n                if data.len() < 4096 {\n                    data.resize(4096, 0);\n                }\n                data\n            }\n        }\n    }\n\n    fn serialize_symbol_data(&self, data: &SymbolData) -> Option<Vec<u8>> {\n        match self {\n            ManganisVersion::Legacy => None,\n            ManganisVersion::New => {\n                let buffer = serialize_const(data, ConstVec::new());\n                let mut bytes = buffer.as_ref().to_vec();\n                if bytes.len() < 4096 {\n                    bytes.resize(4096, 0);\n                }\n                Some(bytes)\n            }\n        }\n    }\n}\n\n/// Result of deserializing a symbol - can be either SymbolData or legacy Asset\n#[derive(Debug, Clone)]\n#[allow(clippy::large_enum_variant)]\nenum SymbolDataOrAsset {\n    /// New unified format (can contain Asset or Permission)\n    SymbolData(Box<SymbolData>),\n\n    /// Old asset format (backward compatibility)\n    Asset(BundledAsset),\n}\n\n#[derive(Clone, Copy)]\nstruct AssetWriteEntry {\n    symbol: ManganisSymbolOffset,\n    asset_index: usize,\n    representation: AssetRepresentation,\n}\n\nimpl AssetWriteEntry {\n    fn new(\n        symbol: ManganisSymbolOffset,\n        asset_index: usize,\n        representation: AssetRepresentation,\n    ) -> Self {\n        Self {\n            symbol,\n            asset_index,\n            representation,\n        }\n    }\n}\n\n#[derive(Clone, Copy)]\nenum AssetRepresentation {\n    /// Serialized as a raw BundledAsset (legacy or new format)\n    RawBundled,\n    /// Serialized as SymbolData::Asset (new CBOR format)\n    SymbolData,\n}\n\nfn legacy_asset_to_modern_asset(\n    legacy_asset: &manganis_core_07::BundledAsset,\n) -> manganis_core::BundledAsset {\n    let bundled_path = legacy_asset.bundled_path();\n    let absolute_path = legacy_asset.absolute_source_path();\n    let legacy_options = legacy_asset.options();\n    let add_hash = legacy_options.hash_suffix();\n    let options = match legacy_options.variant() {\n        manganis_core_07::AssetVariant::Image(image) => {\n            let format = match image.format() {\n                manganis_core_07::ImageFormat::Png => ImageFormat::Png,\n                manganis_core_07::ImageFormat::Jpg => ImageFormat::Jpg,\n                manganis_core_07::ImageFormat::Webp => ImageFormat::Webp,\n                manganis_core_07::ImageFormat::Avif => ImageFormat::Avif,\n                manganis_core_07::ImageFormat::Unknown => ImageFormat::Unknown,\n            };\n            let size = match image.size() {\n                manganis_core_07::ImageSize::Automatic => ImageSize::Automatic,\n                manganis_core_07::ImageSize::Manual { width, height } => {\n                    ImageSize::Manual { width, height }\n                }\n            };\n            let preload = image.preloaded();\n\n            AssetOptions::image()\n                .with_format(format)\n                .with_size(size)\n                .with_preload(preload)\n                .with_hash_suffix(add_hash)\n                .into_asset_options()\n        }\n        manganis_core_07::AssetVariant::Folder(_) => AssetOptions::folder()\n            .with_hash_suffix(add_hash)\n            .into_asset_options(),\n        manganis_core_07::AssetVariant::Css(css) => AssetOptions::css()\n            .with_hash_suffix(add_hash)\n            .with_minify(css.minified())\n            .with_preload(css.preloaded())\n            .with_static_head(css.static_head())\n            .into_asset_options(),\n        manganis_core_07::AssetVariant::CssModule(css_module) => AssetOptions::css_module()\n            .with_hash_suffix(add_hash)\n            .with_minify(css_module.minified())\n            .with_preload(css_module.preloaded())\n            .into_asset_options(),\n        manganis_core_07::AssetVariant::Js(js) => AssetOptions::js()\n            .with_hash_suffix(add_hash)\n            .with_minify(js.minified())\n            .with_preload(js.preloaded())\n            .with_static_head(js.static_head())\n            .into_asset_options(),\n        _ => AssetOptions::builder()\n            .with_hash_suffix(add_hash)\n            .into_asset_options(),\n    };\n\n    BundledAsset::new(absolute_path, bundled_path, options)\n}\n\nfn modern_asset_to_legacy_asset(modern_asset: &BundledAsset) -> manganis_core_07::BundledAsset {\n    let bundled_path = modern_asset.bundled_path();\n    let absolute_path = modern_asset.absolute_source_path();\n    let legacy_options = modern_asset.options();\n    let add_hash = legacy_options.hash_suffix();\n    let options = match legacy_options.variant() {\n        AssetVariant::Image(image) => {\n            let format = match image.format() {\n                ImageFormat::Png => manganis_core_07::ImageFormat::Png,\n                ImageFormat::Jpg => manganis_core_07::ImageFormat::Jpg,\n                ImageFormat::Webp => manganis_core_07::ImageFormat::Webp,\n                ImageFormat::Avif => manganis_core_07::ImageFormat::Avif,\n                ImageFormat::Unknown => manganis_core_07::ImageFormat::Unknown,\n            };\n            let size = match image.size() {\n                ImageSize::Automatic => manganis_core_07::ImageSize::Automatic,\n                ImageSize::Manual { width, height } => {\n                    manganis_core_07::ImageSize::Manual { width, height }\n                }\n            };\n            let preload = image.preloaded();\n\n            manganis_core_07::AssetOptions::image()\n                .with_format(format)\n                .with_size(size)\n                .with_preload(preload)\n                .with_hash_suffix(add_hash)\n                .into_asset_options()\n        }\n        AssetVariant::Folder(_) => manganis_core_07::AssetOptions::folder()\n            .with_hash_suffix(add_hash)\n            .into_asset_options(),\n        AssetVariant::Css(css) => manganis_core_07::AssetOptions::css()\n            .with_hash_suffix(add_hash)\n            .with_minify(css.minified())\n            .with_preload(css.preloaded())\n            .with_static_head(css.static_head())\n            .into_asset_options(),\n        AssetVariant::CssModule(css_module) => manganis_core_07::AssetOptions::css_module()\n            .with_hash_suffix(add_hash)\n            .with_minify(css_module.minified())\n            .with_preload(css_module.preloaded())\n            .into_asset_options(),\n        AssetVariant::Js(js) => manganis_core_07::AssetOptions::js()\n            .with_hash_suffix(add_hash)\n            .with_minify(js.minified())\n            .with_preload(js.preloaded())\n            .with_static_head(js.static_head())\n            .into_asset_options(),\n        _ => manganis_core_07::AssetOptions::builder()\n            .with_hash_suffix(add_hash)\n            .into_asset_options(),\n    };\n\n    manganis_core_07::BundledAsset::new(absolute_path, bundled_path, options)\n}\n\nfn looks_like_manganis_symbol(name: &str) -> Option<ManganisVersion> {\n    if name.contains(\"__MANGANIS__\") {\n        Some(ManganisVersion::Legacy)\n    } else if name.contains(\"__ASSETS__\") {\n        Some(ManganisVersion::New)\n    } else {\n        None\n    }\n}\n\n/// An asset offset in the binary\n#[derive(Clone, Copy)]\nstruct ManganisSymbolOffset {\n    version: ManganisVersion,\n    offset: u64,\n}\n\nimpl ManganisSymbolOffset {\n    fn new(version: ManganisVersion, offset: u64) -> Self {\n        Self { version, offset }\n    }\n}\n\n/// Find the offsets of any manganis symbols in the given file.\nfn find_symbol_offsets<'a, R: ReadRef<'a>>(\n    path: &Path,\n    file_contents: &[u8],\n    file: &File<'a, R>,\n) -> Result<Vec<ManganisSymbolOffset>> {\n    let pdb_file = find_pdb_file(path);\n\n    match file.format() {\n        // We need to handle dynamic offsets in wasm files differently\n        object::BinaryFormat::Wasm => find_wasm_symbol_offsets(file_contents, file),\n        // Windows puts the symbol information in a PDB file alongside the executable.\n        // If this is a windows PE file and we found a PDB file, we will use that to find the symbol offsets.\n        object::BinaryFormat::Pe if pdb_file.is_some() => {\n            find_pdb_symbol_offsets(&pdb_file.unwrap())\n        }\n        // Otherwise, look for manganis symbols in the object file.\n        _ => find_native_symbol_offsets(file),\n    }\n}\n\n/// Find the pdb file matching the executable file.\nfn find_pdb_file(path: &Path) -> Option<PathBuf> {\n    let mut pdb_file = path.with_extension(\"pdb\");\n    // Also try to find it in the same directory as the executable with _'s instead of -'s\n    if let Some(file_name) = pdb_file.file_name() {\n        let new_file_name = file_name.to_string_lossy().replace('-', \"_\");\n        let altrnate_pdb_file = pdb_file.with_file_name(new_file_name);\n        // Keep the most recent pdb file\n        match (pdb_file.metadata(), altrnate_pdb_file.metadata()) {\n            (Ok(pdb_metadata), Ok(alternate_metadata)) => {\n                if let (Ok(pdb_modified), Ok(alternate_modified)) =\n                    (pdb_metadata.modified(), alternate_metadata.modified())\n                {\n                    if pdb_modified < alternate_modified {\n                        pdb_file = altrnate_pdb_file;\n                    }\n                }\n            }\n            (Err(_), Ok(_)) => {\n                pdb_file = altrnate_pdb_file;\n            }\n            _ => {}\n        }\n    }\n    if pdb_file.exists() {\n        Some(pdb_file)\n    } else {\n        None\n    }\n}\n\n/// Find the offsets of any manganis symbols in a pdb file.\nfn find_pdb_symbol_offsets(pdb_file: &Path) -> Result<Vec<ManganisSymbolOffset>> {\n    let pdb_file_handle = std::fs::File::open(pdb_file)?;\n    let mut pdb_file = pdb::PDB::open(pdb_file_handle).context(\"Failed to open PDB file\")?;\n    let Ok(Some(sections)) = pdb_file.sections() else {\n        tracing::error!(\"Failed to read sections from PDB file\");\n        return Ok(Vec::new());\n    };\n    let global_symbols = pdb_file\n        .global_symbols()\n        .context(\"Failed to read global symbols from PDB file\")?;\n    let address_map = pdb_file\n        .address_map()\n        .context(\"Failed to read address map from PDB file\")?;\n    let mut symbols = global_symbols.iter();\n    let mut addresses = Vec::new();\n    while let Ok(Some(symbol)) = symbols.next() {\n        let Ok(pdb::SymbolData::Public(data)) = symbol.parse() else {\n            continue;\n        };\n        let Some(rva) = data.offset.to_section_offset(&address_map) else {\n            continue;\n        };\n\n        let name = data.name.to_string();\n        if let Some(version) = looks_like_manganis_symbol(&name) {\n            let section = sections\n                .get(rva.section as usize - 1)\n                .expect(\"Section index out of bounds\");\n\n            addresses.push(ManganisSymbolOffset::new(\n                version,\n                (section.pointer_to_raw_data + rva.offset) as u64,\n            ));\n        }\n    }\n    Ok(addresses)\n}\n\n/// Find the offsets of any manganis symbols in a native object file.\nfn find_native_symbol_offsets<'a, R: ReadRef<'a>>(\n    file: &File<'a, R>,\n) -> Result<Vec<ManganisSymbolOffset>> {\n    let mut offsets = Vec::new();\n    for (version, symbol, section) in manganis_symbols(file) {\n        let virtual_address = symbol.address();\n\n        let Some((section_range_start, _)) = section.file_range() else {\n            tracing::error!(\n                \"Found __ASSETS__ symbol {:?} in section {}, but the section has no file range\",\n                symbol.name(),\n                section.index()\n            );\n            continue;\n        };\n        // Translate the section_relative_address to the file offset\n        let section_relative_address: u64 = (virtual_address as i128 - section.address() as i128)\n            .try_into()\n            .expect(\"Virtual address should be greater than or equal to section address\");\n        let file_offset = section_range_start + section_relative_address;\n        offsets.push(ManganisSymbolOffset::new(version, file_offset));\n    }\n\n    Ok(offsets)\n}\n\n/// Evaluate a walrus global expression to get its value.\nfn eval_walrus_global_expr(module: &walrus::Module, expr: &walrus::ConstExpr) -> Option<u64> {\n    match expr {\n        walrus::ConstExpr::Value(walrus::ir::Value::I32(value)) => Some(*value as u64),\n        walrus::ConstExpr::Value(walrus::ir::Value::I64(value)) => Some(*value as u64),\n        walrus::ConstExpr::Global(id) => {\n            let global = module.globals.get(*id);\n            if let walrus::GlobalKind::Local(pointer) = &global.kind {\n                eval_walrus_global_expr(module, pointer)\n            } else {\n                None\n            }\n        }\n        _ => None,\n    }\n}\n\n/// Find the value of a global export by name.\nfn find_global_export_value(module: &walrus::Module, name: &str) -> Option<u64> {\n    for export in module.exports.iter() {\n        if export.name == name {\n            if let walrus::ExportItem::Global(g) = export.item {\n                if let walrus::GlobalKind::Local(expr) = &module.globals.get(g).kind {\n                    return eval_walrus_global_expr(module, expr);\n                }\n            }\n        }\n    }\n    None\n}\n\n/// Find the offsets of any manganis symbols in the wasm file.\n///\n/// This handles both standard WASM builds and builds with advanced features like:\n/// - Bulk memory operations (passive data segments)\n/// - Thread Local Storage (TLS)\n/// - Atomics and shared memory\nfn find_wasm_symbol_offsets<'a, R: ReadRef<'a>>(\n    file_contents: &[u8],\n    file: &File<'a, R>,\n) -> Result<Vec<ManganisSymbolOffset>> {\n    let Some(section) = file\n        .sections()\n        .find(|section| section.name() == Ok(\"<data>\"))\n    else {\n        tracing::error!(\"Failed to find <data> section in WASM file\");\n        return Ok(Vec::new());\n    };\n\n    let Some((_, section_range_end)) = section.file_range() else {\n        tracing::error!(\"Failed to find file range for <data> section in WASM file\");\n        return Ok(Vec::new());\n    };\n\n    let section_size = section.data()?.len() as u64;\n    let section_start = section_range_end - section_size;\n\n    // Parse data segments with wasmparser to get file offsets.\n    // Walrus doesn't expose file offset information, so we need wasmparser for this.\n    // With bulk memory operations, there may be multiple data segments.\n    let reader = wasmparser::DataSectionReader::new(wasmparser::BinaryReader::new(\n        &file_contents[section_start as usize..section_range_end as usize],\n        0,\n    ))\n    .context(\"Failed to create WASM data section reader\")?;\n\n    // Collect all data segments with their file offsets and sizes\n    let mut segment_file_info: Vec<(u64, u64)> = Vec::new();\n    for segment in reader.into_iter() {\n        let segment = segment.context(\"Failed to read data segment\")?;\n        segment_file_info.push((\n            (segment.data.as_ptr() as u64)\n                .checked_sub(file_contents.as_ptr() as u64)\n                .expect(\"Data segment should be within file contents\"),\n            segment.data.len() as u64,\n        ));\n    }\n\n    if segment_file_info.is_empty() {\n        return Ok(Vec::new());\n    }\n\n    // Parse the wasm file with walrus to find globals and exports\n    let module = walrus::Module::from_buffer(file_contents)\n        .context(\"Failed to parse WASM module with walrus\")?;\n\n    // Determine the memory base address for symbol lookup\n    let main_memory_walrus = module\n        .data\n        .iter()\n        .next()\n        .context(\"Failed to find main memory in WASM module\")?;\n\n    let main_memory_offset = match &main_memory_walrus.kind {\n        walrus::DataKind::Active { offset, .. } => {\n            // Active segments have an explicit offset expression\n            eval_walrus_global_expr(&module, offset).unwrap_or_default()\n        }\n        walrus::DataKind::Passive => {\n            // For passive segments (bulk memory operations), there's no static offset.\n            // The memory.init instruction determines placement at runtime.\n            //\n            // Try to find the actual memory base from linker exports:\n            // - __memory_base: Set by the linker for bulk-memory builds\n            // - Falls back to 0x100000 (Rust/LLVM default for static data)\n            //\n            // With TLS support, the linker calculates symbol addresses as if TLS data\n            // is at the base address followed by main data. But at runtime, TLS is stored\n            // separately per-thread via __wasm_init_tls. We detect TLS by looking for\n            // __tls_size and adjust accordingly.\n            //\n            // IMPORTANT: The linker aligns main data to a 4-byte boundary after TLS.\n            // This alignment padding exists in MEMORY but NOT in the FILE. We must\n            // use the aligned TLS size for base calculation, but the file segments\n            // are stored without this padding.\n            let memory_base = find_global_export_value(&module, \"__memory_base\");\n            let tls_size = find_global_export_value(&module, \"__tls_size\").unwrap_or(0);\n\n            // If TLS is present and segment 0 matches TLS size, remove TLS segment\n            // from our file info since it's not where data symbols point\n            if tls_size > 0 && !segment_file_info.is_empty() && segment_file_info[0].1 == tls_size {\n                segment_file_info.remove(0);\n            }\n\n            // Align TLS size up to 4 bytes to match linker's memory layout.\n            // The linker aligns main data to a 4-byte boundary after TLS, so symbol\n            // addresses are calculated from (memory_base + aligned_tls_size).\n            // However, file segments are stored without this alignment padding.\n            let tls_aligned = (tls_size + 3) & !3;\n\n            // Use __memory_base if available (set by linker in release builds),\n            // otherwise fall back to 0x100000 (debug builds default)\n            memory_base.unwrap_or(0x100000u64) + tls_aligned\n        }\n    };\n\n    // Find all manganis symbols and calculate their file offsets\n    let mut offsets = Vec::new();\n\n    for export in module.exports.iter() {\n        let Some(version) = looks_like_manganis_symbol(&export.name) else {\n            continue;\n        };\n\n        let walrus::ExportItem::Global(global) = export.item else {\n            continue;\n        };\n\n        let global_data = module.globals.get(global);\n        let walrus::GlobalKind::Local(pointer) = global_data.kind else {\n            continue;\n        };\n\n        let Some(virtual_address) = eval_walrus_global_expr(&module, &pointer) else {\n            tracing::error!(\n                \"Found __ASSETS__ symbol {:?} in WASM file, but the global expression could not be evaluated\",\n                export.name\n            );\n            continue;\n        };\n\n        // Calculate offset relative to the data base address\n        let data_relative_offset =\n            match (virtual_address as i128).checked_sub(main_memory_offset as i128) {\n                Some(offset) if offset >= 0 => offset as u64,\n                _ => {\n                    tracing::error!(\n                        \"Virtual address 0x{:x} is below main memory offset 0x{:x}\",\n                        virtual_address,\n                        main_memory_offset\n                    );\n                    continue;\n                }\n            };\n\n        // Find which segment this offset falls into.\n        // Segments are laid out contiguously in memory.\n        let mut cumulative_offset = 0u64;\n        let mut file_offset = None;\n\n        for (seg_file_offset, seg_size) in segment_file_info.iter() {\n            if data_relative_offset < cumulative_offset + seg_size {\n                let offset_in_segment = data_relative_offset - cumulative_offset;\n                file_offset = Some(seg_file_offset + offset_in_segment);\n                break;\n            }\n            cumulative_offset += seg_size;\n        }\n\n        let Some(file_offset) = file_offset else {\n            tracing::error!(\n                \"Virtual address 0x{:x} is beyond all data segments\",\n                virtual_address\n            );\n            continue;\n        };\n\n        offsets.push(ManganisSymbolOffset::new(version, file_offset));\n    }\n\n    Ok(offsets)\n}\n\n/// Result of extracting symbols from a binary file\n#[derive(Debug, Clone)]\npub(crate) struct SymbolExtractionResult {\n    /// Assets found in the binary\n    pub assets: Vec<BundledAsset>,\n\n    /// Android plugin artifacts discovered in the binary\n    pub android_artifacts: Vec<AndroidArtifactMetadata>,\n\n    /// Swift packages discovered in the binary\n    pub swift_packages: Vec<SwiftPackageMetadata>,\n}\n\n/// Find all assets in the given file, hash them, and write them back to the file.\n/// Also extracts Android/Swift plugin metadata for FFI bindings.\npub(crate) async fn extract_symbols_from_file(\n    path: impl AsRef<Path>,\n) -> Result<SymbolExtractionResult> {\n    let path = path.as_ref();\n    let mut file = open_file_for_writing_with_timeout(\n        path,\n        std::fs::OpenOptions::new().write(true).read(true),\n    )\n    .await?;\n\n    let mut file_contents = Vec::new();\n    file.read_to_end(&mut file_contents)?;\n    let mut reader = Cursor::new(&file_contents);\n    let read_cache = ReadCache::new(&mut reader);\n    let object_file = object::File::parse(&read_cache)?;\n    let offsets = find_symbol_offsets(path, &file_contents, &object_file)?;\n\n    let mut assets = Vec::new();\n    let mut android_artifacts = Vec::new();\n    let mut swift_packages = Vec::new();\n    let mut write_entries = Vec::new();\n\n    // Read each symbol from the data section using the offsets\n    for symbol in offsets.iter().copied() {\n        let version = symbol.version;\n        let offset = symbol.offset;\n\n        // Read data from file_contents (already loaded into memory)\n        // Use a large buffer for variable length data, but don't exceed file size\n        let buffer_size = version\n            .size()\n            .min(file_contents.len().saturating_sub(offset as usize));\n        if buffer_size == 0 {\n            tracing::warn!(\"Symbol at offset {offset} is beyond file size\");\n            continue;\n        }\n\n        let data_in_range = if (offset as usize) + buffer_size <= file_contents.len() {\n            &file_contents[offset as usize..(offset as usize) + buffer_size]\n        } else {\n            &file_contents[offset as usize..]\n        };\n\n        // Try to deserialize - const-serialize will handle variable-length data correctly\n        // The deserialization should work even with padding (zeros) at the end\n        if let Some(result) = version.deserialize(data_in_range) {\n            match result {\n                SymbolDataOrAsset::SymbolData(symbol_data) => match *symbol_data {\n                    SymbolData::Asset(asset) => {\n                        tracing::debug!(\n                            \"Found asset (via SymbolData) at offset {offset}: {:?}\",\n                            asset.absolute_source_path()\n                        );\n                        let asset_index = assets.len();\n                        assets.push(asset);\n                        write_entries.push(AssetWriteEntry::new(\n                            symbol,\n                            asset_index,\n                            AssetRepresentation::SymbolData,\n                        ));\n                    }\n\n                    SymbolData::AndroidArtifact(meta) => {\n                        tracing::debug!(\n                            \"Found Android artifact declaration for plugin {}\",\n                            meta.plugin_name.as_str()\n                        );\n                        android_artifacts.push(meta);\n                    }\n                    SymbolData::SwiftPackage(meta) => {\n                        tracing::debug!(\n                            \"Found Swift package declaration for plugin {}\",\n                            meta.plugin_name.as_str()\n                        );\n                        swift_packages.push(meta);\n                    }\n                    _ => {}\n                },\n                SymbolDataOrAsset::Asset(asset) => {\n                    tracing::debug!(\n                        \"Found asset (old format) at offset {offset}: {:?}\",\n                        asset.absolute_source_path()\n                    );\n                    let asset_index = assets.len();\n                    assets.push(asset);\n                    write_entries.push(AssetWriteEntry::new(\n                        symbol,\n                        asset_index,\n                        AssetRepresentation::RawBundled,\n                    ));\n                }\n            }\n        } else {\n            tracing::warn!(\"Found a symbol at offset {offset} that could not be deserialized. This may be caused by a mismatch between your dioxus and dioxus-cli versions, or the symbol may be in an unsupported format.\");\n        }\n    }\n\n    // Add the hash to each asset in parallel\n    assets\n        .par_iter_mut()\n        .for_each(dioxus_cli_opt::add_hash_to_asset);\n\n    // Write back only assets to the binary file (permissions are not modified)\n    for entry in write_entries {\n        let version = entry.symbol.version;\n        let offset = entry.symbol.offset;\n        let asset = assets\n            .get(entry.asset_index)\n            .copied()\n            .expect(\"asset index collected from symbol scan\");\n\n        match entry.representation {\n            AssetRepresentation::RawBundled => {\n                tracing::debug!(\"Writing asset to offset {offset}: {:?}\", asset);\n                let new_data = version.serialize_asset(&asset);\n                if new_data.len() > version.size() {\n                    tracing::warn!(\n                        \"Asset at offset {offset} serialized to {} bytes, but buffer is only {} bytes. Truncating output.\",\n                        new_data.len(),\n                        version.size()\n                    );\n                }\n                write_serialized_bytes(&mut file, offset, &new_data, version.size())?;\n            }\n            AssetRepresentation::SymbolData => {\n                tracing::debug!(\"Writing asset (SymbolData) to offset {offset}: {:?}\", asset);\n                let Some(new_data) = version.serialize_symbol_data(&SymbolData::Asset(asset))\n                else {\n                    tracing::warn!(\n                        \"Symbol at offset {offset} was stored as SymbolData but the binary format only supports raw assets\"\n                    );\n                    continue;\n                };\n                if new_data.len() > version.size() {\n                    tracing::warn!(\n                        \"SymbolData asset at offset {offset} serialized to {} bytes, but buffer is only {} bytes. Truncating output.\",\n                        new_data.len(),\n                        version.size()\n                    );\n                }\n                write_serialized_bytes(&mut file, offset, &new_data, version.size())?;\n            }\n        }\n    }\n\n    // Ensure the file is flushed to disk\n    file.sync_all()\n        .context(\"Failed to sync file after writing assets\")?;\n\n    // If the file is a macos binary, we need to re-sign the modified binary\n    if object_file.format() == object::BinaryFormat::MachO && !assets.is_empty() {\n        // Spawn the codesign command to re-sign the binary\n        let output = std::process::Command::new(\"codesign\")\n            .arg(\"--force\")\n            .arg(\"--sign\")\n            .arg(\"-\") // Sign with an empty identity\n            .arg(path)\n            .output()\n            .context(\"Failed to run codesign - is `codesign` in your path?\")?;\n        if !output.status.success() {\n            bail!(\n                \"Failed to re-sign the binary with codesign after finalizing the assets: {}\",\n                String::from_utf8_lossy(&output.stderr)\n            );\n        }\n    }\n\n    Ok(SymbolExtractionResult {\n        assets,\n        android_artifacts,\n        swift_packages,\n    })\n}\n\n/// Find all assets in the given file, hash them, and write them back to the file.\n/// Then return an `AssetManifest` containing all the assets found in the file.\n///\n/// This is a convenience function that extracts symbols and returns only assets.\npub(crate) async fn extract_assets_from_file(path: impl AsRef<Path>) -> Result<AssetManifest> {\n    let result = extract_symbols_from_file(path).await?;\n    let mut manifest = AssetManifest::default();\n    for asset in result.assets {\n        manifest.insert_asset(asset);\n    }\n    Ok(manifest)\n}\n\n/// Try to open a file for writing, retrying if the file is already open by another process.\n///\n/// This is useful on windows where antivirus software might grab the executable before we have a chance to read it.\nasync fn open_file_for_writing_with_timeout(\n    file: &Path,\n    options: &mut std::fs::OpenOptions,\n) -> Result<std::fs::File> {\n    let start_time = std::time::Instant::now();\n    let timeout = std::time::Duration::from_secs(5);\n    loop {\n        match options.open(file) {\n            Ok(file) => return Ok(file),\n            Err(e) => {\n                if cfg!(windows) && e.raw_os_error() == Some(32) && start_time.elapsed() < timeout {\n                    // File is already open, wait and retry\n                    tracing::trace!(\n                        \"Failed to open file because another process is using it. Retrying...\"\n                    );\n                    tokio::time::sleep(std::time::Duration::from_millis(50)).await;\n                } else {\n                    return Err(e.into());\n                }\n            }\n        }\n    }\n}\n\nfn write_serialized_bytes(\n    file: &mut std::fs::File,\n    offset: u64,\n    data: &[u8],\n    buffer_size: usize,\n) -> Result<()> {\n    use std::io::SeekFrom;\n\n    file.seek(SeekFrom::Start(offset))?;\n    if data.len() <= buffer_size {\n        file.write_all(data)?;\n        if data.len() < buffer_size {\n            let padding = vec![0; buffer_size - data.len()];\n            file.write_all(&padding)?;\n        }\n    } else {\n        file.write_all(&data[..buffer_size])?;\n    }\n\n    Ok(())\n}\n"
  },
  {
    "path": "packages/cli/src/build/builder.rs",
    "content": "use crate::{\n    build::cache::ObjectCache, serve::WebServer, verbosity_or_default, BuildArtifacts,\n    BuildRequest, BuildStage, BuilderUpdate, BundleFormat, ProgressRx, ProgressTx, Result,\n    RustcArgs, StructuredOutput,\n};\nuse anyhow::{bail, Context, Error};\nuse dioxus_cli_opt::process_file_to;\nuse futures_util::{future::OptionFuture, pin_mut, FutureExt};\nuse itertools::Itertools;\nuse std::{\n    collections::HashSet,\n    env,\n    time::{Duration, Instant, SystemTime},\n};\nuse std::{\n    net::SocketAddr,\n    path::{Path, PathBuf},\n    process::Stdio,\n};\nuse subsecond_types::JumpTable;\nuse target_lexicon::Architecture;\nuse tokio::{\n    io::{AsyncBufReadExt, BufReader, Lines},\n    process::{Child, ChildStderr, ChildStdout, Command},\n    task::JoinHandle,\n};\nuse tokio_stream::wrappers::UnboundedReceiverStream;\n\nuse super::{BuildContext, BuildId, BuildMode, HotpatchModuleCache};\n\n/// The component of the serve engine that watches ongoing builds and manages their state, open handle,\n/// and progress.\n///\n/// Previously, the builder allowed multiple apps to be built simultaneously, but this newer design\n/// simplifies the code and allows only one app and its server to be built at a time.\n///\n/// Here, we track the number of crates being compiled, assets copied, the times of these events, and\n/// other metadata that gives us useful indicators for the UI.\n///\n/// A handle to a running app.\n///\n/// The actual child processes might not be present (web) or running (died/killed).\n///\n/// The purpose of this struct is to accumulate state about the running app and its server, like\n/// any runtime information needed to hotreload the app or send it messages.\n///\n/// We might want to bring in websockets here too, so we know the exact channels the app is using to\n/// communicate with the devserver. Currently that's a broadcast-type system, so this struct isn't super\n/// duper useful.\n///\n/// todo: restructure this such that \"open\" is a running task instead of blocking the main thread\npub(crate) struct AppBuilder {\n    pub tx: ProgressTx,\n    pub rx: ProgressRx,\n\n    // The original request with access to its build directory\n    pub build: BuildRequest,\n\n    // Ongoing build task, if any\n    pub build_task: JoinHandle<Result<BuildArtifacts>>,\n\n    // If a build has already finished, we'll have its artifacts (rustc, link args, etc) to work with\n    pub artifacts: Option<BuildArtifacts>,\n\n    /// The aslr offset of this running app\n    pub aslr_reference: Option<u64>,\n\n    /// The list of patches applied to the app, used to know which ones to reapply and/or iterate from.\n    pub patches: Vec<JumpTable>,\n    pub patch_cache: Option<HotpatchModuleCache>,\n\n    /// The virtual directory that assets will be served from\n    /// Used mostly for apk/ipa builds since they live in simulator\n    pub runtime_asset_dir: Option<PathBuf>,\n\n    // These might be None if the app died or the user did not specify a server\n    pub child: Option<Child>,\n\n    // stdio for the app so we can read its stdout/stderr\n    // we don't map stdin today (todo) but most apps don't need it\n    pub stdout: Option<Lines<BufReader<ChildStdout>>>,\n    pub stderr: Option<Lines<BufReader<ChildStderr>>>,\n\n    // Android logcat stream (treated as stderr for error/warn levels)\n    pub adb_logcat_stdout: Option<UnboundedReceiverStream<String>>,\n\n    /// Handle to the task that's monitoring the child process\n    pub spawn_handle: Option<JoinHandle<Result<()>>>,\n\n    /// The executables but with some extra entropy in their name so we can run two instances of the\n    /// same app without causing collisions on the filesystem.\n    pub entropy_app_exe: Option<PathBuf>,\n    pub builds_opened: usize,\n\n    // Metadata about the build that needs to be managed by watching build updates\n    // used to render the TUI\n    pub stage: BuildStage,\n    pub compiled_crates: usize,\n    pub expected_crates: usize,\n    pub bundling_progress: f64,\n    pub compile_start: Option<Instant>,\n    pub compile_end: Option<Instant>,\n    pub bundle_start: Option<Instant>,\n    pub bundle_end: Option<Instant>,\n\n    /// The debugger for the app - must be enabled with the `d` key\n    pub(crate) pid: Option<u32>,\n\n    /// Cumulative set of workspace crates modified since the last fat build.\n    /// Each patch includes objects from ALL crates in this set.\n    pub modified_crates: HashSet<String>,\n\n    /// Cache of the latest `.rcgu.o` files for each modified workspace crate.\n    pub object_cache: ObjectCache,\n}\n\nimpl AppBuilder {\n    /// Create a new `AppBuilder` and immediately start a build process.\n    ///\n    /// This method initializes the builder with the provided `BuildRequest` and spawns an asynchronous\n    /// task (`build_task`) to handle the build process. The build process involves several stages:\n    ///\n    /// 1. **Tooling Verification**: Ensures that the necessary tools are available for the build.\n    /// 2. **Build Directory Preparation**: Sets up the directory structure required for the build.\n    /// 3. **Build Execution**: Executes the build process asynchronously.\n    /// 4. **Bundling**: Packages the built artifacts into a final bundle.\n    ///\n    /// The `build_task` is a Tokio task that runs the build process in the background. It uses a\n    /// `BuildContext` to manage the build state and communicate progress or errors via a message\n    /// channel (`tx`).\n    ///\n    /// The builder is initialized with default values for various fields, such as the build stage,\n    /// progress metrics, and optional runtime configurations.\n    ///\n    /// # Notes\n    ///\n    /// - The `build_task` is immediately spawned and will run independently of the caller.\n    /// - The caller can use other methods on the `AppBuilder` to monitor the build progress or handle\n    ///   updates (e.g., `wait`, `finish_build`).\n    /// - The build process is designed to be cancellable and restartable using methods like `abort_all`\n    ///   or `rebuild`.\n    pub(crate) fn new(request: &BuildRequest) -> Result<Self> {\n        let (tx, rx) = futures_channel::mpsc::unbounded();\n\n        Ok(Self {\n            build: request.clone(),\n            stage: BuildStage::Initializing,\n            build_task: tokio::task::spawn(std::future::pending()),\n            tx,\n            rx,\n            patches: vec![],\n            compiled_crates: 0,\n            expected_crates: 1,\n            bundling_progress: 0.0,\n            builds_opened: 0,\n            compile_start: Some(Instant::now()),\n            aslr_reference: None,\n            compile_end: None,\n            bundle_start: None,\n            bundle_end: None,\n            runtime_asset_dir: None,\n            child: None,\n            stderr: None,\n            stdout: None,\n            adb_logcat_stdout: None,\n            spawn_handle: None,\n            entropy_app_exe: None,\n            artifacts: None,\n            patch_cache: None,\n            pid: None,\n            modified_crates: HashSet::new(),\n            object_cache: ObjectCache::new(&request.session_cache_dir()),\n        })\n    }\n\n    /// Create a new `AppBuilder` and immediately start a build process.\n    pub fn started(request: &BuildRequest, mode: BuildMode, build_id: BuildId) -> Result<Self> {\n        let mut builder = Self::new(request)?;\n        builder.start(mode, build_id);\n        Ok(builder)\n    }\n\n    pub(crate) fn start(&mut self, mode: BuildMode, build_id: BuildId) {\n        self.build_task = tokio::spawn({\n            let request = self.build.clone();\n            let tx = self.tx.clone();\n            async move {\n                let ctx = BuildContext {\n                    mode,\n                    build_id,\n                    tx: tx.clone(),\n                };\n                request.verify_tooling(&ctx).await?;\n                request.prebuild(&ctx).await?;\n                request.build(&ctx).await\n            }\n        });\n    }\n\n    /// Wait for any new updates to the builder - either it completed or gave us a message etc\n    pub(crate) async fn wait(&mut self) -> BuilderUpdate {\n        use futures_util::StreamExt;\n        use BuilderUpdate::*;\n\n        // Wait for the build to finish or for it to emit a status message\n        let update = tokio::select! {\n            Some(progress) = self.rx.next() => progress,\n            bundle = (&mut self.build_task) => {\n                // Replace the build with an infinitely pending task so we can select it again without worrying about deadlocks/spins\n                self.build_task = tokio::task::spawn(std::future::pending());\n                match bundle {\n                    Ok(Ok(bundle)) => BuilderUpdate::BuildReady { bundle },\n                    Ok(Err(err)) => BuilderUpdate::BuildFailed { err },\n                    Err(err) => BuilderUpdate::BuildFailed { err: anyhow::anyhow!(\"Build panicked! {err:#?}\") },\n                }\n            },\n            Some(Ok(Some(msg))) = OptionFuture::from(self.stdout.as_mut().map(|f| f.next_line())) => {\n                StdoutReceived {  msg }\n            },\n            Some(Ok(Some(msg))) = OptionFuture::from(self.stderr.as_mut().map(|f| f.next_line())) => {\n                StderrReceived {  msg }\n            },\n            Some(msg) = OptionFuture::from(self.spawn_handle.as_mut()) => {\n                // Prevent re-polling the spawn future, similar to above\n                self.spawn_handle = None;\n                match msg {\n                    Ok(Ok(_)) => StdoutReceived { msg: \"Finished launching app\".to_string() },\n                    Ok(Err(err)) => StderrReceived { msg: err.to_string() },\n                    Err(err) => StderrReceived { msg: err.to_string() }\n                }\n            },\n            Some(Some(msg)) = OptionFuture::from(self.adb_logcat_stdout.as_mut().map(|s| s.next())) => {\n                // Send as stderr for errors/warnings, stdout for info/debug\n                // Parse the priority level from a logcat line\n                //\n                // Logcat brief format: \"I/TAG(12345): message\"\n                // Returns the priority char (V, D, I, W, E, F)\n                if matches!(msg.chars().next().unwrap_or('I'), 'E' | 'W' | 'F') {\n                    StderrReceived { msg }\n                } else {\n                    StdoutReceived { msg }\n                }\n            },\n            Some(status) = OptionFuture::from(self.child.as_mut().map(|f| f.wait())) => {\n                match status {\n                    Ok(status) => {\n                        self.child = None;\n                        ProcessExited { status }\n                    },\n                    Err(err) => {\n                        let () = futures_util::future::pending().await;\n                        ProcessWaitFailed { err }\n                    }\n                }\n            }\n        };\n\n        // Update the internal stage of the build so the UI can render it\n        // *VERY IMPORTANT* - DO NOT AWAIT HERE\n        // doing so will cause the changes to be lost since this wait call is called under a cancellable task\n        // todo - move this handling to a separate function that won't be cancelled\n        match &update {\n            BuilderUpdate::Progress { stage } => {\n                // Prevent updates from flowing in after the build has already finished\n                if !self.is_finished() {\n                    self.stage = stage.clone();\n\n                    match stage {\n                        BuildStage::Initializing => {\n                            self.compiled_crates = 0;\n                            self.bundling_progress = 0.0;\n                        }\n                        BuildStage::Starting { crate_count, .. } => {\n                            self.expected_crates = *crate_count.max(&1);\n                        }\n                        BuildStage::InstallingTooling => {}\n                        BuildStage::Compiling { current, total, .. } => {\n                            self.compiled_crates = *current;\n                            self.expected_crates = *total.max(&1);\n\n                            if self.compile_start.is_none() {\n                                self.compile_start = Some(Instant::now());\n                            }\n                        }\n                        BuildStage::Bundling => {\n                            self.complete_compile();\n                            self.bundling_progress = 0.0;\n                            self.bundle_start = Some(Instant::now());\n                        }\n                        BuildStage::OptimizingWasm => {}\n                        BuildStage::CopyingAssets { current, total, .. } => {\n                            self.bundling_progress = *current as f64 / *total as f64;\n                        }\n                        BuildStage::Success => {\n                            self.compiled_crates = self.expected_crates;\n                            self.bundling_progress = 1.0;\n                        }\n                        BuildStage::Failed => {\n                            self.compiled_crates = self.expected_crates;\n                            self.bundling_progress = 1.0;\n                        }\n                        BuildStage::Aborted => {}\n                        BuildStage::Restarting => {\n                            self.compiled_crates = 0;\n                            self.expected_crates = 1;\n                            self.bundling_progress = 0.0;\n                        }\n                        BuildStage::RunningBindgen => {}\n                        _ => {}\n                    }\n                }\n            }\n            BuilderUpdate::CompilerMessage { .. } => {}\n            BuilderUpdate::BuildReady { .. } => {\n                self.compiled_crates = self.expected_crates;\n                self.bundling_progress = 1.0;\n                self.stage = BuildStage::Success;\n\n                self.complete_compile();\n                self.bundle_end = Some(Instant::now());\n            }\n            BuilderUpdate::BuildFailed { .. } => {\n                tracing::debug!(\"Setting builder to failed state\");\n                self.stage = BuildStage::Failed;\n            }\n            StdoutReceived { .. } => {}\n            StderrReceived { .. } => {}\n            ProcessExited { .. } => {}\n            ProcessWaitFailed { .. } => {}\n        }\n\n        update\n    }\n\n    pub(crate) fn patch_rebuild(\n        &mut self,\n        changed_files: Vec<PathBuf>,\n        changed_crates: Vec<String>,\n        build_id: BuildId,\n    ) {\n        // We need the rustc args from the original build to pass to the new build\n        let Some(artifacts) = self.artifacts.as_ref().cloned() else {\n            tracing::warn!(\n                \"Ignoring patch rebuild for {build_id:?} since there is no existing build.\"\n            );\n            return;\n        };\n\n        // On web, our patches are fully relocatable, so we don't need to worry about ASLR, but\n        // for all other platforms, we need to use the ASLR reference to know where to insert the patch.\n        let aslr_reference = match self.aslr_reference {\n            Some(val) => val,\n            None if matches!(\n                self.build.triple.architecture,\n                Architecture::Wasm32 | Architecture::Wasm64\n            ) =>\n            {\n                0\n            }\n            None => {\n                tracing::warn!(\n                    \"Ignoring hotpatch since there is no ASLR reference. Is the client connected?\"\n                );\n                return;\n            }\n        };\n\n        let cache = artifacts\n            .patch_cache\n            .clone()\n            .context(\"Failed to get patch cache\")\n            .unwrap();\n\n        // Pre-compute the cumulative modified_crates set. Every patch includes objects from\n        // ALL crates modified since the fat build. We compute the full cascade closure here\n        // (while we have &mut self) so it doesn't need to be round-tripped through BuildArtifacts.\n        //\n        // Note: compile_workspace_deps() independently computes which crates to compile for THIS\n        // patch (starting from changed_crates + cascade). That serves a different purpose — it only\n        // compiles what changed now, not everything ever modified. Both use workspace_dependents_of\n        // for the BFS, so they stay in sync automatically.\n        let tip_crate_name = self.build.main_target.replace('-', \"_\");\n        self.modified_crates.insert(tip_crate_name.clone());\n\n        // Add changed crates and their transitive workspace dependents (cascade).\n        let mut to_visit: Vec<String> = changed_crates.clone();\n        let mut visited = HashSet::new();\n        while let Some(c) = to_visit.pop() {\n            if !visited.insert(c.clone()) {\n                continue;\n            }\n            self.modified_crates.insert(c.clone());\n            for dep in self.build.workspace_dependents_of(&c) {\n                if dep != tip_crate_name && !visited.contains(&dep) {\n                    to_visit.push(dep);\n                }\n            }\n        }\n\n        tracing::debug!(\n            \"Patch rebuild: changed_crates={:?}, modified_crates={:?}\",\n            changed_crates,\n            self.modified_crates,\n        );\n\n        // Abort all the ongoing builds, cleaning up any loose artifacts and waiting to cleanly exit\n        self.abort_all(BuildStage::Restarting);\n        self.build_task = tokio::spawn({\n            let request = self.build.clone();\n            let ctx = BuildContext {\n                build_id,\n                tx: self.tx.clone(),\n                mode: BuildMode::Thin {\n                    changed_files,\n                    changed_crates,\n                    modified_crates: self.modified_crates.clone(),\n                    workspace_rustc_args: artifacts.workspace_rustc_args,\n                    aslr_reference,\n                    cache,\n                    object_cache: self.object_cache.clone(),\n                },\n            };\n            async move { request.build(&ctx).await }\n        });\n    }\n\n    /// Restart this builder with new build arguments.\n    pub(crate) fn start_rebuild(&mut self, mode: BuildMode, build_id: BuildId) {\n        // Abort all the ongoing builds, cleaning up any loose artifacts and waiting to cleanly exit\n        // And then start a new build, resetting our progress/stage to the beginning and replacing the old tokio task\n        self.abort_all(BuildStage::Restarting);\n        self.artifacts.take();\n        self.patch_cache.take();\n\n        // A full rebuild resets all accumulated hotpatch state — the fat binary is a clean baseline.\n        self.modified_crates.clear();\n        self.object_cache = ObjectCache::new(&self.build.session_cache_dir());\n        self.build_task = tokio::spawn({\n            let request = self.build.clone();\n            let ctx = BuildContext {\n                tx: self.tx.clone(),\n                mode,\n                build_id,\n            };\n            async move { request.build(&ctx).await }\n        });\n    }\n\n    /// Shutdown the current build process\n    ///\n    /// todo: might want to use a cancellation token here to allow cleaner shutdowns\n    pub(crate) fn abort_all(&mut self, stage: BuildStage) {\n        self.stage = stage;\n        self.compiled_crates = 0;\n        self.expected_crates = 1;\n        self.bundling_progress = 0.0;\n        self.compile_start = None;\n        self.bundle_start = None;\n        self.bundle_end = None;\n        self.compile_end = None;\n        self.build_task.abort();\n    }\n\n    /// Wait for the build to finish, returning the final bundle\n    /// Should only be used by code that's not interested in the intermediate updates and only cares about the final bundle\n    ///\n    /// todo(jon): maybe we want to do some logging here? The build/bundle/run screens could be made to\n    /// use the TUI output for prettier outputs.\n    pub(crate) async fn finish_build(&mut self) -> Result<BuildArtifacts> {\n        loop {\n            match self.wait().await {\n                BuilderUpdate::Progress { stage } => {\n                    match &stage {\n                        BuildStage::Compiling {\n                            current,\n                            total,\n                            krate,\n                            ..\n                        } => {\n                            tracing::info!(\"Compiled [{current:>3}/{total}]: {krate}\");\n                        }\n                        BuildStage::RunningBindgen => tracing::info!(\"Running wasm-bindgen...\"),\n                        BuildStage::CopyingAssets {\n                            current,\n                            total,\n                            path,\n                        } => {\n                            tracing::info!(\n                                \"Copying asset ({}/{total}): {}\",\n                                current + 1,\n                                path.display()\n                            );\n                        }\n                        BuildStage::Bundling => tracing::info!(\"Bundling app...\"),\n                        BuildStage::CodeSigning => tracing::info!(\"Code signing app...\"),\n                        _ => {}\n                    }\n\n                    tracing::info!(json = %StructuredOutput::BuildUpdate { stage: stage.clone() });\n                }\n                BuilderUpdate::CompilerMessage { message } => {\n                    tracing::info!(json = %StructuredOutput::RustcOutput { message: message.clone() }, %message);\n                }\n                BuilderUpdate::BuildReady { bundle } => {\n                    tracing::debug!(json = %StructuredOutput::BuildFinished {\n                        artifacts: bundle.clone().into_structured_output(),\n                    });\n                    return Ok(bundle);\n                }\n                BuilderUpdate::BuildFailed { err } => {\n                    // Flush remaining compiler messages\n                    while let Ok(Some(msg)) = self.rx.try_next() {\n                        if let BuilderUpdate::CompilerMessage { message } = msg {\n                            tracing::info!(json = %StructuredOutput::RustcOutput { message: message.clone() }, %message);\n                        }\n                    }\n\n                    return Err(err);\n                }\n                BuilderUpdate::StdoutReceived { .. } => {}\n                BuilderUpdate::StderrReceived { .. } => {}\n                BuilderUpdate::ProcessExited { .. } => {}\n                BuilderUpdate::ProcessWaitFailed { .. } => {}\n            }\n        }\n    }\n\n    /// Create a list of environment variables that the child process will use\n    ///\n    /// We try to emulate running under `cargo` as much as possible, carrying over vars like `CARGO_MANIFEST_DIR`.\n    /// Previously, we didn't want to emulate this behavior, but now we do in order to be a good\n    /// citizen of the Rust ecosystem and allow users to use `cargo` features like `CARGO_MANIFEST_DIR`.\n    ///\n    /// Note that Dioxus apps *should not* rely on this vars being set, but libraries like Bevy do.\n    pub(crate) fn child_environment_variables(\n        &mut self,\n        devserver_ip: Option<SocketAddr>,\n        start_fullstack_on_address: Option<SocketAddr>,\n        always_on_top: bool,\n        build_id: BuildId,\n    ) -> Vec<(String, String)> {\n        let krate = &self.build;\n\n        // Set the env vars that the clients will expect\n        // These need to be stable within a release version (ie 0.6.0)\n        let mut envs: Vec<(String, String)> = vec![\n            (\n                dioxus_cli_config::CLI_ENABLED_ENV.into(),\n                \"true\".to_string(),\n            ),\n            (\n                dioxus_cli_config::APP_TITLE_ENV.into(),\n                krate.config.web.app.title.clone(),\n            ),\n            (\n                dioxus_cli_config::SESSION_CACHE_DIR.into(),\n                self.build.session_cache_dir().display().to_string(),\n            ),\n            (dioxus_cli_config::BUILD_ID.into(), build_id.0.to_string()),\n            (\n                dioxus_cli_config::ALWAYS_ON_TOP_ENV.into(),\n                always_on_top.to_string(),\n            ),\n        ];\n\n        if let Some(devserver_ip) = devserver_ip {\n            envs.push((\n                dioxus_cli_config::DEVSERVER_IP_ENV.into(),\n                devserver_ip.ip().to_string(),\n            ));\n            envs.push((\n                dioxus_cli_config::DEVSERVER_PORT_ENV.into(),\n                devserver_ip.port().to_string(),\n            ));\n        }\n\n        if verbosity_or_default().verbose {\n            envs.push((\"RUST_BACKTRACE\".into(), \"1\".to_string()));\n        }\n\n        if let Some(base_path) = krate.trimmed_base_path() {\n            envs.push((\n                dioxus_cli_config::ASSET_ROOT_ENV.into(),\n                base_path.to_string(),\n            ));\n        }\n\n        if let Some(env_filter) = env::var_os(\"RUST_LOG\").and_then(|e| e.into_string().ok()) {\n            envs.push((\"RUST_LOG\".into(), env_filter));\n        }\n\n        // Launch the server if we were given an address to start it on, and the build includes a server. After we\n        // start the server, consume its stdout/stderr.\n        if let Some(addr) = start_fullstack_on_address {\n            envs.push((\n                dioxus_cli_config::SERVER_IP_ENV.into(),\n                addr.ip().to_string(),\n            ));\n            envs.push((\n                dioxus_cli_config::SERVER_PORT_ENV.into(),\n                addr.port().to_string(),\n            ));\n        }\n\n        // If there's any CARGO vars in the rustc_wrapper files, push those too.\n        // Read from any per-crate args file in the directory (they all share the same CARGO_ envs).\n        if let Ok(entries) = std::fs::read_dir(self.build.rustc_wrapper_args_dir()) {\n            for entry in entries.flatten() {\n                let path = entry.path();\n                if path.extension().is_some_and(|e| e == \"json\") {\n                    if let Ok(contents) = std::fs::read_to_string(&path) {\n                        if let Ok(args) = serde_json::from_str::<RustcArgs>(&contents) {\n                            for (key, value) in args.envs {\n                                if key.starts_with(\"CARGO_\") {\n                                    envs.push((key, value));\n                                }\n                            }\n                            break; // Only need one file for CARGO_ env vars\n                        }\n                    }\n                }\n            }\n        }\n\n        envs\n    }\n\n    #[allow(clippy::too_many_arguments)]\n    pub(crate) async fn open(\n        &mut self,\n        devserver_ip: SocketAddr,\n        open_address: Option<SocketAddr>,\n        start_fullstack_on_address: Option<SocketAddr>,\n        open_browser: bool,\n        always_on_top: bool,\n        build_id: BuildId,\n        args: &[String],\n    ) -> Result<()> {\n        let envs = self.child_environment_variables(\n            Some(devserver_ip),\n            start_fullstack_on_address,\n            always_on_top,\n            build_id,\n        );\n\n        // We try to use stdin/stdout to communicate with the app\n        match self.build.bundle {\n            // Unfortunately web won't let us get a proc handle to it (to read its stdout/stderr) so instead\n            // use use the websocket to communicate with it. I wish we could merge the concepts here,\n            // like say, opening the socket as a subprocess, but alas, it's simpler to do that somewhere else.\n            BundleFormat::Web => {\n                // Only the first build we open the web app, after that the user knows it's running\n                if open_browser {\n                    self.open_web(open_address.unwrap_or(devserver_ip));\n                }\n            }\n\n            BundleFormat::Ios => {\n                if let Some(device) = self.build.device_name.to_owned() {\n                    self.open_ios_device(&device).await?\n                } else {\n                    self.open_ios_sim(envs).await?\n                }\n            }\n\n            BundleFormat::Android => {\n                self.open_android(false, devserver_ip, envs, self.build.device_name.clone())\n                    .await?;\n            }\n\n            // These are all just basically running the main exe, but with slightly different resource dir paths\n            BundleFormat::Server\n            | BundleFormat::MacOS\n            | BundleFormat::Windows\n            | BundleFormat::Linux => self.open_with_main_exe(envs, args)?,\n        };\n\n        self.builds_opened += 1;\n\n        Ok(())\n    }\n\n    /// Gracefully kill the process and all of its children\n    ///\n    /// Uses the `SIGTERM` signal on unix and `taskkill` on windows.\n    /// This complex logic is necessary for things like window state preservation to work properly.\n    ///\n    /// Also wipes away the entropy executables if they exist.\n    pub(crate) async fn soft_kill(&mut self) {\n        use futures_util::FutureExt;\n\n        // Kill any running executables on Windows\n        let Some(mut process) = self.child.take() else {\n            return;\n        };\n\n        let Some(pid) = process.id() else {\n            _ = process.kill().await;\n            return;\n        };\n\n        // on unix, we can send a signal to the process to shut down\n        #[cfg(unix)]\n        {\n            _ = Command::new(\"kill\")\n                .args([\"-s\", \"TERM\", &pid.to_string()])\n                .spawn();\n        }\n\n        // on windows, use the `taskkill` command\n        #[cfg(windows)]\n        {\n            _ = Command::new(\"taskkill\")\n                .args([\"/PID\", &pid.to_string()])\n                .spawn();\n        }\n\n        // join the wait with a 100ms timeout\n        futures_util::select! {\n            _ = process.wait().fuse() => {}\n            _ = tokio::time::sleep(std::time::Duration::from_millis(1000)).fuse() => {}\n        };\n\n        // Wipe out the entropy executables if they exist\n        if let Some(entropy_app_exe) = self.entropy_app_exe.take() {\n            _ = std::fs::remove_file(entropy_app_exe);\n        }\n\n        // Abort the spawn handle monitoring task if it exists\n        if let Some(spawn_handle) = self.spawn_handle.take() {\n            spawn_handle.abort();\n        }\n    }\n\n    pub(crate) async fn hotpatch(\n        &mut self,\n        res: &BuildArtifacts,\n        cache: &HotpatchModuleCache,\n    ) -> Result<JumpTable> {\n        let original = self.build.main_exe();\n        let new = self.build.patch_exe(res.time_start);\n        let asset_dir = self.build.asset_dir();\n\n        // Hotpatch asset!() calls\n        for bundled in res.assets.unique_assets() {\n            let original_artifacts = self\n                .artifacts\n                .as_mut()\n                .context(\"No artifacts to hotpatch\")?;\n\n            if original_artifacts.assets.contains(bundled) {\n                continue;\n            }\n\n            // If this is a new asset, insert it into the artifacts so we can track it when hot reloading\n            original_artifacts.assets.insert_asset(*bundled);\n\n            let from = dunce::canonicalize(PathBuf::from(bundled.absolute_source_path()))?;\n\n            let to = asset_dir.join(bundled.bundled_path());\n\n            tracing::debug!(\"Copying asset from patch: {}\", from.display());\n            if let Err(e) = dioxus_cli_opt::process_file_to(bundled.options(), &from, &to) {\n                tracing::error!(\"Failed to copy asset: {e}\");\n                continue;\n            }\n\n            // If the emulator is android, we need to copy the asset to the device with `adb push asset /data/local/tmp/dx/assets/filename.ext`\n            if self.build.bundle == BundleFormat::Android {\n                let bundled_name = PathBuf::from(bundled.bundled_path());\n                _ = self.copy_file_to_android_tmp(&from, &bundled_name).await;\n            }\n        }\n\n        // Make sure to add `include!()` calls to the watcher so we can watch changes as they evolve\n        for file in res.depinfo.files.iter() {\n            let original_artifacts = self\n                .artifacts\n                .as_mut()\n                .context(\"No artifacts to hotpatch\")?;\n\n            if !original_artifacts.depinfo.files.contains(file) {\n                original_artifacts.depinfo.files.push(file.clone());\n            }\n        }\n\n        tracing::debug!(\"Patching {} -> {}\", original.display(), new.display());\n\n        let mut jump_table = self.build.create_jump_table(&new, cache)?;\n\n        // If it's android, we need to copy the assets to the device and then change the location of the patch\n        if self.build.bundle == BundleFormat::Android {\n            jump_table.lib = self\n                .copy_file_to_android_tmp(&new, &(PathBuf::from(new.file_name().unwrap())))\n                .await?;\n        }\n\n        let changed_files = match &res.mode {\n            BuildMode::Thin { changed_files, .. } => changed_files.clone(),\n            _ => vec![],\n        };\n\n        use crate::styles::{GLOW_STYLE, NOTE_STYLE};\n\n        let changed_file = changed_files.first().unwrap();\n        tracing::info!(\n            \"Hot-patching: {NOTE_STYLE}{}{NOTE_STYLE:#} took {GLOW_STYLE}{:?}ms{GLOW_STYLE:#}\",\n            changed_file\n                .strip_prefix(self.build.workspace_dir())\n                .unwrap_or(changed_file)\n                .display(),\n            SystemTime::now()\n                .duration_since(res.time_start)\n                .unwrap()\n                .as_millis()\n        );\n\n        // Commit this patch\n        self.patches.push(jump_table.clone());\n\n        // Sync the updated object cache back from the build artifacts.\n        self.object_cache = res.object_cache.clone();\n\n        Ok(jump_table)\n    }\n\n    /// Hotreload an asset in the running app.\n    ///\n    /// This will modify the build dir in place! Be careful! We generally assume you want all bundles\n    /// to reflect the latest changes, so we will modify the bundle.\n    ///\n    /// However, not all platforms work like this, so we might also need to update a separate asset\n    /// dir that the system simulator might be providing. We know this is the case for ios simulators\n    /// and haven't yet checked for android.\n    ///\n    /// This will return the bundled name of the assets such that we can send it to the clients letting\n    /// them know what to reload. It's not super important that this is robust since most clients will\n    /// kick all stylsheets without necessarily checking the name.\n    pub(crate) async fn hotreload_bundled_assets(\n        &self,\n        changed_file: &PathBuf,\n    ) -> Option<Vec<PathBuf>> {\n        let artifacts = self.artifacts.as_ref()?;\n\n        // Use the build dir if there's no runtime asset dir as the override. For the case of ios apps,\n        // we won't actually be using the build dir.\n        let asset_dir = match self.runtime_asset_dir.as_ref() {\n            Some(dir) => dir.to_path_buf().join(\"assets/\"),\n            None => self.build.asset_dir(),\n        };\n\n        // Canonicalize the path as Windows may use long-form paths \"\\\\\\\\?\\\\C:\\\\\".\n        let changed_file = dunce::canonicalize(changed_file)\n            .inspect_err(|e| tracing::debug!(\"Failed to canonicalize hotreloaded asset: {e}\"))\n            .ok()?;\n\n        // The asset might've been renamed thanks to the manifest, let's attempt to reload that too\n        let resources = artifacts.assets.get_assets_for_source(&changed_file)?;\n        let mut bundled_names = Vec::new();\n        for resource in resources {\n            let output_path = asset_dir.join(resource.bundled_path());\n\n            tracing::debug!(\"Hotreloading asset {changed_file:?} in target {asset_dir:?}\");\n\n            // Remove the old asset if it exists\n            _ = std::fs::remove_file(&output_path);\n\n            // And then process the asset with the options into the **old** asset location. If we recompiled,\n            // the asset would be in a new location because the contents and hash have changed. Since we are\n            // hotreloading, we need to use the old asset location it was originally written to.\n            let options = *resource.options();\n            let res = process_file_to(&options, &changed_file, &output_path);\n            let bundled_name = PathBuf::from(resource.bundled_path());\n            if let Err(e) = res {\n                tracing::debug!(\"Failed to hotreload asset {e}\");\n            }\n\n            // If the emulator is android, we need to copy the asset to the device with `adb push asset /data/local/tmp/dx/assets/filename.ext`\n            if self.build.bundle == BundleFormat::Android {\n                _ = self\n                    .copy_file_to_android_tmp(&changed_file, &bundled_name)\n                    .await;\n            }\n            bundled_names.push(bundled_name);\n        }\n\n        Some(bundled_names)\n    }\n\n    /// Copy this file to the tmp folder on the android device, returning the path to the copied file\n    ///\n    /// When we push patches (.so), the runtime will dlopen the file from the tmp folder by first copying\n    /// it to shared memory. This is a workaround since not all android devices will be rooted and we\n    /// can't drop the file into the `/data/data/com.org.app/lib/` directory.\n    pub(crate) async fn copy_file_to_android_tmp(\n        &self,\n        changed_file: &Path,\n        bundled_name: &Path,\n    ) -> Result<PathBuf> {\n        let target = dioxus_cli_config::android_session_cache_dir().join(bundled_name);\n        tracing::debug!(\"Pushing asset to device: {target:?}\");\n\n        let res = Command::new(&self.build.workspace.android_tools()?.adb)\n            .arg(\"push\")\n            .arg(changed_file)\n            .arg(&target)\n            .output()\n            .await\n            .context(\"Failed to push asset to device\");\n\n        if let Err(e) = res {\n            tracing::debug!(\"Failed to push asset to device: {e}\");\n        }\n\n        Ok(target)\n    }\n\n    /// Open the native app simply by running its main exe\n    ///\n    /// Eventually, for mac, we want to run the `.app` with `open` to fix issues with `dylib` paths,\n    /// but for now, we just run the exe directly. Very few users should be caring about `dylib` search\n    /// paths right now, but they will when we start to enable things like swift integration.\n    ///\n    /// Server/liveview/desktop are all basically the same, though\n    fn open_with_main_exe(&mut self, envs: Vec<(String, String)>, args: &[String]) -> Result<()> {\n        let main_exe = self.app_exe();\n\n        tracing::debug!(\"Opening app with main exe: {main_exe:?}\");\n\n        let mut child = Command::new(main_exe)\n            .args(args)\n            .envs(envs)\n            .stderr(Stdio::piped())\n            .stdout(Stdio::piped())\n            .kill_on_drop(true)\n            .spawn()?;\n\n        let stdout = BufReader::new(child.stdout.take().unwrap());\n        let stderr = BufReader::new(child.stderr.take().unwrap());\n        self.stdout = Some(stdout.lines());\n        self.stderr = Some(stderr.lines());\n        self.child = Some(child);\n\n        Ok(())\n    }\n\n    /// Open the web app by opening the browser to the given address.\n    /// Check if we need to use https or not, and if so, add the protocol.\n    /// Go to the basepath if that's set too.\n    fn open_web(&self, address: SocketAddr) {\n        let base_path = self.build.base_path();\n        let https = self.build.config.web.https.enabled.unwrap_or_default();\n        let protocol = if https { \"https\" } else { \"http\" };\n        let base_path = match base_path {\n            Some(base_path) => format!(\"/{}\", base_path.trim_matches('/')),\n            None => \"\".to_owned(),\n        };\n        _ = open::that_detached(format!(\"{protocol}://{address}{base_path}\"));\n    }\n\n    /// Use `xcrun` to install the app to the simulator\n    /// With simulators, we're free to basically do anything, so we don't need to do any fancy codesigning\n    /// or entitlements, or anything like that.\n    ///\n    /// However, if there's no simulator running, this *might* fail.\n    ///\n    /// TODO(jon): we should probably check if there's a simulator running before trying to install,\n    /// and open the simulator if we have to.\n    async fn open_ios_sim(&mut self, envs: Vec<(String, String)>) -> Result<()> {\n        tracing::debug!(\"Installing app to simulator {:?}\", self.build.root_dir());\n\n        let res = Command::new(\"xcrun\")\n            .arg(\"simctl\")\n            .arg(\"install\")\n            .arg(\"booted\")\n            .arg(self.build.root_dir())\n            .output()\n            .await?;\n\n        tracing::debug!(\"Installed app to simulator with exit code: {res:?}\");\n\n        // Remap the envs to the correct simctl env vars\n        // iOS sim lets you pass env vars but they need to be in the format \"SIMCTL_CHILD_XXX=XXX\"\n        let ios_envs = envs\n            .iter()\n            .map(|(k, v)| (format!(\"SIMCTL_CHILD_{k}\"), v.clone()));\n\n        let mut child = Command::new(\"xcrun\")\n            .arg(\"simctl\")\n            .arg(\"launch\")\n            .arg(\"--console\")\n            .arg(\"booted\")\n            .arg(self.build.bundle_identifier())\n            .envs(ios_envs)\n            .stderr(Stdio::piped())\n            .stdout(Stdio::piped())\n            .kill_on_drop(true)\n            .spawn()?;\n\n        let stdout = BufReader::new(child.stdout.take().unwrap());\n        let stderr = BufReader::new(child.stderr.take().unwrap());\n        self.stdout = Some(stdout.lines());\n        self.stderr = Some(stderr.lines());\n        self.child = Some(child);\n\n        Ok(())\n    }\n\n    /// Upload the app to the device and launch it\n    async fn open_ios_device(&mut self, device_query: &str) -> Result<()> {\n        let device_query = device_query.to_string();\n        let root_dir = self.build.root_dir().clone();\n        let application_id = self.build.bundle_identifier();\n        self.spawn_handle = Some(tokio::task::spawn(async move {\n            // 1. Find an active device\n            let device_uuid = Self::get_ios_device_uuid(&device_query).await?;\n\n            tracing::info!(\"Uploading app to iOS device, this might take a while...\");\n\n            // 2. Install the app to the device\n            Self::install_ios_app(&device_uuid, &root_dir).await?;\n\n            // 3. Launch the app into the background, paused\n            Self::launch_ios_app_paused(&device_uuid, &application_id).await?;\n\n            Result::Ok(()) as Result<()>\n        }));\n\n        Ok(())\n    }\n\n    /// Parse the xcrun output to get the device based on its name and connected state.\n    ///\n    /// ```json, ignore\n    /// \"connectionProperties\" : {\n    ///   \"authenticationType\" : \"manualPairing\",\n    ///   \"isMobileDeviceOnly\" : false,\n    ///   \"lastConnectionDate\" : \"2025-08-15T01:46:43.182Z\",\n    ///   \"pairingState\" : \"paired\",\n    ///   \"potentialHostnames\" : [\n    ///     \"00008130-0002058401E8001C.coredevice.local\",\n    ///     \"67054C13-C6C8-5AC2-B967-24C040AD3F17.coredevice.local\"\n    ///   ],\n    ///   \"transportType\" : \"localNetwork\",\n    ///   \"tunnelState\" : \"disconnected\",\n    ///   \"tunnelTransportProtocol\" : \"tcp\"\n    /// },\n    /// \"deviceProperties\" : {\n    ///   \"bootedFromSnapshot\" : true,\n    ///   \"bootedSnapshotName\" : \"com.apple.os.update-A771E2B3E8C155D1B1188896B3247851B64737ACDE91A5B6F6C1F03A541406AA\",\n    ///   \"ddiServicesAvailable\" : false,\n    ///   \"developerModeStatus\" : \"enabled\",\n    ///   \"hasInternalOSBuild\" : false,\n    ///   \"name\" : \"Jon’s iPhone (2)\",\n    ///   \"osBuildUpdate\" : \"22G86\",\n    ///   \"osVersionNumber\" : \"18.6\",\n    ///   \"rootFileSystemIsWritable\" : false\n    /// }\n    /// ```\n    async fn get_ios_device_uuid(device_name_query: &str) -> Result<String> {\n        use serde_json::Value;\n\n        let tmpfile = tempfile::NamedTempFile::new()\n            .context(\"Failed to create temporary file for device list\")?;\n\n        Command::new(\"xcrun\")\n            .args([\n                \"devicectl\".to_string(),\n                \"list\".to_string(),\n                \"devices\".to_string(),\n                \"--json-output\".to_string(),\n                tmpfile.path().to_str().unwrap().to_string(),\n            ])\n            .output()\n            .await?;\n\n        let json: Value = serde_json::from_str(&std::fs::read_to_string(tmpfile.path())?)\n            .context(\"Failed to parse xcrun output\")?;\n\n        let devices = json\n            .get(\"result\")\n            .context(\"Failed to parse xcrun output\")?\n            .get(\"devices\")\n            .context(\"Failed to parse xcrun output\")?\n            .as_array()\n            .context(\"Failed to get devices from xcrun output\")?;\n\n        // by default, we just pick the first available device and then look for better fits.\n        let mut device_idx = 0;\n\n        match device_name_query.is_empty() {\n            // If the user provided a query, then we look through the device list looking for the right one.\n            // This searches both UUIDs and names, making it possible to paste an ID or a name.\n            false => {\n                use nucleo::{chars, Config, Matcher, Utf32Str};\n                let normalize = |c: char| chars::to_lower_case(chars::normalize(c));\n                let mut matcher = Matcher::new(Config::DEFAULT);\n                let mut best_score = 0;\n                let needle = device_name_query.chars().map(normalize).collect::<String>();\n                for (idx, device) in devices.iter().enumerate() {\n                    let device_name = device\n                        .get(\"deviceProperties\")\n                        .and_then(|f| f.get(\"name\"))\n                        .and_then(|n| n.as_str())\n                        .unwrap_or_default();\n                    let device_uuid = device\n                        .get(\"identifier\")\n                        .and_then(|n| n.as_str())\n                        .unwrap_or_default();\n                    let haystack = format!(\"{device_name} {device_uuid}\")\n                        .chars()\n                        .map(normalize)\n                        .collect::<String>();\n                    let name_score = matcher.fuzzy_match(\n                        Utf32Str::Ascii(haystack.as_bytes()),\n                        Utf32Str::Ascii(needle.as_bytes()),\n                    );\n                    if let Some(score) = name_score {\n                        if score > best_score {\n                            best_score = score;\n                            device_idx = idx;\n                        }\n                    }\n                }\n\n                if best_score == 0 {\n                    tracing::warn!(\n                        \"No device found matching query: {device_name_query}. Using first available device.\"\n                    );\n                }\n            }\n\n            // If the query is empty, then we just find the first connected/available device\n            // This is somewhat based on the bundle format, since we don't want to accidentally upload\n            // iOS apps to watches/tvs\n            true => {\n                for (idx, device) in devices.iter().enumerate() {\n                    let is_paired = device\n                        .get(\"connectionProperties\")\n                        .and_then(|g| g.get(\"pairingState\"))\n                        .map(|s| s.as_str() == Some(\"paired\"))\n                        .unwrap_or(false);\n\n                    let is_ios_device = matches!(\n                        device\n                            .get(\"hardwareProperties\")\n                            .and_then(|h| h.get(\"deviceType\"))\n                            .and_then(|s| s.as_str()),\n                        Some(\"iPhone\") | Some(\"iPad\") | Some(\"iPod\")\n                    );\n\n                    let is_available = device\n                        .get(\"connectionProperties\")\n                        .and_then(|c| c.get(\"tunnelState\"))\n                        .and_then(|s| s.as_str())\n                        != Some(\"unavailable\");\n\n                    if is_paired && is_ios_device && is_available {\n                        device_idx = idx;\n                        break;\n                    }\n                }\n            }\n        }\n\n        devices\n            .get(device_idx)\n            .context(\"No devices found\")?\n            .get(\"identifier\")\n            .and_then(|id| id.as_str())\n            .map(|s| s.to_string())\n            .context(\"Failed to extract device UUID\")\n    }\n\n    async fn install_ios_app(device_uuid: &str, app_path: &Path) -> Result<()> {\n        let tmpfile = tempfile::NamedTempFile::new()\n            .context(\"Failed to create temporary file for device list\")?;\n\n        // xcrun devicectl device install app --device <uuid> --path <path> --json-output\n        let output = Command::new(\"xcrun\")\n            .args([\n                \"devicectl\",\n                \"device\",\n                \"install\",\n                \"app\",\n                \"--device\",\n                device_uuid,\n                &app_path.display().to_string(),\n                \"--json-output\",\n            ])\n            .arg(tmpfile.path())\n            .output()\n            .await?;\n\n        if !output.status.success() {\n            let stderr = String::from_utf8_lossy(&output.stderr);\n\n            if stderr.contains(\"DeviceLocked\") || stderr.contains(\"device is locked\") {\n                bail!(\n                    \"Failed to install app: your device is locked.\\n\\\n                     Unlock your iPhone/iPad and try again.\"\n                );\n            }\n\n            if stderr.contains(\"cannot be installed on this device\")\n                || stderr.contains(\"0xe8008012\")\n            {\n                bail!(\n                    \"Failed to install app: your device is not registered in the provisioning profile.\\n\\n\\\n                     Your device UDID needs to be added to your Apple Developer account and the \\\n                     provisioning profile regenerated.\\n\\n\\\n                     To fix this:\\n  \\\n                     1. Accept the latest Program License Agreement at:\\n     \\\n                        https://developer.apple.com/account\\n  \\\n                     2. Register your device at:\\n     \\\n                        https://developer.apple.com/account/resources/devices\\n  \\\n                     3. Regenerate your provisioning profile to include the new device\\n  \\\n                     4. Or open any project in Xcode, select your device, and build —\\n     \\\n                        Xcode will update the profile automatically\\n\\n\\\n                     Raw error: {stderr}\"\n                );\n            }\n\n            if stderr.contains(\"provisioning profile\")\n                || stderr.contains(\"ApplicationVerificationFailed\")\n                || stderr.contains(\"code signature\")\n            {\n                bail!(\n                    \"Failed to install app: code signing error.\\n\\\n                     A valid provisioning profile was not found for this app.\\n\\n\\\n                     To fix this:\\n  \\\n                     1. Accept the latest Program License Agreement at:\\n     \\\n                        https://developer.apple.com/account\\n  \\\n                     2. Open the project in Xcode, select your device, and build once —\\n     \\\n                        Xcode will set up signing and provisioning automatically\\n  \\\n                     3. Ensure your device is registered in your Apple Developer account\\n\\n\\\n                     Raw error: {stderr}\"\n                );\n            }\n\n            bail!(\"Failed to install app to device {device_uuid}: {stderr}\");\n        }\n\n        Ok(())\n    }\n\n    async fn launch_ios_app_paused(device_uuid: &str, application_id: &str) -> Result<()> {\n        let tmpfile = tempfile::NamedTempFile::new()\n            .context(\"Failed to create temporary file for device list\")?;\n        let output = Command::new(\"xcrun\")\n            .args([\n                \"devicectl\",\n                \"device\",\n                \"process\",\n                \"launch\",\n                \"--no-activate\",\n                \"--verbose\",\n                \"--device\",\n                device_uuid,\n                application_id,\n                \"--json-output\",\n            ])\n            .arg(tmpfile.path())\n            .output()\n            .await?;\n\n        if !output.status.success() {\n            bail!(\"Failed to launch app: {output:?}\");\n        }\n\n        let json: serde_json::Value =\n            serde_json::from_str(&std::fs::read_to_string(tmpfile.path())?)\n                .context(\"Failed to parse xcrun output\")?;\n\n        let status_pid = json[\"result\"][\"process\"][\"processIdentifier\"]\n            .as_u64()\n            .context(\"Failed to extract process identifier\")?;\n\n        let output = Command::new(\"xcrun\")\n            .args([\n                \"devicectl\",\n                \"device\",\n                \"process\",\n                \"resume\",\n                \"--device\",\n                device_uuid,\n                \"--pid\",\n                &status_pid.to_string(),\n            ])\n            .output()\n            .await?;\n\n        if !output.status.success() {\n            bail!(\"Failed to resume app: {output:?}\");\n        }\n\n        Ok(())\n    }\n\n    /// Launch the Android simulator and deploy the application.\n    ///\n    /// This function handles the process of starting the Android simulator, installing the APK,\n    /// forwarding the development server port, and launching the application on the simulator.\n    ///\n    /// The following `adb` commands are executed:\n    ///\n    /// 1. **Enable Root Access**:\n    ///    - `adb root`: Enables root access on the Android simulator, allowing for advanced operations like pushing files to restricted directories.\n    ///\n    /// 2. **Port Forwarding**:\n    ///    - `adb reverse tcp:<port> tcp:<port>`: Forwards the development server port from the host\n    ///      machine to the Android simulator, enabling communication between the app and the dev server.\n    ///\n    /// 3. **APK Installation**:\n    ///    - `adb install -r <apk_path>`: Installs the APK onto the Android simulator. The `-r` flag\n    ///      ensures that any existing installation of the app is replaced.\n    ///\n    /// 4. **Environment Variables**:\n    ///    - Writes environment variables to a `.env` file in the session cache directory.\n    ///    - `adb push <local_env_file> <device_env_file>`: Pushes the `.env` file to the Android device\n    ///      to configure runtime environment variables for the app.\n    ///\n    /// 5. **App Launch**:\n    ///    - `adb shell am start -n <package_name>/<activity_name>`: Launches the app on the Android\n    ///      simulator. The `<package_name>` and `<activity_name>` are derived from the app's configuration.\n    ///\n    /// # Notes\n    ///\n    /// - This function is asynchronous and spawns a background task to handle the simulator setup and app launch.\n    /// - The Android tools (`adb`) must be available in the system's PATH for this function to work.\n    /// - If the app fails to launch, errors are logged for debugging purposes.\n    ///\n    /// # Resources:\n    /// - <https://developer.android.com/studio/run/emulator-commandline>\n    async fn open_android(\n        &mut self,\n        root: bool,\n        devserver_socket: SocketAddr,\n        envs: Vec<(String, String)>,\n        device_name_query: Option<String>,\n    ) -> Result<()> {\n        let apk_path = self.build.debug_apk_path();\n        let session_cache = self.build.session_cache_dir();\n        let application_id = self.build.bundle_identifier();\n        let adb = self.build.workspace.android_tools()?.adb.clone();\n        let (stdout_tx, stdout_rx) = tokio::sync::mpsc::unbounded_channel::<String>();\n\n        // Start backgrounded since .open() is called while in the arm of the top-level match\n        let task = tokio::task::spawn(async move {\n            // call `adb root` so we can push patches to the device\n            if root {\n                if let Err(e) = Command::new(&adb).arg(\"root\").output().await {\n                    tracing::error!(\"Failed to run `adb root`: {e}\");\n                }\n            }\n\n            // Try to get the transport ID for the device in case there are multiple specified devices\n            // All future commands should use this since its the most recent.\n            let transport_id_args =\n                Self::get_android_device_transport_id(&adb, device_name_query.as_deref()).await;\n\n            // Wait for device to be ready\n            let cmd = Command::new(&adb)\n                .args(transport_id_args)\n                .arg(\"wait-for-device\")\n                .arg(\"shell\")\n                .arg(r#\"while [[ -z $(getprop sys.boot_completed) ]]; do sleep 1; done;\"#)\n                .output();\n            let cmd_future = cmd.fuse();\n            pin_mut!(cmd_future);\n            tokio::select! {\n                _ = &mut cmd_future => {}\n                _ = tokio::time::sleep(Duration::from_millis(50)) => {\n                    tracing::info!(\"Waiting for android emulator to be ready...\");\n                    _ = cmd_future.await;\n                }\n            }\n\n            let port = devserver_socket.port();\n            if let Err(e) = Command::new(&adb)\n                .arg(\"reverse\")\n                .arg(format!(\"tcp:{port}\"))\n                .arg(format!(\"tcp:{port}\"))\n                .output()\n                .await\n            {\n                tracing::error!(\"failed to forward port {port}: {e}\");\n            }\n\n            // Install\n            // adb install -r app-debug.apk\n            let res = Command::new(&adb)\n                .arg(\"install\")\n                .arg(\"-r\")\n                .arg(apk_path)\n                .output()\n                .await?;\n            let std_err = String::from_utf8_lossy(&res.stderr);\n            if !std_err.is_empty() {\n                tracing::error!(\"Failed to install apk with `adb`: {std_err}\");\n            }\n\n            // Clear the session cache dir on the device\n            Command::new(&adb)\n                .arg(\"shell\")\n                .arg(\"rm\")\n                .arg(\"-rf\")\n                .arg(dioxus_cli_config::android_session_cache_dir())\n                .output()\n                .await?;\n\n            // Write the env vars to a .env file in our session cache\n            let env_file = session_cache.join(\".env\");\n            _ = std::fs::write(\n                &env_file,\n                envs.iter()\n                    .map(|(key, value)| format!(\"{key}={value}\"))\n                    .collect::<Vec<_>>()\n                    .join(\"\\n\"),\n            );\n\n            // Push the env file to the device\n            Command::new(&adb)\n                .arg(\"push\")\n                .arg(env_file)\n                .arg(dioxus_cli_config::android_session_cache_dir().join(\".env\"))\n                .output()\n                .await?;\n\n            // eventually, use the user's MainActivity, not our MainActivity\n            // adb shell am start -n dev.dioxus.main/dev.dioxus.main.MainActivity\n            let activity_name = format!(\"{application_id}/dev.dioxus.main.MainActivity\");\n            let res = Command::new(&adb)\n                .arg(\"shell\")\n                .arg(\"am\")\n                .arg(\"start\")\n                .arg(\"-n\")\n                .arg(activity_name)\n                .output()\n                .await?;\n            let std_err = String::from_utf8_lossy(res.stderr.trim_ascii());\n            if !std_err.is_empty() {\n                tracing::error!(\"Failed to start app with `adb`: {std_err}\");\n            }\n\n            // Try to get the transport ID for the device\n            let transport_id_args =\n                Self::get_android_device_transport_id(&adb, device_name_query.as_deref()).await;\n\n            // Get the app's PID with retries\n            // Retry up to 10 times (10 seconds total) since app launch is asynchronous\n            let mut pid: Option<String> = None;\n            for attempt in 1..=10 {\n                match Self::get_android_app_pid(&adb, &application_id, &transport_id_args).await {\n                    Ok(p) => {\n                        pid = Some(p);\n                        break;\n                    }\n                    Err(_) if attempt < 10 => {\n                        tracing::debug!(\n                            \"App PID not found yet, retrying in 1 second... (attempt {}/10)\",\n                            attempt\n                        );\n                        tokio::time::sleep(Duration::from_secs(1)).await;\n                    }\n                    Err(e) => {\n                        return Err(e).context(\n                            \"Failed to get app PID after 10 attempts - app may not have started\",\n                        );\n                    }\n                }\n            }\n\n            let pid = pid.context(\"Failed to get app PID\")?;\n\n            // Spawn logcat with filtering\n            // By default: show only RustStdoutStderr (app Rust logs) and fatal errors\n            // With tracing enabled: show all logs from the app process\n            // Note: We always capture at DEBUG level, then filter in Rust based on trace flag\n            let mut child = Command::new(&adb)\n                .args(&transport_id_args)\n                .arg(\"logcat\")\n                .arg(\"-v\")\n                .arg(\"brief\")\n                .arg(\"--pid\")\n                .arg(&pid)\n                .arg(\"*:D\") // Capture all logs at DEBUG level (filtered in Rust)\n                .stdout(Stdio::piped())\n                .stderr(Stdio::null())\n                .kill_on_drop(true)\n                .spawn()?;\n\n            let stdout = child.stdout.take().unwrap();\n            let mut reader = BufReader::new(stdout).lines();\n\n            while let Ok(Some(line)) = reader.next_line().await {\n                _ = stdout_tx.send(line);\n            }\n\n            Ok::<(), Error>(())\n        });\n\n        self.spawn_handle = Some(task);\n        self.adb_logcat_stdout = Some(UnboundedReceiverStream::new(stdout_rx));\n\n        Ok(())\n    }\n\n    fn make_entropy_path(exe: &PathBuf) -> PathBuf {\n        let id = uuid::Uuid::new_v4();\n        let name = id.to_string();\n        let some_entropy = name.split('-').next().unwrap();\n\n        // Split up the exe into the file stem and extension\n        let extension = exe.extension().unwrap_or_default();\n        let file_stem = exe.file_stem().unwrap().to_str().unwrap();\n\n        // Make a copy of the server exe with a new name\n        let entropy_server_exe = exe\n            .with_file_name(format!(\"{}-{}\", file_stem, some_entropy))\n            .with_extension(extension);\n\n        std::fs::copy(exe, &entropy_server_exe).unwrap();\n\n        entropy_server_exe\n    }\n\n    fn app_exe(&mut self) -> PathBuf {\n        let mut main_exe = self.build.main_exe();\n\n        // The requirement here is based on the platform, not necessarily our current architecture.\n        let requires_entropy = match self.build.bundle {\n            // When running \"bundled\", we don't need entropy\n            BundleFormat::Web | BundleFormat::MacOS | BundleFormat::Ios | BundleFormat::Android => {\n                false\n            }\n\n            // But on platforms that aren't running as \"bundled\", we do.\n            BundleFormat::Windows | BundleFormat::Linux | BundleFormat::Server => true,\n        };\n\n        if requires_entropy || crate::devcfg::should_force_entropy() {\n            // If we already have an entropy app exe, return it - this is useful for re-opening the same app\n            if let Some(existing_app_exe) = self.entropy_app_exe.clone() {\n                return existing_app_exe;\n            }\n\n            let entropy_app_exe = Self::make_entropy_path(&main_exe);\n            self.entropy_app_exe = Some(entropy_app_exe.clone());\n            main_exe = entropy_app_exe;\n        }\n\n        main_exe\n    }\n\n    fn complete_compile(&mut self) {\n        if self.compile_end.is_none() {\n            self.compiled_crates = self.expected_crates;\n            self.compile_end = Some(Instant::now());\n        }\n    }\n\n    /// Get the total duration of the build, if all stages have completed\n    pub(crate) fn total_build_time(&self) -> Option<Duration> {\n        Some(self.compile_duration()? + self.bundle_duration()?)\n    }\n\n    pub(crate) fn compile_duration(&self) -> Option<Duration> {\n        Some(\n            self.compile_end\n                .unwrap_or_else(Instant::now)\n                .duration_since(self.compile_start?),\n        )\n    }\n\n    pub(crate) fn bundle_duration(&self) -> Option<Duration> {\n        Some(\n            self.bundle_end\n                .unwrap_or_else(Instant::now)\n                .duration_since(self.bundle_start?),\n        )\n    }\n\n    /// Return a number between 0 and 1 representing the progress of the app build\n    pub(crate) fn compile_progress(&self) -> f64 {\n        self.compiled_crates as f64 / self.expected_crates as f64\n    }\n\n    pub(crate) fn bundle_progress(&self) -> f64 {\n        self.bundling_progress\n    }\n\n    pub(crate) fn is_finished(&self) -> bool {\n        match self.stage {\n            BuildStage::Success => true,\n            BuildStage::Failed => true,\n            BuildStage::Aborted => true,\n            BuildStage::Restarting => false,\n            _ => false,\n        }\n    }\n\n    /// Check if the queued build is blocking hotreloads\n    pub(crate) fn can_receive_hotreloads(&self) -> bool {\n        matches!(&self.stage, BuildStage::Success | BuildStage::Failed)\n    }\n\n    pub(crate) async fn open_debugger(&mut self, server: &WebServer) -> Result<()> {\n        let url = match self.build.bundle {\n            BundleFormat::MacOS\n            | BundleFormat::Windows\n            | BundleFormat::Linux\n            | BundleFormat::Server => {\n                let Some(Some(pid)) = self.child.as_mut().map(|f| f.id()) else {\n                    tracing::warn!(\"No process to attach debugger to\");\n                    return Ok(());\n                };\n\n                format!(\n                    \"vscode://vadimcn.vscode-lldb/launch/config?{{'request':'attach','pid':{pid}}}\"\n                )\n            }\n\n            BundleFormat::Web => {\n                // code --open-url \"vscode://DioxusLabs.dioxus/debugger?uri=http://127.0.0.1:8080\"\n                // todo - debugger could open to the *current* page afaik we don't have a way to have that info\n                let address = server.devserver_address();\n                let base_path = self.build.base_path();\n                let https = self.build.config.web.https.enabled.unwrap_or_default();\n                let protocol = if https { \"https\" } else { \"http\" };\n                let base_path = match base_path {\n                    Some(base_path) => format!(\"/{}\", base_path.trim_matches('/')),\n                    None => \"\".to_owned(),\n                };\n                format!(\"vscode://DioxusLabs.dioxus/debugger?uri={protocol}://{address}{base_path}\")\n            }\n\n            BundleFormat::Ios => {\n                let Some(pid) = self.pid else {\n                    tracing::warn!(\"No process to attach debugger to\");\n                    return Ok(());\n                };\n\n                format!(\n                    \"vscode://vadimcn.vscode-lldb/launch/config?{{'request':'attach','pid':{pid}}}\"\n                )\n            }\n\n            // https://stackoverflow.com/questions/53733781/how-do-i-use-lldb-to-debug-c-code-on-android-on-command-line/64997332#64997332\n            // https://android.googlesource.com/platform/development/+/refs/heads/main/scripts/gdbclient.py\n            // run lldbserver on the device and then connect\n            //\n            // # TODO: https://code.visualstudio.com/api/references/vscode-api#debug and\n            // #       https://code.visualstudio.com/api/extension-guides/debugger-extension and\n            // #       https://github.com/vadimcn/vscode-lldb/blob/6b775c439992b6615e92f4938ee4e211f1b060cf/extension/pickProcess.ts#L6\n            //\n            // res = {\n            //     \"name\": \"(lldbclient.py) Attach {} (port: {})\".format(binary_name.split(\"/\")[-1], port),\n            //     \"type\": \"lldb\",\n            //     \"request\": \"custom\",\n            //     \"relativePathBase\": root,\n            //     \"sourceMap\": { \"/b/f/w\" : root, '': root, '.': root },\n            //     \"initCommands\": ['settings append target.exec-search-paths {}'.format(' '.join(solib_search_path))],\n            //     \"targetCreateCommands\": [\"target create {}\".format(binary_name),\n            //                              \"target modules search-paths add / {}/\".format(sysroot)],\n            //     \"processCreateCommands\": [\"gdb-remote {}\".format(str(port))]\n            // }\n            //\n            // https://github.com/vadimcn/codelldb/issues/213\n            //\n            // lots of pain to figure this out:\n            //\n            // (lldb) image add target/dx/tw6/debug/android/app/app/src/main/jniLibs/arm64-v8a/libdioxusmain.so\n            // (lldb) settings append target.exec-search-paths target/dx/tw6/debug/android/app/app/src/main/jniLibs/arm64-v8a/libdioxusmain.so\n            // (lldb) process handle SIGSEGV --pass true --stop false --notify true (otherwise the java threads cause crash)\n            //\n            BundleFormat::Android => {\n                // adb push ./sdk/ndk/29.0.13113456/toolchains/llvm/prebuilt/darwin-x86_64/lib/clang/20/lib/linux/aarch64/lldb-server /tmp\n                // adb shell \"/tmp/lldb-server --server --listen ...\"\n                // \"vscode://vadimcn.vscode-lldb/launch/config?{{'request':'connect','port': {}}}\",\n                // format!(\n                //     \"vscode://vadimcn.vscode-lldb/launch/config?{{'request':'attach','pid':{pid}}}\"\n                // )\n                let tools = &self.build.workspace.android_tools()?;\n\n                // get the pid of the app\n                let pid = Command::new(&tools.adb)\n                    .arg(\"shell\")\n                    .arg(\"pidof\")\n                    .arg(self.build.bundle_identifier())\n                    .output()\n                    .await\n                    .ok()\n                    .and_then(|output| String::from_utf8(output.stdout).ok())\n                    .and_then(|s| s.trim().parse::<u32>().ok())\n                    .unwrap();\n\n                // copy the lldb-server to the device\n                let lldb_server = tools\n                    .android_tools_dir()\n                    .parent()\n                    .unwrap()\n                    .join(\"lib\")\n                    .join(\"clang\")\n                    .join(\"20\")\n                    .join(\"lib\")\n                    .join(\"linux\")\n                    .join(\"aarch64\")\n                    .join(\"lldb-server\");\n\n                tracing::info!(\"Copying lldb-server to device: {lldb_server:?}\");\n\n                _ = Command::new(&tools.adb)\n                    .arg(\"push\")\n                    .arg(lldb_server)\n                    .arg(\"/tmp/lldb-server\")\n                    .output()\n                    .await;\n\n                // Forward requests on 10086 to the device\n                _ = Command::new(&tools.adb)\n                    .arg(\"forward\")\n                    .arg(\"tcp:10086\")\n                    .arg(\"tcp:10086\")\n                    .output()\n                    .await;\n\n                // start the server - running it multiple times will make the subsequent ones fail (which is fine)\n                _ = Command::new(&tools.adb)\n                    .arg(\"shell\")\n                    .arg(r#\"cd /tmp && ./lldb-server platform --server --listen '*:10086'\"#)\n                    .kill_on_drop(false)\n                    .stdin(Stdio::null())\n                    .stdout(Stdio::piped())\n                    .stderr(Stdio::piped())\n                    .spawn();\n\n                let program_path = self.build.main_exe();\n                format!(\n                    r#\"vscode://vadimcn.vscode-lldb/launch/config?{{\n                        'name':'Attach to Android',\n                        'type':'lldb',\n                        'request':'attach',\n                        'pid': '{pid}',\n                        'processCreateCommands': [\n                            'platform select remote-android',\n                            'platform connect connect://localhost:10086',\n                            'settings set target.inherit-env false',\n                            'settings set target.inline-breakpoint-strategy always',\n                            'settings set target.process.thread.step-avoid-regexp \\\"JavaBridge|JDWP|Binder|ReferenceQueueDaemon\\\"',\n                            'process handle SIGSEGV --pass true --stop false --notify true\"',\n                            'settings append target.exec-search-paths {program_path}',\n                            'attach --pid {pid}',\n                            'continue'\n                        ]\n                    }}\"#,\n                    program_path = program_path.display(),\n                )\n                .lines()\n                .map(|line| line.trim())\n                .join(\"\")\n            }\n        };\n\n        tracing::info!(\"Opening debugger for [{}]: {url}\", self.build.bundle);\n\n        _ = tokio::process::Command::new(\"code\")\n            .arg(\"--open-url\")\n            .arg(url)\n            .spawn();\n\n        Ok(())\n    }\n\n    async fn get_android_device_transport_id(\n        adb: &PathBuf,\n        device_name_query: Option<&str>,\n    ) -> Vec<String> {\n        // If there are multiple devices, we pick the one matching the query\n        let mut device_specifier_args = vec![];\n\n        if let Some(device_name_query) = device_name_query {\n            if let Ok(res) = Command::new(adb).arg(\"devices\").arg(\"-l\").output().await {\n                let devices = String::from_utf8_lossy(&res.stdout);\n                let mut best_score = 0;\n                let mut device_identifier = \"\".to_string();\n                use nucleo::{chars, Config, Matcher, Utf32Str};\n                let mut matcher = Matcher::new(Config::DEFAULT);\n                let normalize = |c: char| chars::to_lower_case(chars::normalize(c));\n                let needle = device_name_query.chars().map(normalize).collect::<Vec<_>>();\n\n                for line in devices.lines() {\n                    let device_name = line.split_whitespace().next().unwrap_or(\"\");\n                    let Some(transport_id) = line\n                        .split_whitespace()\n                        .find(|s| s.starts_with(\"transport_id:\"))\n                        .map(|s| s.trim_start_matches(\"transport_id:\"))\n                    else {\n                        continue;\n                    };\n\n                    let device_name = device_name.chars().map(normalize).collect::<Vec<_>>();\n                    let score = matcher\n                        .fuzzy_match(Utf32Str::Unicode(&device_name), Utf32Str::Unicode(&needle));\n                    if let Some(score) = score {\n                        if score > best_score {\n                            best_score = score;\n                            device_identifier = transport_id.to_string();\n                        }\n                    }\n                }\n\n                if best_score != 0 {\n                    device_specifier_args.push(\"-t\".to_string());\n                    device_specifier_args.push(device_identifier.to_string());\n                }\n            }\n\n            if device_specifier_args.is_empty() {\n                tracing::warn!(\n                    \"No device found matching query: {device_name_query}. Using default transport ID.\"\n                );\n            }\n        }\n\n        device_specifier_args\n    }\n\n    /// Get the PID of the running Android app\n    async fn get_android_app_pid(\n        adb: &Path,\n        application_id: &str,\n        transport_id_args: &[String],\n    ) -> Result<String> {\n        let output = Command::new(adb)\n            .args(transport_id_args)\n            .arg(\"shell\")\n            .arg(\"pidof\")\n            .arg(application_id)\n            .output()\n            .await?;\n\n        let pid = String::from_utf8(output.stdout)?.trim().to_string();\n\n        if pid.is_empty() {\n            anyhow::bail!(\"App process not found - may not have started yet\");\n        }\n\n        Ok(pid)\n    }\n}\n"
  },
  {
    "path": "packages/cli/src/build/cache.rs",
    "content": "//! Object file cache for workspace hotpatching.\n//!\n//! Maintains the latest `.rcgu.o` files for each crate in the cumulative `modified_crates` set.\n//! Used for accumulated relinking: combining objects from all modified crates into a single\n//! patch dylib.\n//!\n//! Objects are stored on disk under `session_cache_dir/object_cache/{crate_name}/` so they\n//! persist across patches without holding file contents in memory. The session cache dir\n//! lives in `/tmp/` and is cleaned up by the OS.\n//!\n//! - **Dep crates:** objects are extracted from their rlib in `target/deps/`.\n//! - **Tip crate:** objects are copied from linker arg paths since incremental compilation\n//!   overwrites them in place.\n\nuse std::collections::HashMap;\nuse std::io::Read;\nuse std::path::{Path, PathBuf};\n\n/// Cache of compiled object files on disk, keyed by crate name.\n///\n/// After each compilation, the cache is updated for the compiled crate by extracting\n/// objects to `dir/{crate_name}/`. On relink, paths from all crates in `modified_crates`\n/// are passed directly to the linker — no intermediate copy needed.\n#[derive(Clone, Debug, Default, PartialEq)]\npub struct ObjectCache {\n    /// Root directory: `session_cache_dir/object_cache/`\n    dir: PathBuf,\n\n    /// crate_name -> object file paths on disk\n    objects: HashMap<String, Vec<PathBuf>>,\n}\n\nimpl ObjectCache {\n    pub fn new(session_cache_dir: &Path) -> Self {\n        let dir = session_cache_dir.join(\"object_cache\");\n        Self {\n            dir,\n            objects: HashMap::new(),\n        }\n    }\n\n    /// Extract `.rcgu.o` files from an rlib archive and write them to\n    /// `dir/{crate_name}/`. Replaces any previously cached objects for this crate.\n    pub fn cache_from_rlib(&mut self, crate_name: &str, rlib_path: &Path) -> anyhow::Result<()> {\n        let crate_dir = self.dir.join(crate_name);\n        // Clear previous objects for this crate\n        if crate_dir.exists() {\n            std::fs::remove_dir_all(&crate_dir)?;\n        }\n        std::fs::create_dir_all(&crate_dir)?;\n\n        let rlib_contents = std::fs::read(rlib_path)?;\n        let mut reader = ar::Archive::new(std::io::Cursor::new(rlib_contents));\n        let mut paths = Vec::new();\n\n        while let Some(Ok(mut entry)) = reader.next_entry() {\n            let name = std::str::from_utf8(entry.header().identifier())\n                .unwrap_or_default()\n                .to_string();\n\n            // Skip rmeta and empty entries\n            if name.ends_with(\".rmeta\") || entry.header().size() == 0 {\n                continue;\n            }\n\n            // Only keep object files\n            if !name.ends_with(\".o\") {\n                continue;\n            }\n\n            let mut data = Vec::with_capacity(entry.header().size() as usize);\n            entry.read_to_end(&mut data)?;\n\n            let obj_path = crate_dir.join(&name);\n            std::fs::write(&obj_path, &data)?;\n            paths.push(obj_path);\n        }\n\n        self.objects.insert(crate_name.to_string(), paths);\n        Ok(())\n    }\n\n    /// Cache tip crate objects by copying them from their filesystem paths.\n    ///\n    /// Tip crate `.rcgu.o` files get overwritten on recompilation, so we copy\n    /// them into our cache directory for stable access.\n    pub fn cache_from_paths(\n        &mut self,\n        crate_name: &str,\n        object_paths: &[impl AsRef<Path>],\n    ) -> anyhow::Result<()> {\n        let crate_dir = self.dir.join(crate_name);\n        if crate_dir.exists() {\n            std::fs::remove_dir_all(&crate_dir)?;\n        }\n        std::fs::create_dir_all(&crate_dir)?;\n\n        let mut paths = Vec::with_capacity(object_paths.len());\n\n        for path in object_paths {\n            let path = path.as_ref();\n            let name = path\n                .file_name()\n                .and_then(|n| n.to_str())\n                .unwrap_or_default();\n            let dest = crate_dir.join(name);\n            std::fs::copy(path, &dest)?;\n            paths.push(dest);\n        }\n\n        self.objects.insert(crate_name.to_string(), paths);\n        Ok(())\n    }\n\n    /// Get cached object file paths for a crate.\n    pub fn get(&self, crate_name: &str) -> Option<&Vec<PathBuf>> {\n        self.objects.get(crate_name)\n    }\n}\n"
  },
  {
    "path": "packages/cli/src/build/context.rs",
    "content": "//! Report progress about the build to the user. We use channels to report progress back to the CLI.\n\nuse super::BuildMode;\nuse crate::{BuildArtifacts, BuildStage, Error, TraceSrc};\nuse cargo_metadata::diagnostic::Diagnostic;\nuse futures_channel::mpsc::{UnboundedReceiver, UnboundedSender};\nuse serde::{Deserialize, Serialize};\nuse std::{path::PathBuf, process::ExitStatus};\n\n/// The context of the build process. While the BuildRequest is a \"plan\" for the build, the BuildContext\n/// provides some dynamic configuration that is only known at runtime. For example, the Progress channel\n/// and the BuildMode can change while serving.\n///\n/// The structure of this is roughly taken from cargo itself which uses a similar pattern.\n#[derive(Debug, Clone)]\npub struct BuildContext {\n    pub tx: ProgressTx,\n    pub mode: BuildMode,\n    pub build_id: BuildId,\n}\n\npub type ProgressTx = UnboundedSender<BuilderUpdate>;\npub type ProgressRx = UnboundedReceiver<BuilderUpdate>;\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]\npub struct BuildId(pub(crate) usize);\nimpl BuildId {\n    pub const PRIMARY: Self = Self(0);\n    pub const SECONDARY: Self = Self(1);\n}\n\n#[allow(clippy::large_enum_variant)]\npub enum BuilderUpdate {\n    Progress {\n        stage: BuildStage,\n    },\n\n    CompilerMessage {\n        message: Diagnostic,\n    },\n\n    /// The build completed successfully and the artifacts are ready. The artifacts are dependent on\n    /// the build mode (fat vs thin vs base).\n    BuildReady {\n        bundle: BuildArtifacts,\n    },\n\n    /// The build failed. This might be because of a compilation error, or an error internal to DX.\n    BuildFailed {\n        err: Error,\n    },\n\n    /// A running process has received a stdout.\n    /// May or may not be a complete line - do not treat it as a line. It will include a line if it is a complete line.\n    ///\n    /// We will poll lines and any content in a 50ms interval\n    StdoutReceived {\n        msg: String,\n    },\n\n    /// A running process has received a stderr.\n    /// May or may not be a complete line - do not treat it as a line. It will include a line if it is a complete line.\n    ///\n    /// We will poll lines and any content in a 50ms interval\n    StderrReceived {\n        msg: String,\n    },\n\n    /// The running app (DUT) has exited and is no longer running.\n    ProcessExited {\n        status: ExitStatus,\n    },\n\n    /// Waiting for the process failed. This might be because it's hung or being debugged.\n    /// This is not the same as the process exiting, so it should just be logged but not treated as an error.\n    ProcessWaitFailed {\n        err: std::io::Error,\n    },\n}\n\nimpl BuildContext {\n    /// Returns true if this is a client build - basically, is this the primary build?\n    /// We try not to duplicate work between client and server builds, like asset copying.\n    pub(crate) fn is_primary_build(&self) -> bool {\n        self.build_id == BuildId::PRIMARY\n    }\n\n    pub(crate) fn status_wasm_bindgen_start(&self) {\n        _ = self.tx.unbounded_send(BuilderUpdate::Progress {\n            stage: BuildStage::RunningBindgen,\n        });\n    }\n\n    pub(crate) fn status_splitting_bundle(&self) {\n        _ = self.tx.unbounded_send(BuilderUpdate::Progress {\n            stage: BuildStage::SplittingBundle,\n        });\n    }\n\n    pub(crate) fn status_start_bundle(&self) {\n        _ = self.tx.unbounded_send(BuilderUpdate::Progress {\n            stage: BuildStage::Bundling,\n        });\n    }\n\n    pub(crate) fn status_running_gradle(&self) {\n        _ = self.tx.unbounded_send(BuilderUpdate::Progress {\n            stage: BuildStage::RunningGradle,\n        })\n    }\n\n    pub(crate) fn status_compiling_native_plugins(&self, detail: impl Into<String>) {\n        _ = self.tx.unbounded_send(BuilderUpdate::Progress {\n            stage: BuildStage::CompilingNativePlugins {\n                detail: detail.into(),\n            },\n        });\n    }\n\n    pub(crate) fn status_codesigning(&self) {\n        _ = self.tx.unbounded_send(BuilderUpdate::Progress {\n            stage: BuildStage::CodeSigning,\n        });\n    }\n\n    pub(crate) fn status_build_diagnostic(&self, message: Diagnostic) {\n        _ = self\n            .tx\n            .unbounded_send(BuilderUpdate::CompilerMessage { message });\n    }\n\n    pub(crate) fn status_build_error(&self, line: String) {\n        tracing::warn!(dx_src = ?TraceSrc::Cargo, \"{line}\");\n    }\n\n    pub(crate) fn status_build_message(&self, line: String) {\n        tracing::trace!(dx_src = ?TraceSrc::Cargo, \"{line}\");\n    }\n\n    pub(crate) fn status_build_progress(&self, count: usize, total: usize, name: String) {\n        _ = self.tx.unbounded_send(BuilderUpdate::Progress {\n            stage: BuildStage::Compiling {\n                current: count,\n                total,\n                krate: name,\n            },\n        });\n    }\n\n    pub(crate) fn status_starting_build(&self, crate_count: usize) {\n        _ = self.tx.unbounded_send(BuilderUpdate::Progress {\n            stage: BuildStage::Starting {\n                patch: matches!(self.mode, BuildMode::Thin { .. }),\n                crate_count,\n            },\n        });\n    }\n\n    pub(crate) fn status_starting_link(&self) {\n        _ = self.tx.unbounded_send(BuilderUpdate::Progress {\n            stage: BuildStage::Linking,\n        });\n    }\n\n    pub(crate) fn status_copied_asset(\n        progress: &UnboundedSender<BuilderUpdate>,\n        current: usize,\n        total: usize,\n        path: PathBuf,\n    ) {\n        _ = progress.unbounded_send(BuilderUpdate::Progress {\n            stage: BuildStage::CopyingAssets {\n                current,\n                total,\n                path,\n            },\n        });\n    }\n\n    pub(crate) fn status_optimizing_wasm(&self) {\n        _ = self.tx.unbounded_send(BuilderUpdate::Progress {\n            stage: BuildStage::OptimizingWasm,\n        });\n    }\n\n    pub(crate) fn status_hotpatching(&self) {\n        _ = self.tx.unbounded_send(BuilderUpdate::Progress {\n            stage: BuildStage::Hotpatching,\n        });\n    }\n\n    pub(crate) fn status_installing_tooling(&self) {\n        _ = self.tx.unbounded_send(BuilderUpdate::Progress {\n            stage: BuildStage::InstallingTooling,\n        });\n    }\n\n    pub(crate) fn status_compressing_assets(&self) {\n        _ = self.tx.unbounded_send(BuilderUpdate::Progress {\n            stage: BuildStage::CompressingAssets,\n        });\n    }\n    pub(crate) fn status_extracting_assets(&self) {\n        _ = self.tx.unbounded_send(BuilderUpdate::Progress {\n            stage: BuildStage::ExtractingAssets,\n        });\n    }\n}\n"
  },
  {
    "path": "packages/cli/src/build/ios_swift.rs",
    "content": "//! iOS/macOS Swift package manifest helpers and compilation.\n\nuse crate::Result;\nuse anyhow::Context;\nuse manganis_core::SwiftPackageMetadata;\nuse std::path::{Path, PathBuf};\nuse target_lexicon::{OperatingSystem, Triple};\nuse tokio::process::Command;\n\n/// Create a proper framework bundle from a dylib for iOS/macOS.\n///\n/// iOS uses a flat structure while macOS uses a versioned structure.\n/// Both require an Info.plist for proper App Store submission.\npub async fn create_framework_bundle(\n    dylib_path: &Path,\n    framework_name: &str,\n    output_dir: &Path,\n    target_triple: &Triple,\n    bundle_identifier: &str,\n) -> Result<PathBuf> {\n    let is_ios = matches!(target_triple.operating_system, OperatingSystem::IOS(_));\n    let min_os_version = if is_ios { \"13.0\" } else { \"11.0\" };\n\n    let framework_dir = output_dir.join(format!(\"{}.framework\", framework_name));\n\n    // Remove existing framework if present\n    if framework_dir.exists() {\n        std::fs::remove_dir_all(&framework_dir)?;\n    }\n\n    if is_ios {\n        // iOS uses flat structure: Framework.framework/FrameworkName + Info.plist\n        std::fs::create_dir_all(&framework_dir)?;\n\n        // Copy dylib as the framework executable (no extension)\n        let exec_path = framework_dir.join(framework_name);\n        std::fs::copy(dylib_path, &exec_path)?;\n\n        // Set the install name using install_name_tool\n        let output = Command::new(\"xcrun\")\n            .arg(\"install_name_tool\")\n            .arg(\"-id\")\n            .arg(format!(\n                \"@rpath/{}.framework/{}\",\n                framework_name, framework_name\n            ))\n            .arg(&exec_path)\n            .output()\n            .await?;\n\n        if !output.status.success() {\n            let stderr = String::from_utf8_lossy(&output.stderr);\n            anyhow::bail!(\"install_name_tool failed: {}\", stderr);\n        }\n\n        // Create Info.plist\n        let info_plist = format!(\n            r#\"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n    <key>CFBundleDevelopmentRegion</key>\n    <string>en</string>\n    <key>CFBundleExecutable</key>\n    <string>{framework_name}</string>\n    <key>CFBundleIdentifier</key>\n    <string>{bundle_identifier}</string>\n    <key>CFBundleInfoDictionaryVersion</key>\n    <string>6.0</string>\n    <key>CFBundleName</key>\n    <string>{framework_name}</string>\n    <key>CFBundlePackageType</key>\n    <string>FMWK</string>\n    <key>CFBundleShortVersionString</key>\n    <string>1.0</string>\n    <key>CFBundleVersion</key>\n    <string>1</string>\n    <key>MinimumOSVersion</key>\n    <string>{min_os_version}</string>\n    <key>CFBundleSupportedPlatforms</key>\n    <array>\n        <string>iPhoneOS</string>\n    </array>\n</dict>\n</plist>\"#\n        );\n\n        std::fs::write(framework_dir.join(\"Info.plist\"), info_plist)?;\n    } else {\n        // macOS uses versioned structure with symlinks\n        let versions_a = framework_dir.join(\"Versions\").join(\"A\");\n        let resources_dir = versions_a.join(\"Resources\");\n        std::fs::create_dir_all(&resources_dir)?;\n\n        // Copy dylib as the framework executable\n        let exec_path = versions_a.join(framework_name);\n        std::fs::copy(dylib_path, &exec_path)?;\n\n        // Set install name\n        let output = Command::new(\"xcrun\")\n            .arg(\"install_name_tool\")\n            .arg(\"-id\")\n            .arg(format!(\n                \"@rpath/{}.framework/Versions/A/{}\",\n                framework_name, framework_name\n            ))\n            .arg(&exec_path)\n            .output()\n            .await?;\n\n        if !output.status.success() {\n            let stderr = String::from_utf8_lossy(&output.stderr);\n            anyhow::bail!(\"install_name_tool failed: {}\", stderr);\n        }\n\n        // Create Info.plist in Resources\n        let info_plist = format!(\n            r#\"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n    <key>CFBundleDevelopmentRegion</key>\n    <string>en</string>\n    <key>CFBundleExecutable</key>\n    <string>{framework_name}</string>\n    <key>CFBundleIdentifier</key>\n    <string>{bundle_identifier}</string>\n    <key>CFBundleInfoDictionaryVersion</key>\n    <string>6.0</string>\n    <key>CFBundleName</key>\n    <string>{framework_name}</string>\n    <key>CFBundlePackageType</key>\n    <string>FMWK</string>\n    <key>CFBundleShortVersionString</key>\n    <string>1.0</string>\n    <key>CFBundleVersion</key>\n    <string>1</string>\n    <key>LSMinimumSystemVersion</key>\n    <string>{min_os_version}</string>\n</dict>\n</plist>\"#\n        );\n\n        std::fs::write(resources_dir.join(\"Info.plist\"), info_plist)?;\n\n        // Create symbolic links (required for macOS framework structure)\n        let versions_dir = framework_dir.join(\"Versions\");\n        #[cfg(unix)]\n        {\n            std::os::unix::fs::symlink(\"A\", versions_dir.join(\"Current\"))?;\n            std::os::unix::fs::symlink(\n                format!(\"Versions/Current/{}\", framework_name),\n                framework_dir.join(framework_name),\n            )?;\n            std::os::unix::fs::symlink(\n                \"Versions/Current/Resources\",\n                framework_dir.join(\"Resources\"),\n            )?;\n        }\n    }\n\n    tracing::debug!(\n        \"Created {} framework bundle: {}\",\n        if is_ios { \"iOS\" } else { \"macOS\" },\n        framework_dir.display()\n    );\n\n    Ok(framework_dir)\n}\n\n/// Compile Swift sources and return the path to the dynamic framework bundle.\n///\n/// This function:\n/// 1. Generates an umbrella Package.swift that includes all Swift plugins\n/// 2. Runs `swift build` to compile into a dynamic library\n/// 3. Wraps the dylib in a proper .framework bundle for iOS/macOS\n/// 4. Returns the path to the resulting `.framework` bundle\npub async fn compile_swift_sources(\n    swift_sources: &[SwiftPackageMetadata],\n    target_triple: &Triple,\n    build_dir: &Path,\n    release: bool,\n) -> Result<Option<PathBuf>> {\n    if swift_sources.is_empty() {\n        return Ok(None);\n    }\n\n    tracing::debug!(\n        \"Compiling {} Swift plugin(s) for {}\",\n        swift_sources.len(),\n        target_triple\n    );\n\n    // Create the plugins build directory\n    let plugins_dir = build_dir.join(\"swift-plugins\");\n    std::fs::create_dir_all(&plugins_dir)?;\n\n    // Copy and prepare all Swift source packages\n    let mut plugin_paths = Vec::new();\n    for source in swift_sources {\n        let source_path = PathBuf::from(source.package_path.as_str());\n        let plugin_name = source.plugin_name.as_str();\n        let product_name = source.product.as_str();\n\n        if !source_path.exists() {\n            tracing::warn!(\n                \"Swift package path does not exist: {} (for plugin {})\",\n                source_path.display(),\n                plugin_name\n            );\n            continue;\n        }\n\n        let dest_path = plugins_dir.join(plugin_name);\n        if dest_path.exists() {\n            std::fs::remove_dir_all(&dest_path)?;\n        }\n        copy_dir_recursive(&source_path, &dest_path)?;\n\n        // Modify the Package.swift to produce a dynamic library\n        if let Err(e) = modify_package_for_dynamic_library(&dest_path, product_name) {\n            tracing::warn!(\"Failed to modify Package.swift for dynamic library: {}\", e);\n        }\n\n        plugin_paths.push((plugin_name.to_string(), product_name.to_string(), dest_path));\n        tracing::debug!(\n            \"Copied Swift plugin '{}' from {} to {}\",\n            plugin_name,\n            source_path.display(),\n            plugins_dir.join(plugin_name).display()\n        );\n    }\n\n    if plugin_paths.is_empty() {\n        tracing::warn!(\"No valid Swift packages found to compile\");\n        return Ok(None);\n    }\n\n    // Determine Swift target triple and SDK\n    let (swift_triple, sdk_name) = swift_target_and_sdk(target_triple)?;\n    let sdk_path = lookup_sdk_path(&sdk_name).await?;\n\n    // Build configuration\n    let configuration = if release { \"release\" } else { \"debug\" };\n\n    // Build each plugin package individually\n    for (plugin_name, product_name, package_path) in &plugin_paths {\n        tracing::debug!(\n            \"Building Swift plugin '{}' (product: {})\",\n            plugin_name,\n            product_name\n        );\n\n        let build_path = package_path.join(\".build\");\n\n        let mut cmd = Command::new(\"xcrun\");\n        cmd.args([\"swift\", \"build\"])\n            .arg(\"--package-path\")\n            .arg(package_path)\n            .arg(\"--configuration\")\n            .arg(configuration)\n            .arg(\"--triple\")\n            .arg(&swift_triple)\n            .arg(\"--sdk\")\n            .arg(&sdk_path)\n            .arg(\"--product\")\n            .arg(product_name)\n            .arg(\"--build-path\")\n            .arg(&build_path);\n\n        tracing::debug!(\"Running: xcrun swift build for {}\", product_name);\n\n        let output = cmd.output().await?;\n\n        if !output.status.success() {\n            let stderr = String::from_utf8_lossy(&output.stderr);\n            let stdout = String::from_utf8_lossy(&output.stdout);\n            anyhow::bail!(\n                \"Swift build failed for plugin '{}':\\n{}\\n{}\",\n                plugin_name,\n                stdout,\n                stderr\n            );\n        }\n\n        if !output.stderr.is_empty() {\n            let stderr = String::from_utf8_lossy(&output.stderr);\n            tracing::debug!(\"Swift build warnings for {}:\\n{}\", plugin_name, stderr);\n        }\n    }\n\n    // Find the output dynamic library for each plugin\n    // Swift puts the output in .build/<triple>/<configuration>/lib<ProductName>.dylib\n    // or .build/<configuration>/lib<ProductName>.dylib depending on the version\n    let mut all_dylibs = Vec::new();\n\n    for (_, product_name, package_path) in &plugin_paths {\n        let build_path = package_path.join(\".build\");\n        let lib_name = format!(\"lib{}.dylib\", product_name);\n\n        let lib_search_paths = [\n            build_path.join(&swift_triple).join(configuration),\n            build_path.join(configuration),\n            build_path.clone(),\n        ];\n\n        let mut found = false;\n        for search_path in &lib_search_paths {\n            let lib_path = search_path.join(&lib_name);\n            if lib_path.exists() {\n                tracing::debug!(\"Found Swift dynamic library: {}\", lib_path.display());\n                all_dylibs.push((product_name.clone(), lib_path));\n                found = true;\n                break;\n            }\n        }\n\n        if !found {\n            tracing::warn!(\n                \"Could not find compiled Swift dynamic library for product '{}' (expected {})\",\n                product_name,\n                lib_name\n            );\n        }\n    }\n\n    if all_dylibs.is_empty() {\n        tracing::warn!(\"No Swift dynamic libraries were compiled successfully\");\n        return Ok(None);\n    }\n\n    // For dynamic libraries, we need to wrap each in a framework bundle\n    // If there's only one library, create a single framework\n    // If there are multiple, we'll create frameworks for each (they're independent)\n    // The first one is the \"primary\" framework that gets returned\n\n    let (_primary_name, primary_dylib) = all_dylibs.remove(0);\n\n    // Create the framework bundle from the dylib\n    // Use \"DioxusSwiftPlugins\" as the umbrella framework name\n    let framework_name = \"DioxusSwiftPlugins\";\n    let bundle_identifier = \"com.dioxus.swift.plugins\";\n\n    let framework_path = create_framework_bundle(\n        &primary_dylib,\n        framework_name,\n        build_dir,\n        target_triple,\n        bundle_identifier,\n    )\n    .await?;\n\n    // If there are additional dylibs, create separate framework bundles for them\n    for (name, dylib_path) in all_dylibs {\n        let extra_framework = create_framework_bundle(\n            &dylib_path,\n            &name,\n            build_dir,\n            target_triple,\n            &format!(\"com.dioxus.swift.{}\", name.to_lowercase()),\n        )\n        .await?;\n        tracing::debug!(\n            \"Created additional framework: {}\",\n            extra_framework.display()\n        );\n    }\n\n    Ok(Some(framework_path))\n}\n\n/// Modify a Package.swift to produce a dynamic library instead of static.\n/// This is needed for runtime class lookup via NSClassFromString.\nfn modify_package_for_dynamic_library(package_path: &Path, product_name: &str) -> Result<()> {\n    let package_swift_path = package_path.join(\"Package.swift\");\n    if !package_swift_path.exists() {\n        anyhow::bail!(\n            \"Package.swift not found at {}\",\n            package_swift_path.display()\n        );\n    }\n\n    let content = std::fs::read_to_string(&package_swift_path)?;\n\n    // Replace .static with .dynamic for the library type\n    let modified = content\n        .replace(\"type: .static\", \"type: .dynamic\")\n        .replace(\"type:.static\", \"type: .dynamic\");\n\n    // If no library type was specified, we need to add it\n    // Look for .library(name: \"ProductName\", targets: [...]) and change to\n    // .library(name: \"ProductName\", type: .dynamic, targets: [...])\n    let pattern = format!(\n        r#\".library\\s*\\(\\s*name\\s*:\\s*\"{}\"\\s*,\\s*targets\"#,\n        regex::escape(product_name)\n    );\n    let replacement = format!(\n        r#\".library(name: \"{}\", type: .dynamic, targets\"#,\n        product_name\n    );\n\n    let modified = if let Ok(re) = regex::Regex::new(&pattern) {\n        re.replace_all(&modified, replacement.as_str()).to_string()\n    } else {\n        modified\n    };\n\n    std::fs::write(&package_swift_path, modified)?;\n    Ok(())\n}\n\n/// Convert a Rust target triple to Swift target triple and SDK name\nfn swift_target_and_sdk(triple: &Triple) -> Result<(String, String)> {\n    use target_lexicon::{Architecture, Environment, OperatingSystem};\n\n    // Check if this is a simulator target using the environment field\n    let is_simulator = triple.environment == Environment::Sim;\n\n    let swift_triple = match (&triple.architecture, &triple.operating_system) {\n        (Architecture::Aarch64(_), OperatingSystem::IOS(_)) => {\n            if is_simulator {\n                \"arm64-apple-ios-simulator\"\n            } else {\n                \"arm64-apple-ios\"\n            }\n        }\n        (Architecture::Aarch64(_), OperatingSystem::MacOSX { .. } | OperatingSystem::Darwin(_)) => {\n            \"arm64-apple-macosx\"\n        }\n        (Architecture::X86_64, OperatingSystem::IOS(_)) => \"x86_64-apple-ios-simulator\",\n        (Architecture::X86_64, OperatingSystem::MacOSX { .. } | OperatingSystem::Darwin(_)) => {\n            \"x86_64-apple-macosx\"\n        }\n        _ => anyhow::bail!(\"Unsupported target for Swift compilation: {}\", triple),\n    };\n\n    let sdk_name = match &triple.operating_system {\n        OperatingSystem::IOS(_) => {\n            // Check if this is a simulator target using the environment field\n            if is_simulator {\n                \"iphonesimulator\"\n            } else {\n                \"iphoneos\"\n            }\n        }\n        OperatingSystem::MacOSX { .. } | OperatingSystem::Darwin(_) => \"macosx\",\n        _ => anyhow::bail!(\n            \"Unsupported operating system for Swift compilation: {:?}\",\n            triple.operating_system\n        ),\n    };\n\n    Ok((swift_triple.to_string(), sdk_name.to_string()))\n}\n\n/// Look up the SDK path using xcrun\nasync fn lookup_sdk_path(sdk_name: &str) -> Result<String> {\n    let output = Command::new(\"xcrun\")\n        .args([\"--sdk\", sdk_name, \"--show-sdk-path\"])\n        .output()\n        .await\n        .context(\"Failed to run xcrun to find SDK path\")?;\n\n    if !output.status.success() {\n        let stderr = String::from_utf8_lossy(&output.stderr);\n        anyhow::bail!(\"Failed to find SDK '{}': {}\", sdk_name, stderr);\n    }\n\n    let sdk_path = String::from_utf8(output.stdout)\n        .context(\"Invalid UTF-8 in SDK path\")?\n        .trim()\n        .to_string();\n\n    if sdk_path.is_empty() {\n        anyhow::bail!(\"SDK path for '{}' is empty\", sdk_name);\n    }\n\n    Ok(sdk_path)\n}\n\n/// Recursively copy a directory\nfn copy_dir_recursive(src: &Path, dst: &Path) -> Result<()> {\n    std::fs::create_dir_all(dst)?;\n\n    for entry in std::fs::read_dir(src)? {\n        let entry = entry?;\n        let ty = entry.file_type()?;\n        let src_path = entry.path();\n        let dst_path = dst.join(entry.file_name());\n\n        if ty.is_dir() {\n            // Skip .build directories\n            if entry.file_name() == \".build\" {\n                continue;\n            }\n            copy_dir_recursive(&src_path, &dst_path)?;\n        } else {\n            std::fs::copy(&src_path, &dst_path)?;\n        }\n    }\n\n    Ok(())\n}\n\n/// Extract Swift metadata from object files in link arguments\npub fn extract_swift_metadata_from_link_args(\n    link_args: &[String],\n    workspace_dir: &Path,\n) -> Vec<SwiftPackageMetadata> {\n    let mut swift_packages = Vec::new();\n\n    // Look through rlibs and object files for Swift metadata\n    for arg in link_args {\n        let path = PathBuf::from(arg);\n\n        // Only process files in our workspace\n        if !path.starts_with(workspace_dir) {\n            continue;\n        }\n\n        // Check for .rlib files\n        if arg.ends_with(\".rlib\") {\n            if let Ok(swift_meta) = extract_swift_from_rlib(&path) {\n                swift_packages.extend(swift_meta);\n            }\n        }\n        // Check for .o files\n        else if arg.ends_with(\".o\") || arg.ends_with(\".obj\") {\n            if let Ok(swift_meta) = extract_swift_from_object(&path) {\n                swift_packages.extend(swift_meta);\n            }\n        }\n    }\n\n    // Deduplicate by plugin name\n    swift_packages.sort_by(|a, b| a.plugin_name.as_str().cmp(b.plugin_name.as_str()));\n    swift_packages.dedup_by(|a, b| a.plugin_name.as_str() == b.plugin_name.as_str());\n\n    swift_packages\n}\n\n/// Extract Swift metadata from an rlib file\nfn extract_swift_from_rlib(rlib_path: &Path) -> Result<Vec<SwiftPackageMetadata>> {\n    let mut results = Vec::new();\n\n    let rlib_contents = std::fs::read(rlib_path)?;\n    let mut reader = ar::Archive::new(std::io::Cursor::new(rlib_contents));\n\n    while let Some(Ok(entry)) = reader.next_entry() {\n        let name = std::str::from_utf8(entry.header().identifier()).unwrap_or_default();\n\n        // Only process .o files\n        if !name.ends_with(\".rcgu.o\") && !name.ends_with(\".obj\") {\n            continue;\n        }\n\n        // Read the object file contents\n        let mut obj_contents = Vec::new();\n        std::io::Read::read_to_end(&mut std::io::BufReader::new(entry), &mut obj_contents)?;\n\n        if let Ok(swift_meta) = extract_swift_from_bytes(&obj_contents) {\n            results.extend(swift_meta);\n        }\n    }\n\n    Ok(results)\n}\n\n/// Extract Swift metadata from an object file\nfn extract_swift_from_object(obj_path: &Path) -> Result<Vec<SwiftPackageMetadata>> {\n    let obj_contents = std::fs::read(obj_path)?;\n    extract_swift_from_bytes(&obj_contents)\n}\n\n/// Extract Swift metadata from raw object file bytes\nfn extract_swift_from_bytes(bytes: &[u8]) -> Result<Vec<SwiftPackageMetadata>> {\n    use manganis_core::SymbolData;\n    use object::{Object, ObjectSection, ObjectSymbol};\n\n    let mut results = Vec::new();\n\n    let file = match object::File::parse(bytes) {\n        Ok(f) => f,\n        Err(_) => return Ok(results),\n    };\n\n    // Look for __ASSETS__ symbols\n    for symbol in file.symbols() {\n        let name = match symbol.name() {\n            Ok(n) => n,\n            Err(_) => continue,\n        };\n\n        if !name.starts_with(\"__ASSETS__\") {\n            continue;\n        }\n\n        // Try to get the symbol's data\n        if let Some(section_idx) = symbol.section().index() {\n            if let Ok(section) = file.section_by_index(section_idx) {\n                if let Ok(data) = section.data() {\n                    // Try to find the symbol data in the section\n                    let addr = symbol.address();\n                    let section_addr = section.address();\n                    let offset = (addr - section_addr) as usize;\n\n                    if offset < data.len() {\n                        let symbol_data = &data[offset..];\n                        // Try to deserialize as SymbolData\n                        if let Some((_, SymbolData::SwiftPackage(meta))) =\n                            const_serialize::deserialize_const!(SymbolData, symbol_data)\n                        {\n                            results.push(meta);\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    Ok(results)\n}\n\n/// Recursively collect all Swift source files from a directory\nfn collect_swift_files(dir: &Path) -> Result<Vec<PathBuf>> {\n    let mut swift_files = Vec::new();\n\n    if !dir.exists() {\n        return Ok(swift_files);\n    }\n\n    for entry in std::fs::read_dir(dir)? {\n        let entry = entry?;\n        let path = entry.path();\n\n        if path.is_dir() {\n            // Recursively collect from subdirectories\n            swift_files.extend(collect_swift_files(&path)?);\n        } else if path.extension().is_some_and(|ext| ext == \"swift\") {\n            swift_files.push(path);\n        }\n    }\n\n    Ok(swift_files)\n}\n\n/// Information about an Apple Widget Extension to compile\npub struct AppleWidgetSource {\n    /// Path to the Swift package source directory\n    pub source_path: PathBuf,\n    /// Display name for the widget (shown in system UI)\n    pub display_name: String,\n    /// Bundle ID suffix (appended to app bundle ID)\n    pub bundle_id_suffix: String,\n    /// Minimum deployment target (e.g., \"16.0\")\n    pub deployment_target: String,\n    /// Swift module name for the widget.\n    /// This MUST match the module name used by the main app's Swift plugin\n    /// for ActivityKit type matching to work (e.g., both must define\n    /// `ModuleName.LocationPermissionAttributes` as the same type).\n    pub module_name: String,\n}\n\n/// Compile an Apple Widget Extension from a Swift package source.\n///\n/// Widget Extensions are compiled as executables (not libraries) and bundled\n/// as .appex bundles which are installed in the app's PlugIns folder.\n///\n/// **Important**: Widget extensions are XPC services that require special initialization.\n/// We use `-e _NSExtensionMain` as the entry point instead of the default `_main` that\n/// Swift generates with `@main`. The `_NSExtensionMain` entry point (provided by Foundation):\n/// 1. Sets up the XPC listener\n/// 2. Initializes ExtensionFoundation's `_EXRunningExtension` singleton\n/// 3. Registers with PlugInKit\n/// 4. Then calls your Widget code\n///\n/// # Arguments\n/// * `widget` - Widget extension source configuration\n/// * `target_triple` - The target platform (e.g., aarch64-apple-ios)\n/// * `build_dir` - Directory for intermediate build files\n/// * `app_bundle_id` - The main app's bundle identifier (widget ID is derived from this)\n/// * `release` - Whether to build in release mode\n///\n/// # Returns\n/// Path to the compiled .appex bundle, ready to be installed to PlugIns/\npub async fn compile_apple_widget(\n    widget: &AppleWidgetSource,\n    target_triple: &Triple,\n    build_dir: &Path,\n    app_bundle_id: &str,\n    release: bool,\n) -> Result<PathBuf> {\n    use target_lexicon::OperatingSystem;\n\n    // Validate we're on an Apple platform\n    let is_ios = matches!(target_triple.operating_system, OperatingSystem::IOS(_));\n    let is_macos = matches!(\n        target_triple.operating_system,\n        OperatingSystem::MacOSX { .. } | OperatingSystem::Darwin(_)\n    );\n\n    if !is_ios && !is_macos {\n        anyhow::bail!(\n            \"Apple Widget Extensions are only supported on iOS and macOS, not {:?}\",\n            target_triple.operating_system\n        );\n    }\n\n    // Validate source path exists\n    if !widget.source_path.exists() {\n        anyhow::bail!(\n            \"Widget Extension source path does not exist: {}\",\n            widget.source_path.display()\n        );\n    }\n\n    tracing::debug!(\n        \"Compiling Apple Widget Extension '{}' for {}\",\n        widget.display_name,\n        target_triple\n    );\n\n    // Create the widget build directory\n    let widget_build_dir = build_dir.join(\"widget-extensions\");\n    std::fs::create_dir_all(&widget_build_dir)?;\n\n    // Copy the Swift package to build directory\n    // Use the bundle_id_suffix as a unique name since the folder name might just be \"widget\"\n    let widget_name = widget.bundle_id_suffix.replace(\"-\", \"_\");\n    let source_dir = widget_build_dir.join(format!(\"{}_src\", widget_name));\n    if source_dir.exists() {\n        std::fs::remove_dir_all(&source_dir)?;\n    }\n    copy_dir_recursive(&widget.source_path, &source_dir)?;\n\n    // Get Swift target triple and SDK\n    let (swift_triple, sdk_name) = swift_target_and_sdk(target_triple)?;\n\n    // Collect all Swift source files from the Sources directory\n    let swift_sources_dir = source_dir.join(\"Sources\");\n    let swift_files = collect_swift_files(&swift_sources_dir)?;\n\n    if swift_files.is_empty() {\n        anyhow::bail!(\n            \"No Swift source files found in widget extension Sources directory: {}\",\n            swift_sources_dir.display()\n        );\n    }\n\n    tracing::debug!(\n        \"Found {} Swift files for widget: {:?}\",\n        swift_files.len(),\n        swift_files\n    );\n\n    // Build output path\n    let exec_path = widget_build_dir.join(&widget_name);\n\n    // Compile the widget extension using swiftc directly\n    // Widget extensions are XPC services that require _NSExtensionMain as the entry point\n    let mut cmd = Command::new(\"xcrun\");\n    cmd.arg(\"--sdk\").arg(&sdk_name).arg(\"swiftc\");\n\n    // Add all Swift source files\n    for swift_file in &swift_files {\n        cmd.arg(swift_file);\n    }\n\n    // Output executable\n    cmd.arg(\"-o\").arg(&exec_path);\n\n    // Target triple with proper iOS version\n    // Format: arm64-apple-ios17.0 or arm64-apple-ios17.0-simulator\n    let is_simulator = swift_triple.contains(\"simulator\");\n    let base_triple = swift_triple.replace(\"-simulator\", \"\");\n    let swift_target = if is_simulator {\n        format!(\"{}{}-simulator\", base_triple, widget.deployment_target)\n    } else {\n        format!(\"{}{}\", base_triple, widget.deployment_target)\n    };\n    cmd.arg(\"-target\").arg(&swift_target);\n\n    // Module name - use a consistent name that matches the main app's plugin module\n    // This is critical for ActivityKit type matching between app and widget\n    cmd.arg(\"-module-name\").arg(&widget.module_name);\n\n    // Optimization flags\n    if release {\n        cmd.arg(\"-O\").arg(\"-whole-module-optimization\");\n    }\n\n    // Extension-specific flags\n    cmd.arg(\"-application-extension\");\n\n    // Critical: Use _NSExtensionMain as the entry point for widget extensions\n    // Without this, the widget crashes because ExtensionFoundation's singleton isn't initialized\n    cmd.arg(\"-Xlinker\")\n        .arg(\"-e\")\n        .arg(\"-Xlinker\")\n        .arg(\"_NSExtensionMain\");\n\n    // Link Objective-C runtime (required for Swift/ObjC interop)\n    cmd.arg(\"-lobjc\");\n\n    // Link required frameworks\n    cmd.arg(\"-framework\").arg(\"Foundation\");\n    cmd.arg(\"-framework\").arg(\"SwiftUI\");\n    cmd.arg(\"-framework\").arg(\"WidgetKit\");\n    cmd.arg(\"-framework\").arg(\"ActivityKit\");\n\n    tracing::debug!(\"Running swiftc for widget: {:?}\", cmd);\n\n    let output = cmd.output().await?;\n\n    if !output.status.success() {\n        let stderr = String::from_utf8_lossy(&output.stderr);\n        let stdout = String::from_utf8_lossy(&output.stdout);\n        anyhow::bail!(\n            \"Swift compilation failed for widget extension '{}':\\n{}\\n{}\",\n            widget_name,\n            stdout,\n            stderr\n        );\n    }\n\n    tracing::debug!(\"Compiled widget executable: {}\", exec_path.display());\n\n    // Create the .appex bundle\n    let appex_name = format!(\"{}.appex\", widget_name);\n    let appex_dir = widget_build_dir.join(&appex_name);\n\n    // Remove existing appex if present\n    if appex_dir.exists() {\n        std::fs::remove_dir_all(&appex_dir)?;\n    }\n    std::fs::create_dir_all(&appex_dir)?;\n\n    // Copy the executable into the appex bundle\n    let bundle_exec = appex_dir.join(&widget_name);\n    std::fs::copy(&exec_path, &bundle_exec)?;\n\n    // Create Info.plist for the widget extension\n    let widget_bundle_id = format!(\"{}.{}\", app_bundle_id, widget.bundle_id_suffix);\n    let min_os_version = &widget.deployment_target;\n\n    let platform_info = if is_ios {\n        format!(\n            r#\"    <key>MinimumOSVersion</key>\n    <string>{min_os_version}</string>\n    <key>CFBundleSupportedPlatforms</key>\n    <array>\n        <string>iPhoneOS</string>\n    </array>\n    <key>UIDeviceFamily</key>\n    <array>\n        <integer>1</integer>\n        <integer>2</integer>\n    </array>\"#\n        )\n    } else {\n        format!(\n            r#\"    <key>LSMinimumSystemVersion</key>\n    <string>{min_os_version}</string>\n    <key>CFBundleSupportedPlatforms</key>\n    <array>\n        <string>MacOSX</string>\n    </array>\"#\n        )\n    };\n\n    let info_plist = format!(\n        r#\"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n    <key>CFBundleDevelopmentRegion</key>\n    <string>en</string>\n    <key>CFBundleDisplayName</key>\n    <string>{display_name}</string>\n    <key>CFBundleExecutable</key>\n    <string>{widget_name}</string>\n    <key>CFBundleIdentifier</key>\n    <string>{widget_bundle_id}</string>\n    <key>CFBundleInfoDictionaryVersion</key>\n    <string>6.0</string>\n    <key>CFBundleName</key>\n    <string>{widget_name}</string>\n    <key>CFBundlePackageType</key>\n    <string>XPC!</string>\n    <key>CFBundleShortVersionString</key>\n    <string>1.0</string>\n    <key>CFBundleVersion</key>\n    <string>1</string>\n{platform_info}\n    <key>NSExtension</key>\n    <dict>\n        <key>NSExtensionPointIdentifier</key>\n        <string>com.apple.widgetkit-extension</string>\n    </dict>\n    <key>NSSupportsLiveActivities</key>\n    <true/>\n</dict>\n</plist>\"#,\n        display_name = widget.display_name,\n        widget_name = widget_name,\n        widget_bundle_id = widget_bundle_id,\n        platform_info = platform_info,\n    );\n\n    std::fs::write(appex_dir.join(\"Info.plist\"), info_plist)?;\n\n    tracing::debug!(\"Created Widget Extension bundle: {}\", appex_dir.display());\n\n    Ok(appex_dir)\n}\n"
  },
  {
    "path": "packages/cli/src/build/manifest.rs",
    "content": "//! The build manifest for `dx` applications, containing metadata about the build including\n//! the CLI version, Rust version, and all bundled assets.\n//!\n//! We eventually plan to use this manifest to support tighter integration with deployment platforms\n//! and CDNs.\n//!\n//! This manifest contains the list of assets, rust version, and cli version used to build the app.\n//! Eventually, we might want to expand this to include more metadata about the build, including\n//! build time, target platform, etc.\n\nuse dioxus_cli_opt::AssetManifest;\nuse serde::{Deserialize, Serialize};\n\n#[derive(Default, Serialize, Deserialize)]\npub struct AppManifest {\n    /// Stable since 0.7.0\n    pub cli_version: String,\n\n    /// Stable since 0.7.0\n    pub rust_version: String,\n\n    /// Stable since 0.7.0\n    pub assets: AssetManifest,\n}\n"
  },
  {
    "path": "packages/cli/src/build/manifest_mapper.rs",
    "content": "//! Maps unified Dioxus.toml config to platform-specific manifest data.\n//!\n//! This module converts cross-platform declarations (permissions, deep links,\n//! background modes) into platform-specific identifiers:\n//! - Android: `<uses-permission>` entries, intent filters, foreground service types\n//! - iOS/macOS: Info.plist keys, URL schemes, UIBackgroundModes\n\nuse crate::config::{\n    AndroidConfig, BackgroundConfig, DeepLinkConfig, IosConfig, LocationPrecision, MacosConfig,\n    PermissionsConfig, StorageAccess,\n};\n\n/// Android permission entry for AndroidManifest.xml\n#[derive(Debug, Clone)]\npub struct AndroidPermissionEntry {\n    /// Full Android permission string (e.g., \"android.permission.CAMERA\")\n    pub permission: String,\n    /// User-facing description (used for documentation)\n    pub description: String,\n}\n\n/// iOS/macOS plist entry for Info.plist\n#[derive(Debug, Clone)]\npub struct PlistEntry {\n    /// Plist key (e.g., \"NSCameraUsageDescription\")\n    pub key: String,\n    /// User-facing description shown in permission dialogs\n    pub value: String,\n}\n\n/// Maps unified permissions, deep links, and background modes to platform-specific identifiers\n#[derive(Debug, Default)]\npub struct ManifestMapper {\n    pub android_permissions: Vec<AndroidPermissionEntry>,\n    pub android_features: Vec<String>,\n    pub ios_plist_entries: Vec<PlistEntry>,\n    pub macos_plist_entries: Vec<PlistEntry>,\n\n    /// URL schemes for iOS CFBundleURLTypes (merged from deep_links.schemes + ios.url_schemes)\n    pub ios_url_schemes: Vec<String>,\n    /// URL schemes for macOS CFBundleURLTypes (merged from deep_links.schemes + macos.url_schemes)\n    pub macos_url_schemes: Vec<String>,\n    /// URL schemes for Android intent-filter (merged from deep_links.schemes + android.url_schemes)\n    pub android_url_schemes: Vec<String>,\n    /// Associated domains for iOS (from deep_links.hosts → \"applinks:host\")\n    pub ios_associated_domains: Vec<String>,\n    /// Android intent filters from config (android.intent_filters)\n    pub android_intent_filters: Vec<crate::config::AndroidIntentFilter>,\n    /// App link hosts for Android auto-verify (from deep_links.hosts)\n    pub android_app_link_hosts: Vec<String>,\n\n    /// iOS UIBackgroundModes (merged from BackgroundConfig + ios.background_modes)\n    pub ios_background_modes: Vec<String>,\n    /// Android foreground service types (from BackgroundConfig + android.foreground_service_types)\n    pub android_foreground_service_types: Vec<String>,\n}\n\nimpl ManifestMapper {\n    /// Create a new permission mapper from the unified config\n    pub fn from_config(\n        permissions: &PermissionsConfig,\n        deep_links: &DeepLinkConfig,\n        background: &BackgroundConfig,\n        android: &AndroidConfig,\n        ios: &IosConfig,\n        macos: &MacosConfig,\n    ) -> Self {\n        let mut mapper = Self::default();\n\n        // Map unified permissions\n        mapper.map_location(permissions);\n        mapper.map_camera(permissions);\n        mapper.map_microphone(permissions);\n        mapper.map_notifications(permissions);\n        mapper.map_photos(permissions);\n        mapper.map_bluetooth(permissions);\n        mapper.map_background_location(permissions);\n        mapper.map_contacts(permissions);\n        mapper.map_calendar(permissions);\n        mapper.map_biometrics(permissions);\n        mapper.map_nfc(permissions);\n        mapper.map_motion(permissions);\n        mapper.map_health(permissions);\n        mapper.map_speech(permissions);\n        mapper.map_media_library(permissions);\n        mapper.map_siri(permissions);\n        mapper.map_homekit(permissions);\n        mapper.map_local_network(permissions);\n        mapper.map_nearby_wifi(permissions);\n\n        // Add raw Android permissions\n        for (perm, config) in &android.permissions {\n            mapper.android_permissions.push(AndroidPermissionEntry {\n                permission: perm.clone(),\n                description: config.description.clone(),\n            });\n        }\n\n        // Add Android features\n        mapper.android_features.extend(android.features.clone());\n\n        // Map deep links\n        mapper.map_deep_links(deep_links, android, ios, macos);\n\n        // Map background modes\n        mapper.map_background_modes(background, android, ios);\n\n        // Log mapped permissions for debugging\n        for perm in &mapper.android_permissions {\n            tracing::debug!(\n                \"Android permission: {} - {}\",\n                perm.permission,\n                perm.description\n            );\n        }\n        for entry in &mapper.ios_plist_entries {\n            tracing::debug!(\"iOS plist: {} = {}\", entry.key, entry.value);\n        }\n\n        mapper\n    }\n\n    fn map_location(&mut self, permissions: &PermissionsConfig) {\n        if let Some(loc) = &permissions.location {\n            let android_perm = match loc.precision {\n                LocationPrecision::Fine => \"android.permission.ACCESS_FINE_LOCATION\",\n                LocationPrecision::Coarse => \"android.permission.ACCESS_COARSE_LOCATION\",\n            };\n\n            self.android_permissions.push(AndroidPermissionEntry {\n                permission: android_perm.to_string(),\n                description: loc.description.clone(),\n            });\n\n            // For fine location, also add coarse as it's often needed\n            if loc.precision == LocationPrecision::Fine {\n                self.android_permissions.push(AndroidPermissionEntry {\n                    permission: \"android.permission.ACCESS_COARSE_LOCATION\".to_string(),\n                    description: loc.description.clone(),\n                });\n            }\n\n            self.ios_plist_entries.push(PlistEntry {\n                key: \"NSLocationWhenInUseUsageDescription\".to_string(),\n                value: loc.description.clone(),\n            });\n\n            self.macos_plist_entries.push(PlistEntry {\n                key: \"NSLocationUsageDescription\".to_string(),\n                value: loc.description.clone(),\n            });\n        }\n    }\n\n    fn map_camera(&mut self, permissions: &PermissionsConfig) {\n        if let Some(cam) = &permissions.camera {\n            self.android_permissions.push(AndroidPermissionEntry {\n                permission: \"android.permission.CAMERA\".to_string(),\n                description: cam.description.clone(),\n            });\n\n            self.ios_plist_entries.push(PlistEntry {\n                key: \"NSCameraUsageDescription\".to_string(),\n                value: cam.description.clone(),\n            });\n\n            self.macos_plist_entries.push(PlistEntry {\n                key: \"NSCameraUsageDescription\".to_string(),\n                value: cam.description.clone(),\n            });\n        }\n    }\n\n    fn map_microphone(&mut self, permissions: &PermissionsConfig) {\n        if let Some(mic) = &permissions.microphone {\n            self.android_permissions.push(AndroidPermissionEntry {\n                permission: \"android.permission.RECORD_AUDIO\".to_string(),\n                description: mic.description.clone(),\n            });\n\n            self.ios_plist_entries.push(PlistEntry {\n                key: \"NSMicrophoneUsageDescription\".to_string(),\n                value: mic.description.clone(),\n            });\n\n            self.macos_plist_entries.push(PlistEntry {\n                key: \"NSMicrophoneUsageDescription\".to_string(),\n                value: mic.description.clone(),\n            });\n        }\n    }\n\n    fn map_notifications(&mut self, permissions: &PermissionsConfig) {\n        if let Some(notif) = &permissions.notifications {\n            self.android_permissions.push(AndroidPermissionEntry {\n                permission: \"android.permission.POST_NOTIFICATIONS\".to_string(),\n                description: notif.description.clone(),\n            });\n            // iOS notifications are handled at runtime, no plist entry needed\n        }\n    }\n\n    fn map_photos(&mut self, permissions: &PermissionsConfig) {\n        if let Some(photos) = &permissions.photos {\n            match photos.access {\n                StorageAccess::Read => {\n                    self.android_permissions.push(AndroidPermissionEntry {\n                        permission: \"android.permission.READ_MEDIA_IMAGES\".to_string(),\n                        description: photos.description.clone(),\n                    });\n                    self.ios_plist_entries.push(PlistEntry {\n                        key: \"NSPhotoLibraryUsageDescription\".to_string(),\n                        value: photos.description.clone(),\n                    });\n                }\n                StorageAccess::Write => {\n                    self.android_permissions.push(AndroidPermissionEntry {\n                        permission: \"android.permission.WRITE_EXTERNAL_STORAGE\".to_string(),\n                        description: photos.description.clone(),\n                    });\n                    self.ios_plist_entries.push(PlistEntry {\n                        key: \"NSPhotoLibraryAddUsageDescription\".to_string(),\n                        value: photos.description.clone(),\n                    });\n                }\n                StorageAccess::ReadWrite => {\n                    self.android_permissions.push(AndroidPermissionEntry {\n                        permission: \"android.permission.READ_MEDIA_IMAGES\".to_string(),\n                        description: photos.description.clone(),\n                    });\n                    self.android_permissions.push(AndroidPermissionEntry {\n                        permission: \"android.permission.WRITE_EXTERNAL_STORAGE\".to_string(),\n                        description: photos.description.clone(),\n                    });\n                    self.ios_plist_entries.push(PlistEntry {\n                        key: \"NSPhotoLibraryUsageDescription\".to_string(),\n                        value: photos.description.clone(),\n                    });\n                    self.ios_plist_entries.push(PlistEntry {\n                        key: \"NSPhotoLibraryAddUsageDescription\".to_string(),\n                        value: photos.description.clone(),\n                    });\n                }\n            }\n\n            self.macos_plist_entries.push(PlistEntry {\n                key: \"NSPhotoLibraryUsageDescription\".to_string(),\n                value: photos.description.clone(),\n            });\n        }\n    }\n\n    fn map_bluetooth(&mut self, permissions: &PermissionsConfig) {\n        if let Some(bt) = &permissions.bluetooth {\n            self.android_permissions.push(AndroidPermissionEntry {\n                permission: \"android.permission.BLUETOOTH_CONNECT\".to_string(),\n                description: bt.description.clone(),\n            });\n            self.android_permissions.push(AndroidPermissionEntry {\n                permission: \"android.permission.BLUETOOTH_SCAN\".to_string(),\n                description: bt.description.clone(),\n            });\n\n            self.ios_plist_entries.push(PlistEntry {\n                key: \"NSBluetoothAlwaysUsageDescription\".to_string(),\n                value: bt.description.clone(),\n            });\n\n            self.macos_plist_entries.push(PlistEntry {\n                key: \"NSBluetoothAlwaysUsageDescription\".to_string(),\n                value: bt.description.clone(),\n            });\n        }\n    }\n\n    fn map_background_location(&mut self, permissions: &PermissionsConfig) {\n        if let Some(bg_loc) = &permissions.background_location {\n            self.android_permissions.push(AndroidPermissionEntry {\n                permission: \"android.permission.ACCESS_BACKGROUND_LOCATION\".to_string(),\n                description: bg_loc.description.clone(),\n            });\n\n            self.ios_plist_entries.push(PlistEntry {\n                key: \"NSLocationAlwaysAndWhenInUseUsageDescription\".to_string(),\n                value: bg_loc.description.clone(),\n            });\n        }\n    }\n\n    fn map_contacts(&mut self, permissions: &PermissionsConfig) {\n        if let Some(contacts) = &permissions.contacts {\n            match contacts.access {\n                StorageAccess::Read => {\n                    self.android_permissions.push(AndroidPermissionEntry {\n                        permission: \"android.permission.READ_CONTACTS\".to_string(),\n                        description: contacts.description.clone(),\n                    });\n                }\n                StorageAccess::Write => {\n                    self.android_permissions.push(AndroidPermissionEntry {\n                        permission: \"android.permission.WRITE_CONTACTS\".to_string(),\n                        description: contacts.description.clone(),\n                    });\n                }\n                StorageAccess::ReadWrite => {\n                    self.android_permissions.push(AndroidPermissionEntry {\n                        permission: \"android.permission.READ_CONTACTS\".to_string(),\n                        description: contacts.description.clone(),\n                    });\n                    self.android_permissions.push(AndroidPermissionEntry {\n                        permission: \"android.permission.WRITE_CONTACTS\".to_string(),\n                        description: contacts.description.clone(),\n                    });\n                }\n            }\n\n            self.ios_plist_entries.push(PlistEntry {\n                key: \"NSContactsUsageDescription\".to_string(),\n                value: contacts.description.clone(),\n            });\n\n            self.macos_plist_entries.push(PlistEntry {\n                key: \"NSContactsUsageDescription\".to_string(),\n                value: contacts.description.clone(),\n            });\n        }\n    }\n\n    fn map_calendar(&mut self, permissions: &PermissionsConfig) {\n        if let Some(cal) = &permissions.calendar {\n            match cal.access {\n                StorageAccess::Read => {\n                    self.android_permissions.push(AndroidPermissionEntry {\n                        permission: \"android.permission.READ_CALENDAR\".to_string(),\n                        description: cal.description.clone(),\n                    });\n                }\n                StorageAccess::Write => {\n                    self.android_permissions.push(AndroidPermissionEntry {\n                        permission: \"android.permission.WRITE_CALENDAR\".to_string(),\n                        description: cal.description.clone(),\n                    });\n                }\n                StorageAccess::ReadWrite => {\n                    self.android_permissions.push(AndroidPermissionEntry {\n                        permission: \"android.permission.READ_CALENDAR\".to_string(),\n                        description: cal.description.clone(),\n                    });\n                    self.android_permissions.push(AndroidPermissionEntry {\n                        permission: \"android.permission.WRITE_CALENDAR\".to_string(),\n                        description: cal.description.clone(),\n                    });\n                }\n            }\n\n            self.ios_plist_entries.push(PlistEntry {\n                key: \"NSCalendarsUsageDescription\".to_string(),\n                value: cal.description.clone(),\n            });\n\n            self.macos_plist_entries.push(PlistEntry {\n                key: \"NSCalendarsUsageDescription\".to_string(),\n                value: cal.description.clone(),\n            });\n        }\n    }\n\n    fn map_biometrics(&mut self, permissions: &PermissionsConfig) {\n        if let Some(bio) = &permissions.biometrics {\n            self.android_permissions.push(AndroidPermissionEntry {\n                permission: \"android.permission.USE_BIOMETRIC\".to_string(),\n                description: bio.description.clone(),\n            });\n\n            self.ios_plist_entries.push(PlistEntry {\n                key: \"NSFaceIDUsageDescription\".to_string(),\n                value: bio.description.clone(),\n            });\n        }\n    }\n\n    fn map_nfc(&mut self, permissions: &PermissionsConfig) {\n        if let Some(nfc) = &permissions.nfc {\n            self.android_permissions.push(AndroidPermissionEntry {\n                permission: \"android.permission.NFC\".to_string(),\n                description: nfc.description.clone(),\n            });\n\n            self.ios_plist_entries.push(PlistEntry {\n                key: \"NFCReaderUsageDescription\".to_string(),\n                value: nfc.description.clone(),\n            });\n        }\n    }\n\n    fn map_motion(&mut self, permissions: &PermissionsConfig) {\n        if let Some(motion) = &permissions.motion {\n            self.android_permissions.push(AndroidPermissionEntry {\n                permission: \"android.permission.ACTIVITY_RECOGNITION\".to_string(),\n                description: motion.description.clone(),\n            });\n\n            self.ios_plist_entries.push(PlistEntry {\n                key: \"NSMotionUsageDescription\".to_string(),\n                value: motion.description.clone(),\n            });\n        }\n    }\n\n    fn map_health(&mut self, permissions: &PermissionsConfig) {\n        if let Some(health) = &permissions.health {\n            self.android_permissions.push(AndroidPermissionEntry {\n                permission: \"android.permission.BODY_SENSORS\".to_string(),\n                description: health.description.clone(),\n            });\n\n            match health.access {\n                StorageAccess::Read => {\n                    self.ios_plist_entries.push(PlistEntry {\n                        key: \"NSHealthShareUsageDescription\".to_string(),\n                        value: health.description.clone(),\n                    });\n                }\n                StorageAccess::Write => {\n                    self.ios_plist_entries.push(PlistEntry {\n                        key: \"NSHealthUpdateUsageDescription\".to_string(),\n                        value: health.description.clone(),\n                    });\n                }\n                StorageAccess::ReadWrite => {\n                    self.ios_plist_entries.push(PlistEntry {\n                        key: \"NSHealthShareUsageDescription\".to_string(),\n                        value: health.description.clone(),\n                    });\n                    self.ios_plist_entries.push(PlistEntry {\n                        key: \"NSHealthUpdateUsageDescription\".to_string(),\n                        value: health.description.clone(),\n                    });\n                }\n            }\n        }\n    }\n\n    fn map_speech(&mut self, permissions: &PermissionsConfig) {\n        if let Some(speech) = &permissions.speech {\n            // Speech recognition uses microphone on Android\n            self.android_permissions.push(AndroidPermissionEntry {\n                permission: \"android.permission.RECORD_AUDIO\".to_string(),\n                description: speech.description.clone(),\n            });\n\n            self.ios_plist_entries.push(PlistEntry {\n                key: \"NSSpeechRecognitionUsageDescription\".to_string(),\n                value: speech.description.clone(),\n            });\n        }\n    }\n\n    fn map_media_library(&mut self, permissions: &PermissionsConfig) {\n        if let Some(media) = &permissions.media_library {\n            self.android_permissions.push(AndroidPermissionEntry {\n                permission: \"android.permission.READ_MEDIA_AUDIO\".to_string(),\n                description: media.description.clone(),\n            });\n\n            self.ios_plist_entries.push(PlistEntry {\n                key: \"NSAppleMusicUsageDescription\".to_string(),\n                value: media.description.clone(),\n            });\n        }\n    }\n\n    fn map_siri(&mut self, permissions: &PermissionsConfig) {\n        if let Some(siri) = &permissions.siri {\n            // Siri is iOS only\n            self.ios_plist_entries.push(PlistEntry {\n                key: \"NSSiriUsageDescription\".to_string(),\n                value: siri.description.clone(),\n            });\n        }\n    }\n\n    fn map_homekit(&mut self, permissions: &PermissionsConfig) {\n        if let Some(homekit) = &permissions.homekit {\n            // HomeKit is iOS only\n            self.ios_plist_entries.push(PlistEntry {\n                key: \"NSHomeKitUsageDescription\".to_string(),\n                value: homekit.description.clone(),\n            });\n        }\n    }\n\n    fn map_local_network(&mut self, permissions: &PermissionsConfig) {\n        if let Some(network) = &permissions.local_network {\n            // Local network is iOS only\n            self.ios_plist_entries.push(PlistEntry {\n                key: \"NSLocalNetworkUsageDescription\".to_string(),\n                value: network.description.clone(),\n            });\n        }\n    }\n\n    fn map_nearby_wifi(&mut self, permissions: &PermissionsConfig) {\n        if let Some(wifi) = &permissions.nearby_wifi {\n            // Nearby WiFi is Android only\n            self.android_permissions.push(AndroidPermissionEntry {\n                permission: \"android.permission.NEARBY_WIFI_DEVICES\".to_string(),\n                description: wifi.description.clone(),\n            });\n        }\n    }\n\n    /// Map deep link config to platform-specific URL schemes, associated domains, and intent filters\n    fn map_deep_links(\n        &mut self,\n        deep_links: &DeepLinkConfig,\n        android: &AndroidConfig,\n        ios: &IosConfig,\n        macos: &MacosConfig,\n    ) {\n        // Merge unified schemes with platform-specific overrides\n        let mut ios_schemes: Vec<String> = deep_links.schemes.clone();\n        ios_schemes.extend(ios.url_schemes.clone());\n        ios_schemes.dedup();\n        self.ios_url_schemes = ios_schemes;\n\n        let mut macos_schemes: Vec<String> = deep_links.schemes.clone();\n        macos_schemes.extend(macos.url_schemes.clone());\n        macos_schemes.dedup();\n        self.macos_url_schemes = macos_schemes;\n\n        let mut android_schemes: Vec<String> = deep_links.schemes.clone();\n        android_schemes.extend(android.url_schemes.clone());\n        android_schemes.dedup();\n        self.android_url_schemes = android_schemes;\n\n        // Map universal link hosts to iOS associated domains\n        for host in &deep_links.hosts {\n            self.ios_associated_domains.push(format!(\"applinks:{host}\"));\n        }\n\n        // Store app link hosts for Android auto-verify intent filters\n        self.android_app_link_hosts = deep_links.hosts.clone();\n\n        // Add explicit Android intent filters from config\n        self.android_intent_filters = android.intent_filters.clone();\n    }\n\n    /// Map background mode config to platform-specific background capabilities\n    fn map_background_modes(\n        &mut self,\n        background: &BackgroundConfig,\n        android: &AndroidConfig,\n        ios: &IosConfig,\n    ) {\n        // Build iOS UIBackgroundModes from unified config\n        let mut ios_modes: Vec<String> = Vec::new();\n        if background.location {\n            ios_modes.push(\"location\".to_string());\n        }\n        if background.audio {\n            ios_modes.push(\"audio\".to_string());\n        }\n        if background.fetch {\n            ios_modes.push(\"fetch\".to_string());\n        }\n        if background.remote_notifications {\n            ios_modes.push(\"remote-notification\".to_string());\n        }\n        if background.voip {\n            ios_modes.push(\"voip\".to_string());\n        }\n        if background.bluetooth {\n            ios_modes.push(\"bluetooth-central\".to_string());\n            ios_modes.push(\"bluetooth-peripheral\".to_string());\n        }\n        if background.external_accessory {\n            ios_modes.push(\"external-accessory\".to_string());\n        }\n        if background.processing {\n            ios_modes.push(\"processing\".to_string());\n        }\n        // Merge platform-specific overrides\n        for mode in &ios.background_modes {\n            if !ios_modes.contains(mode) {\n                ios_modes.push(mode.clone());\n            }\n        }\n        self.ios_background_modes = ios_modes;\n\n        // Build Android foreground service types and permissions\n        let mut android_types: Vec<String> = Vec::new();\n        if background.location {\n            self.android_permissions.push(AndroidPermissionEntry {\n                permission: \"android.permission.ACCESS_BACKGROUND_LOCATION\".to_string(),\n                description: \"Background location updates\".to_string(),\n            });\n        }\n        if background.audio {\n            android_types.push(\"mediaPlayback\".to_string());\n        }\n        if background.voip {\n            android_types.push(\"phoneCall\".to_string());\n        }\n        if background.bluetooth {\n            android_types.push(\"connectedDevice\".to_string());\n        }\n        // Merge platform-specific overrides\n        for stype in &android.foreground_service_types {\n            if !android_types.contains(stype) {\n                android_types.push(stype.clone());\n            }\n        }\n        // If we have any foreground service types, add the FOREGROUND_SERVICE permission\n        if !android_types.is_empty() {\n            self.android_permissions.push(AndroidPermissionEntry {\n                permission: \"android.permission.FOREGROUND_SERVICE\".to_string(),\n                description: \"Run foreground services\".to_string(),\n            });\n        }\n        self.android_foreground_service_types = android_types;\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use crate::config::{LocationPermission, SimplePermission};\n\n    #[test]\n    fn test_location_permission_mapping() {\n        let permissions = PermissionsConfig {\n            location: Some(LocationPermission {\n                precision: LocationPrecision::Fine,\n                description: \"Track your runs\".to_string(),\n            }),\n            ..Default::default()\n        };\n\n        let mapper = ManifestMapper::from_config(\n            &permissions,\n            &DeepLinkConfig::default(),\n            &BackgroundConfig::default(),\n            &AndroidConfig::default(),\n            &IosConfig::default(),\n            &MacosConfig::default(),\n        );\n\n        // Should have both fine and coarse for Android\n        assert!(mapper\n            .android_permissions\n            .iter()\n            .any(|p| p.permission == \"android.permission.ACCESS_FINE_LOCATION\"));\n        assert!(mapper\n            .android_permissions\n            .iter()\n            .any(|p| p.permission == \"android.permission.ACCESS_COARSE_LOCATION\"));\n\n        // Should have iOS location plist entry\n        assert!(mapper\n            .ios_plist_entries\n            .iter()\n            .any(|e| e.key == \"NSLocationWhenInUseUsageDescription\"));\n    }\n\n    #[test]\n    fn test_camera_permission_mapping() {\n        let permissions = PermissionsConfig {\n            camera: Some(SimplePermission {\n                description: \"Take photos\".to_string(),\n            }),\n            ..Default::default()\n        };\n\n        let mapper = ManifestMapper::from_config(\n            &permissions,\n            &DeepLinkConfig::default(),\n            &BackgroundConfig::default(),\n            &AndroidConfig::default(),\n            &IosConfig::default(),\n            &MacosConfig::default(),\n        );\n\n        assert!(mapper\n            .android_permissions\n            .iter()\n            .any(|p| p.permission == \"android.permission.CAMERA\"));\n        assert!(mapper\n            .ios_plist_entries\n            .iter()\n            .any(|e| e.key == \"NSCameraUsageDescription\"));\n    }\n\n    #[test]\n    fn test_android_camera_permission_data() {\n        let permissions = PermissionsConfig {\n            camera: Some(SimplePermission {\n                description: \"Take photos\".to_string(),\n            }),\n            ..Default::default()\n        };\n\n        let mapper = ManifestMapper::from_config(\n            &permissions,\n            &DeepLinkConfig::default(),\n            &BackgroundConfig::default(),\n            &AndroidConfig::default(),\n            &IosConfig::default(),\n            &MacosConfig::default(),\n        );\n\n        assert!(mapper\n            .android_permissions\n            .iter()\n            .any(|p| p.permission == \"android.permission.CAMERA\"));\n    }\n}\n"
  },
  {
    "path": "packages/cli/src/build/mod.rs",
    "content": "//! The core build module for `dx`, enabling building, bundling, and runtime hot-patching of Rust\n//! applications. This module defines the entire end-to-end build process, including bundling for\n//! all major platforms including Mac, Windows, Linux, iOS, Android, and WebAssembly.\n//!\n//! The bulk of the builder code is contained within the [`request`] module which establishes the\n//! arguments and flow of the build process. The [`context`] module contains the context for the build\n//! including status updates and build customization. The [`patch`] module contains the logic for\n//! hot-patching Rust code through binary analysis and a custom linker. The [`builder`] module contains\n//! the management of the ongoing build and methods to open the build as a running app.\n\nmod assets;\nmod builder;\nmod cache;\nmod context;\nmod ios_swift;\nmod manifest;\nmod manifest_mapper;\nmod patch;\nmod pre_render;\nmod request;\nmod tools;\n\npub(crate) use assets::*;\npub(crate) use builder::*;\npub(crate) use cache::*;\npub(crate) use context::*;\npub(crate) use manifest::*;\npub(crate) use patch::*;\npub(crate) use pre_render::*;\npub(crate) use request::*;\npub(crate) use tools::*;\n"
  },
  {
    "path": "packages/cli/src/build/patch.rs",
    "content": "use anyhow::Context;\nuse itertools::Itertools;\nuse object::{\n    macho::{self},\n    read::File,\n    write::{MachOBuildVersion, SectionId, StandardSection, Symbol, SymbolId, SymbolSection},\n    Endianness, Object, ObjectSection, ObjectSymbol, SymbolFlags, SymbolKind, SymbolScope,\n};\nuse rayon::prelude::{IntoParallelRefIterator, ParallelIterator};\nuse std::{\n    collections::{BTreeMap, HashMap, HashSet},\n    ops::{Deref, Range},\n    path::Path,\n    path::PathBuf,\n    sync::{Arc, RwLock},\n};\nuse subsecond_types::{AddressMap, JumpTable};\nuse target_lexicon::{Architecture, OperatingSystem, PointerWidth, Triple};\nuse thiserror::Error;\nuse walrus::{\n    ConstExpr, DataKind, ElementItems, ElementKind, FunctionBuilder, FunctionId, FunctionKind,\n    ImportKind, Module, ModuleConfig, TableId,\n};\nuse wasmparser::{\n    BinaryReader, BinaryReaderError, Linking, LinkingSectionReader, Payload, SymbolInfo,\n};\n\ntype Result<T, E = PatchError> = std::result::Result<T, E>;\n\n#[derive(Debug, Error)]\npub enum PatchError {\n    #[error(\"Failed to read file: {0}\")]\n    ReadFs(#[from] std::io::Error),\n\n    #[error(\"No debug symbols in the patch output. Check your profile's `opt-level` and debug symbols config.\")]\n    MissingSymbols,\n\n    #[error(\"Failed to parse wasm section: {0}\")]\n    ParseSection(#[from] wasmparser::BinaryReaderError),\n\n    #[error(\"Failed to parse object file, {0}\")]\n    ParseObjectFile(#[from] object::read::Error),\n\n    #[error(\"Failed to write object file: {0}\")]\n    WriteObjectFIle(#[from] object::write::Error),\n\n    #[error(\"Failed to emit module: {0}\")]\n    RuntimeError(#[from] anyhow::Error),\n\n    #[error(\"Failed to read module's PDB file: {0}\")]\n    PdbLoadError(#[from] pdb::Error),\n\n    #[error(\"{0}\")]\n    InvalidModule(String),\n\n    #[error(\"Unsupported platform: {0}\")]\n    UnsupportedPlatform(String),\n}\n\n/// A cache for the hotpatching engine that stores the original module's parsed symbol table.\n/// For large projects, this can shave up to 50% off the total patching time. Since we compile the base\n/// module with every symbol in it, it can be quite large (hundreds of MB), so storing this here lets\n/// us avoid re-parsing the module every time we want to patch it.\n///\n/// On the Dioxus Docsite, it dropped the patch time from 3s to 1.1s (!)\n#[derive(Default)]\npub struct HotpatchModuleCache {\n    pub path: PathBuf,\n\n    // .... wasm stuff\n    pub symbol_ifunc_map: HashMap<String, i32>,\n    pub old_wasm: Module,\n    pub old_bytes: Vec<u8>,\n    pub old_exports: HashSet<String>,\n    pub old_imports: HashSet<String>,\n\n    // ... native stuff\n    pub symbol_table: HashMap<String, CachedSymbol>,\n\n    /// Contents of the .tdata section from the original binary (TLS initialization image).\n    /// Used to provide correct init data for TLS symbol stubs instead of garbage addresses.\n    pub tls_init_data: Vec<u8>,\n\n    /// Map from `$tlv$init` symbol name to (offset_in_tdata, computed_size).\n    /// On macOS, Mach-O nlist doesn't carry symbol sizes, so we compute them from\n    /// adjacent symbol addresses in the `__thread_data` section. This lets us provide\n    /// correctly-sized TLS init data in stubs instead of defaulting to pointer_width.\n    pub tls_init_sizes: HashMap<String, (u64, u64)>,\n}\n\npub struct CachedSymbol {\n    pub address: u64,\n    pub kind: SymbolKind,\n    pub is_undefined: bool,\n    pub is_weak: bool,\n    pub size: u64,\n    pub flags: SymbolFlags<SectionId, SymbolId>,\n}\n\nimpl PartialEq for HotpatchModuleCache {\n    fn eq(&self, other: &Self) -> bool {\n        self.path == other.path\n    }\n}\n\nimpl std::fmt::Debug for HotpatchModuleCache {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_struct(\"HotpatchModuleCache\")\n            .field(\"_path\", &self.path)\n            .finish()\n    }\n}\n\nimpl HotpatchModuleCache {\n    /// This caching step is crucial for performance on large projects. The original module can be\n    /// quite large (hundreds of MB), so this step drastically speeds it up.\n    pub fn new(original: &Path, triple: &Triple) -> Result<Self> {\n        let cache = match triple.operating_system {\n            OperatingSystem::Windows => {\n                use pdb::FallibleIterator;\n\n                // due to lifetimes, this code is unfortunately duplicated.\n                // the pdb crate doesn't bind the lifetime of the items in the iterator to the symbol table,\n                // so we're stuck with local lifetime.s\n                let old_pdb_file = original.with_extension(\"pdb\");\n                let old_pdb_file_handle = std::fs::File::open(old_pdb_file)?;\n                let mut pdb_file = pdb::PDB::open(old_pdb_file_handle)?;\n                let global_symbols = pdb_file.global_symbols()?;\n                let address_map = pdb_file.address_map()?;\n                let mut symbol_table = HashMap::new();\n                let mut symbols = global_symbols.iter();\n                while let Ok(Some(symbol)) = symbols.next() {\n                    match symbol.parse() {\n                        Ok(pdb::SymbolData::Public(data)) => {\n                            let rva = data.offset.to_rva(&address_map);\n                            let is_undefined = rva.is_none();\n\n                            // treat undefined symbols as 0 to match macho/elf\n                            let rva = rva.unwrap_or_default();\n\n                            symbol_table.insert(\n                                data.name.to_string().to_string(),\n                                CachedSymbol {\n                                    address: rva.0 as u64,\n                                    kind: if data.function {\n                                        SymbolKind::Text\n                                    } else {\n                                        SymbolKind::Data\n                                    },\n                                    is_undefined,\n                                    is_weak: false,\n                                    size: 0,\n                                    flags: SymbolFlags::None,\n                                },\n                            );\n                        }\n\n                        Ok(pdb::SymbolData::Data(data)) => {\n                            let rva = data.offset.to_rva(&address_map);\n                            let is_undefined = rva.is_none();\n\n                            // treat undefined symbols as 0 to match macho/elf\n                            let rva = rva.unwrap_or_default();\n\n                            symbol_table.insert(\n                                data.name.to_string().to_string(),\n                                CachedSymbol {\n                                    address: rva.0 as u64,\n                                    kind: SymbolKind::Data,\n                                    is_undefined,\n                                    is_weak: false,\n                                    size: 0,\n                                    flags: SymbolFlags::None,\n                                },\n                            );\n                        }\n\n                        _ => {}\n                    }\n                }\n\n                HotpatchModuleCache {\n                    symbol_table,\n                    path: original.to_path_buf(),\n                    ..Default::default()\n                }\n            }\n\n            // We need to load the ifunc table from the original module since that gives us the map\n            // of name to address (since ifunc entries are also pointers in wasm - ie 0x30 is the 30th\n            // entry in the ifunc table)\n            //\n            // One detail here is that with high optimization levels, the names of functions in the ifunc\n            // table will be smaller than the total number of functions in the module. This is because\n            // in high opt-levels, functions are merged. Fortunately, the symbol table remains intact\n            // and functions with different names point to the same function index (not to be confused\n            // with the function index in the module!).\n            //\n            // We need to take an extra step to account for merged functions by mapping function index\n            // to a set of functions that point to the same index.\n            _ if triple.architecture == Architecture::Wasm32 => {\n                let bytes = std::fs::read(original)?;\n                let ParsedModule {\n                    module, symbols, ..\n                } = parse_module_with_ids(&bytes)?;\n\n                if symbols.symbols.is_empty() {\n                    return Err(PatchError::MissingSymbols);\n                }\n\n                let name_to_ifunc_old = collect_func_ifuncs(&module);\n\n                // These are the \"real\" bindings for functions in the module\n                // Basically a map between a function's index and its real name\n                let func_to_index = module\n                    .funcs\n                    .par_iter()\n                    .filter_map(|f| {\n                        let name = f.name.as_deref()?;\n                        Some((*symbols.code_symbol_map.get(name)?, name))\n                    })\n                    .collect::<HashMap<usize, &str>>();\n\n                // Find the corresponding function that shares the same index, but in the ifunc table\n                let name_to_ifunc_old: HashMap<_, _> = symbols\n                    .code_symbol_map\n                    .par_iter()\n                    .filter_map(|(name, idx)| {\n                        let new_modules_unified_function = func_to_index.get(idx)?;\n                        let offset = name_to_ifunc_old.get(new_modules_unified_function)?;\n                        Some((*name, *offset))\n                    })\n                    .collect();\n\n                let symbol_ifunc_map = name_to_ifunc_old\n                    .par_iter()\n                    .map(|(name, idx)| (name.to_string(), *idx))\n                    .collect::<HashMap<_, _>>();\n\n                let old_exports = module\n                    .exports\n                    .iter()\n                    .map(|e| e.name.to_string())\n                    .collect::<HashSet<_>>();\n\n                let old_imports = module\n                    .imports\n                    .iter()\n                    .map(|i| i.name.to_string())\n                    .collect::<HashSet<_>>();\n\n                HotpatchModuleCache {\n                    path: original.to_path_buf(),\n                    old_bytes: bytes,\n                    symbol_ifunc_map,\n                    old_exports,\n                    old_imports,\n                    old_wasm: module,\n                    ..Default::default()\n                }\n            }\n            _ => {\n                let old_bytes = std::fs::read(original)?;\n                let obj = File::parse(&old_bytes as &[u8])?;\n                let symbol_table = obj\n                    .symbols()\n                    .filter_map(|s| {\n                        let flags = match s.flags() {\n                            SymbolFlags::None => SymbolFlags::None,\n                            SymbolFlags::Elf { st_info, st_other } => {\n                                SymbolFlags::Elf { st_info, st_other }\n                            }\n                            SymbolFlags::MachO { n_desc } => SymbolFlags::MachO { n_desc },\n                            _ => SymbolFlags::None,\n                        };\n\n                        Some((\n                            s.name().ok()?.to_string(),\n                            CachedSymbol {\n                                address: s.address(),\n                                is_undefined: s.is_undefined(),\n                                is_weak: s.is_weak(),\n                                kind: s.kind(),\n                                size: s.size(),\n                                flags,\n                            },\n                        ))\n                    })\n                    .collect::<HashMap<_, _>>();\n\n                // Extract TLS initialization data and section metadata.\n                // This is used to correctly initialize TLS symbols in the stub\n                // instead of writing bogus absolute addresses into .tdata.\n                let tls_section = obj\n                    .sections()\n                    .find(|s| matches!(s.name(), Ok(\".tdata\" | \"__thread_data\")));\n\n                let tls_init_data = tls_section\n                    .as_ref()\n                    .and_then(|s| s.data().ok())\n                    .unwrap_or(&[])\n                    .to_vec();\n\n                // Build TLS init size map for macOS. Mach-O nlist doesn't carry symbol\n                // sizes, so we compute them from adjacent symbols in __thread_data.\n                // LLVM/rustc names init data symbols as `FOO$tlv$init` in __thread_data.\n                let tls_data_addr = tls_section.as_ref().map(|s| s.address()).unwrap_or(0);\n                let tls_data_size = tls_section.as_ref().map(|s| s.size()).unwrap_or(0);\n                let tls_section_index = tls_section.as_ref().map(|s| s.index());\n\n                let mut tls_init_syms: Vec<(u64, String)> = Vec::new();\n                for sym in obj.symbols() {\n                    if let (Some(section_idx), Ok(sname)) = (sym.section_index(), sym.name()) {\n                        if Some(section_idx) == tls_section_index {\n                            let offset = sym.address().saturating_sub(tls_data_addr);\n                            tls_init_syms.push((offset, sname.to_string()));\n                        }\n                    }\n                }\n                tls_init_syms.sort_by_key(|(addr, _)| *addr);\n                tls_init_syms.dedup_by_key(|(addr, _)| *addr);\n\n                let mut tls_init_sizes: HashMap<String, (u64, u64)> = HashMap::new();\n                for (i, (offset, sname)) in tls_init_syms.iter().enumerate() {\n                    let size = if i + 1 < tls_init_syms.len() {\n                        tls_init_syms[i + 1].0 - offset\n                    } else {\n                        tls_data_size.saturating_sub(*offset)\n                    };\n                    tls_init_sizes.insert(sname.clone(), (*offset, size));\n                }\n\n                HotpatchModuleCache {\n                    symbol_table,\n                    path: original.to_path_buf(),\n                    old_bytes,\n                    tls_init_data,\n                    tls_init_sizes,\n                    ..Default::default()\n                }\n            }\n        };\n\n        Ok(cache)\n    }\n}\n\npub fn create_windows_jump_table(patch: &Path, cache: &HotpatchModuleCache) -> Result<JumpTable> {\n    use pdb::FallibleIterator;\n    let old_name_to_addr = &cache.symbol_table;\n\n    let mut new_name_to_addr = HashMap::new();\n    let new_pdb_file_handle = std::fs::File::open(patch.with_extension(\"pdb\"))?;\n    let mut pdb_file = pdb::PDB::open(new_pdb_file_handle)?;\n    let symbol_table = pdb_file.global_symbols()?;\n    let address_map = pdb_file.address_map()?;\n    let mut symbol_iter = symbol_table.iter();\n    while let Ok(Some(symbol)) = symbol_iter.next() {\n        if let Ok(pdb::SymbolData::Public(data)) = symbol.parse() {\n            let rva = data.offset.to_rva(&address_map);\n            if let Some(rva) = rva {\n                new_name_to_addr.insert(data.name.to_string(), rva.0 as u64);\n            }\n        }\n    }\n\n    let mut map = AddressMap::default();\n    for (new_name, new_addr) in new_name_to_addr.iter() {\n        if let Some(old_addr) = old_name_to_addr.get(new_name.as_ref()) {\n            map.insert(old_addr.address, *new_addr);\n        }\n    }\n\n    let new_base_address = new_name_to_addr\n        .get(\"main\")\n        .cloned()\n        .context(\"failed to find 'main' symbol in patch\")?;\n\n    let aslr_reference = old_name_to_addr\n        .get(\"main\")\n        .map(|s| s.address)\n        .context(\"failed to find '_main' symbol in original module\")?;\n\n    Ok(JumpTable {\n        lib: patch.to_path_buf(),\n        map,\n        new_base_address,\n        aslr_reference,\n        ifunc_count: 0,\n    })\n}\n\n/// Assemble a jump table for \"nix\" architectures. This uses the `object` crate to parse both\n/// executable's symbol tables and then creates a mapping between the two. Unlike windows, the symbol\n/// tables are stored within the binary itself, so we can use the `object` crate to parse them.\n///\n/// We use the `_aslr_reference` as a reference point in the base program to calculate the aslr slide\n/// both at compile time and at runtime.\n///\n/// This does not work for WASM since the `object` crate does not support emitting the WASM format,\n/// and because WASM requires more logic to handle the wasm-bindgen transformations.\npub fn create_native_jump_table(\n    patch: &Path,\n    triple: &Triple,\n    cache: &HotpatchModuleCache,\n) -> Result<JumpTable> {\n    let old_name_to_addr = &cache.symbol_table;\n    let obj2_bytes = std::fs::read(patch)?;\n    let obj2 = File::parse(&obj2_bytes as &[u8])?;\n    let mut map = AddressMap::default();\n    let new_syms = obj2.symbol_map();\n\n    let new_name_to_addr = new_syms\n        .symbols()\n        .par_iter()\n        .map(|s| (s.name(), s.address()))\n        .collect::<HashMap<_, _>>();\n\n    for (new_name, new_addr) in new_name_to_addr.iter() {\n        if let Some(old_addr) = old_name_to_addr.get(*new_name) {\n            map.insert(old_addr.address, *new_addr);\n        }\n    }\n\n    let sentinel = main_sentinel(triple);\n    let new_base_address = new_name_to_addr\n        .get(sentinel)\n        .cloned()\n        .context(\"failed to find 'main' symbol in base - are deubg symbols enabled?\")?;\n    let aslr_reference = old_name_to_addr\n        .get(sentinel)\n        .map(|s| s.address)\n        .context(\"failed to find 'main' symbol in original module - are debug symbols enabled?\")?;\n\n    Ok(JumpTable {\n        lib: patch.to_path_buf(),\n        map,\n        new_base_address,\n        aslr_reference,\n        ifunc_count: 0,\n    })\n}\n\n/// In the web, our patchable functions are actually ifuncs\n///\n/// We need to line up the ifuncs from the main module to the ifuncs in the patch.\n///\n/// According to the dylink spec, there will be two sets of entries:\n///\n/// - got.func: functions in the indirect function table\n/// - got.mem: data objects in the data segments\n///\n/// It doesn't seem like we can compile the base module to export these, sadly, so we're going\n/// to manually satisfy them here, removing their need to be imported.\n///\n/// <https://github.com/WebAssembly/tool-conventions/blob/main/DynamicLinking.md>\npub fn create_wasm_jump_table(patch: &Path, cache: &HotpatchModuleCache) -> Result<JumpTable> {\n    let name_to_ifunc_old = &cache.symbol_ifunc_map;\n    let old = &cache.old_wasm;\n    let old_symbols =\n        parse_bytes_to_data_segment(&cache.old_bytes).context(\"Failed to parse data segment\")?;\n    let new_bytes = std::fs::read(patch).context(\"Could not read patch file\")?;\n\n    let mut new = Module::from_buffer(&new_bytes)?;\n    let mut got_mems = vec![];\n    let mut got_funcs = vec![];\n    let mut wbg_funcs = vec![];\n    let mut env_funcs = vec![];\n\n    // Collect all the GOT entries from the new module.\n    // The GOT imports come from the wasm-ld implementation of the dynamic linking spec\n    //\n    // https://github.com/WebAssembly/tool-conventions/blob/main/DynamicLinking.md#imports\n    //\n    // Normally, the base module would synthesize these as exports, but we're not compiling the base\n    // module with `--pie` (nor does wasm-bindgen support it yet), so we need to manually satisfy them.\n    //\n    // One thing to watch out for here is that GOT.func entries have no visibility to any de-duplication\n    // or merging, so we need to take great care in the base module to export *every* symbol even if\n    // they point to the same function.\n    //\n    // The other thing to watch out for here is the __wbindgen_placeholder__ entries. These are meant\n    // to be satisfied by wasm-bindgen via manual code generation, but we can't run wasm-bindgen on the\n    // patch, so we need to do it ourselves. This involves preventing their elimination in the base module\n    // by prefixing them with `__saved_wbg_`. When handling the imports here, we need modify the imported\n    // name to match the prefixed export name in the base module.\n    for import in new.imports.iter() {\n        match import.module.as_str() {\n            \"GOT.func\" => {\n                let Some(entry) = name_to_ifunc_old.get(import.name.as_str()).cloned() else {\n                    return Err(PatchError::InvalidModule(format!(\n                        \"Expected to find GOT.func entry in ifunc table: {}\",\n                        import.name.as_str()\n                    )));\n                };\n                got_funcs.push((import.id(), entry));\n            }\n            \"GOT.mem\" => got_mems.push(import.id()),\n            \"env\" => env_funcs.push(import.id()),\n            \"__wbindgen_placeholder__\" => wbg_funcs.push(import.id()),\n            m => tracing::trace!(\"Unknown import: {m}:{}\", import.name),\n        }\n    }\n\n    // We need to satisfy the GOT.func imports of this side module. The GOT imports come from the wasm-ld\n    // implementation of the dynamic linking spec\n    //\n    // https://github.com/WebAssembly/tool-conventions/blob/main/DynamicLinking.md#imports\n    //\n    // Most importantly, these functions are functions meant to be called indirectly. In normal wasm\n    // code generation, only functions that Rust code references via pointers are given a slot in\n    // the indirection function table. The optimization here traditionally meaning that if a function\n    // can be called directly, then it doesn't need to be referenced indirectly and potentially inlined\n    // or dissolved during LTO.\n    //\n    // In our \"fat build\" setup, we aggregated all symbols from dependencies into a `dependencies.ar` file.\n    // By promoting these functions to the dynamic scope, we also prevent their inlining because the\n    // linker can still expect some form of interposition to happen, requiring the symbol *actually*\n    // exists.\n    //\n    // Our technique here takes advantage of that and the [`prepare_wasm_base_module`] function promotes\n    // every possible function to the indirect function table. This means that the GOT imports that\n    // `relocation-model=pic` synthesizes can reference the functions via the indirect function table\n    // even if they are not normally synthesized in regular wasm code generation.\n    //\n    // Normally, the dynamic linker setup would resolve GOT.func against the same GOT.func export in\n    // the main module, but we don't have that. Instead, we simply re-parse the main module, aggregate\n    // its ifunc table, and then resolve directly to the index in that table.\n    for (import_id, ifunc_index) in got_funcs {\n        let import = new.imports.get(import_id);\n        let ImportKind::Global(id) = import.kind else {\n            return Err(PatchError::InvalidModule(format!(\n                \"Expected GOT.func import to be a global: {}\",\n                import.name\n            )));\n        };\n\n        // \"satisfying\" the import means removing it from the import table and replacing its target\n        // value with a local global.\n        new.imports.delete(import_id);\n        new.globals.get_mut(id).kind =\n            walrus::GlobalKind::Local(ConstExpr::Value(walrus::ir::Value::I32(ifunc_index)));\n    }\n\n    // We need to satisfy the GOT.mem imports of this side module. The GOT.mem imports come from the wasm-ld\n    // implementation of the dynamic linking spec\n    //\n    // https://github.com/WebAssembly/tool-conventions/blob/main/DynamicLinking.md#imports\n    //\n    // Unlike the ifunc table, the GOT.mem imports do not need any additional post-processing of the\n    // base module to satisfy. Since our patching approach works but leveraging the experimental dynamic\n    // PIC support in rustc[wasm] and wasm-ld, we are using the GOT.mem imports as a way of identifying\n    // data segments that are present in the base module.\n    //\n    // Normally, the dynamic linker would synthesize corresponding GOT.mem exports in the main module,\n    // but since we're patching on-the-fly, this table will always be out-of-date.\n    //\n    // Instead, we use the symbol table from the base module to find the corresponding data symbols\n    // and then resolve the offset of the data segment in the main module. Using the symbol table\n    // can be somewhat finicky if the user compiled the code with a high-enough opt level that nukes\n    // the names of the data segments, but otherwise this system works well.\n    //\n    // We simply use the name of the import as a key into the symbol table and then its offset into\n    // its data segment as the value within the global.\n    for mem in got_mems {\n        let import = new.imports.get(mem);\n        let data_symbol_idx = *old_symbols\n            .data_symbol_map\n            .get(import.name.as_str())\n            .with_context(|| {\n                format!(\"Failed to find GOT.mem import by its name: {}\", import.name)\n            })?;\n        let data_symbol = old_symbols\n            .data_symbols\n            .get(&data_symbol_idx)\n            .context(\"Failed to find data symbol by its index\")?;\n        let data = old\n            .data\n            .iter()\n            .nth(data_symbol.which_data_segment)\n            .context(\"Missing data segment in the main module\")?;\n\n        let offset = match data.kind {\n            DataKind::Active {\n                offset: ConstExpr::Value(walrus::ir::Value::I32(idx)),\n                ..\n            } => idx,\n            DataKind::Active {\n                offset: ConstExpr::Value(walrus::ir::Value::I64(idx)),\n                ..\n            } => idx as i32,\n            _ => {\n                return Err(PatchError::InvalidModule(format!(\n                    \"Data segment of invalid table: {:?}\",\n                    data.kind\n                )));\n            }\n        };\n\n        let ImportKind::Global(global_id) = import.kind else {\n            return Err(PatchError::InvalidModule(\n                \"Expected GOT.mem import to be a global\".to_string(),\n            ));\n        };\n\n        // \"satisfying\" the import means removing it from the import table and replacing its target\n        // value with a local global.\n        new.imports.delete(mem);\n        new.globals.get_mut(global_id).kind = walrus::GlobalKind::Local(ConstExpr::Value(\n            walrus::ir::Value::I32(offset + data_symbol.segment_offset as i32),\n        ));\n    }\n\n    // wasm-bindgen has a limit on the number of exports a module can have, so we need to call the main\n    // module's functions indirectly. This is done by dropping the env import and replacing it with a\n    // local function that calls the indirect function from the table.\n    //\n    // https://github.com/emscripten-core/emscripten/issues/22863\n    let ifunc_table_initializer = new\n        .elements\n        .iter()\n        .find_map(|e| match e.kind {\n            ElementKind::Active { table, .. } => Some(table),\n            _ => None,\n        })\n        .context(\"Missing ifunc table\")?;\n    for env_func_import in env_funcs {\n        let import = new.imports.get(env_func_import);\n        let ImportKind::Function(func_id) = import.kind else {\n            continue;\n        };\n\n        if cache.old_exports.contains(import.name.as_str())\n            || cache.old_imports.contains(import.name.as_str())\n        {\n            continue;\n        }\n        let name = import.name.as_str().to_string();\n\n        if let Some(table_idx) = name_to_ifunc_old.get(import.name.as_str()) {\n            new.imports.delete(env_func_import);\n            convert_func_to_ifunc_call(\n                &mut new,\n                ifunc_table_initializer,\n                func_id,\n                *table_idx,\n                name.clone(),\n            );\n            continue;\n        }\n\n        if name_is_bindgen_symbol(&name) {\n            new.imports.delete(env_func_import);\n            convert_func_to_ifunc_call(&mut new, ifunc_table_initializer, func_id, 0, name);\n            continue;\n        }\n\n        tracing::warn!(\"[hotpatching]: Symbol slipped through the cracks: {}\", name);\n    }\n\n    // Wire up the preserved intrinsic functions that we saved before running wasm-bindgen to the expected\n    // imports from the patch.\n    for import_id in wbg_funcs {\n        let import = new.imports.get_mut(import_id);\n        let ImportKind::Function(func_id) = import.kind else {\n            continue;\n        };\n\n        import.module = \"env\".into();\n        import.name = format!(\"__saved_wbg_{}\", import.name);\n\n        if name_is_bindgen_symbol(&import.name) {\n            let name = import.name.as_str().to_string();\n            new.imports.delete(import_id);\n            convert_func_to_ifunc_call(&mut new, ifunc_table_initializer, func_id, 0, name);\n        }\n    }\n\n    // Rewrite the wbg_cast functions to call the indirect functions from the original module.\n    // This is necessary because wasm-bindgen uses these calls to perform dynamic type casting through\n    // the JS layer. If we don't rewrite these, they end up as calls to `breaks_if_inlined` functions\n    // which are no-ops and get rewritten by the wbindgen post-processing step.\n    //\n    // Here, we find the corresponding wbg_cast function in the old module by name and then rewrite\n    // the patch module's cast function to call the indirect function from the original module.\n    //\n    // See the wbg_cast implementation in wasm-bindgen for more details:\n    // <https://github.com/wasm-bindgen/wasm-bindgen/blob/f61a588f674304964a2062b2307edb304aed4d16/src/rt/mod.rs#L30>\n    let new_func_ids = new.funcs.iter().map(|f| f.id()).collect::<Vec<_>>();\n    for func_id in new_func_ids {\n        let Some(name) = new.funcs.get(func_id).name.as_deref() else {\n            continue;\n        };\n\n        if name.contains(\"wasm_bindgen4__rt8wbg_cast\") && !name.contains(\"breaks_if_inline\") {\n            let name = name.to_string();\n            let old_idx = name_to_ifunc_old\n                    .get(&name)\n                    .copied()\n                    .ok_or_else(|| anyhow::anyhow!(\"Could not find matching wbg_cast function for [{name}] - must generate new JS bindings.\"))?;\n\n            convert_func_to_ifunc_call(&mut new, ifunc_table_initializer, func_id, old_idx, name);\n        }\n    }\n\n    // Wipe away the unnecessary sections\n    let customs = new.customs.iter().map(|f| f.0).collect::<Vec<_>>();\n    for custom_id in customs {\n        if let Some(custom) = new.customs.get_mut(custom_id) {\n            if custom.name().contains(\"manganis\") || custom.name().contains(\"__wasm_bindgen\") {\n                new.customs.delete(custom_id);\n            }\n        }\n    }\n\n    // Clear the start function from the patch - we don't want any code automatically running!\n    new.start = None;\n\n    // Update the wasm module on the filesystem to use the newly lifted version\n    let lib = patch.to_path_buf();\n    std::fs::write(&lib, new.emit_wasm())?;\n\n    // And now assemble the jump table by mapping the old ifunc table to the new one, by name\n    //\n    // The ifunc_count will be passed to the dynamic loader so it can allocate the right amount of space\n    // in the indirect function table when loading the patch.\n    let name_to_ifunc_new = collect_func_ifuncs(&new);\n    let ifunc_count = name_to_ifunc_new.len() as u64;\n    let mut map = AddressMap::default();\n    for (name, idx) in name_to_ifunc_new.iter() {\n        // Find the corresponding ifunc in the old module by name\n        if let Some(old_idx) = name_to_ifunc_old.get(*name) {\n            map.insert(*old_idx as u64, *idx as u64);\n            continue;\n        }\n    }\n\n    Ok(JumpTable {\n        map,\n        lib,\n        ifunc_count,\n        aslr_reference: 0,\n        new_base_address: 0,\n    })\n}\n\nfn convert_func_to_ifunc_call(\n    new: &mut Module,\n    ifunc_table_initializer: TableId,\n    func_id: FunctionId,\n    table_idx: i32,\n    name: String,\n) {\n    use walrus::ir;\n\n    let func = new.funcs.get_mut(func_id);\n    let ty_id = func.ty();\n\n    // Convert the import function to a local function that calls the indirect function from the table\n    let ty = new.types.get(ty_id);\n    let params = ty.params().to_vec();\n    let results = ty.results().to_vec();\n    let locals: Vec<_> = params.iter().map(|ty| new.locals.add(*ty)).collect();\n\n    // New function that calls the indirect function\n    let mut builder = FunctionBuilder::new(&mut new.types, &params, &results);\n    let mut body = builder.name(name).func_body();\n\n    // Push the params onto the stack\n    for arg in locals.iter() {\n        body.local_get(*arg);\n    }\n\n    // And then the address of the indirect function\n    body.instr(ir::Instr::Const(ir::Const {\n        value: ir::Value::I32(table_idx),\n    }));\n\n    // And call it\n    body.instr(ir::Instr::CallIndirect(ir::CallIndirect {\n        ty: ty_id,\n        table: ifunc_table_initializer,\n    }));\n\n    new.funcs.get_mut(func_id).kind = FunctionKind::Local(builder.local_func(locals));\n}\n\nfn collect_func_ifuncs(m: &Module) -> HashMap<&str, i32> {\n    // Collect all the functions in the module that are ifuncs\n    let mut func_to_offset = HashMap::new();\n    for el in m.elements.iter() {\n        let ElementKind::Active { offset, .. } = &el.kind else {\n            continue;\n        };\n\n        let offset = match offset {\n            // Handle explicit offsets\n            ConstExpr::Value(value) => match value {\n                walrus::ir::Value::I32(idx) => *idx,\n                walrus::ir::Value::I64(idx) => *idx as i32,\n                _ => continue,\n            },\n\n            // Globals are usually imports and thus don't add a specific offset\n            // ie the ifunc table is offset by a global, so we don't need to push the offset out\n            ConstExpr::Global(_) => 0,\n            _ => continue,\n        };\n\n        match &el.items {\n            ElementItems::Functions(ids) => {\n                for (idx, id) in ids.iter().enumerate() {\n                    if let Some(name) = m.funcs.get(*id).name.as_deref() {\n                        func_to_offset.insert(name, offset + idx as i32);\n                    }\n                }\n            }\n            ElementItems::Expressions(_ref_type, _const_exprs) => {}\n        }\n    }\n\n    func_to_offset\n}\n\n/// Resolve the undefined symbols in the incrementals against the original binary, returning an object\n/// file that can be linked along the incrementals.\n///\n/// This makes it possible to dlopen the resulting object file and use the original binary's symbols\n/// bypassing the dynamic linker.\n///\n/// This is very similar to malware :) but it's not!\n///\n/// Note - this function is not defined to run on WASM binaries. The `object` crate does not\n///\n/// todo... we need to wire up the cache\npub fn create_undefined_symbol_stub(\n    cache: &HotpatchModuleCache,\n    incrementals: &[PathBuf],\n    triple: &Triple,\n    aslr_reference: u64,\n) -> Result<Vec<u8>> {\n    let sorted: Vec<_> = incrementals.iter().sorted().collect();\n\n    // Find all the undefined symbols in the incrementals\n    let mut undefined_symbols = HashSet::new();\n    let mut defined_symbols = HashSet::new();\n\n    for path in sorted {\n        let bytes = std::fs::read(path).with_context(|| format!(\"failed to read {path:?}\"))?;\n        let file = File::parse(bytes.deref() as &[u8])?;\n        for symbol in file.symbols() {\n            if symbol.is_undefined() {\n                undefined_symbols.insert(symbol.name()?.to_string());\n            } else if symbol.is_global() {\n                defined_symbols.insert(symbol.name()?.to_string());\n            }\n        }\n    }\n    let undefined_symbols: Vec<_> = undefined_symbols\n        .difference(&defined_symbols)\n        .cloned()\n        .collect();\n\n    tracing::trace!(\"Undefined symbols: {:#?}\", undefined_symbols);\n\n    // Create a new object file (architecture doesn't matter much for our purposes)\n    let mut obj = object::write::Object::new(\n        match triple.binary_format {\n            target_lexicon::BinaryFormat::Elf => object::BinaryFormat::Elf,\n            target_lexicon::BinaryFormat::Macho => object::BinaryFormat::MachO,\n            target_lexicon::BinaryFormat::Coff => object::BinaryFormat::Coff,\n            target_lexicon::BinaryFormat::Wasm => object::BinaryFormat::Wasm,\n            target_lexicon::BinaryFormat::Xcoff => object::BinaryFormat::Xcoff,\n            _ => return Err(PatchError::UnsupportedPlatform(triple.to_string())),\n        },\n        match triple.architecture {\n            Architecture::Aarch64(_) => object::Architecture::Aarch64,\n            Architecture::Wasm32 => object::Architecture::Wasm32,\n            Architecture::X86_64 => object::Architecture::X86_64,\n            _ => return Err(PatchError::UnsupportedPlatform(triple.to_string())),\n        },\n        match triple.endianness() {\n            Ok(target_lexicon::Endianness::Little) => Endianness::Little,\n            Ok(target_lexicon::Endianness::Big) => Endianness::Big,\n            _ => Endianness::Little,\n        },\n    );\n\n    // Write the headers so we load properly in ios/macos\n    #[allow(clippy::identity_op)]\n    match triple.operating_system {\n        OperatingSystem::Darwin(_) => {\n            obj.set_macho_build_version({\n                let mut build_version = MachOBuildVersion::default();\n                build_version.platform = macho::PLATFORM_MACOS;\n                build_version.minos = (11 << 16) | (0 << 8) | 0; // 11.0.0\n                build_version.sdk = (11 << 16) | (0 << 8) | 0; // SDK 11.0.0\n                build_version\n            });\n        }\n        OperatingSystem::IOS(_) => {\n            obj.set_macho_build_version({\n                let mut build_version = MachOBuildVersion::default();\n                build_version.platform = match triple.environment {\n                    target_lexicon::Environment::Sim => macho::PLATFORM_IOSSIMULATOR,\n                    _ => macho::PLATFORM_IOS,\n                };\n                build_version.minos = (14 << 16) | (0 << 8) | 0; // 14.0.0\n                build_version.sdk = (14 << 16) | (0 << 8) | 0; // SDK 14.0.0\n                build_version\n            });\n        }\n\n        _ => {}\n    }\n\n    // Get the offset from the main module and adjust the addresses by the slide;\n    let aslr_ref_address = cache\n        .symbol_table\n        .get(main_sentinel(triple))\n        .context(\"failed to find '_main' symbol in patch\")?\n        .address;\n\n    if aslr_reference < aslr_ref_address {\n        return Err(PatchError::InvalidModule(\n            format!(\n            \"ASLR reference is less than the main module's address - is there a `main`?. {aslr_reference:x} < {aslr_ref_address:x}\" )\n        ));\n    }\n\n    let aslr_offset = aslr_reference - aslr_ref_address;\n\n    // we need to assemble a PLT/GOT so direct calls to the patch symbols work\n    // for each symbol we either write the address directly (as a symbol) or create a PLT/GOT entry\n    let text_section = obj.section_id(StandardSection::Text);\n    for name in undefined_symbols {\n        let Some(sym) = cache\n            .symbol_table\n            .get(name.as_str().trim_start_matches(\"__imp_\"))\n        else {\n            tracing::debug!(\"Symbol not found: {}\", name);\n            continue;\n        };\n\n        // Undefined symbols tend to be import symbols (darwin gives them an address of 0 until defined).\n        // If we fail to skip these, then we end up with stuff like alloc at 0x0 which is quite bad!\n        if sym.is_undefined {\n            continue;\n        }\n\n        // ld64 likes to prefix symbols in intermediate object files with an underscore, but our symbol\n        // table doesn't, so we need to strip it off.\n        let name_offset = match triple.operating_system {\n            OperatingSystem::MacOSX(_) | OperatingSystem::Darwin(_) | OperatingSystem::IOS(_) => 1,\n            _ => 0,\n        };\n\n        let abs_addr = sym.address + aslr_offset;\n\n        match sym.kind {\n            // Handle synthesized window linker cross-dll statics.\n            //\n            // The `__imp_` prefix is a rather poorly documented feature of link.exe that makes it possible\n            // to reference statics in DLLs via text sections. The linker will synthesize a function\n            // that returns the address of the static, so calling that function will return the address.\n            // We want to satisfy it by creating a data symbol with the contents of the *actual* symbol\n            // in the original binary.\n            //\n            // We ca't use the `__imp_` from the original binary because it was not properly compiled\n            // with this in mind. Instead we have to create the new symbol.\n            //\n            // This is currently only implemented for 64bit architectures (haven't tested 32bit yet).\n            //\n            // https://stackoverflow.com/questions/5159353/how-can-i-get-rid-of-the-imp-prefix-in-the-linker-in-vc\n            _ if name.starts_with(\"__imp_\") => {\n                let data_section = obj.section_id(StandardSection::Data);\n\n                // Add a pointer to the resolved address\n                let offset = obj.append_section_data(\n                    data_section,\n                    &abs_addr.to_le_bytes(),\n                    8, // Use proper alignment\n                );\n\n                // Add the symbol as a data symbol in our data section\n                obj.add_symbol(Symbol {\n                    name: name.as_bytes().to_vec(),\n                    value: offset, // Offset within the data section\n                    size: 8,       // Size of pointer\n                    scope: SymbolScope::Linkage,\n                    kind: SymbolKind::Data, // Always Data for IAT entries\n                    weak: false,\n                    section: SymbolSection::Section(data_section),\n                    flags: SymbolFlags::None,\n                });\n            }\n\n            // Text symbols are normal code symbols. We need to assemble stubs that resolve the undefined\n            // symbols and jump to the original address in the original binary.\n            //\n            // Unfortunately this isn't simply cross-platform, so we need to handle Unix and Windows\n            // calling conventions separately. It also depends on the architecture, making it even more\n            // complicated.\n            SymbolKind::Text => {\n                let jump_asm = match triple.operating_system {\n                    // The windows ABI and calling convention is different than the SystemV ABI.\n                    OperatingSystem::Windows => match triple.architecture {\n                        Architecture::X86_64 => {\n                            // Windows x64 has specific requirements for alignment and position-independent code\n                            let mut code = vec![\n                                0x48, 0xB8, // movabs RAX, imm64 (move 64-bit immediate to RAX)\n                            ];\n                            // Append the absolute 64-bit address\n                            code.extend_from_slice(&abs_addr.to_le_bytes());\n                            // jmp RAX (jump to the address in RAX)\n                            code.extend_from_slice(&[0xFF, 0xE0]);\n                            code\n                        }\n                        Architecture::X86_32(_) => {\n                            // On Windows 32-bit, we can use direct jump but need proper alignment\n                            let mut code = vec![\n                                0xB8, // mov EAX, imm32 (move immediate value to EAX)\n                            ];\n                            // Append the absolute 32-bit address\n                            code.extend_from_slice(&(abs_addr as u32).to_le_bytes());\n                            // jmp EAX (jump to the address in EAX)\n                            code.extend_from_slice(&[0xFF, 0xE0]);\n                            code\n                        }\n                        Architecture::Aarch64(_) => {\n                            // Use MOV/MOVK sequence to load 64-bit address into X16\n                            // This is more reliable than ADRP+LDR for direct hotpatching\n                            let mut code = Vec::new();\n\n                            // MOVZ X16, #imm16_0 (bits 0-15 of address)\n                            let imm16_0 = (abs_addr & 0xFFFF) as u16;\n                            let movz = 0xD2800010u32 | ((imm16_0 as u32) << 5);\n                            code.extend_from_slice(&movz.to_le_bytes());\n\n                            // MOVK X16, #imm16_1, LSL #16 (bits 16-31 of address)\n                            let imm16_1 = ((abs_addr >> 16) & 0xFFFF) as u16;\n                            let movk1 = 0xF2A00010u32 | ((imm16_1 as u32) << 5);\n                            code.extend_from_slice(&movk1.to_le_bytes());\n\n                            // MOVK X16, #imm16_2, LSL #32 (bits 32-47 of address)\n                            let imm16_2 = ((abs_addr >> 32) & 0xFFFF) as u16;\n                            let movk2 = 0xF2C00010u32 | ((imm16_2 as u32) << 5);\n                            code.extend_from_slice(&movk2.to_le_bytes());\n\n                            // MOVK X16, #imm16_3, LSL #48 (bits 48-63 of address)\n                            let imm16_3 = ((abs_addr >> 48) & 0xFFFF) as u16;\n                            let movk3 = 0xF2E00010u32 | ((imm16_3 as u32) << 5);\n                            code.extend_from_slice(&movk3.to_le_bytes());\n\n                            // BR X16 (Branch to address in X16)\n                            code.extend_from_slice(&[0x00, 0x02, 0x1F, 0xD6]);\n\n                            code\n                        }\n                        Architecture::Arm(_) => {\n                            // For Windows 32-bit ARM, we need a different approach\n                            let mut code = Vec::new();\n                            // LDR r12, [pc, #8] ; Load the address into r12\n                            code.extend_from_slice(&[0x08, 0xC0, 0x9F, 0xE5]);\n                            // BX r12 ; Branch to the address in r12\n                            code.extend_from_slice(&[0x1C, 0xFF, 0x2F, 0xE1]);\n                            // 4-byte alignment padding\n                            code.extend_from_slice(&[0x00, 0x00, 0x00, 0x00]);\n                            // Store the 32-bit address - 4-byte aligned\n                            code.extend_from_slice(&(abs_addr as u32).to_le_bytes());\n                            code\n                        }\n                        _ => return Err(PatchError::UnsupportedPlatform(triple.to_string())),\n                    },\n                    _ => match triple.architecture {\n                        Architecture::X86_64 => {\n                            // Use JMP instruction to absolute address: FF 25 followed by 32-bit offset\n                            // Then the 64-bit absolute address\n                            let mut code = vec![0xFF, 0x25, 0x00, 0x00, 0x00, 0x00]; // jmp [rip+0]\n                                                                                     // Append the 64-bit address\n                            code.extend_from_slice(&abs_addr.to_le_bytes());\n                            code\n                        }\n                        Architecture::X86_32(_) => {\n                            // For 32-bit Intel, use JMP instruction with absolute address\n                            let mut code = vec![0xE9]; // jmp rel32\n                            let rel_addr = abs_addr as i32 - 5; // Relative address (offset from next instruction)\n                            code.extend_from_slice(&rel_addr.to_le_bytes());\n                            code\n                        }\n                        Architecture::Aarch64(_) => {\n                            // For ARM64, we load the address into a register and branch\n                            let mut code = Vec::new();\n                            // LDR X16, [PC, #0]  ; Load from the next instruction\n                            code.extend_from_slice(&[0x50, 0x00, 0x00, 0x58]);\n                            // BR X16            ; Branch to the address in X16\n                            code.extend_from_slice(&[0x00, 0x02, 0x1F, 0xD6]);\n                            // Store the 64-bit address\n                            code.extend_from_slice(&abs_addr.to_le_bytes());\n                            code\n                        }\n                        Architecture::Arm(_) => {\n                            // For 32-bit ARM, use LDR PC, [PC, #-4] to load the address and branch\n                            let mut code = Vec::new();\n                            // LDR PC, [PC, #-4] ; Load the address into PC (branching to it)\n                            code.extend_from_slice(&[0x04, 0xF0, 0x1F, 0xE5]);\n                            // Store the 32-bit address\n                            code.extend_from_slice(&(abs_addr as u32).to_le_bytes());\n                            code\n                        }\n                        _ => return Err(PatchError::UnsupportedPlatform(triple.to_string())),\n                    },\n                };\n                let offset = obj.append_section_data(text_section, &jump_asm, 8);\n                obj.add_symbol(Symbol {\n                    name: name.as_bytes()[name_offset..].to_vec(),\n                    value: offset,\n                    size: jump_asm.len() as u64,\n                    scope: SymbolScope::Linkage,\n                    kind: SymbolKind::Text,\n                    weak: false,\n                    section: SymbolSection::Section(text_section),\n                    flags: SymbolFlags::None, // ignore for these stubs\n                });\n            }\n\n            // Rust code typically generates Tls accessors as functions (text), but they are referenced\n            // indirectly as data symbols. We end up handling this by adding the TLS symbol as a data\n            // symbol with the initializer as the address of the original tls initializer. That way\n            // if new TLS are added at runtime, they get initialized properly, but otherwise, the\n            // tls initialization check (cbz) properly skips re-initialization on patches.\n            //\n            // ```\n            // __ZN17crossbeam_channel5waker17current_thread_id9THREAD_ID29_$u7b$$u7b$constant$u7d$$u7d$28_$u7b$$u7b$closure$u7d$$u7d$17h33618d877d86bb77E:\n            //    stp     x20, x19, [sp, #-0x20]!\n            //    stp     x29, x30, [sp, #0x10]\n            //    add     x29, sp, #0x10\n            //    adrp    x19, 21603 ; 0x1054bd000\n            //    add     x19, x19, #0x998\n            //    ldr     x20, [x19]\n            //    mov     x0, x19\n            //    blr     x20\n            //    ldr     x8, [x0]\n            //    cbz     x8, 0x10005acc0\n            //    mov     x0, x19\n            //    blr     x20\n            //    ldp     x29, x30, [sp, #0x10]\n            //    ldp     x20, x19, [sp], #0x20\n            //    ret\n            //    mov     x0, x19\n            //    blr     x20\n            //    bl      __ZN3std3sys12thread_local6native4lazy20Storage$LT$T$C$D$GT$10initialize17h818476638edff4e6E\n            //    b       0x10005acac\n            // ```\n            SymbolKind::Tls => {\n                let tls_section = obj.section_id(StandardSection::Tls);\n\n                let pointer_width = match triple.pointer_width().unwrap() {\n                    PointerWidth::U16 => 2,\n                    PointerWidth::U32 => 4,\n                    PointerWidth::U64 => 8,\n                };\n\n                // Resolve the TLS init data offset and size.\n                //\n                // On ELF: sym.address IS the TLS offset and sym.size is the data size.\n                // On Mach-O: sym.address points to __thread_vars (TLV descriptor), NOT\n                // __thread_data. Mach-O nlist has no size field (always 0). We look up\n                // the corresponding $tlv$init symbol (LLVM convention) to get the real\n                // offset and size within __thread_data.\n                //\n                // Note: each patch gets its own TLS copy (not shared with the main exe).\n                // TLS variables reset to their initial value on patch.\n                // Use the full name (with Mach-O `_` prefix) since tls_init_sizes\n                // keys come from the same symbol table and include the prefix.\n                let init_key = format!(\"{}$tlv$init\", name);\n                let (tls_offset, size) =\n                    if let Some(&(offset, size)) = cache.tls_init_sizes.get(&init_key) {\n                        // macOS: found the $tlv$init symbol with correct offset and size\n                        (offset, size)\n                    } else if sym.size > 0 {\n                        // ELF: sym.address is the TLS offset, sym.size is the data size\n                        (sym.address, sym.size)\n                    } else if !cache.tls_init_sizes.is_empty() {\n                        // macOS fallback: $tlv$init not found but map isn't empty (binary\n                        // might be partially stripped). Use entire tdata as upper bound.\n                        (0, cache.tls_init_data.len() as u64)\n                    } else {\n                        // Last resort (ELF with size=0): use pointer width\n                        (sym.address, pointer_width)\n                    };\n\n                let align = size.min(pointer_width).next_power_of_two();\n\n                let start = tls_offset as usize;\n                let end = start + size as usize;\n                let init = if end <= cache.tls_init_data.len() {\n                    cache.tls_init_data[start..end].to_vec()\n                } else {\n                    // Beyond .tdata bounds (.tbss) or Mach-O fallback: zero-init\n                    vec![0u8; size as usize]\n                };\n\n                // Use add_symbol_data() so the object crate's Mach-O writer auto-creates\n                // __thread_vars TLV descriptors (via macho_add_thread_var). Without this,\n                // the symbol stays in __thread_data and the runtime misinterprets raw init\n                // bytes as a TLV descriptor — first 8 bytes become the thunk pointer.\n                let sym_id = obj.add_symbol(Symbol {\n                    name: name.as_bytes()[name_offset..].to_vec(),\n                    value: 0,\n                    size: 0,\n                    scope: SymbolScope::Linkage,\n                    kind: SymbolKind::Tls,\n                    weak: false,\n                    section: SymbolSection::Undefined,\n                    flags: SymbolFlags::None,\n                });\n                obj.add_symbol_data(sym_id, tls_section, &init, align);\n            }\n\n            // We just assume all non-text symbols are data (globals, statics, etc)\n            _ => {\n                // darwin statics show up as \"unknown\" symbols even though they are data symbols.\n                let kind = match sym.kind {\n                    SymbolKind::Unknown => SymbolKind::Data,\n                    k => k,\n                };\n\n                // plain linux *wants* these flags, but android doesn't.\n                // unsure what's going on here, but this is special cased for now.\n                // I think the more advanced linkers don't want these flags, but the default linux linker (ld) does.\n                let flags = match triple.environment {\n                    target_lexicon::Environment::Android => SymbolFlags::None,\n                    _ => sym.flags,\n                };\n\n                obj.add_symbol(Symbol {\n                    name: name.as_bytes()[name_offset..].to_vec(),\n                    value: abs_addr,\n                    size: 0,\n                    scope: SymbolScope::Linkage,\n                    kind,\n                    weak: sym.is_weak,\n                    section: SymbolSection::Absolute,\n                    flags,\n                });\n            }\n        }\n    }\n\n    Ok(obj.write()?)\n}\n\n/// Prepares the base module before running wasm-bindgen.\n///\n/// This tries to work around how wasm-bindgen works by intelligently promoting non-wasm-bindgen functions\n/// to the export table.\n///\n/// It also moves all functions and memories to be callable indirectly.\npub fn prepare_wasm_base_module(bytes: &[u8]) -> Result<Vec<u8>> {\n    let ParsedModule {\n        mut module,\n        ids,\n        symbols,\n        ..\n    } = parse_module_with_ids(bytes)?;\n\n    // Due to monomorphizations, functions will get merged and multiple names will point to the same function.\n    // Walrus loses this information, so we need to manually parse the names table to get the indices\n    // and names of these functions.\n    //\n    // Unfortunately, the indices it gives us ARE NOT VALID.\n    // We need to work around it by using the FunctionId from the module as a link between the merged function names.\n    let ifunc_map = collect_func_ifuncs(&module);\n    let ifuncs = module\n        .funcs\n        .par_iter()\n        .filter_map(|f| ifunc_map.get(f.name.as_deref()?).map(|_| f.id()))\n        .collect::<HashSet<_>>();\n\n    let imported_funcs = module\n        .imports\n        .iter()\n        .filter_map(|i| match i.kind {\n            ImportKind::Function(id) => Some((id, i.id())),\n            _ => None,\n        })\n        .collect::<HashMap<_, _>>();\n\n    let mut exported = HashSet::new();\n\n    // Wasm-bindgen will synthesize imports to satisfy its external calls. This facilitates things\n    // like inline-js, snippets, and literally the `#[wasm_bindgen]` macro. All calls to JS are\n    // just `extern \"wbg\"` blocks!\n    //\n    // However, wasm-bindgen will run a GC pass on the module, removing any unused imports.\n    let mut make_indirect = vec![];\n    for (imported_func, importid) in imported_funcs {\n        let import = module.imports.get(importid);\n        let name_is_wbg =\n            import.name.starts_with(\"__wbindgen\") || import.name.starts_with(\"__wbg_\");\n\n        if name_is_wbg && !name_is_bindgen_symbol(import.name.as_str()) {\n            let func = module.funcs.get(imported_func);\n\n            let ty = module.types.get(func.ty());\n            let params = ty.params().to_vec();\n            let results = ty.results().to_vec();\n\n            let mut builder = FunctionBuilder::new(&mut module.types, &params, &results);\n            let mut body = builder\n                .name(format!(\"__saved_wbg_{}\", import.name))\n                .func_body();\n\n            let locals = params\n                .iter()\n                .map(|ty| module.locals.add(*ty))\n                .collect::<Vec<_>>();\n\n            for l in locals.iter() {\n                body.local_get(*l);\n            }\n\n            body.call(imported_func);\n\n            let new_func_id = module.funcs.add_local(builder.local_func(locals));\n\n            let saved_name = format!(\"__saved_wbg_{}\", import.name);\n            if exported.insert(saved_name.clone()) {\n                module.exports.add(&saved_name, new_func_id);\n            }\n\n            make_indirect.push(new_func_id);\n        }\n    }\n\n    for (name, index) in symbols.code_symbol_map.iter() {\n        if name_is_bindgen_symbol(name) {\n            continue;\n        }\n\n        let func = module.funcs.get(ids[*index]);\n\n        // We want to preserve the intrinsics from getting gc-ed out.\n        //\n        // These will create corresponding shim functions in the main module, that the patches will\n        // then call. Wasm-bindgen doesn't actually check if anyone uses the `__wbindgen` exports and\n        // forcefully deletes them literally by checking for symbols that start with `__wbindgen`. We\n        // preserve these symbols by naming them `__saved_wbg_<name>` and then exporting them.\n        //\n        // When wasm-bindgen runs, it will wrap these intrinsics with an `externref shim`, but we\n        // want to preserve the actual underlying function so side modules can call them directly.\n        //\n        // https://github.com/rustwasm/wasm-bindgen/blob/c35cc9369d5e0dc418986f7811a0dd702fb33ef9/crates/cli-support/src/wit/mod.rs#L1505\n        if name.starts_with(\"__wbindgen\") {\n            let saved_name = format!(\"__saved_wbg_{}\", name);\n            if exported.insert(saved_name.clone()) {\n                module.exports.add(&saved_name, func.id());\n            }\n        }\n\n        // This is basically `--export-all` but designed to work around wasm-bindgen not properly gc-ing\n        // imports like __wbindgen_placeholder__ and __wbindgen_externref__\n        //\n        // We only export local functions, and then make sure they can be accessible indirectly.\n        // If we weren't dealing with PIC code, then we could just create local ifuncs in the patch that\n        // call the original function directly. Unfortunately, this would require adding a new relocation\n        // to corresponding GOT.func entry, which we don't want to deal with.\n        //\n        // Note that we don't export via the export table, but rather the ifunc table. This is to work\n        // around issues on large projects where we hit the maximum number of exports.\n        //\n        // https://github.com/emscripten-core/emscripten/issues/22863\n        if let FunctionKind::Local(_) = &func.kind {\n            if !ifuncs.contains(&func.id()) {\n                make_indirect.push(func.id());\n            }\n        }\n    }\n\n    // Now we need to make sure to add the new ifuncs to the ifunc segment initializer.\n    // We just assume the last segment is the safest one we can add to which is common practice.\n    let segment = module\n        .elements\n        .iter_mut()\n        .last()\n        .context(\"Missing ifunc table\")?;\n    let make_indirect_count = make_indirect.len() as u64;\n    let ElementItems::Functions(segment_ids) = &mut segment.items else {\n        return Err(PatchError::InvalidModule(\n            \"Expected ifunc table to be a function table\".into(),\n        ));\n    };\n\n    for func in make_indirect {\n        segment_ids.push(func);\n    }\n\n    if let ElementKind::Active { table, .. } = segment.kind {\n        let table = module.tables.get_mut(table);\n        table.initial += make_indirect_count;\n        if let Some(max) = table.maximum {\n            table.maximum = Some(max + make_indirect_count);\n        }\n    }\n\n    Ok(module.emit_wasm())\n}\n\n/// Check if the name is a wasm-bindgen symbol\n///\n/// todo(jon): I believe we can just look at all the functions the wasm_bindgen describe export references.\n/// this is kinda hacky on slow.\n///\n/// Uses the heuristics from the wasm-bindgen source code itself:\n///\n/// <https://github.com/rustwasm/wasm-bindgen/blob/c35cc9369d5e0dc418986f7811a0dd702fb33ef9/crates/cli-support/src/wit/mod.rs#L1165>\nfn name_is_bindgen_symbol(name: &str) -> bool {\n    name.contains(\"__wbindgen_describe\")\n        || name.contains(\"__wbindgen_externref\")\n        || name.contains(\"wasm_bindgen8describe6inform\")\n        || name.contains(\"wasm_bindgen..describe..WasmDescribe\")\n        || name.contains(\"wasm_bindgen..closure..WasmClosure$GT$8describe\")\n        || name.contains(\"wasm_bindgen7closure16Closure$LT$T$GT$4wrap8describe\")\n}\n\n/// Manually parse the data section from a wasm module\n///\n/// We need to do this for data symbols because walrus doesn't provide the right range and offset\n/// information for data segments. Fortunately, it provides it for code sections, so we only need to\n/// do a small amount extra of parsing here.\nfn parse_bytes_to_data_segment(bytes: &[u8]) -> Result<RawDataSection<'_>> {\n    let parser = wasmparser::Parser::new(0);\n    let mut parser = parser.parse_all(bytes);\n    let mut segments = vec![];\n    let mut data_range = 0..0;\n    let mut symbols = vec![];\n\n    // Process the payloads in the raw wasm file so we can extract the specific sections we need\n    while let Some(Ok(payload)) = parser.next() {\n        match payload {\n            Payload::DataSection(section) => {\n                data_range = section.range();\n                segments = section\n                    .into_iter()\n                    .collect::<Result<Vec<_>, BinaryReaderError>>()?\n            }\n            Payload::CustomSection(section) if section.name() == \"linking\" => {\n                let reader = BinaryReader::new(section.data(), 0);\n                let reader = LinkingSectionReader::new(reader)?;\n                for subsection in reader.subsections() {\n                    if let Linking::SymbolTable(map) = subsection? {\n                        symbols = map.into_iter().collect::<Result<Vec<_>, _>>()?;\n                    }\n                }\n            }\n            Payload::CustomSection(section) => {\n                tracing::trace!(\"Skipping Custom section: {:?}\", section.name());\n            }\n            _ => {}\n        }\n    }\n\n    // Accumulate the data symbols into a btreemap for later use\n    let mut data_symbols = BTreeMap::new();\n    let mut data_symbol_map = HashMap::new();\n    let mut code_symbol_map = BTreeMap::new();\n    for (index, symbol) in symbols.iter().enumerate() {\n        if let SymbolInfo::Func { name, index, .. } = symbol {\n            if let Some(name) = name {\n                code_symbol_map.insert(*name, *index as usize);\n            }\n            continue;\n        }\n\n        let SymbolInfo::Data {\n            symbol: Some(symbol),\n            name,\n            ..\n        } = symbol\n        else {\n            continue;\n        };\n\n        data_symbol_map.insert(*name, index);\n\n        let data_segment = segments\n            .get(symbol.index as usize)\n            .context(\"Failed to find data segment\")?;\n        let offset: usize =\n            data_segment.range.end - data_segment.data.len() + (symbol.offset as usize);\n        let range = offset..(offset + symbol.size as usize);\n\n        data_symbols.insert(\n            index,\n            DataSymbol {\n                _index: index,\n                _range: range,\n                segment_offset: symbol.offset as usize,\n                _symbol_size: symbol.size as usize,\n                which_data_segment: symbol.index as usize,\n            },\n        );\n    }\n\n    Ok(RawDataSection {\n        _data_range: data_range,\n        symbols,\n        data_symbols,\n        data_symbol_map,\n        code_symbol_map,\n    })\n}\n\nstruct RawDataSection<'a> {\n    _data_range: Range<usize>,\n    symbols: Vec<SymbolInfo<'a>>,\n    code_symbol_map: BTreeMap<&'a str, usize>,\n    data_symbols: BTreeMap<usize, DataSymbol>,\n    data_symbol_map: HashMap<&'a str, usize>,\n}\n\n#[derive(Debug)]\nstruct DataSymbol {\n    _index: usize,\n    _range: Range<usize>,\n    segment_offset: usize,\n    _symbol_size: usize,\n    which_data_segment: usize,\n}\n\nstruct ParsedModule<'a> {\n    module: Module,\n    ids: Vec<FunctionId>,\n    symbols: RawDataSection<'a>,\n}\n\n/// Parse a module and return the mapping of index to FunctionID.\n/// We'll use this mapping to remap ModuleIDs\nfn parse_module_with_ids(bindgened: &[u8]) -> Result<ParsedModule<'_>> {\n    let ids = Arc::new(RwLock::new(Vec::new()));\n    let ids_ = ids.clone();\n    let module = Module::from_buffer_with_config(\n        bindgened,\n        ModuleConfig::new().on_parse(move |_m, our_ids| {\n            let mut ids = ids_.write().expect(\"No shared writers\");\n            let mut idx = 0;\n            while let Ok(entry) = our_ids.get_func(idx) {\n                ids.push(entry);\n                idx += 1;\n            }\n\n            Ok(())\n        }),\n    )?;\n    let mut ids_ = ids.write().expect(\"No shared writers\");\n    let mut ids = vec![];\n    std::mem::swap(&mut ids, &mut *ids_);\n\n    let symbols = parse_bytes_to_data_segment(bindgened).context(\"Failed to parse data segment\")?;\n\n    Ok(ParsedModule {\n        module,\n        ids,\n        symbols,\n    })\n}\n\n/// Get the main sentinel symbol for the given target triple\n///\n/// We need to special case darwin since `main` is the entrypoint but `_main` is the actual symbol.\n/// The entrypoint ends up outside the text section, seemingly, and breaks our aslr detection.\nfn main_sentinel(triple: &Triple) -> &'static str {\n    match triple.operating_system {\n        // The symbol in the symtab is called \"_main\" but in the dysymtab it is called \"main\"\n        OperatingSystem::MacOSX(_) | OperatingSystem::Darwin(_) | OperatingSystem::IOS(_) => {\n            \"_main\"\n        }\n\n        _ => \"main\",\n    }\n}\n"
  },
  {
    "path": "packages/cli/src/build/pre_render.rs",
    "content": "use anyhow::Context;\nuse dioxus_cli_config::{server_ip, server_port};\nuse dioxus_dx_wire_format::BuildStage;\nuse futures_util::{stream::FuturesUnordered, StreamExt};\nuse std::{\n    net::{IpAddr, Ipv4Addr, SocketAddr},\n    time::Duration,\n};\nuse tokio::process::Command;\n\nuse crate::BuildId;\n\nuse super::{AppBuilder, BuilderUpdate};\n\n/// Pre-render the static routes, performing static-site generation\npub(crate) async fn pre_render_static_routes(\n    devserver_ip: Option<SocketAddr>,\n    builder: &mut AppBuilder,\n    updates: Option<&futures_channel::mpsc::UnboundedSender<BuilderUpdate>>,\n) -> anyhow::Result<()> {\n    if let Some(updates) = updates {\n        updates\n            .unbounded_send(BuilderUpdate::Progress {\n                stage: BuildStage::Prerendering,\n            })\n            .unwrap();\n    }\n    let server_exe = builder.build.main_exe();\n\n    // Use the address passed in through environment variables or default to localhost:9999. We need\n    // to default to a value that is different than the CLI default address to avoid conflicts\n    let ip = server_ip().unwrap_or_else(|| IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)));\n    let port = server_port().unwrap_or(9999);\n    let fullstack_address = SocketAddr::new(ip, port);\n    let address = fullstack_address.ip().to_string();\n    let port = fullstack_address.port().to_string();\n\n    // Borrow port and address so we can easily move them into multiple tasks below\n    let address = &address;\n    let port = &port;\n\n    tracing::info!(\"Running SSG at http://{address}:{port} for {server_exe:?}\");\n\n    let vars = builder.child_environment_variables(\n        devserver_ip,\n        Some(fullstack_address),\n        false,\n        BuildId::SECONDARY,\n    );\n    // Run the server executable\n    let _child = Command::new(&server_exe)\n        .envs(vars)\n        .current_dir(server_exe.parent().unwrap())\n        .stdout(std::process::Stdio::null())\n        .stderr(std::process::Stdio::null())\n        .kill_on_drop(true)\n        .spawn()?;\n\n    // Borrow reqwest_client so we only move the reference into the futures\n    let reqwest_client = reqwest::Client::new();\n    let reqwest_client = &reqwest_client;\n\n    // Get the routes from the `/static_routes` endpoint\n    let mut routes = None;\n\n    // The server may take a few seconds to start up. Try fetching the route up to 5 times with a one second delay\n    const RETRY_ATTEMPTS: usize = 5;\n    for i in 0..=RETRY_ATTEMPTS {\n        tracing::debug!(\n            \"Attempting to get static routes from server. Attempt {i} of {RETRY_ATTEMPTS}\"\n        );\n\n        let request = reqwest_client\n            .post(format!(\"http://{address}:{port}/api/static_routes\"))\n            .body(\"{}\".to_string())\n            .send()\n            .await;\n        match request {\n            Ok(request) => {\n                routes = Some(request\n                    .json::<Vec<String>>()\n                    .await\n                    .inspect(|text| tracing::debug!(\"Got static routes: {text:?}\"))\n                    .context(\"Failed to parse static routes from the server. Make sure your server function returns Vec<String> with the (default) json encoding\")?);\n                break;\n            }\n            Err(err) => {\n                // If the request fails, try  up to 5 times with a one second delay\n                // If it fails 5 times, return the error\n                if i == RETRY_ATTEMPTS {\n                    return Err(err).context(\"Failed to get static routes from server. Make sure you have a server function at the `/api/static_routes` endpoint that returns Vec<String> of static routes.\");\n                }\n                tokio::time::sleep(std::time::Duration::from_secs(1)).await;\n            }\n        }\n    }\n\n    let routes = routes.expect(\n        \"static routes should exist or an error should have been returned on the last attempt\",\n    );\n\n    // Create a pool of futures that cache each route\n    let mut resolved_routes = routes\n        .into_iter()\n        .map(|route| async move {\n            tracing::info!(\"Rendering {route} for SSG\");\n\n            // For each route, ping the server to force it to cache the response for ssg\n            let request = reqwest_client\n                .get(format!(\"http://{address}:{port}{route}\"))\n                .header(\"Accept\", \"text/html\")\n                .send()\n                .await?;\n\n            // If it takes longer than 30 seconds to resolve the route, log a warning\n            let warning_task = tokio::spawn({\n                let route = route.clone();\n                async move {\n                    tokio::time::sleep(Duration::from_secs(30)).await;\n                    tracing::warn!(\"Route {route} has been rendering for 30 seconds\");\n                }\n            });\n\n            // Wait for the streaming response to completely finish before continuing. We don't use the html it returns directly\n            // because it may contain artifacts of intermediate streaming steps while the page is loading. The SSG app should write\n            // the final clean HTML to the disk automatically after the request completes.\n            let _html = request.text().await?;\n\n            // Cancel the warning task if it hasn't already run\n            warning_task.abort();\n\n            Ok::<_, reqwest::Error>(route)\n        })\n        .collect::<FuturesUnordered<_>>();\n\n    while let Some(route) = resolved_routes.next().await {\n        match route {\n            Ok(route) => tracing::debug!(\"ssg success: {route:?}\"),\n            Err(err) => tracing::error!(\"ssg error: {err:?}\"),\n        }\n    }\n\n    tracing::info!(\"SSG complete\");\n\n    drop(_child);\n\n    Ok(())\n}\n"
  },
  {
    "path": "packages/cli/src/build/request.rs",
    "content": "//! # [`BuildRequest`] - the core of the build process\n//!\n//! The [`BuildRequest`] object is the core of the build process. It contains all the resolved arguments\n//! flowing in from the CLI, dioxus.toml, env vars, and the workspace.\n//!\n//! Every BuildRequest is tied to a given workspace and BuildArgs. For simplicity's sake, the BuildArgs\n//! struct is used to represent the CLI arguments and all other configuration is basically just\n//! extra CLI arguments, but in a configuration format.\n//!\n//! When [`BuildRequest::build`] is called, it will prepare its work directory in the target folder\n//! and then start running the build process. A [`BuildContext`] is required to customize this\n//! build process, containing a channel for progress updates and the build mode.\n//!\n//! The [`BuildMode`] is extremely important since it influences how the build is performed. Most\n//! \"normal\" builds just use [`BuildMode::Base`], but we also support [`BuildMode::Fat`] and\n//! [`BuildMode::Thin`]. These builds are used together to power the hot-patching and fast-linking\n//! engine.\n//! - BuildMode::Base: A normal build generated using `cargo rustc`\n//! - BuildMode::Fat: A \"fat\" build where all dependency rlibs are merged into a static library\n//! - BuildMode::Thin: A \"thin\" build that dynamically links against the artifacts produced by the \"fat\" build\n//!\n//! The BuildRequest is also responsible for writing the final build artifacts to disk. This includes\n//!\n//! - Writing the executable\n//! - Processing assets from the artifact\n//! - Writing any metadata or configuration files (Info.plist, AndroidManifest.xml)\n//! - Bundle splitting (for wasm) and wasm-bindgen\n//!\n//! In some cases, the BuildRequest also handles the linking of the final executable. Specifically,\n//! - For Android, we use `dx` as an opaque linker to dynamically find the true android linker\n//! - For hotpatching, the CLI manually links the final executable with a stub file\n//!\n//! ## Build formats:\n//!\n//! We support building for the most popular platforms:\n//! - Web via wasm-bindgen\n//! - macOS via app-bundle\n//! - iOS via app-bundle\n//! - Android via gradle\n//! - Linux via app-image\n//! - Windows via exe, msi/msix\n//!\n//! Note that we are missing some setups that we *should* support:\n//! - PWAs, WebWorkers, ServiceWorkers\n//! - Web Extensions\n//! - Linux via flatpak/snap\n//!\n//! There are some less popular formats that we might want to support eventually:\n//! - TVOS, watchOS\n//! - OpenHarmony\n//!\n//! Also, some deploy platforms have their own bespoke formats:\n//! - Cloudflare workers\n//! - AWS Lambda\n//!\n//! Currently, we defer most of our deploy-based bundling to Tauri bundle, though we should migrate\n//! to just bundling everything ourselves. This would require us to implement code-signing which\n//! is a bit of a pain, but fortunately a solved process (<https://github.com/rust-mobile/xbuild>).\n//!\n//! ## Build Structure\n//!\n//! Builds generally follow the same structure everywhere:\n//! - A main executable\n//! - Sidecars (alternate entrypoints, framewrok plugins, etc)\n//! - Assets (images, fonts, etc)\n//! - Metadata (Info.plist, AndroidManifest.xml)\n//! - Glue code (java, kotlin, javascript etc)\n//! - Entitlements for code-signing and verification\n//!\n//! We need to be careful to not try and put a \"round peg in a square hole,\" but most platforms follow\n//! the same pattern.\n//!\n//! As such, we try to assemble a build directory that's somewhat sensible:\n//! - A main \"staging\" dir for a given app\n//! - Per-profile dirs (debug/release)\n//! - A platform dir (ie web/desktop/android/ios)\n//! - The \"bundle\" dir which is basically the `.app` format or `wwww` dir.\n//! - The \"executable\" dir where the main exe is housed\n//! - The \"assets\" dir where the assets are housed\n//! - The \"meta\" dir where stuff like Info.plist, AndroidManifest.xml, etc are housed\n//!\n//! There's also some \"quirky\" folders that need to be stable between builds but don't influence the\n//! bundle itself:\n//! - session_cache_dir which stores stuff like window position\n//!\n//! ### Web:\n//!\n//! Create a folder that is somewhat similar to an app-image (exe + asset)\n//! The server is dropped into the `web` folder, even if there's no `public` folder.\n//! If there's no server (SPA), we still use the `web` folder, but it only contains the\n//! public folder.\n//!\n//! ```\n//! web/\n//!     server\n//!     assets/\n//!     public/\n//!         index.html\n//!         wasm/\n//!            app.wasm\n//!            glue.js\n//!            snippets/\n//!                ...\n//!         assets/\n//!            logo.png\n//! ```\n//!\n//! ### Linux:\n//!\n//! <https://docs.appimage.org/reference/appdir.html#ref-appdir>\n//! current_exe.join(\"Assets\")\n//! ```\n//! app.appimage/\n//!     AppRun\n//!     app.desktop\n//!     package.json\n//!     assets/\n//!         logo.png\n//! ```\n//!\n//! ### Macos\n//!\n//! We simply use the macos format where binaries are in `Contents/MacOS` and assets are in `Contents/Resources`\n//! We put assets in an assets dir such that it generally matches every other platform and we can\n//! output `/assets/blah` from manganis.\n//! ```\n//! App.app/\n//!     Contents/\n//!         Info.plist\n//!         MacOS/\n//!             Frameworks/\n//!         Resources/\n//!             assets/\n//!                 blah.icns\n//!                 blah.png\n//!         CodeResources\n//!         _CodeSignature/\n//! ```\n//!\n//! ### iOS\n//!\n//! Not the same as mac! ios apps are a bit \"flattened\" in comparison. simpler format, presumably\n//! since most ios apps don't ship frameworks/plugins and such.\n//!\n//! todo(jon): include the signing and entitlements in this format diagram.\n//! ```\n//! App.app/\n//!     main\n//!     assets/\n//! ```\n//!\n//! ### Android:\n//!\n//! Currently we need to generate a `src` type structure, not a pre-packaged apk structure, since\n//! we need to compile kotlin and java. This pushes us into using gradle and following a structure\n//! similar to that of cargo mobile2. Eventually I'd like to slim this down (drop buildSrc) and\n//! drive the kotlin build ourselves. This would let us drop gradle (yay! no plugins!) but requires\n//! us to manage dependencies (like kotlinc) ourselves (yuck!).\n//!\n//! <https://github.com/WanghongLin/miscellaneous/blob/master/tools/build-apk-manually.sh>\n//!\n//! Unfortunately, it seems that while we can drop the `android` build plugin, we still will need\n//! gradle since kotlin is basically gradle-only.\n//!\n//! Pre-build:\n//! ```\n//! app.apk/\n//!     .gradle\n//!     app/\n//!         src/\n//!             main/\n//!                 assets/\n//!                 jniLibs/\n//!                 java/\n//!                 kotlin/\n//!                 res/\n//!                 AndroidManifest.xml\n//!             build.gradle.kts\n//!             proguard-rules.pro\n//!         buildSrc/\n//!             build.gradle.kts\n//!             src/\n//!                 main/\n//!                     kotlin/\n//!                          BuildTask.kt\n//!     build.gradle.kts\n//!     gradle.properties\n//!     gradlew\n//!     gradlew.bat\n//!     settings.gradle\n//! ```\n//!\n//! Final build:\n//! ```\n//! app.apk/\n//!   AndroidManifest.xml\n//!   classes.dex\n//!   assets/\n//!       logo.png\n//!   lib/\n//!       armeabi-v7a/\n//!           libmyapp.so\n//!       arm64-v8a/\n//!           libmyapp.so\n//!       x86/\n//!           libmyapp.so\n//!       x86_64/\n//!           libmyapp.so\n//! ```\n//! Notice that we *could* feasibly build this ourselves :)\n//!\n//! ### Windows:\n//! <https://superuser.com/questions/749447/creating-a-single-file-executable-from-a-directory-in-windows>\n//! Windows does not provide an AppImage format, so instead we're going build the same folder\n//! structure as an AppImage, but when distributing, we'll create a .exe that embeds the resources\n//! as an embedded .zip file. When the app runs, it will implicitly unzip its resources into the\n//! Program Files folder. Any subsequent launches of the parent .exe will simply call the AppRun.exe\n//! entrypoint in the associated Program Files folder.\n//!\n//! This is, in essence, the same as an installer, so we might eventually just support something like msi/msix\n//! which functionally do the same thing but with a sleeker UI.\n//!\n//! This means no installers are required and we can bake an updater into the host exe.\n//!\n//! ## Handling asset lookups:\n//! current_exe.join(\"assets\")\n//! ```\n//! app.appimage/\n//!     main.exe\n//!     main.desktop\n//!     package.json\n//!     assets/\n//!         logo.png\n//! ```\n//!\n//! Since we support just a few locations, we could just search for the first that exists\n//! - usr\n//! - ../Resources\n//! - assets\n//! - Assets\n//! - $cwd/assets\n//!\n//! ```\n//! assets::root() ->\n//!     mac -> ../Resources/\n//!     ios -> ../Resources/\n//!     android -> assets/\n//!     server -> assets/\n//!     liveview -> assets/\n//!     web -> /assets/\n//! root().join(bundled)\n//! ```\n//!\n//! Every dioxus app can have an optional server executable which will influence the final bundle.\n//! This is built in parallel with the app executable during the `build` phase and the progres/status\n//! of the build is aggregated.\n//!\n//! The server will *always* be dropped into the `web` folder since it is considered \"web\" in nature,\n//! and will likely need to be combined with the public dir to be useful.\n//!\n//! We do our best to assemble read-to-go bundles here, such that the \"bundle\" step for each platform\n//! can just use the build dir\n//!\n//! When we write the AppBundle to a folder, it'll contain each bundle for each platform under the app's name:\n//! ```\n//! dog-app/\n//!   build/\n//!       web/\n//!         server.exe\n//!         assets/\n//!           some-secret-asset.txt (a server-side asset)\n//!         public/\n//!           index.html\n//!           assets/\n//!             logo.png\n//!       desktop/\n//!          App.app\n//!          App.appimage\n//!          App.exe\n//!          server/\n//!              server\n//!              assets/\n//!                some-secret-asset.txt (a server-side asset)\n//!       ios/\n//!          App.app\n//!          App.ipa\n//!       android/\n//!          App.apk\n//!   bundle/\n//!       build.json\n//!       Desktop.app\n//!       Mobile_x64.ipa\n//!       Mobile_arm64.ipa\n//!       Mobile_rosetta.ipa\n//!       web.appimage\n//!       web/\n//!         server.exe\n//!         assets/\n//!             some-secret-asset.txt\n//!         public/\n//!             index.html\n//!             assets/\n//!                 logo.png\n//!                 style.css\n//! ```\n//!\n//! When deploying, the build.json file will provide all the metadata that dx-deploy will use to\n//! push the app to stores, set up infra, manage versions, etc.\n//!\n//! The format of each build will follow the name plus some metadata such that when distributing you\n//! can easily trim off the metadata.\n//!\n//! The idea here is that we can run any of the programs in the same way that they're deployed.\n//!\n//! ## Bundle structure links\n//! - apple: <https://developer.apple.com/documentation/bundleresources/placing_content_in_a_bundle>\n//! - appimage: <https://docs.appimage.org/packaging-guide/manual.html#ref-manual>\n//!\n//! ## Extra links\n//! - xbuild: <https://github.com/rust-mobile/xbuild/blob/master/xbuild/src/command/build.rs>\n\nuse super::HotpatchModuleCache;\nuse crate::{\n    AndroidTools, AppManifest, BuildContext, BuildId, BundleFormat, DioxusConfig, Error,\n    LinkAction, LinkerFlavor, ObjectCache, Platform, Renderer, Result, RustcArgs, TargetArgs,\n    TraceSrc, WasmBindgen, WasmOptConfig, Workspace, DX_RUSTC_WRAPPER_ENV_VAR,\n};\nuse anyhow::{bail, Context};\nuse cargo_metadata::diagnostic::Diagnostic;\nuse cargo_toml::{Profile, Profiles, StripSetting};\nuse depinfo::RustcDepInfo;\nuse dioxus_cli_config::{format_base_path_meta_element, PRODUCT_NAME_ENV};\nuse dioxus_cli_config::{APP_TITLE_ENV, ASSET_ROOT_ENV};\nuse dioxus_cli_opt::{process_file_to, AssetManifest};\nuse itertools::Itertools;\nuse krates::{cm::TargetKind, NodeId};\nuse manganis::{AssetOptions, BundledAsset, SwiftPackageMetadata};\nuse manganis_core::{AndroidArtifactMetadata, AssetVariant};\nuse rayon::prelude::{IntoParallelRefIterator, ParallelIterator};\nuse serde::{Deserialize, Serialize};\nuse std::{borrow::Cow, ffi::OsString};\nuse std::{\n    collections::{BTreeMap, HashMap, HashSet},\n    io::Write,\n    path::{Path, PathBuf},\n    process::Stdio,\n    sync::{\n        atomic::{AtomicUsize, Ordering},\n        Arc,\n    },\n    time::{SystemTime, UNIX_EPOCH},\n};\nuse subsecond_types::JumpTable;\nuse target_lexicon::{Architecture, OperatingSystem, Triple};\nuse tempfile::TempDir;\nuse tokio::{io::AsyncBufReadExt, process::Command};\nuse uuid::Uuid;\n\n/// This struct is used to plan the build process.\n///\n/// The point here is to be able to take in the user's config from the CLI without modifying the\n/// arguments in place. Creating a buildplan \"resolves\" their config into a build plan that can be\n/// introspected. For example, the users might not specify a \"Triple\" in the CLI but the triple will\n/// be guaranteed to be resolved here.\n///\n/// Creating a buildplan also lets us introspect build requests and modularize our build process.\n/// This will, however, lead to duplicate fields between the CLI and the build engine. This is fine\n/// since we have the freedom to evolve the schema internally without breaking the API.\n///\n/// All updates from the build will be sent on a global \"BuildProgress\" channel.\n#[derive(Clone)]\npub(crate) struct BuildRequest {\n    pub(crate) workspace: Arc<Workspace>,\n    pub(crate) config: DioxusConfig,\n    pub(crate) crate_package: NodeId,\n    pub(crate) crate_target: krates::cm::Target,\n    pub(crate) profile: String,\n    pub(crate) release: bool,\n    pub(crate) bundle: BundleFormat,\n    pub(crate) triple: Triple,\n    pub(crate) device_name: Option<String>,\n    pub(crate) should_codesign: bool,\n    pub(crate) package: String,\n    pub(crate) main_target: String,\n    pub(crate) features: Vec<String>,\n    pub(crate) rustflags: cargo_config2::Flags,\n    pub(crate) extra_cargo_args: Vec<String>,\n    pub(crate) extra_rustc_args: Vec<String>,\n    pub(crate) no_default_features: bool,\n    pub(crate) all_features: bool,\n    pub(crate) target_dir: PathBuf,\n    pub(crate) skip_assets: bool,\n    pub(crate) wasm_split: bool,\n    pub(crate) debug_symbols: bool,\n    pub(crate) keep_names: bool,\n    pub(crate) inject_loading_scripts: bool,\n    pub(crate) custom_linker: Option<PathBuf>,\n    pub(crate) base_path: Option<String>,\n    pub(crate) using_dioxus_explicitly: bool,\n    pub(crate) apple_entitlements: Option<PathBuf>,\n    pub(crate) apple_team_id: Option<String>,\n    pub(crate) session_cache_dir: PathBuf,\n    pub(crate) raw_json_diagnostics: bool,\n    pub(crate) windows_subsystem: Option<String>,\n}\n\n/// dx can produce different \"modes\" of a build. A \"regular\" build is a \"base\" build. The Fat and Thin\n/// modes are used together to achieve binary patching and linking.\n///\n/// Guide:\n/// ----------\n/// - Base: A normal build generated using `cargo rustc`, intended for production use cases\n///\n/// - Fat: A \"fat\" build with -Wl,-all_load and no_dead_strip, keeping *every* symbol in the binary.\n///        Intended for development for larger up-front builds with faster link times and the ability\n///        to binary patch the final binary. On WASM, this also forces wasm-bindgen to generate all\n///        JS-WASM bindings, saving us the need to re-wasmbindgen the final binary.\n///\n/// - Thin: A \"thin\" build that dynamically links against the dependencies produced by the \"fat\" build.\n///         This is generated by calling rustc *directly* and might be more fragile to construct, but\n///         generates *much* faster than a regular base or fat build.\n#[allow(clippy::large_enum_variant)]\n#[derive(Clone, Debug, PartialEq)]\npub enum BuildMode {\n    /// A normal build generated using `cargo rustc`\n    ///\n    /// \"run\" indicates whether this build is intended to be run immediately after building.\n    /// This means we try to capture the build environment, saving vars like `CARGO_MANIFEST_DIR`\n    /// for the running executable.\n    Base { run: bool },\n\n    /// A \"Fat\" build generated with cargo rustc and dx as a custom linker without -Wl,-dead-strip\n    Fat,\n\n    /// A \"thin\" build generated with `rustc` directly and dx as a custom linker\n    Thin {\n        /// List of changed files causing this rebuild. Mostly used for diagnostics\n        changed_files: Vec<PathBuf>,\n\n        /// Which workspace crates had source file changes in this edit.\n        changed_crates: Vec<String>,\n\n        /// The ASLR slide of the running program, used to hardcode symbol jumps\n        aslr_reference: u64,\n\n        /// The captured RustcArgs for every crate in the workspace, collected by RUSTC_WORKSPACE_WRAPPER\n        /// This is used for replaying rustc invocations for workspace hotpatching\n        workspace_rustc_args: HashMap<String, RustcArgs>,\n\n        /// Cumulative set of all workspace crates modified since the fat build.\n        modified_crates: HashSet<String>,\n\n        /// Cache of compiled objects from previous thin builds, used by future re-linking\n        object_cache: ObjectCache,\n\n        /// Cache of initial binary parsing which speeds up stub creation\n        cache: Arc<HotpatchModuleCache>,\n    },\n}\n\n/// The end result of a build.\n///\n/// Contains the final asset manifest, the executable, and metadata about the build.\n/// Note that the `exe` might be stale and/or overwritten by the time you read it!\n///\n/// The patch cache is only populated on fat builds and then used for thin builds (see `BuildMode::Thin`).\n#[derive(Clone, Debug)]\npub struct BuildArtifacts {\n    pub(crate) root_dir: PathBuf,\n    pub(crate) exe: PathBuf,\n    pub(crate) workspace_rustc_args: HashMap<String, RustcArgs>,\n    pub(crate) time_start: SystemTime,\n    pub(crate) time_end: SystemTime,\n    pub(crate) assets: AssetManifest,\n    pub(crate) android_artifacts: Vec<AndroidArtifactMetadata>,\n    pub(crate) swift_sources: Vec<SwiftPackageMetadata>,\n    pub(crate) mode: BuildMode,\n    pub(crate) patch_cache: Option<Arc<HotpatchModuleCache>>,\n    pub(crate) depinfo: RustcDepInfo,\n    pub(crate) build_id: BuildId,\n    pub(crate) object_cache: ObjectCache,\n}\n\nimpl BuildRequest {\n    /// Create a new build request.\n    ///\n    /// This method consolidates various inputs into a single source of truth. It combines:\n    /// - Command-line arguments provided by the user.\n    /// - The crate's `Cargo.toml`.\n    /// - The `dioxus.toml` configuration file.\n    /// - User-specific CLI settings.\n    /// - The workspace metadata.\n    /// - Host-specific details (e.g., Android tools, installed frameworks).\n    /// - The intended target platform.\n    ///\n    /// Fields may be duplicated from the inputs to allow for autodetection and resolution.\n    ///\n    /// Autodetection is performed for unspecified fields where possible.\n    ///\n    /// Note: Build requests are typically created only when the CLI is invoked or when significant\n    /// changes are detected in the `Cargo.toml` (e.g., features added or removed).\n    pub(crate) async fn new(args: &TargetArgs, workspace: Arc<Workspace>) -> Result<Self> {\n        let crate_package = workspace.find_main_package(args.package.clone())?;\n\n        let target_kind = match args.example.is_some() {\n            true => TargetKind::Example,\n            false => TargetKind::Bin,\n        };\n\n        let main_package = &workspace.krates[crate_package];\n\n        let target_name = args\n            .example\n            .clone()\n            .or(args.bin.clone())\n            .or_else(|| {\n                if let Some(default_run) = &main_package.default_run {\n                    return Some(default_run.to_string());\n                }\n\n                let bin_count = main_package\n                    .targets\n                    .iter()\n                    .filter(|x| x.kind.contains(&target_kind))\n                    .count();\n\n                if bin_count != 1 {\n                    return None;\n                }\n\n                main_package.targets.iter().find_map(|x| {\n                    if x.kind.contains(&target_kind) {\n                        Some(x.name.clone())\n                    } else {\n                        None\n                    }\n                })\n            })\n            .unwrap_or(workspace.krates[crate_package].name.clone());\n\n        // Use the main_target for the client + server build if it is set, otherwise use the target name for this\n        // specific build. This is important for @client @server syntax so we use the client's output directory for the bundle.\n        let main_target = args.client_target.clone().unwrap_or(target_name.clone());\n\n        let crate_target = main_package\n            .targets\n            .iter()\n            .find(|target| {\n                target_name == target.name.as_str() && target.kind.contains(&target_kind)\n            })\n            .with_context(|| {\n                let target_of_kind = |kind|-> String {\n                    let filtered_packages = main_package\n                .targets\n                .iter()\n                .filter_map(|target| {\n                    target.kind.contains(kind).then_some(target.name.as_str())\n                }).collect::<Vec<_>>();\n                filtered_packages.join(\", \")};\n                if let Some(example) = &args.example {\n                    let examples = target_of_kind(&TargetKind::Example);\n                    format!(\"Failed to find example {example}. \\nAvailable examples are:\\n{examples}\")\n                } else if let Some(bin) = &args.bin {\n                    let binaries = target_of_kind(&TargetKind::Bin);\n                    format!(\"Failed to find binary {bin}. \\nAvailable binaries are:\\n{binaries}\")\n                } else {\n                    format!(\"Failed to find target {target_name}. \\nIt looks like you are trying to build dioxus in a library crate. \\\n                    You either need to run dx from inside a binary crate or build a specific example with the `--example` flag. \\\n                    Available examples are:\\n{}\", target_of_kind(&TargetKind::Example))\n                }\n            })?\n            .clone();\n\n        // Load config from Dioxus.toml and/or inline config in the target's source file.\n        // Inline config in doc comments takes precedence over Dioxus.toml.\n        let config = workspace\n            .load_dioxus_config(crate_package, Some(crate_target.src_path.as_std_path()))?\n            .unwrap_or_default();\n\n        // We usually use the simulator unless --device is passed *or* a device is detected by probing.\n        // For now, though, since we don't have probing, it just defaults to false\n        // Tools like xcrun/adb can detect devices\n        let device = args.device.clone();\n\n        let using_dioxus_explicitly = main_package\n            .dependencies\n            .iter()\n            .any(|dep| dep.name == \"dioxus\");\n\n        /*\n        Determine which features, triple, profile, etc to pass to the build.\n\n        Most of the time, users should use `dx serve --<platform>` where the platform name directly\n        corresponds to the feature in their cargo.toml. So,\n        - `dx serve --web` will enable the `web` feature\n        - `dx serve --mobile` will enable the `mobile` feature\n        - `dx serve --desktop` will enable the `desktop` feature\n\n        In this case, we set default-features to false and then add back the default features that\n        aren't renderers, and then add the feature for the given renderer (ie web/desktop/mobile).\n        We call this \"no-default-features-stripped.\"\n\n        There are a few cases where the user doesn't need to pass a platform.\n        - they selected one via `dioxus = { features = [\"web\"] }`\n        - they have a single platform in their default features `default = [\"web\"]`\n        - there is only a single non-server renderer as a feature `web = [\"dioxus/web\"], server = [\"dioxus/server\"]`\n        - they compose the super triple via triple + bundleformat + features\n\n        Note that we only use the names of the features to correspond with the platform.\n        Platforms are \"super triples\", meaning they contain information about\n        - bundle format\n        - target triple\n        - how to serve\n        - enabled features\n\n        By default, the --platform presets correspond to:\n        - web:          bundle(web), triple(wasm32), serve(http-serve), features(\"web\")\n        - desktop:      alias to mac/win/linux\n        - mac:          bundle(mac), triple(host), serve(appbundle-open), features(\"desktop\")\n        - windows:      bundle(exefolder), triple(host), serve(run-exe), features(\"desktop\")\n        - linux:        bundle(appimage), triple(host), serve(run-exe), features(\"desktop\")\n        - ios:          bundle(ios), triple(arm64-apple-ios), serve(ios-simulator/xcrun), features(\"mobile\")\n        - android:      bundle(android), triple(arm64-apple-ios), serve(android-emulator/adb), features(\"mobile\")\n        - server:       bundle(server), triple(host), serve(run-exe), features(\"server\") (and disables the client)\n        - liveview:     bundle(liveview), triple(host), serve(run-exe), features(\"liveview\")\n        - unknown:      <auto or default to desktop>\n\n        Fullstack usage is inferred from the presence of the fullstack feature or --fullstack.\n        */\n        let mut features = args.features.clone();\n        let no_default_features = args.no_default_features;\n        let all_features = args.all_features;\n        let mut triple = args.target.clone();\n        let mut renderer = args.renderer;\n        let mut bundle_format = args.bundle;\n        let mut platform = args.platform;\n\n        // the crate might be selecting renderers but the user also passes a renderer. this is weird\n        // ie dioxus = { features = [\"web\"] } but also --platform desktop\n        // anyways, we collect it here in the event we need it if platform is not specified.\n        let dioxus_direct_renderer = Self::renderer_enabled_by_dioxus_dependency(main_package);\n        let known_features_as_renderers = Self::features_that_enable_renderers(main_package);\n\n        // The crate might enable multiple platforms or no platforms at\n        // We collect all the platforms it enables first and then select based on the --platform arg\n        let enabled_renderers = if no_default_features {\n            vec![]\n        } else {\n            Self::enabled_cargo_toml_default_features_renderers(main_package)\n        };\n\n        // Try the easy autodetects.\n        // - if the user has `dioxus = { features = [\"web\"] }`\n        // - if the `default =[\"web\"]` or `default = [\"dioxus/web\"]`\n        // - if there's only one non-server platform ie `web = [\"dioxus/web\"], server = [\"dioxus/server\"]`\n        // Only do this if we're explicitly using dioxus\n        if matches!(platform, Platform::Unknown) && using_dioxus_explicitly {\n            let auto = dioxus_direct_renderer\n                .or_else(|| {\n                    if enabled_renderers.len() == 1 {\n                        Some(enabled_renderers[0].clone())\n                    } else {\n                        None\n                    }\n                })\n                .or_else(|| {\n                    // If multiple renderers are enabled, pick the first non-server one\n                    if enabled_renderers.len() == 2\n                        && enabled_renderers\n                            .iter()\n                            .any(|f| matches!(f.0, Renderer::Server))\n                    {\n                        return Some(\n                            enabled_renderers\n                                .iter()\n                                .find(|f| !matches!(f.0, Renderer::Server))\n                                .cloned()\n                                .unwrap(),\n                        );\n                    }\n                    None\n                })\n                .or_else(|| {\n                    // Pick the first non-server feature in the cargo.toml\n                    let non_server_features = known_features_as_renderers\n                        .iter()\n                        .filter(|f| f.1.as_str() != \"server\")\n                        .collect::<Vec<_>>();\n                    if non_server_features.len() == 1 {\n                        Some(non_server_features[0].clone())\n                    } else {\n                        None\n                    }\n                });\n\n            if let Some((direct, feature)) = auto {\n                match direct {\n                    _ if feature == \"mobile\" || feature == \"dioxus/mobile\" => {\n                        bail!(\n                            \"Could not autodetect mobile platform. Use --ios or --android instead.\"\n                        );\n                    }\n                    Renderer::Webview | Renderer::Native => {\n                        if cfg!(target_os = \"macos\") {\n                            platform = Platform::MacOS;\n                        } else if cfg!(target_os = \"linux\") {\n                            platform = Platform::Linux;\n                        } else if cfg!(target_os = \"windows\") {\n                            platform = Platform::Windows;\n                        }\n                    }\n                    Renderer::Server => platform = Platform::Server,\n                    Renderer::Liveview => platform = Platform::Liveview,\n                    Renderer::Web => platform = Platform::Web,\n                }\n                renderer = renderer.or(Some(direct));\n            }\n        }\n\n        // Set the super triple from the platform if it's provided.\n        // Otherwise, we attempt to guess it from the rest of their inputs.\n        match platform {\n            Platform::Unknown => {}\n\n            Platform::Web => {\n                if main_package.features.contains_key(\"web\") && renderer.is_none() {\n                    features.push(\"web\".into());\n                }\n                renderer = renderer.or(Some(Renderer::Web));\n                bundle_format = bundle_format.or(Some(BundleFormat::Web));\n                triple = triple.or(Some(\"wasm32-unknown-unknown\".parse()?));\n            }\n            Platform::MacOS => {\n                if main_package.features.contains_key(\"desktop\") && renderer.is_none() {\n                    features.push(\"desktop\".into());\n                }\n                renderer = renderer.or(Some(Renderer::Webview));\n                bundle_format = bundle_format.or(Some(BundleFormat::MacOS));\n                triple = triple.or(Some(Triple::host()));\n            }\n            Platform::Windows => {\n                if main_package.features.contains_key(\"desktop\") && renderer.is_none() {\n                    features.push(\"desktop\".into());\n                }\n                renderer = renderer.or(Some(Renderer::Webview));\n                bundle_format = bundle_format.or(Some(BundleFormat::Windows));\n                triple = triple.or(Some(Triple::host()));\n            }\n            Platform::Linux => {\n                if main_package.features.contains_key(\"desktop\") && renderer.is_none() {\n                    features.push(\"desktop\".into());\n                }\n                renderer = renderer.or(Some(Renderer::Webview));\n                bundle_format = bundle_format.or(Some(BundleFormat::Linux));\n                triple = triple.or(Some(Triple::host()));\n            }\n            Platform::Ios => {\n                if main_package.features.contains_key(\"mobile\") && renderer.is_none() {\n                    features.push(\"mobile\".into());\n                }\n                renderer = renderer.or(Some(Renderer::Webview));\n                bundle_format = bundle_format.or(Some(BundleFormat::Ios));\n                match device.is_some() {\n                    // If targeting device, we want to build for the device which is always aarch64\n                    true => triple = triple.or(Some(\"aarch64-apple-ios\".parse()?)),\n\n                    // If the host is aarch64, we assume the user wants to build for iOS simulator\n                    false if matches!(Triple::host().architecture, Architecture::Aarch64(_)) => {\n                        triple = triple.or(Some(\"aarch64-apple-ios-sim\".parse()?))\n                    }\n\n                    // Otherwise, it's the x86_64 simulator, which is just x86_64-apple-ios\n                    _ => triple = triple.or(Some(\"x86_64-apple-ios\".parse()?)),\n                }\n            }\n            Platform::Android => {\n                if main_package.features.contains_key(\"mobile\") && renderer.is_none() {\n                    features.push(\"mobile\".into());\n                }\n\n                renderer = renderer.or(Some(Renderer::Webview));\n                bundle_format = bundle_format.or(Some(BundleFormat::Android));\n\n                // maybe probe adb?\n                if let Some(_device_name) = device.as_ref() {\n                    if triple.is_none() {\n                        triple = Some(\n                            crate::get_android_tools()\n                                .context(\"Failed to get android tools\")?\n                                .autodetect_android_device_triple()\n                                .await,\n                        );\n                    }\n                } else {\n                    triple = triple.or(Some({\n                        match Triple::host().architecture {\n                            Architecture::X86_32(_) => \"i686-linux-android\".parse()?,\n                            Architecture::X86_64 => \"x86_64-linux-android\".parse()?,\n                            Architecture::Aarch64(_) => \"aarch64-linux-android\".parse()?,\n                            _ => \"aarch64-linux-android\".parse()?,\n                        }\n                    }));\n                }\n            }\n            Platform::Server => {\n                if main_package.features.contains_key(\"server\") && renderer.is_none() {\n                    features.push(\"server\".into());\n                }\n                renderer = renderer.or(Some(Renderer::Server));\n                bundle_format = bundle_format.or(Some(BundleFormat::Server));\n                triple = triple.or(Some(Triple::host()));\n            }\n            Platform::Liveview => {\n                if main_package.features.contains_key(\"liveview\") && renderer.is_none() {\n                    features.push(\"liveview\".into());\n                }\n                renderer = renderer.or(Some(Renderer::Liveview));\n                bundle_format = bundle_format.or(Some(BundleFormat::Server));\n                triple = triple.or(Some(Triple::host()));\n            }\n        }\n\n        // If default features are enabled, we need to add the default features\n        // which don't enable a renderer\n        if !no_default_features {\n            features.extend(Self::rendererless_features(main_package));\n            features.dedup();\n            features.sort();\n        }\n\n        // The triple will be the triple passed or the host if using dioxus.\n        let triple = if using_dioxus_explicitly {\n            triple.context(\"Could not automatically detect target triple\")?\n        } else {\n            triple.unwrap_or(Triple::host())\n        };\n\n        // The bundle format will be the bundle format passed or the host.\n        let bundle = if using_dioxus_explicitly {\n            bundle_format.context(\"Could not automatically detect bundle format\")?\n        } else {\n            bundle_format.unwrap_or(BundleFormat::host())\n        };\n\n        // Add any features required to turn on the client\n        if let Some(renderer) = renderer {\n            if let Some(feature) =\n                Self::feature_for_platform_and_renderer(main_package, &triple, renderer)\n            {\n                features.push(feature);\n                features.dedup();\n            }\n        }\n\n        // Set the profile of the build if it's not already set\n        // This is mostly used for isolation of builds (preventing thrashing) but also useful to have multiple performance profiles\n        // We might want to move some of these profiles into dioxus.toml and make them \"virtual\".\n        let profile = match args.profile.clone() {\n            Some(profile) => profile,\n            None => bundle.profile_name(args.release),\n        };\n\n        // Determine if we should codesign\n        let should_codesign =\n            args.codesign || device.is_some() || args.apple_entitlements.is_some();\n\n        // Determining release mode is based on the profile, actually, so we need to check that\n        let release = workspace.is_release_profile(&profile);\n\n        // Determine the --package we'll pass to cargo.\n        // todo: I think this might be wrong - we don't want to use main_package necessarily...\n        let package = args\n            .package\n            .clone()\n            .unwrap_or_else(|| main_package.name.clone());\n\n        // Somethings we override are also present in the user's config.\n        // If we can't get them by introspecting cargo, then we need to get them from the config\n        //\n        // This involves specifically two fields:\n        // - The linker since we override it for Android and hotpatching\n        // - RUSTFLAGS since we also override it for Android and hotpatching\n        let cargo_config = cargo_config2::Config::load().unwrap();\n        let mut custom_linker = cargo_config.linker(triple.to_string()).ok().flatten();\n        let mut rustflags = cargo_config2::Flags::default();\n\n        // Make sure to take into account the RUSTFLAGS env var and the CARGO_TARGET_<triple>_RUSTFLAGS\n        for env in [\n            \"RUSTFLAGS\".to_string(),\n            format!(\"CARGO_TARGET_{triple}_RUSTFLAGS\"),\n        ] {\n            if let Ok(flags) = std::env::var(env) {\n                rustflags\n                    .flags\n                    .extend(cargo_config2::Flags::from_space_separated(&flags).flags);\n            }\n        }\n\n        // Use the user's linker if the specify it at the target level\n        if let Ok(target) = cargo_config.target(triple.to_string()) {\n            if let Some(flags) = target.rustflags {\n                rustflags.flags.extend(flags.flags);\n            }\n        }\n\n        // When we do android builds we need to make sure we link against the android libraries\n        // We also `--export-dynamic` to make sure we can do shenanigans like `dlsym` the `main` symbol\n        if matches!(bundle, BundleFormat::Android) {\n            rustflags.flags.extend([\n                \"-Clink-arg=-landroid\".to_string(),\n                \"-Clink-arg=-llog\".to_string(),\n                \"-Clink-arg=-lOpenSLES\".to_string(),\n                \"-Clink-arg=-lc++abi\".to_string(),\n                \"-Clink-arg=-Wl,--export-dynamic\".to_string(),\n                format!(\n                    \"-Clink-arg=-Wl,--sysroot={}\",\n                    workspace.android_tools()?.sysroot().display()\n                ),\n            ]);\n        }\n\n        // Make sure we set the sysroot for ios builds in the event the user doesn't have it set\n        if matches!(bundle, BundleFormat::Ios)\n            && matches!(\n                triple.operating_system,\n                target_lexicon::OperatingSystem::IOS(_)\n            )\n        {\n            let xcode_path = Workspace::get_xcode_path()\n                .await\n                .unwrap_or_else(|| \"/Applications/Xcode.app\".to_string().into());\n\n            let sysroot_location = match triple.environment {\n                target_lexicon::Environment::Sim => xcode_path\n                    .join(\"Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk\"),\n                _ => xcode_path.join(\"Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk\"),\n            };\n\n            if sysroot_location.exists() && !rustflags.flags.iter().any(|f| f == \"-isysroot\") {\n                rustflags.flags.extend([\n                    \"-Clink-arg=-isysroot\".to_string(),\n                    format!(\"-Clink-arg={}\", sysroot_location.display()),\n                ]);\n            }\n        }\n\n        // automatically set the getrandom backend for web builds if the user requested it\n        if matches!(bundle, BundleFormat::Web) && args.wasm_js_cfg {\n            rustflags.flags.extend(\n                cargo_config2::Flags::from_space_separated(r#\"--cfg getrandom_backend=\"wasm_js\"\"#)\n                    .flags,\n            );\n        }\n\n        // If no custom linker is set, then android falls back to us as the linker\n        if custom_linker.is_none() && bundle == BundleFormat::Android {\n            let min_sdk_version = config.application.android_min_sdk_version.unwrap_or(28);\n            custom_linker = Some(\n                workspace\n                    .android_tools()?\n                    .android_cc(&triple, min_sdk_version),\n            );\n        }\n\n        let target_dir = std::env::var(\"CARGO_TARGET_DIR\")\n            .ok()\n            .map(PathBuf::from)\n            .or_else(|| cargo_config.build.target_dir.clone())\n            .unwrap_or_else(|| workspace.workspace_root().join(\"target\"));\n\n        // If the user provided a profile and wasm_split is enabled, we should check that LTO=true and debug=true\n        if args.wasm_split {\n            if let Some(profile_data) = workspace.cargo_toml.profile.custom.get(&profile) {\n                use cargo_toml::{DebugSetting, LtoSetting};\n                if matches!(profile_data.lto, Some(LtoSetting::None) | None) {\n                    tracing::warn!(\"wasm-split requires LTO to be enabled in the profile. \\\n                        Please set `lto = true` in the `[profile.{profile}]` section of your Cargo.toml\");\n                }\n                if matches!(profile_data.debug, Some(DebugSetting::None) | None) {\n                    tracing::warn!(\"wasm-split requires debug symbols to be enabled in the profile. \\\n                        Please set `debug = true` in the `[profile.{profile}]` section of your Cargo.toml\");\n                }\n            }\n        }\n\n        #[allow(deprecated)]\n        let session_cache_dir = args\n            .session_cache_dir\n            .clone()\n            .unwrap_or_else(|| TempDir::new().unwrap().into_path());\n\n        let extra_rustc_args = shell_words::split(&args.rustc_args.clone().unwrap_or_default())\n            .context(\"Failed to parse rustc args\")?;\n\n        let extra_cargo_args = shell_words::split(&args.cargo_args.clone().unwrap_or_default())\n            .context(\"Failed to parse cargo args\")?;\n\n        tracing::debug!(\n            r#\"Target Info:\n                • features: {features:?}\n                • triple: {triple}\n                • bundle format: {bundle:?}\n                • session cache dir: {session_cache_dir:?}\n                • linker: {custom_linker:?}\n                • target_dir: {target_dir:?}\"#,\n        );\n\n        Ok(Self {\n            features,\n            bundle,\n            // We hardcode passing `--no-default-features` to Cargo because dx manually enables\n            // the default features we want.\n            no_default_features: true,\n            all_features,\n            crate_package,\n            crate_target,\n            profile,\n            triple,\n            device_name: device,\n            workspace,\n            config,\n            target_dir,\n            custom_linker,\n            extra_rustc_args,\n            extra_cargo_args,\n            release,\n            package,\n            main_target,\n            rustflags,\n            using_dioxus_explicitly,\n            should_codesign,\n            session_cache_dir,\n            skip_assets: args.skip_assets,\n            base_path: args.base_path.clone(),\n            wasm_split: args.wasm_split,\n            debug_symbols: args.debug_symbols,\n            keep_names: args.keep_names,\n            inject_loading_scripts: args.inject_loading_scripts,\n            apple_entitlements: args.apple_entitlements.clone(),\n            apple_team_id: args.apple_team_id.clone(),\n            raw_json_diagnostics: args.raw_json_diagnostics,\n            windows_subsystem: args.windows_subsystem.clone(),\n        })\n    }\n\n    pub(crate) async fn prebuild(&self, ctx: &BuildContext) -> Result<()> {\n        // Create the session cache directory\n        let cache_dir = self.session_cache_dir();\n        _ = std::fs::create_dir_all(&cache_dir);\n        _ = std::fs::create_dir_all(self.rustc_wrapper_args_dir());\n        _ = std::fs::File::create_new(self.link_err_file());\n        _ = std::fs::File::create_new(self.link_args_file());\n        _ = std::fs::File::create_new(self.windows_command_file());\n\n        if !matches!(ctx.mode, BuildMode::Thin { .. }) {\n            self.prepare_build_dir(ctx)?;\n        }\n\n        if !ctx.is_primary_build() {\n            return Ok(());\n        }\n\n        // Run the tailwind build before bundling anything else\n        _ = crate::TailwindCli::run_once(\n            self.package_manifest_dir(),\n            self.config.application.tailwind_input.clone(),\n            self.config.application.tailwind_output.clone(),\n        )\n        .await;\n\n        // We want to copy over the prebuilt OpenSSL binaries to ~/.dx/prebuilt/openssl-<version>\n        if self.bundle == BundleFormat::Android {\n            AndroidTools::unpack_prebuilt_openssl()?;\n        }\n\n        Ok(())\n    }\n\n    pub(crate) async fn build(&self, ctx: &BuildContext) -> Result<BuildArtifacts> {\n        let time_start = SystemTime::now();\n\n        // If we forget to do this, then we won't get the linker args since rust skips the full build\n        // We need to make sure to not react to this though, so the filemap must cache it\n        _ = self.bust_fingerprint(ctx);\n\n        // Run the cargo build to produce our artifacts.\n        // For thin builds this also pre-compiles workspace dep crates before the tip.\n        let mut artifacts = self.cargo_build(ctx).await?;\n\n        // Write the build artifacts to the bundle on the disk\n        match &ctx.mode {\n            BuildMode::Thin {\n                aslr_reference,\n                cache,\n                modified_crates,\n                ..\n            } => {\n                self.write_patch(ctx, *aslr_reference, &mut artifacts, cache, modified_crates)\n                    .await?;\n            }\n\n            BuildMode::Base { .. } | BuildMode::Fat => {\n                ctx.status_start_bundle();\n\n                self.strip_binary(&artifacts).await?;\n\n                self.write_executable(ctx, &artifacts.exe, &mut artifacts.assets)\n                    .await\n                    .context(\"Failed to write executable\")?;\n\n                self.write_frameworks(ctx, &artifacts)\n                    .await\n                    .context(\"Failed to write frameworks\")?;\n                self.write_assets(ctx, &artifacts.assets)\n                    .await\n                    .context(\"Failed to write assets\")?;\n                self.write_metadata()\n                    .await\n                    .context(\"Failed to write metadata\")?;\n\n                // Install prebuilt Android plugin artifacts (AARs + Gradle deps)\n                if self.bundle == BundleFormat::Android && !artifacts.android_artifacts.is_empty() {\n                    let names: Vec<_> = artifacts\n                        .android_artifacts\n                        .iter()\n                        .map(|a| a.plugin_name.as_str().to_string())\n                        .collect();\n                    ctx.status_compiling_native_plugins(format!(\n                        \"Kotlin build: {}\",\n                        names.join(\", \")\n                    ));\n                    self.install_android_artifacts(&artifacts.android_artifacts)\n                        .context(\"Failed to install Android plugin artifacts\")?;\n                }\n\n                if matches!(self.bundle, BundleFormat::Ios | BundleFormat::MacOS)\n                    && !artifacts.swift_sources.is_empty()\n                {\n                    let names: Vec<_> = artifacts\n                        .swift_sources\n                        .iter()\n                        .map(|s| s.plugin_name.as_str().to_string())\n                        .collect();\n                    ctx.status_compiling_native_plugins(format!(\n                        \"Swift build: {}\",\n                        names.join(\", \")\n                    ));\n\n                    // Compile Swift packages from source\n                    self.compile_swift_sources(&artifacts.swift_sources)\n                        .await\n                        .context(\"Failed to compile Swift packages\")?;\n\n                    // Then embed Swift standard libraries\n                    self.embed_swift_stdlibs(&artifacts.swift_sources)\n                        .await\n                        .context(\"Failed to embed Swift standard libraries\")?;\n                }\n\n                // Compile and install Apple Widget Extensions from Dioxus.toml config\n                if matches!(self.bundle, BundleFormat::Ios | BundleFormat::MacOS)\n                    && !self.config.ios.widget_extensions.is_empty()\n                {\n                    let names: Vec<_> = self\n                        .config\n                        .ios\n                        .widget_extensions\n                        .iter()\n                        .map(|w| w.display_name.clone())\n                        .collect();\n                    ctx.status_compiling_native_plugins(format!(\n                        \"Widget build: {}\",\n                        names.join(\", \")\n                    ));\n                    self.compile_widget_extensions()\n                        .await\n                        .context(\"Failed to compile widget extensions\")?;\n                }\n\n                self.optimize(ctx)\n                    .await\n                    .context(\"Failed to optimize build\")?;\n\n                self.assemble(ctx)\n                    .await\n                    .context(\"Failed to assemble build\")?;\n\n                // Populate the patch cache if we're in fat mode\n                if matches!(ctx.mode, BuildMode::Fat) {\n                    artifacts.patch_cache =\n                        Some(Arc::new(self.create_patch_cache(&artifacts.exe)?));\n                }\n\n                tracing::debug!(\"Bundle created at {}\", self.root_dir().display());\n            }\n        }\n\n        // Record the build duration as a telemetry event\n        self.record_build_duration(time_start, ctx);\n\n        Ok(artifacts)\n    }\n    /// Run the cargo build by assembling the build command and executing it.\n    ///\n    /// This method needs to be very careful with processing output since errors being swallowed will\n    /// be very confusing to the user.\n    async fn cargo_build(&self, ctx: &BuildContext) -> Result<BuildArtifacts> {\n        let time_start = SystemTime::now();\n\n        // For thin builds, compile workspace dep crates before the tip.\n        // This updates dep rlibs on disk so cargo links the tip against fresh code.\n        let object_cache = self.compile_workspace_deps(ctx).await?;\n\n        // Extract the unit count of the crate graph so build_cargo has more accurate data\n        // \"Thin\" builds only build the final exe, so we only need to build one crate\n        let crate_count = match ctx.mode {\n            BuildMode::Thin { .. } => 1,\n            _ => self.get_unit_count_estimate(&ctx.mode).await,\n        };\n\n        // Update the status to show that we're starting the build and how many crates we expect to build\n        ctx.status_starting_build(crate_count);\n\n        let mut cmd = self.build_command(&ctx.mode)?;\n        tracing::debug!(dx_src = ?TraceSrc::Build, \"Executing cargo for {} using {}\", self.bundle, self.triple);\n\n        let mut child = cmd\n            .stdout(Stdio::piped())\n            .stderr(Stdio::piped())\n            .spawn()\n            .context(\"Failed to spawn cargo build\")?;\n\n        let stdout = tokio::io::BufReader::new(child.stdout.take().unwrap());\n        let stderr = tokio::io::BufReader::new(child.stderr.take().unwrap());\n        let mut output_location: Option<PathBuf> = None;\n        let mut stdout = stdout.lines();\n        let mut stderr = stderr.lines();\n        let mut units_compiled = 0;\n        let mut emitting_error = false;\n\n        loop {\n            use cargo_metadata::Message;\n\n            let line = tokio::select! {\n                Ok(Some(line)) = stdout.next_line() => line,\n                Ok(Some(line)) = stderr.next_line() => line,\n                else => break,\n            };\n\n            // If raw JSON diagnostics are requested, relay the line directly\n            if self.raw_json_diagnostics {\n                println!(\"{}\", line);\n            }\n\n            let Some(Ok(message)) = Message::parse_stream(std::io::Cursor::new(line)).next() else {\n                continue;\n            };\n\n            match message {\n                Message::BuildScriptExecuted(_) => units_compiled += 1,\n                Message::CompilerMessage(msg) => ctx.status_build_diagnostic(msg.message),\n                Message::TextLine(line) => {\n                    // Handle the case where we're getting lines directly from rustc.\n                    // These are in a different format than the normal cargo output, though I imagine\n                    // this parsing code is quite fragile/sensitive to changes in cargo, cargo_metadata, rustc, etc.\n                    #[derive(Deserialize)]\n                    struct RustcArtifact {\n                        artifact: PathBuf,\n                        emit: String,\n                    }\n\n                    // These outputs look something like:\n                    //\n                    // { \"artifact\":\"target/debug/deps/libdioxus_core-4f2a0b3c1e5f8b7c.rlib\", \"emit\":\"link\" }\n                    //\n                    // There are other outputs like depinfo that we might be interested in the future.\n                    if let Ok(artifact) = serde_json::from_str::<RustcArtifact>(&line) {\n                        if artifact.emit == \"link\" {\n                            output_location = Some(artifact.artifact);\n                        }\n                    }\n\n                    // Handle direct rustc diagnostics\n                    if let Ok(diag) = serde_json::from_str::<Diagnostic>(&line) {\n                        ctx.status_build_diagnostic(diag);\n                    }\n\n                    // For whatever reason, if there's an error while building, we still receive the TextLine\n                    // instead of an \"error\" message. However, the following messages *also* tend to\n                    // be the error message, and don't start with \"error:\". So we'll check if we've already\n                    // emitted an error message and if so, we'll emit all following messages as errors too.\n                    //\n                    // todo: This can lead to some really ugly output though, so we might want to look\n                    // into a more reliable way to detect errors propagating out of the compiler. If\n                    // we always wrapped rustc, then we could store this data somewhere in a much more\n                    // reliable format.\n                    if line.trim_start().starts_with(\"error:\") {\n                        emitting_error = true;\n                    }\n\n                    // Note that previous text lines might have set emitting_error to true\n                    match emitting_error {\n                        true => ctx.status_build_error(line),\n                        false => ctx.status_build_message(line),\n                    }\n                }\n                Message::CompilerArtifact(artifact) => {\n                    units_compiled += 1;\n                    ctx.status_build_progress(units_compiled, crate_count, artifact.target.name);\n                    output_location = artifact.executable.map(Into::into);\n                }\n                // todo: this can occasionally swallow errors, so we should figure out what exactly is going wrong\n                //       since that is a really bad user experience.\n                Message::BuildFinished(finished) => {\n                    if !finished.success {\n                        bail!(\n                            \"cargo build finished with errors for target: {} [{}]\",\n                            self.main_target,\n                            self.triple\n                        );\n                    }\n                }\n                _ => {}\n            }\n        }\n\n        // Load per-crate rustc args from the wrapper directory.\n        // Each workspace crate compiled through the wrapper has its own JSON file:\n        // \"{crate_name}.lib.json\" (key: \"{crate_name}.lib\") for lib targets and\n        // \"{crate_name}.bin.json\" (key: \"{crate_name}.bin\") for bin targets.\n        let mut workspace_rustc_args = HashMap::new();\n        let args_dir = self.rustc_wrapper_args_dir();\n        if let Ok(entries) = std::fs::read_dir(&args_dir) {\n            for entry in entries.flatten() {\n                let path = entry.path();\n                if path.extension().is_some_and(|e| e == \"json\") {\n                    if let Ok(contents) = std::fs::read_to_string(&path) {\n                        if let Ok(args) = serde_json::from_str::<RustcArgs>(&contents) {\n                            if let Some(stem) = path.file_stem().and_then(|s| s.to_str()) {\n                                workspace_rustc_args.insert(stem.to_string(), args);\n                            }\n                        }\n                    }\n                }\n            }\n        }\n\n        tracing::trace!(\n            \"Loaded workspace rustc args from {}: keys={:?}\",\n            args_dir.display(),\n            workspace_rustc_args.keys().collect::<Vec<_>>(),\n        );\n\n        // If there's any warnings from the linker, we should print them out\n        if let Ok(linker_warnings) = std::fs::read_to_string(self.link_err_file()) {\n            if !linker_warnings.is_empty() {\n                if output_location.is_none() {\n                    tracing::error!(\"Linker warnings: {}\", linker_warnings);\n                } else {\n                    tracing::debug!(\"Linker warnings: {}\", linker_warnings);\n                }\n            }\n        }\n\n        // Collect the linker args and attach them to the tip crate's bin entry\n        let tip_crate_name = self.tip_crate_name();\n        let tip_bin_key = format!(\"{tip_crate_name}.bin\");\n        if let Some(tip_args) = workspace_rustc_args.get_mut(&tip_bin_key) {\n            tip_args.link_args = std::fs::read_to_string(self.link_args_file())\n                .context(\"Failed to read link args from file\")?\n                .lines()\n                .map(|s| s.to_string())\n                .collect();\n        }\n\n        let exe = output_location.context(\"Cargo build failed - no output location. Toggle tracing mode (press `t`) for more information.\")?;\n\n        // Fat builds need to be linked with the fat linker. Would also like to link here for thin builds\n        if matches!(ctx.mode, BuildMode::Fat) {\n            ctx.status_starting_link();\n            let link_start = SystemTime::now();\n            self.run_fat_link(\n                &exe,\n                &workspace_rustc_args\n                    .get(&tip_bin_key)\n                    .cloned()\n                    .unwrap_or_default(),\n            )\n            .await?;\n            tracing::debug!(\n                \"Fat linking completed in {}us\",\n                SystemTime::now()\n                    .duration_since(link_start)\n                    .unwrap()\n                    .as_micros()\n            );\n        }\n\n        // Extract all linker metadata (assets, Android/iOS plugins, widget extensions) in a single pass.\n        let (assets, android_artifacts, swift_sources) =\n            self.collect_assets_and_metadata(&exe, ctx).await?;\n\n        let time_end = SystemTime::now();\n        let mode = ctx.mode.clone();\n        let depinfo = RustcDepInfo::from_file(&exe.with_extension(\"d\")).unwrap_or_default();\n\n        tracing::debug!(\n            \"Build completed successfully in {}us: {:?}\",\n            time_end.duration_since(time_start).unwrap().as_micros(),\n            exe\n        );\n\n        Ok(BuildArtifacts {\n            time_end,\n            exe,\n            workspace_rustc_args,\n            time_start,\n            assets,\n            android_artifacts,\n            swift_sources,\n            mode,\n            depinfo,\n            root_dir: self.root_dir(),\n            patch_cache: None,\n            build_id: ctx.build_id,\n            object_cache,\n        })\n    }\n\n    /// For thin builds, compile workspace dep crates BEFORE the tip crate.\n    ///\n    /// This updates dep rlibs on disk so cargo links the tip against fresh code. Handles cascade\n    /// (recompiling workspace dependents for SVH consistency) and lib+bin tip targets.\n    ///\n    /// Returns the updated `ObjectCache` — defaulting to empty for non-thin builds.\n    async fn compile_workspace_deps(&self, ctx: &BuildContext) -> Result<ObjectCache> {\n        let BuildMode::Thin {\n            workspace_rustc_args,\n            changed_crates,\n            object_cache,\n            ..\n        } = &ctx.mode\n        else {\n            return Ok(ObjectCache::new(&self.session_cache_dir()));\n        };\n\n        let tip_name = self.tip_crate_name();\n        let mut object_cache = object_cache.clone();\n\n        // Compile workspace dep crates with cascade. Start with the explicitly changed dep\n        // crates (already in leaf-first order from handle_file_change). As we compile each,\n        // add the crate's workspace dependents so their rlibs have consistent SVH references.\n        let mut crates_to_compile: Vec<String> = changed_crates\n            .iter()\n            .filter(|c| *c != &tip_name)\n            .cloned()\n            .collect();\n        let mut compiled = HashSet::new();\n        let mut idx = 0;\n\n        while idx < crates_to_compile.len() {\n            let crate_name = crates_to_compile[idx].clone();\n            idx += 1;\n\n            if !compiled.insert(crate_name.clone()) || crate_name == tip_name {\n                continue;\n            }\n\n            let Some(rustc_args) = workspace_rustc_args.get(&format!(\"{crate_name}.lib\")) else {\n                tracing::warn!(\"No captured rustc args for workspace crate {crate_name}, skipping\");\n                continue;\n            };\n\n            tracing::debug!(\"Compiling workspace dep crate: {crate_name}\");\n            self.compile_dep_crate(&crate_name, rustc_args)\n                .await\n                .with_context(|| format!(\"Failed to compile workspace dep crate '{crate_name}'\"))?;\n\n            if let Some(rlib_path) = self.find_rlib_for_crate(&crate_name, rustc_args) {\n                if let Err(e) = object_cache.cache_from_rlib(&crate_name, &rlib_path) {\n                    tracing::warn!(\"Failed to cache objects from rlib for {crate_name}: {e}\");\n                }\n            }\n\n            for dependent in self.workspace_dependents_of(&crate_name) {\n                if dependent != tip_name && !compiled.contains(&dependent) {\n                    tracing::debug!(\n                        \"Cascade: recompiling {dependent} (depends on recompiled {crate_name})\"\n                    );\n                    crates_to_compile.push(dependent);\n                }\n            }\n        }\n\n        // If the tip crate has a lib target (src/lib.rs + src/main.rs), compile it\n        // before the bin target so the bin links against the fresh lib rlib.\n        let lib_key = format!(\"{tip_name}.lib\");\n        if let Some(lib_args) = workspace_rustc_args.get(&lib_key) {\n            let rlib_pre = self.find_rlib_for_crate(&tip_name, lib_args);\n            let pre_modified = rlib_pre\n                .as_ref()\n                .and_then(|p| std::fs::metadata(p).ok())\n                .and_then(|m| m.modified().ok());\n\n            tracing::info!(\"Compiling tip lib target: {lib_key}\");\n            if let Err(e) = self.compile_dep_crate(&tip_name, lib_args).await {\n                tracing::warn!(\"Failed to compile tip lib target: {e}\");\n            } else if let Some(rlib_path) = self.find_rlib_for_crate(&tip_name, lib_args) {\n                let post_modified = std::fs::metadata(&rlib_path)\n                    .ok()\n                    .and_then(|m| m.modified().ok());\n                let rlib_changed = match (pre_modified, post_modified) {\n                    (Some(pre), Some(post)) => post > pre,\n                    _ => true,\n                };\n                tracing::info!(\n                    \"Found lib rlib at: {} (modified={})\",\n                    rlib_path.display(),\n                    rlib_changed,\n                );\n\n                match object_cache.cache_from_rlib(&lib_key, &rlib_path) {\n                    Ok(()) => {\n                        let count = object_cache.get(&lib_key).map(|v| v.len()).unwrap_or(0);\n                        tracing::info!(\"Cached {count} objects from tip lib rlib\");\n                    }\n                    Err(e) => tracing::warn!(\"Failed to cache tip lib objects: {e}\"),\n                }\n            } else {\n                tracing::warn!(\"Could not find rlib for tip lib target {tip_name}\");\n            }\n        } else {\n            tracing::debug!(\n                \"No lib target for tip crate (key '{lib_key}' not in workspace_rustc_args, keys={:?})\",\n                workspace_rustc_args.keys().collect::<Vec<_>>()\n            );\n        }\n\n        Ok(object_cache)\n    }\n\n    /// Collect assets and plugin metadata from the final executable in one pass\n    ///\n    /// This method extracts assets and FFI plugin metadata (Android/Swift) from the\n    /// binary. Permissions are now read from Dioxus.toml, not extracted from the binary.\n    async fn collect_assets_and_metadata(\n        &self,\n        exe: &Path,\n        ctx: &BuildContext,\n    ) -> Result<(\n        AssetManifest,\n        Vec<AndroidArtifactMetadata>,\n        Vec<SwiftPackageMetadata>,\n    )> {\n        use super::assets::extract_symbols_from_file;\n\n        let skip_assets = self.skip_assets;\n        let needs_android_artifacts = self.bundle == BundleFormat::Android;\n        let needs_swift_packages = matches!(self.bundle, BundleFormat::Ios | BundleFormat::MacOS);\n\n        if skip_assets && !needs_android_artifacts && !needs_swift_packages {\n            return Ok((AssetManifest::default(), Vec::new(), Vec::new()));\n        }\n\n        ctx.status_extracting_assets();\n        let super::assets::SymbolExtractionResult {\n            assets: extracted_assets,\n            android_artifacts,\n            swift_packages,\n        } = extract_symbols_from_file(exe).await?;\n\n        let asset_manifest = if skip_assets {\n            AssetManifest::default()\n        } else {\n            let mut manifest = AssetManifest::default();\n            for asset in extracted_assets {\n                manifest.insert_asset(asset);\n            }\n\n            if matches!(self.bundle, BundleFormat::Web)\n                && matches!(ctx.mode, BuildMode::Base { .. } | BuildMode::Fat)\n            {\n                if let Some(dir) = self.user_public_dir() {\n                    for entry in walkdir::WalkDir::new(&dir)\n                        .into_iter()\n                        .filter_map(|e| e.ok())\n                        .filter(|e| e.file_type().is_file())\n                    {\n                        let from = entry.path().to_path_buf();\n                        let relative_path = from.strip_prefix(&dir).unwrap();\n                        let to = format!(\"../{}\", relative_path.display());\n                        manifest.insert_asset(BundledAsset::new(\n                            from.to_string_lossy().as_ref(),\n                            to.as_str(),\n                            manganis_core::AssetOptions::builder()\n                                .with_hash_suffix(false)\n                                .into_asset_options(),\n                        ));\n                    }\n                }\n            }\n\n            manifest\n        };\n\n        if !android_artifacts.is_empty() {\n            tracing::debug!(\n                \"Found {} Android artifact declaration(s)\",\n                android_artifacts.len()\n            );\n            for artifact in android_artifacts.iter() {\n                tracing::debug!(\n                    \"  Plugin: {} Artifact: {}\",\n                    artifact.plugin_name.as_str(),\n                    artifact.artifact_path.as_str()\n                );\n            }\n        }\n\n        if !swift_packages.is_empty() {\n            tracing::debug!(\n                \"Found {} Swift package declaration(s) for {:?}\",\n                swift_packages.len(),\n                self.bundle\n            );\n            for source in &swift_packages {\n                tracing::debug!(\n                    \"  Plugin: {} (Swift package path={} product={})\",\n                    source.plugin_name.as_str(),\n                    source.package_path.as_str(),\n                    source.product.as_str()\n                );\n            }\n        }\n\n        Ok((asset_manifest, android_artifacts, swift_packages))\n    }\n\n    /// Install Android plugin artifacts by bundling source folders as Gradle submodules.\n    ///\n    /// This function handles both prebuilt AARs and source folders:\n    /// - If `artifact_path` is a file (ends in .aar), copy it to libs/ and add file dependency\n    /// - If `artifact_path` is a directory, copy it as a Gradle submodule and add project dependency\n    ///\n    /// All sources are bundled first, then a single Gradle build compiles everything in `assemble()`.\n    fn install_android_artifacts(\n        &self,\n        android_artifacts: &[AndroidArtifactMetadata],\n    ) -> Result<()> {\n        let libs_dir = self.root_dir().join(\"app\").join(\"libs\");\n        std::fs::create_dir_all(&libs_dir)?;\n\n        let plugins_dir = self.root_dir().join(\"plugins\");\n        let build_gradle = self.root_dir().join(\"app\").join(\"build.gradle.kts\");\n        let settings_gradle = self.root_dir().join(\"settings.gradle\");\n\n        for artifact in android_artifacts {\n            let artifact_path = PathBuf::from(artifact.artifact_path.as_str());\n            let plugin_name = artifact.plugin_name.as_str();\n\n            if artifact_path.is_dir() {\n                // It's a source folder - copy it as a Gradle submodule\n                tracing::debug!(\n                    \"Bundling Android plugin '{}' from source: {}\",\n                    plugin_name,\n                    artifact_path.display()\n                );\n\n                // Create module directory\n                let module_dir = plugins_dir.join(plugin_name);\n                self.copy_build_dir_recursive(&artifact_path, &module_dir)?;\n\n                // Strip version specifiers from build.gradle.kts to avoid conflicts with parent project\n                self.strip_gradle_plugin_versions(&module_dir)?;\n\n                // Add to settings.gradle\n                self.ensure_settings_gradle_include(&settings_gradle, plugin_name)?;\n\n                // Add project dependency to app/build.gradle.kts\n                let dep_line = format!(\"implementation(project(\\\":plugins:{}\\\"))\", plugin_name);\n                self.ensure_gradle_dependency(&build_gradle, &dep_line)?;\n\n                tracing::debug!(\n                    \"Added Android plugin module :plugins:{} from {}\",\n                    plugin_name,\n                    artifact_path.display()\n                );\n            } else if artifact_path.extension().is_some_and(|ext| ext == \"aar\") {\n                // It's a prebuilt AAR - copy directly to libs\n                if !artifact_path.exists() {\n                    anyhow::bail!(\n                        \"Android plugin artifact not found: {}\",\n                        artifact_path.display()\n                    );\n                }\n\n                let filename = artifact_path\n                    .file_name()\n                    .ok_or_else(|| {\n                        anyhow::anyhow!(\n                            \"Android plugin artifact path has no filename: {}\",\n                            artifact_path.display()\n                        )\n                    })?\n                    .to_owned();\n                let dest_file = libs_dir.join(&filename);\n                std::fs::copy(&artifact_path, &dest_file)?;\n                tracing::debug!(\n                    \"Copied Android artifact {} -> {}\",\n                    artifact_path.display(),\n                    dest_file.display()\n                );\n\n                let dep_line = format!(\n                    \"implementation(files(\\\"libs/{}\\\"))\",\n                    filename.to_string_lossy()\n                );\n                self.ensure_gradle_dependency(&build_gradle, &dep_line)?;\n            } else {\n                anyhow::bail!(\n                    \"Android artifact path is neither a directory nor an AAR file: {}\",\n                    artifact_path.display()\n                );\n            }\n\n            // Add any extra Gradle dependencies specified by the plugin\n            for dependency in artifact\n                .gradle_dependencies\n                .as_str()\n                .lines()\n                .map(str::trim)\n                .filter(|line| !line.is_empty())\n            {\n                self.ensure_gradle_dependency(&build_gradle, dependency)?;\n            }\n        }\n\n        Ok(())\n    }\n\n    /// Recursively copy a directory and its contents.\n    #[allow(clippy::only_used_in_recursion)]\n    fn copy_build_dir_recursive(&self, src: &Path, dst: &Path) -> Result<()> {\n        std::fs::create_dir_all(dst)?;\n\n        for entry in std::fs::read_dir(src)? {\n            let entry = entry?;\n            let src_path = entry.path();\n            let dst_path = dst.join(entry.file_name());\n\n            if src_path.is_dir() {\n                // Skip build directories and hidden folders\n                let name = entry.file_name();\n                let name_str = name.to_string_lossy();\n                if name_str == \"build\" || name_str == \".gradle\" || name_str.starts_with('.') {\n                    continue;\n                }\n\n                self.copy_build_dir_recursive(&src_path, &dst_path)?;\n            } else {\n                std::fs::copy(&src_path, &dst_path)?;\n            }\n        }\n\n        Ok(())\n    }\n\n    /// Strip version specifiers from build.gradle.kts plugins block.\n    ///\n    /// When a plugin module is included as a subproject, having version specifiers in the\n    /// plugins block causes conflicts because the parent project already has the plugins\n    /// on the classpath. This function removes version specifications like:\n    /// - `version \"8.4.2\"` or `version \"1.9.24\"`\n    /// - Entire version calls from plugin declarations\n    fn strip_gradle_plugin_versions(&self, module_dir: &Path) -> Result<()> {\n        use std::fs;\n\n        let build_gradle = module_dir.join(\"build.gradle.kts\");\n        if !build_gradle.exists() {\n            return Ok(());\n        }\n\n        let contents = fs::read_to_string(&build_gradle)?;\n\n        // Remove version specifications from plugin declarations\n        // Matches: id(\"com.android.library\") version \"8.4.2\" -> id(\"com.android.library\")\n        // Matches: kotlin(\"android\") version \"1.9.24\" -> kotlin(\"android\")\n        let version_pattern = regex::Regex::new(r#\"\\s+version\\s+\"[^\"]+\"\"#).expect(\"Invalid regex\");\n        let cleaned = version_pattern.replace_all(&contents, \"\");\n\n        if cleaned != contents {\n            fs::write(&build_gradle, cleaned.as_ref())?;\n            tracing::debug!(\n                \"Stripped version specifiers from {}\",\n                build_gradle.display()\n            );\n        }\n\n        Ok(())\n    }\n\n    /// Add a module include to settings.gradle if not already present.\n    fn ensure_settings_gradle_include(\n        &self,\n        settings_gradle: &Path,\n        plugin_name: &str,\n    ) -> Result<()> {\n        use std::fs;\n\n        let include_line = format!(\"include ':plugins:{}'\", plugin_name);\n        let mut contents = fs::read_to_string(settings_gradle)?;\n\n        if contents.contains(&include_line) {\n            return Ok(());\n        }\n\n        // Add the include at the end\n        contents.push_str(&format!(\"\\n{}\\n\", include_line));\n        fs::write(settings_gradle, contents)?;\n\n        Ok(())\n    }\n\n    /// Bundle and compile Swift packages from source into dynamic frameworks.\n    ///\n    /// This function:\n    /// 1. Calls ios_swift::compile_swift_sources to compile Swift packages\n    /// 2. The function creates proper .framework bundles from the dylibs\n    /// 3. Installs the frameworks to the app's Frameworks folder\n    async fn compile_swift_sources(&self, swift_sources: &[SwiftPackageMetadata]) -> Result<()> {\n        if swift_sources.is_empty() {\n            return Ok(());\n        }\n\n        let build_dir = self.target_dir.join(\"swift-build\");\n        std::fs::create_dir_all(&build_dir)?;\n\n        // Compile Swift sources and get the framework bundle path\n        let framework_path = super::ios_swift::compile_swift_sources(\n            swift_sources,\n            &self.triple,\n            &build_dir,\n            self.release,\n        )\n        .await?;\n\n        // If a framework was created, install it to the Frameworks folder\n        if let Some(framework) = framework_path {\n            self.install_swift_framework(&framework).await?;\n        }\n\n        Ok(())\n    }\n\n    /// Install a Swift framework bundle into the app's Frameworks directory.\n    async fn install_swift_framework(&self, framework_path: &Path) -> Result<()> {\n        let frameworks_dir = self.frameworks_folder();\n        std::fs::create_dir_all(&frameworks_dir)?;\n\n        let framework_name = framework_path\n            .file_name()\n            .ok_or_else(|| anyhow::anyhow!(\"Invalid framework path: no filename\"))?;\n        let dest = frameworks_dir.join(framework_name);\n\n        // Remove existing framework if present\n        if dest.exists() {\n            std::fs::remove_dir_all(&dest)?;\n        }\n\n        // Copy the entire framework bundle\n        self.copy_build_dir_recursive(framework_path, &dest)?;\n\n        tracing::debug!(\n            \"Installed Swift framework '{}' to {}\",\n            framework_name.to_string_lossy(),\n            frameworks_dir.display()\n        );\n\n        Ok(())\n    }\n\n    /// Embed Swift standard libraries into the app bundle when Swift plugins are present.\n    async fn embed_swift_stdlibs(&self, swift_sources: &[SwiftPackageMetadata]) -> Result<()> {\n        if swift_sources.is_empty() {\n            return Ok(());\n        }\n\n        let platform_flag = match self.bundle {\n            BundleFormat::Ios => {\n                let triple_str = self.triple.to_string();\n                if triple_str.contains(\"sim\") || triple_str.contains(\"x86_64\") {\n                    \"iphonesimulator\"\n                } else {\n                    \"iphoneos\"\n                }\n            }\n            BundleFormat::MacOS => \"macosx\",\n            _ => return Ok(()),\n        };\n\n        let frameworks_dir = self.frameworks_folder();\n        std::fs::create_dir_all(&frameworks_dir)?;\n\n        let exe_path = self.main_exe();\n        if !exe_path.exists() {\n            anyhow::bail!(\n                \"Expected executable at {} when embedding Swift stdlibs\",\n                exe_path.display()\n            );\n        }\n\n        // Use swift-stdlib-tool to copy Swift runtime libraries needed by:\n        // 1. The main executable (--scan-executable)\n        // 2. Any Swift frameworks in the Frameworks folder (--scan-folder)\n        let output = Command::new(\"xcrun\")\n            .arg(\"swift-stdlib-tool\")\n            .arg(\"--copy\")\n            .arg(\"--platform\")\n            .arg(platform_flag)\n            .arg(\"--scan-executable\")\n            .arg(&exe_path)\n            .arg(\"--scan-folder\")\n            .arg(&frameworks_dir)\n            .arg(\"--destination\")\n            .arg(&frameworks_dir)\n            .output()\n            .await?;\n\n        if !output.status.success() {\n            let stderr = String::from_utf8_lossy(&output.stderr);\n            let stdout = String::from_utf8_lossy(&output.stdout);\n            anyhow::bail!(\n                \"swift-stdlib-tool failed: {}{}\",\n                stderr.trim(),\n                if stdout.trim().is_empty() {\n                    \"\".to_string()\n                } else {\n                    format!(\" | {}\", stdout.trim())\n                }\n            );\n        }\n\n        Ok(())\n    }\n\n    /// Compile and install Apple Widget Extensions from Dioxus.toml config.\n    ///\n    /// This processes widget extensions declared in `[[ios.widget_extensions]]` by:\n    /// 1. Compiling the Swift package as a Widget Extension executable\n    /// 2. Creating the .appex bundle structure with Info.plist\n    /// 3. Installing to the app's PlugIns folder\n    async fn compile_widget_extensions(&self) -> Result<()> {\n        let widget_configs = &self.config.ios.widget_extensions;\n        if widget_configs.is_empty() {\n            return Ok(());\n        }\n\n        tracing::debug!(\n            \"Compiling {} Apple Widget Extension(s)\",\n            widget_configs.len()\n        );\n\n        let build_dir = self.target_dir.join(\"widget-build\");\n        std::fs::create_dir_all(&build_dir)?;\n\n        let app_bundle_id = self.bundle_identifier();\n        let default_deployment_target = self\n            .config\n            .ios\n            .deployment_target\n            .as_deref()\n            .unwrap_or(\"16.0\");\n\n        let plugins_dir = self.plugins_folder();\n        std::fs::create_dir_all(&plugins_dir)?;\n\n        for widget_config in widget_configs {\n            let source_path = self.package_manifest_dir().join(&widget_config.source);\n            let deployment_target = widget_config\n                .deployment_target\n                .as_deref()\n                .unwrap_or(default_deployment_target);\n\n            let widget_source = super::ios_swift::AppleWidgetSource {\n                source_path,\n                display_name: widget_config.display_name.clone(),\n                bundle_id_suffix: widget_config.bundle_id_suffix.clone(),\n                deployment_target: deployment_target.to_string(),\n                module_name: widget_config.module_name.clone(),\n            };\n\n            let appex_path = super::ios_swift::compile_apple_widget(\n                &widget_source,\n                &self.triple,\n                &build_dir,\n                &app_bundle_id,\n                self.release,\n            )\n            .await\n            .with_context(|| {\n                format!(\n                    \"Failed to compile widget extension '{}'\",\n                    widget_source.display_name\n                )\n            })?;\n\n            // Install the .appex bundle to PlugIns/\n            let appex_name = appex_path\n                .file_name()\n                .map(|n| n.to_string_lossy().to_string())\n                .unwrap_or_else(|| \"Widget.appex\".to_string());\n            let dest_path = plugins_dir.join(&appex_name);\n\n            if dest_path.exists() {\n                std::fs::remove_dir_all(&dest_path)?;\n            }\n\n            self.copy_build_dir_recursive(&appex_path, &dest_path)?;\n\n            tracing::debug!(\n                \"Installed widget extension '{}' to {}\",\n                widget_source.display_name,\n                dest_path.display()\n            );\n        }\n\n        Ok(())\n    }\n\n    /// Take the output of rustc and make it into the main exe of the bundle\n    ///\n    /// For wasm, we'll want to run `wasm-bindgen` to make it a wasm binary along with some other optimizations\n    /// Other platforms we might do some stripping or other optimizations\n    /// Move the executable to the workdir\n    async fn write_executable(\n        &self,\n        ctx: &BuildContext,\n        exe: &Path,\n        assets: &mut AssetManifest,\n    ) -> Result<()> {\n        match self.bundle {\n            // Run wasm-bindgen on the wasm binary and set its output to be in the bundle folder\n            // Also run wasm-opt on the wasm binary, and sets the index.html since that's also the \"executable\".\n            //\n            // The wasm stuff will be in a folder called \"wasm\" in the workdir.\n            //\n            // Final output format:\n            // ```\n            // dx/\n            //     app/\n            //         web/\n            //             bundle/\n            //             build/\n            //                 server.exe\n            //                 public/\n            //                     index.html\n            //                     wasm/\n            //                        app.wasm\n            //                        glue.js\n            //                        snippets/\n            //                            ...\n            //                     assets/\n            //                        logo.png\n            // ```\n            BundleFormat::Web => {\n                self.bundle_web(ctx, exe, assets).await?;\n            }\n\n            // this will require some extra oomf to get the multi architecture builds...\n            // for now, we just copy the exe into the current arch (which, sorry, is hardcoded for my m1)\n            // we'll want to do multi-arch builds in the future, so there won't be *one* exe dir to worry about\n            // eventually `exe_dir` and `main_exe` will need to take in an arch and return the right exe path\n            //\n            // todo(jon): maybe just symlink this rather than copy it?\n            // we might want to eventually use the objcopy logic to handle this\n            //\n            // https://github.com/rust-mobile/xbuild/blob/master/xbuild/template/lib.rs\n            // https://github.com/rust-mobile/xbuild/blob/master/apk/src/lib.rs#L19\n            //\n            // These are all super simple, just copy the exe into the folder\n            // eventually, perhaps, maybe strip + encrypt the exe?\n            BundleFormat::Android\n            | BundleFormat::MacOS\n            | BundleFormat::Windows\n            | BundleFormat::Linux\n            | BundleFormat::Ios\n            | BundleFormat::Server => {\n                std::fs::create_dir_all(self.exe_dir())?;\n                std::fs::copy(exe, self.main_exe())?;\n            }\n        }\n\n        Ok(())\n    }\n\n    async fn write_frameworks(\n        &self,\n        _ctx: &BuildContext,\n        artifacts: &BuildArtifacts,\n    ) -> Result<()> {\n        let framework_dir = self.frameworks_folder();\n\n        // We use the rustc for the tip crate `main.rs` because that's where the linking happens\n        let direct_rustc = artifacts\n            .workspace_rustc_args\n            .get(&format!(\"{}.bin\", self.tip_crate_name()))\n            .cloned()\n            .unwrap_or_default();\n\n        // We have some prebuilt stuff that needs to be copied into the framework dir\n        let openssl_dir = AndroidTools::openssl_lib_dir(&self.triple);\n        let openssl_dir_disp = openssl_dir.display().to_string();\n\n        for arg in &direct_rustc.link_args {\n            // todo - how do we handle windows dlls? we don't want to bundle the system dlls\n            // for now, we don't do anything with dlls, and only use .dylibs and .so files\n\n            // Write dylibs and dlls to the frameworks folder\n            if arg.ends_with(\".dylib\") | arg.ends_with(\".so\") {\n                let from = PathBuf::from(arg);\n                let to = framework_dir.join(from.file_name().unwrap());\n                _ = std::fs::remove_file(&to);\n\n                tracing::debug!(\"Copying framework from {from:?} to {to:?}\");\n\n                _ = std::fs::create_dir_all(&framework_dir);\n\n                // in dev and on normal oses, we want to symlink the file\n                // otherwise, just copy it (since in release you want to distribute the framework)\n                if cfg!(any(windows, unix)) && !self.release {\n                    #[cfg(windows)]\n                    std::os::windows::fs::symlink_file(from, to).with_context(|| {\n                        \"Failed to symlink framework into bundle: {from:?} -> {to:?}\"\n                    })?;\n\n                    #[cfg(unix)]\n                    std::os::unix::fs::symlink(from, to).with_context(|| {\n                        \"Failed to symlink framework into bundle: {from:?} -> {to:?}\"\n                    })?;\n                } else {\n                    std::fs::copy(from, to)?;\n                }\n            }\n\n            // Always create the framework dir for android\n            if self.bundle == BundleFormat::Android {\n                _ = std::fs::create_dir_all(&framework_dir);\n            }\n\n            // On android, the c++_shared flag means we need to copy the libc++_shared.so precompiled\n            // library to the jniLibs folder\n            if self.bundle == BundleFormat::Android && arg.contains(\"-lc++_shared\") {\n                std::fs::copy(\n                    self.workspace.android_tools()?.libcpp_shared(&self.triple),\n                    framework_dir.join(\"libc++_shared.so\"),\n                )\n                .with_context(|| \"Failed to copy libc++_shared.so into bundle\")?;\n            }\n\n            // Copy over libssl and libcrypto if they are present in the link args\n            if self.bundle == BundleFormat::Android && arg.contains(openssl_dir_disp.as_str()) {\n                let libssl_source = openssl_dir.join(\"libssl.so\");\n                let libcrypto_source = openssl_dir.join(\"libcrypto.so\");\n                let libssl_target = framework_dir.join(\"libssl.so\");\n                let libcrypto_target = framework_dir.join(\"libcrypto.so\");\n                std::fs::copy(&libssl_source, &libssl_target).with_context(|| {\n                    format!(\"Failed to copy libssl.so into bundle\\nfrom {libssl_source:?}\\nto {libssl_target:?}\")\n                })?;\n                std::fs::copy(&libcrypto_source, &libcrypto_target).with_context(\n                    || format!(\"Failed to copy libcrypto.so into bundle\\nfrom {libcrypto_source:?}\\nto {libcrypto_target:?}\"),\n                )?;\n            }\n        }\n\n        Ok(())\n    }\n\n    fn frameworks_folder(&self) -> PathBuf {\n        match self.triple.operating_system {\n            OperatingSystem::Darwin(_) | OperatingSystem::MacOSX(_) => {\n                self.root_dir().join(\"Contents\").join(\"Frameworks\")\n            }\n            OperatingSystem::IOS(_) => self.root_dir().join(\"Frameworks\"),\n            OperatingSystem::Linux if self.bundle == BundleFormat::Android => {\n                let arch = match self.triple.architecture {\n                    Architecture::Aarch64(_) => \"arm64-v8a\",\n                    Architecture::Arm(_) => \"armeabi-v7a\",\n                    Architecture::X86_32(_) => \"x86\",\n                    Architecture::X86_64 => \"x86_64\",\n                    _ => panic!(\n                        \"Unsupported architecture for Android: {:?}\",\n                        self.triple.architecture\n                    ),\n                };\n\n                self.root_dir()\n                    .join(\"app\")\n                    .join(\"src\")\n                    .join(\"main\")\n                    .join(\"jniLibs\")\n                    .join(arch)\n            }\n            OperatingSystem::Linux | OperatingSystem::Windows => self.root_dir(),\n            _ => self.root_dir(),\n        }\n    }\n\n    /// Get the folder where Apple Widget Extensions (.appex bundles) are installed.\n    /// This is only applicable to iOS and macOS bundles.\n    fn plugins_folder(&self) -> PathBuf {\n        match self.triple.operating_system {\n            OperatingSystem::Darwin(_) | OperatingSystem::MacOSX(_) => {\n                self.root_dir().join(\"Contents\").join(\"PlugIns\")\n            }\n            OperatingSystem::IOS(_) => self.root_dir().join(\"PlugIns\"),\n            _ => self.root_dir().join(\"PlugIns\"),\n        }\n    }\n\n    /// Copy the assets out of the manifest and into the target location\n    ///\n    /// Should be the same on all platforms - just copy over the assets from the manifest into the output directory\n    async fn write_assets(&self, ctx: &BuildContext, assets: &AssetManifest) -> Result<()> {\n        // Server doesn't need assets - web will provide them\n        if !ctx.is_primary_build() {\n            return Ok(());\n        }\n\n        let asset_dir = self.asset_dir();\n\n        // First, clear the asset dir of any files that don't exist in the new manifest\n        _ = std::fs::create_dir_all(&asset_dir);\n\n        // Create a set of all the paths that new files will be bundled to\n        let mut keep_bundled_output_paths: HashSet<_> = assets\n            .unique_assets()\n            .map(|a| asset_dir.join(a.bundled_path()))\n            .collect();\n\n        // The CLI creates a .manifest.json file in the asset dir to keep track of the assets and\n        // other build metadata. If we can't parse this file (or the CLI version changed), then we\n        // want to re-copy all the assets rather than trying to do an incremental update.\n        let clear_cache = self\n            .load_manifest()\n            .map(|manifest| manifest.cli_version != crate::VERSION.as_str())\n            .unwrap_or(true);\n        if clear_cache {\n            keep_bundled_output_paths.clear();\n        }\n\n        tracing::trace!(\n            \"Keeping bundled output paths: {:#?}\",\n            keep_bundled_output_paths\n        );\n\n        // todo(jon): we also want to eventually include options for each asset's optimization and compression, which we currently aren't\n        let mut assets_to_transfer = vec![];\n\n        // Queue the bundled assets (skip sidecar assets that require special processing)\n        for bundled in assets.unique_assets() {\n            let from = PathBuf::from(bundled.absolute_source_path());\n            let to = asset_dir.join(bundled.bundled_path());\n\n            // prefer to log using a shorter path relative to the workspace dir by trimming the workspace dir\n            let from_ = from\n                .strip_prefix(self.workspace_dir())\n                .unwrap_or(from.as_path());\n            let to_ = from\n                .strip_prefix(self.workspace_dir())\n                .unwrap_or(to.as_path());\n\n            tracing::debug!(\"Copying asset {from_:?} to {to_:?}\");\n            assets_to_transfer.push((from, to, *bundled.options()));\n        }\n\n        let asset_count = assets_to_transfer.len();\n        let started_processing = AtomicUsize::new(0);\n        let copied = AtomicUsize::new(0);\n\n        // Parallel Copy over the assets and keep track of progress with an atomic counter\n        let progress = ctx.tx.clone();\n        let ws_dir = self.workspace_dir();\n\n        // Optimizing assets is expensive and blocking, so we do it in a tokio spawn blocking task\n        tokio::task::spawn_blocking(move || {\n            assets_to_transfer\n                .par_iter()\n                .try_for_each(|(from, to, options)| {\n                    let processing = started_processing.fetch_add(1, Ordering::SeqCst);\n                    let from_ = from.strip_prefix(&ws_dir).unwrap_or(from);\n                    tracing::trace!(\n                        \"Starting asset copy {processing}/{asset_count} from {from_:?}\"\n                    );\n\n                    let res = process_file_to(options, from, to);\n                    if let Err(err) = res.as_ref() {\n                        tracing::error!(\"Failed to copy asset {from:?}: {err}\");\n                    }\n\n                    let finished = copied.fetch_add(1, Ordering::SeqCst);\n                    BuildContext::status_copied_asset(\n                        &progress,\n                        finished,\n                        asset_count,\n                        from.to_path_buf(),\n                    );\n\n                    res.map(|_| ())\n                })\n        })\n        .await\n        .map_err(|e| anyhow::anyhow!(\"A task failed while trying to copy assets: {e}\"))??;\n\n        // Remove the wasm dir if we packaged it to an \"asset\"-type app\n        if self.should_bundle_to_asset() {\n            _ = std::fs::remove_dir_all(self.wasm_bindgen_out_dir());\n        }\n\n        // Write the version file so we know what version of the optimizer we used\n        self.write_app_manifest(assets).await?;\n\n        Ok(())\n    }\n\n    /// Run our custom linker setup to generate a patch file in the right location\n    ///\n    /// This should be the only case where the cargo output is a \"dummy\" file and requires us to\n    /// manually do any linking.\n    ///\n    /// We also run some post processing steps here, like extracting out any new assets.\n    ///\n    /// `extra_objects` contains additional object file paths from compiled workspace dep crates\n    /// that should be included in the patch dylib. These are combined with the tip crate's\n    /// `.rcgu.o` files extracted from linker args, creating a self-contained patch.\n    async fn write_patch(\n        &self,\n        ctx: &BuildContext,\n        aslr_reference: u64,\n        artifacts: &mut BuildArtifacts,\n        cache: &Arc<HotpatchModuleCache>,\n        modified_crates: &HashSet<String>,\n    ) -> Result<()> {\n        ctx.status_hotpatching();\n\n        let tip_name = self.tip_crate_name();\n\n        // Cache tip crate objects from the FRESH linker args (from the just-completed\n        // thin build, not the stale ones from ctx.mode's fat build).\n        let tip_bin_key = format!(\"{tip_name}.bin\");\n        let args = artifacts\n            .workspace_rustc_args\n            .get(&tip_bin_key)\n            .cloned()\n            .with_context(|| {\n                format!(\n                    \"Missing rustc args for tip bin target '{tip_bin_key}' \\\n                     (available keys: {:?})\",\n                    artifacts.workspace_rustc_args.keys().collect::<Vec<_>>()\n                )\n            })?;\n\n        // Collect objs from tip and re-cache them in the obj cache map\n        let tip_object_paths: Vec<PathBuf> = args\n            .link_args\n            .iter()\n            .filter(|arg| arg.ends_with(\".rcgu.o\"))\n            .map(PathBuf::from)\n            .collect();\n        if !tip_object_paths.is_empty() {\n            artifacts\n                .object_cache\n                .cache_from_paths(&tip_name, &tip_object_paths)\n                .context(\"Failed to cache objs during patch\")?;\n        }\n\n        // Collect cached object paths from all modified dep crates.\n        // Objects are already on disk in the object cache directory.\n        // These must NOT be deleted after linking — they persist across patches.\n        let mut cached_objects: Vec<PathBuf> = Vec::new();\n        for dep_name in modified_crates.iter().filter(|c| *c != &tip_name) {\n            if let Some(paths) = artifacts.object_cache.get(dep_name) {\n                cached_objects.extend(paths.iter().cloned());\n            }\n        }\n\n        // If the tip has a lib target (lib+bin crate), include its cached objects too.\n        let lib_key = format!(\"{tip_name}.lib\");\n        if let Some(paths) = artifacts.object_cache.get(&lib_key) {\n            cached_objects.extend(paths.iter().cloned());\n        }\n\n        // Extract out the incremental object files.\n        //\n        // This is sadly somewhat of a hack, but it might be a moderately reliable hack.\n        //\n        // When rustc links your project, it passes the args as how a linker would expect, but with\n        // a somewhat reliable ordering. These are all internal details to cargo/rustc, so we can't\n        // rely on them *too* much, but the *are* fundamental to how rust compiles your projects, and\n        // linker interfaces probably won't change drastically for another 40 years.\n        //\n        // We need to tear apart this command and only pass the args that are relevant to our thin link.\n        // Mainly, we don't want any rlibs to be linked. Occasionally some libraries like objc_exception\n        // export a folder with their artifacts - unsure if we actually need to include them. Generally\n        // you can err on the side that most *libraries* don't need to be linked here since dlopen\n        // satisfies those symbols anyways when the binary is loaded.\n        //\n        // Many args are passed twice, too, which can be confusing, but generally don't have any real\n        // effect. Note that on macos/ios, there's a special macho header that needs to be set, otherwise\n        // dyld will complain.\n        //\n        // Also, some flags in darwin land might become deprecated, need to be super conservative:\n        // - https://developer.apple.com/forums/thread/773907\n        //\n        // The format of this command roughly follows:\n        // ```\n        // clang\n        //     /dioxus/target/debug/subsecond-cli\n        //     /var/folders/zs/gvrfkj8x33d39cvw2p06yc700000gn/T/rustcAqQ4p2/symbols.o\n        //     /dioxus/target/subsecond-dev/deps/subsecond_harness-acfb69cb29ffb8fa.05stnb4bovskp7a00wyyf7l9s.rcgu.o\n        //     /dioxus/target/subsecond-dev/deps/subsecond_harness-acfb69cb29ffb8fa.08rgcutgrtj2mxoogjg3ufs0g.rcgu.o\n        //     /dioxus/target/subsecond-dev/deps/subsecond_harness-acfb69cb29ffb8fa.0941bd8fa2bydcv9hfmgzzne9.rcgu.o\n        //     /dioxus/target/subsecond-dev/deps/libbincode-c215feeb7886f81b.rlib\n        //     /dioxus/target/subsecond-dev/deps/libanyhow-e69ac15c094daba6.rlib\n        //     /dioxus/target/subsecond-dev/deps/libratatui-c3364579b86a1dfc.rlib\n        //     /.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/aarch64-apple-darwin/lib/libstd-019f0f6ae6e6562b.rlib\n        //     /.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/aarch64-apple-darwin/lib/libpanic_unwind-7387d38173a2eb37.rlib\n        //     /.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/aarch64-apple-darwin/lib/libobject-2b03cf6ece171d21.rlib\n        //     -framework AppKit\n        //     -lc\n        //     -framework Foundation\n        //     -framework Carbon\n        //     -lSystem\n        //     -framework CoreFoundation\n        //     -lobjc\n        //     -liconv\n        //     -lm\n        //     -arch arm64\n        //     -mmacosx-version-min=11.0.0\n        //     -L /dioxus/target/subsecond-dev/build/objc_exception-dc226cad0480ea65/out\n        //     -o /dioxus/target/subsecond-dev/deps/subsecond_harness-acfb69cb29ffb8fa\n        //     -nodefaultlibs\n        //     -Wl,-all_load\n        // ```\n        let mut dylibs = vec![];\n\n        // Tip objects from link_args are temps — safe to delete after linking.\n        let temp_objects: Vec<PathBuf> = args\n            .link_args\n            .iter()\n            .filter(|arg| arg.ends_with(\".rcgu.o\"))\n            .sorted()\n            .map(PathBuf::from)\n            .collect();\n\n        // Merge both sets for the linker.\n        let mut object_files: Vec<PathBuf> =\n            Vec::with_capacity(cached_objects.len() + temp_objects.len());\n        object_files.append(&mut cached_objects);\n        object_files.extend(temp_objects.iter().cloned());\n\n        // On non-wasm platforms, we generate a special shim object file which converts symbols from\n        // fat binary into direct addresses from the running process.\n        //\n        // Our wasm approach is quite specific to wasm. We don't need to resolve any missing symbols\n        // there since wasm is relocatable, but there is considerable pre and post processing work to\n        // satisfy undefined symbols that we do by munging the binary directly.\n        //\n        // todo: can we adjust our wasm approach to also use a similar system?\n        // todo: don't require the aslr reference and just patch the got when loading.\n        //\n        // Requiring the ASLR offset here is necessary but unfortunately might be flakey in practice.\n        // Android apps can take a long time to open, and a hot patch might've been issued in the interim,\n        // making this hotpatch a failure.\n        if !self.is_wasm_or_wasi() {\n            let stub_bytes = crate::build::create_undefined_symbol_stub(\n                cache,\n                &object_files,\n                &self.triple,\n                aslr_reference,\n            )\n            .expect(\"failed to resolve patch symbols\");\n\n            // Currently we're dropping stub.o in the exe dir, but should probably just move to a tempfile?\n            let patch_file = self.main_exe().with_file_name(\"stub.o\");\n            std::fs::write(&patch_file, stub_bytes)?;\n            object_files.push(patch_file);\n\n            // Add the dylibs/sos to the linker args\n            // Make sure to use the one in the bundle, not the ones in the target dir or system.\n            for arg in &args.link_args {\n                if arg.ends_with(\".dylib\") || arg.ends_with(\".so\") {\n                    let path = PathBuf::from(arg);\n                    dylibs.push(self.frameworks_folder().join(path.file_name().unwrap()));\n                }\n            }\n        }\n\n        // And now we can run the linker with our new args\n        let linker = self.select_linker()?;\n        let out_exe = self.patch_exe(artifacts.time_start);\n        let out_arg = match self.triple.operating_system {\n            OperatingSystem::Windows => vec![format!(\"/OUT:{}\", out_exe.display())],\n            _ => vec![\"-o\".to_string(), out_exe.display().to_string()],\n        };\n\n        tracing::trace!(\"Linking with {:?} using args: {:#?}\", linker, object_files);\n\n        let mut out_args: Vec<OsString> = vec![];\n        out_args.extend(object_files.iter().map(Into::into));\n        out_args.extend(dylibs.iter().map(Into::into));\n        out_args.extend(self.thin_link_args(&args.link_args)?.iter().map(Into::into));\n        out_args.extend(out_arg.iter().map(Into::into));\n\n        if cfg!(windows) {\n            let cmd_contents: String = out_args\n                .iter()\n                .map(|s| format!(\"\\\"{}\\\"\", s.to_string_lossy()))\n                .join(\" \");\n            std::fs::write(self.windows_command_file(), cmd_contents)\n                .context(\"Failed to write linker command file\")?;\n            out_args = vec![format!(\"@{}\", self.windows_command_file().display()).into()];\n        }\n\n        // Add more search paths for the linker\n        let mut command_envs = args.envs.clone();\n\n        // On linux, we need to set a more complete PATH for the linker to find its libraries\n        if cfg!(target_os = \"linux\") {\n            command_envs.push((\"PATH\".to_string(), std::env::var(\"PATH\").unwrap()));\n        }\n\n        // Run the linker directly!\n        //\n        // We dump its output directly into the patch exe location which is different than how rustc\n        // does it since it uses llvm-objcopy into the `target/debug/` folder.\n        let res = Command::new(linker)\n            .args(out_args)\n            .env_clear()\n            .envs(command_envs)\n            .output()\n            .await?;\n\n        if !res.stderr.is_empty() {\n            let errs = String::from_utf8_lossy(&res.stderr);\n            if !self.patch_exe(artifacts.time_start).exists() || !res.status.success() {\n                tracing::error!(\n                    telemetry = %serde_json::json!({ \"event\": \"hotpatch_linker_failed\" }),\n                    \"Failed to generate patch: {}\",\n                    errs.trim()\n                );\n            } else {\n                tracing::trace!(\"Linker output during thin linking: {}\", errs.trim());\n            }\n        }\n\n        // For some really weird reason that I think is because of dlopen caching, future loads of the\n        // jump library will fail if we don't remove the original fat file. I think this could be\n        // because of library versioning and namespaces, but really unsure.\n        //\n        // The errors if you forget to do this are *extremely* cryptic - missing symbols that never existed.\n        //\n        // Fortunately, this binary exists in two places - the deps dir and the target out dir. We\n        // can just remove the one in the deps dir and the problem goes away.\n        if let Some(idx) = args.link_args.iter().position(|arg| *arg == \"-o\") {\n            _ = std::fs::remove_file(PathBuf::from(args.link_args[idx + 1].as_str()));\n        }\n\n        // Now extract linker metadata from the fat binary (assets, plugin data)\n        let (assets, android_artifacts, swift_sources) = self\n            .collect_assets_and_metadata(&self.patch_exe(artifacts.time_start), ctx)\n            .await?;\n        artifacts.assets = assets;\n        artifacts.android_artifacts = android_artifacts;\n        artifacts.swift_sources = swift_sources;\n\n        // If this is a web build, reset the index.html file in case it was modified by SSG\n        self.write_index_html(&artifacts.assets)\n            .context(\"Failed to write index.html\")?;\n\n        // Clean up temp object files (tip incremental objects + stub.o).\n        // Cached dep objects in object_cache/ are NOT deleted — they persist across patches.\n        for file in &temp_objects {\n            _ = std::fs::remove_file(file);\n        }\n\n        Ok(())\n    }\n\n    /// Take the original args passed to the \"fat\" build and then create the \"thin\" variant.\n    ///\n    /// This is basically just stripping away the rlibs and other libraries that will be satisfied\n    /// by our stub step.\n    fn thin_link_args(&self, original_args: &[String]) -> Result<Vec<String>> {\n        let mut out_args = vec![];\n\n        match self.linker_flavor() {\n            // wasm32-unknown-unknown -> use wasm-ld (gnu-lld)\n            //\n            // We need to import a few things - namely the memory and ifunc table.\n            //\n            // We can safely export everything, I believe, though that led to issues with the \"fat\"\n            // binaries that also might lead to issues here too. wasm-bindgen chokes on some symbols\n            // and the resulting JS has issues.\n            //\n            // We turn on both --pie and --experimental-pic but I think we only need --pie.\n            //\n            // We don't use *any* of the original linker args since they do lots of custom exports\n            // and other things that we don't need.\n            //\n            // The trickiest one here is -Crelocation-model=pic, which forces data symbols\n            // into a GOT, making it possible to import them from the main module.\n            //\n            // I think we can make relocation-model=pic work for non-wasm platforms, enabling\n            // fully relocatable modules with no host coordination in lieu of sending out\n            // the aslr slide at runtime.\n            LinkerFlavor::WasmLld => {\n                out_args.extend([\n                    \"--fatal-warnings\".to_string(),\n                    \"--verbose\".to_string(),\n                    \"--import-memory\".to_string(),\n                    \"--import-table\".to_string(),\n                    \"--growable-table\".to_string(),\n                    \"--export\".to_string(),\n                    \"main\".to_string(),\n                    \"--allow-undefined\".to_string(),\n                    \"--no-demangle\".to_string(),\n                    \"--no-entry\".to_string(),\n                    \"--pie\".to_string(),\n                    \"--experimental-pic\".to_string(),\n                ]);\n\n                // retain exports so post-processing has hooks to work with\n                for (idx, arg) in original_args.iter().enumerate() {\n                    if *arg == \"--export\" {\n                        out_args.push(arg.to_string());\n                        out_args.push(original_args[idx + 1].to_string());\n                    }\n                }\n            }\n\n            // This uses \"cc\" and these args need to be ld compatible\n            //\n            // Most importantly, we want to pass `-dylib` to both CC and the linker to indicate that\n            // we want to generate the shared library instead of an executable.\n            LinkerFlavor::Darwin => {\n                out_args.extend([\"-Wl,-dylib\".to_string()]);\n\n                // Preserve the original args. We only preserve:\n                // -framework\n                // -arch\n                // -lxyz\n                // There might be more, but some flags might break our setup.\n                for (idx, arg) in original_args.iter().enumerate() {\n                    if *arg == \"-framework\"\n                        || *arg == \"-arch\"\n                        || *arg == \"-L\"\n                        || *arg == \"-target\"\n                        || (*arg == \"-isysroot\"\n                            && matches!(\n                                self.triple.operating_system,\n                                target_lexicon::OperatingSystem::IOS(_)\n                            ))\n                    {\n                        out_args.push(arg.to_string());\n                        out_args.push(original_args[idx + 1].to_string());\n                    }\n\n                    if arg.starts_with(\"-l\")\n                        || arg.starts_with(\"-m\")\n                        || arg.starts_with(\"-nodefaultlibs\")\n                    {\n                        out_args.push(arg.to_string());\n                    }\n                }\n            }\n\n            // android/linux need to be compatible with lld\n            //\n            // android currently drags along its own libraries and other zany flags\n            LinkerFlavor::Gnu => {\n                out_args.extend([\n                    \"-shared\".to_string(),\n                    \"-Wl,--eh-frame-hdr\".to_string(),\n                    \"-Wl,-z,noexecstack\".to_string(),\n                    \"-Wl,-z,relro,-z,now\".to_string(),\n                    \"-nodefaultlibs\".to_string(),\n                    \"-Wl,-Bdynamic\".to_string(),\n                ]);\n\n                // Preserve the original args. We only preserve:\n                // -L <path>\n                // -arch\n                // -lxyz\n                // There might be more, but some flags might break our setup.\n                for (idx, arg) in original_args.iter().enumerate() {\n                    if *arg == \"-L\" {\n                        out_args.push(arg.to_string());\n                        out_args.push(original_args[idx + 1].to_string());\n                    }\n\n                    if arg.starts_with(\"-l\")\n                        || arg.starts_with(\"-m\")\n                        || arg.starts_with(\"-Wl,--target=\")\n                        || arg.starts_with(\"-Wl,-fuse-ld\")\n                        || arg.starts_with(\"-fuse-ld\")\n                        || arg.contains(\"-ld-path\")\n                    {\n                        out_args.push(arg.to_string());\n                    }\n                }\n            }\n\n            LinkerFlavor::Msvc => {\n                out_args.extend([\n                    \"shlwapi.lib\".to_string(),\n                    \"kernel32.lib\".to_string(),\n                    \"advapi32.lib\".to_string(),\n                    \"ntdll.lib\".to_string(),\n                    \"userenv.lib\".to_string(),\n                    \"ws2_32.lib\".to_string(),\n                    \"dbghelp.lib\".to_string(),\n                    \"/defaultlib:msvcrt\".to_string(),\n                    \"/DLL\".to_string(),\n                    \"/DEBUG\".to_string(),\n                    \"/PDBALTPATH:%_PDB%\".to_string(),\n                    \"/EXPORT:main\".to_string(),\n                    \"/HIGHENTROPYVA:NO\".to_string(),\n                ]);\n            }\n\n            LinkerFlavor::Unsupported => {\n                bail!(\"Unsupported platform for thin linking\")\n            }\n        }\n\n        let extract_value = |arg: &str| -> Option<String> {\n            original_args\n                .iter()\n                .position(|a| *a == arg)\n                .map(|i| original_args[i + 1].to_string())\n        };\n\n        if let Some(vale) = extract_value(\"-target\") {\n            out_args.push(\"-target\".to_string());\n            out_args.push(vale);\n        }\n\n        if let Some(vale) = extract_value(\"-isysroot\") {\n            if matches!(\n                self.triple.operating_system,\n                target_lexicon::OperatingSystem::IOS(_)\n            ) {\n                out_args.push(\"-isysroot\".to_string());\n                out_args.push(vale);\n            }\n        }\n\n        Ok(out_args)\n    }\n\n    /// Patches are stored in the same directory as the main executable, but with a name based on the\n    /// time the patch started compiling.\n    ///\n    /// - lib{name}-patch-{time}.(so/dll/dylib) (next to the main exe)\n    ///\n    /// Note that weirdly enough, the name of dylibs can actually matter. In some environments, libs\n    /// can override each other with symbol interposition.\n    ///\n    /// Also, on Android - and some Linux, we *need* to start the lib name with `lib` for the dynamic\n    /// loader to consider it a shared library.\n    ///\n    /// todo: the time format might actually be problematic if two platforms share the same build folder.\n    pub(crate) fn patch_exe(&self, time_start: SystemTime) -> PathBuf {\n        let path = self.main_exe().with_file_name(format!(\n            \"lib{}-patch-{}\",\n            self.executable_name(),\n            time_start\n                .duration_since(UNIX_EPOCH)\n                .map(|f| f.as_millis())\n                .unwrap_or(0),\n        ));\n\n        let extension = match self.linker_flavor() {\n            LinkerFlavor::Darwin => \"dylib\",\n            LinkerFlavor::Gnu => \"so\",\n            LinkerFlavor::WasmLld => \"wasm\",\n            LinkerFlavor::Msvc => \"dll\",\n            LinkerFlavor::Unsupported => \"\",\n        };\n\n        path.with_extension(extension)\n    }\n\n    /// When we link together the fat binary, we need to make sure every `.o` file in *every* rlib\n    /// is taken into account. This is the same work that the rust compiler does when assembling\n    /// staticlibs.\n    ///\n    /// <https://github.com/rust-lang/rust/blob/191df20fcad9331d3a948aa8e8556775ec3fe69d/compiler/rustc_codegen_ssa/src/back/link.rs#L448>\n    ///\n    /// Since we're going to be passing these to the linker, we need to make sure and not provide any\n    /// weird files (like the rmeta) file that rustc generates.\n    ///\n    /// We discovered the need for this after running into issues with wasm-ld not being able to\n    /// handle the rmeta file.\n    ///\n    /// <https://github.com/llvm/llvm-project/issues/55786>\n    ///\n    /// Also, crates might not drag in all their dependent code. The monorphizer won't lift trait-based generics:\n    ///\n    /// <https://github.com/rust-lang/rust/blob/191df20fcad9331d3a948aa8e8556775ec3fe69d/compiler/rustc_monomorphize/src/collector.rs>\n    ///\n    /// When Rust normally handles this, it uses the +whole-archive directive which adjusts how the rlib\n    /// is written to disk.\n    ///\n    /// Since creating this object file can be a lot of work, we cache it in the target dir by hashing\n    /// the names of the rlibs in the command and storing it in the target dir. That way, when we run\n    /// this command again, we can just used the cached object file.\n    ///\n    /// In theory, we only need to do this for every crate accessible by the current crate, but that's\n    /// hard acquire without knowing the exported symbols from each crate.\n    ///\n    /// todo: I think we can traverse our immediate dependencies and inspect their symbols, unless they `pub use` a crate\n    /// todo: we should try and make this faster with memmapping\n    pub(crate) async fn run_fat_link(&self, exe: &Path, rustc_args: &RustcArgs) -> Result<()> {\n        // Filter out the rlib files from the arguments\n        let rlibs = rustc_args\n            .link_args\n            .iter()\n            .filter(|arg| arg.ends_with(\".rlib\"))\n            .map(PathBuf::from)\n            .collect::<Vec<_>>();\n\n        // Acquire a hash from the rlib names, sizes, modified times, and dx's git commit hash\n        // This ensures that any changes in dx or the rlibs will cause a new hash to be generated\n        // The hash relies on both dx and rustc hashes, so it should be thoroughly unique. Keep it\n        // short to avoid long file names.\n        let hash_id = Uuid::new_v5(\n            &Uuid::NAMESPACE_OID,\n            rlibs\n                .iter()\n                .map(|p| {\n                    format!(\n                        \"{}-{}-{}-{}\",\n                        p.file_name().unwrap().to_string_lossy(),\n                        p.metadata().map(|m| m.len()).unwrap_or_default(),\n                        p.metadata()\n                            .ok()\n                            .and_then(|m| m.modified().ok())\n                            .and_then(|f| f.duration_since(UNIX_EPOCH).map(|f| f.as_secs()).ok())\n                            .unwrap_or_default(),\n                        crate::dx_build_info::GIT_COMMIT_HASH.unwrap_or_default()\n                    )\n                })\n                .collect::<String>()\n                .as_bytes(),\n        )\n        .to_string()\n        .chars()\n        .take(8)\n        .collect::<String>();\n\n        // Check if we already have a cached object file\n        let out_ar_path = exe.with_file_name(format!(\"libdeps-{hash_id}.a\",));\n        let out_rlibs_list = exe.with_file_name(format!(\"rlibs-{hash_id}.txt\"));\n        let mut archive_has_contents = out_ar_path.exists();\n\n        // Use the rlibs list if it exists\n        let mut compiler_rlibs = std::fs::read_to_string(&out_rlibs_list)\n            .ok()\n            .map(|s| s.lines().map(PathBuf::from).collect::<Vec<_>>())\n            .unwrap_or_default();\n\n        // Create it by dumping all the rlibs into it\n        // This will include the std rlibs too, which can severely bloat the size of the archive\n        //\n        // The nature of this process involves making extremely fat archives, so we should try and\n        // speed up the future linking process by caching the archive.\n        //\n        // Since we're using the git hash for the CLI entropy, debug builds should always regenerate\n        // the archive since their hash might not change, but the logic might.\n        if !archive_has_contents || cfg!(debug_assertions) {\n            compiler_rlibs.clear();\n\n            let mut bytes = vec![];\n            let mut out_ar = ar::Builder::new(&mut bytes);\n            for rlib in &rlibs {\n                // Skip compiler rlibs since they're missing bitcode\n                //\n                // https://github.com/rust-lang/rust/issues/94232#issuecomment-1048342201\n                //\n                // if the rlib is not in the target directory, we skip it.\n                if !rlib.starts_with(self.workspace_dir()) {\n                    compiler_rlibs.push(rlib.clone());\n                    tracing::trace!(\"Skipping rlib: {:?}\", rlib);\n                    continue;\n                }\n\n                tracing::trace!(\"Adding rlib to staticlib: {:?}\", rlib);\n\n                let rlib_contents = std::fs::read(rlib)?;\n                let mut reader = ar::Archive::new(std::io::Cursor::new(rlib_contents));\n                let mut keep_linker_rlib = false;\n                while let Some(Ok(object_file)) = reader.next_entry() {\n                    let name = std::str::from_utf8(object_file.header().identifier()).unwrap();\n                    if name.ends_with(\".rmeta\") {\n                        continue;\n                    }\n\n                    if object_file.header().size() == 0 {\n                        continue;\n                    }\n\n                    // rlibs might contain dlls/sos/lib files which we don't want to include\n                    //\n                    // This catches .dylib, .so, .dll, .lib, .o, etc files that are not compatible with\n                    // our \"fat archive\" linking process.\n                    //\n                    // We only trust `.rcgu.o` files to make it into the --all_load archive.\n                    // This is a temporary stopgap to prevent issues with libraries that generate\n                    // object files that are not compatible with --all_load.\n                    // see https://github.com/DioxusLabs/dioxus/issues/4237\n                    if !(name.ends_with(\".rcgu.o\") || name.ends_with(\".obj\")) {\n                        keep_linker_rlib = true;\n                        continue;\n                    }\n\n                    archive_has_contents = true;\n                    out_ar\n                        .append(&object_file.header().clone(), object_file)\n                        .context(\"Failed to add object file to archive\")?;\n                }\n\n                // Some rlibs contain weird artifacts that we don't want to include in the fat archive.\n                // However, we still want them around in the linker in case the regular linker can handle them.\n                if keep_linker_rlib {\n                    compiler_rlibs.push(rlib.clone());\n                }\n            }\n\n            let bytes = out_ar.into_inner().context(\"Failed to finalize archive\")?;\n            std::fs::write(&out_ar_path, bytes).context(\"Failed to write archive\")?;\n            tracing::debug!(\"Wrote fat archive to {:?}\", out_ar_path);\n\n            // Run the ranlib command to index the archive. This slows down this process a bit,\n            // but is necessary for some linkers to work properly.\n            // We ignore its error in case it doesn't recognize the architecture\n            if self.linker_flavor() == LinkerFlavor::Darwin {\n                if let Some(ranlib) = Workspace::select_ranlib() {\n                    _ = Command::new(ranlib).arg(&out_ar_path).output().await;\n                }\n            }\n        }\n\n        compiler_rlibs.dedup();\n\n        // We're going to replace the first rlib in the args with our fat archive\n        // And then remove the rest of the rlibs\n        //\n        // We also need to insert the -force_load flag to force the linker to load the archive\n        let mut args: Vec<_> = rustc_args.link_args.clone();\n        if let Some(last_object) = args.iter().rposition(|arg| arg.ends_with(\".o\")) {\n            if archive_has_contents {\n                match self.linker_flavor() {\n                    LinkerFlavor::WasmLld => {\n                        args.insert(last_object, \"--whole-archive\".to_string());\n                        args.insert(last_object + 1, out_ar_path.display().to_string());\n                        args.insert(last_object + 2, \"--no-whole-archive\".to_string());\n                        args.retain(|arg| !arg.ends_with(\".rlib\"));\n                        for rlib in compiler_rlibs.iter().rev() {\n                            args.insert(last_object + 3, rlib.display().to_string());\n                        }\n                    }\n                    LinkerFlavor::Gnu => {\n                        args.insert(last_object, \"-Wl,--whole-archive\".to_string());\n                        args.insert(last_object + 1, out_ar_path.display().to_string());\n                        args.insert(last_object + 2, \"-Wl,--no-whole-archive\".to_string());\n                        args.retain(|arg| !arg.ends_with(\".rlib\"));\n                        for rlib in compiler_rlibs.iter().rev() {\n                            args.insert(last_object + 3, rlib.display().to_string());\n                        }\n                    }\n                    LinkerFlavor::Darwin => {\n                        args.insert(last_object, \"-Wl,-force_load\".to_string());\n                        args.insert(last_object + 1, out_ar_path.display().to_string());\n                        args.retain(|arg| !arg.ends_with(\".rlib\"));\n                        for rlib in compiler_rlibs.iter().rev() {\n                            args.insert(last_object + 2, rlib.display().to_string());\n                        }\n                    }\n                    LinkerFlavor::Msvc => {\n                        args.insert(\n                            last_object,\n                            format!(\"/WHOLEARCHIVE:{}\", out_ar_path.display()),\n                        );\n                        args.retain(|arg| !arg.ends_with(\".rlib\"));\n                        for rlib in compiler_rlibs.iter().rev() {\n                            args.insert(last_object + 1, rlib.display().to_string());\n                        }\n                    }\n                    LinkerFlavor::Unsupported => {\n                        tracing::error!(\"Unsupported platform for fat linking: {}\", self.triple);\n                    }\n                };\n            }\n        }\n\n        // Add custom args to the linkers\n        match self.linker_flavor() {\n            LinkerFlavor::Gnu => {\n                // Export `main` so subsecond can use it for a reference point\n                args.push(\"-Wl,--export-dynamic-symbol,main\".to_string());\n            }\n            LinkerFlavor::Darwin => {\n                args.push(\"-Wl,-exported_symbol,_main\".to_string());\n            }\n            LinkerFlavor::Msvc => {\n                // Prevent alsr from overflowing 32 bits\n                args.push(\"/HIGHENTROPYVA:NO\".to_string());\n\n                // Export `main` so subsecond can use it for a reference point\n                args.push(\"/EXPORT:main\".to_string());\n            }\n            LinkerFlavor::WasmLld | LinkerFlavor::Unsupported => {}\n        }\n\n        // We also need to remove the `-o` flag since we want the linker output to end up in the\n        // rust exe location, not in the deps dir as it normally would.\n        if let Some(idx) = args\n            .iter()\n            .position(|arg| *arg == \"-o\" || *arg == \"--output\")\n        {\n            args.remove(idx + 1);\n            args.remove(idx);\n        }\n\n        // same but windows support\n        if let Some(idx) = args.iter().position(|arg| arg.starts_with(\"/OUT\")) {\n            args.remove(idx);\n        }\n\n        // We want to go through wasm-ld directly, so we need to remove the -flavor flag\n        if let Some(flavor_idx) = args.iter().position(|arg| *arg == \"-flavor\") {\n            args.remove(flavor_idx + 1);\n            args.remove(flavor_idx);\n        }\n\n        // Note: Swift sources are now compiled as dynamic frameworks during the main build flow.\n        // Dynamic frameworks are loaded at runtime, not linked statically, so we don't add\n        // them to the linker args here. The framework will be installed to the Frameworks\n        // folder by compile_swift_sources() in the main bundle creation phase.\n        if matches!(\n            self.triple.operating_system,\n            OperatingSystem::IOS(_) | OperatingSystem::MacOSX { .. } | OperatingSystem::Darwin(_)\n        ) {\n            let workspace_dir = self.workspace_dir();\n            let swift_sources = super::ios_swift::extract_swift_metadata_from_link_args(\n                &rustc_args.link_args,\n                &workspace_dir,\n            );\n\n            if !swift_sources.is_empty() {\n                tracing::debug!(\n                    \"Found {} Swift plugin source(s) - will be compiled as dynamic framework during bundle creation\",\n                    swift_sources.len()\n                );\n            }\n        }\n\n        // Set the output file\n        match self.triple.operating_system {\n            OperatingSystem::Windows => args.push(format!(\"/OUT:{}\", exe.display())),\n            _ => args.extend([\"-o\".to_string(), exe.display().to_string()]),\n        }\n\n        // And now we can run the linker with our new args\n        let linker = self.select_linker()?;\n\n        tracing::trace!(\"Fat linking with args: {:?} {:#?}\", linker, args);\n        tracing::trace!(\"Fat linking with env:\");\n        for e in rustc_args.envs.iter() {\n            tracing::trace!(\"  {}={}\", e.0, e.1);\n        }\n\n        // Handle windows command files\n        let mut out_args = args.clone();\n        if cfg!(windows) {\n            let cmd_contents: String = out_args.iter().map(|f| format!(\"\\\"{f}\\\"\")).join(\" \");\n            std::fs::write(self.windows_command_file(), cmd_contents)\n                .context(\"Failed to write linker command file\")?;\n            out_args = vec![format!(\"@{}\", self.windows_command_file().display())];\n        }\n\n        // Add more search paths for the linker\n        let mut command_envs = rustc_args.envs.clone();\n\n        // On linux, we need to set a more complete PATH for the linker to find its libraries\n        if cfg!(target_os = \"linux\") {\n            command_envs.push((\"PATH\".to_string(), std::env::var(\"PATH\").unwrap()));\n        }\n\n        // Run the linker directly!\n        let res = Command::new(linker)\n            .args(out_args)\n            .env_clear()\n            .envs(command_envs)\n            .output()\n            .await?;\n\n        if !res.stderr.is_empty() {\n            let errs = String::from_utf8_lossy(&res.stderr);\n            if !res.status.success() {\n                tracing::error!(\n                    telemetry = %serde_json::json!({ \"event\": \"hotpatch_fat_binary_generation_failed\" }),\n                    \"Failed to generate fat binary: {}\",\n                    errs.trim()\n                );\n            } else {\n                tracing::trace!(\"Warnings during fat linking: {}\", errs.trim());\n            }\n        }\n\n        if !res.stdout.is_empty() {\n            let out = String::from_utf8_lossy(&res.stdout);\n            tracing::trace!(\"Output from fat linking: {}\", out.trim());\n        }\n\n        // Clean up the temps manually\n        for f in args.iter().filter(|arg| arg.ends_with(\".rcgu.o\")) {\n            _ = std::fs::remove_file(f);\n        }\n\n        // Cache the rlibs list\n        _ = std::fs::write(\n            &out_rlibs_list,\n            compiler_rlibs\n                .into_iter()\n                .map(|s| s.display().to_string())\n                .join(\"\\n\"),\n        );\n\n        Ok(())\n    }\n\n    pub(crate) fn create_jump_table(\n        &self,\n        patch: &Path,\n        cache: &HotpatchModuleCache,\n    ) -> Result<JumpTable> {\n        use crate::build::patch::{\n            create_native_jump_table, create_wasm_jump_table, create_windows_jump_table,\n        };\n\n        let root_dir = self.root_dir();\n        let base_path = self.base_path();\n        let triple = &self.triple;\n\n        // Symbols are stored differently based on the platform, so we need to handle them differently.\n        // - Wasm requires the walrus crate and actually modifies the patch file\n        // - windows requires the pdb crate and pdb files\n        // - nix requires the object crate\n        let mut jump_table = match triple.operating_system {\n            OperatingSystem::Windows => create_windows_jump_table(patch, cache)?,\n            _ if triple.architecture == Architecture::Wasm32 => {\n                create_wasm_jump_table(patch, cache)?\n            }\n            _ => create_native_jump_table(patch, triple, cache)?,\n        };\n\n        // root_dir: &Path,\n        //     base_path: Option<&str>,\n        // Rebase the wasm binary to be relocatable once the jump table is generated\n        if triple.architecture == target_lexicon::Architecture::Wasm32 {\n            // Make sure we use the dir relative to the public dir, so the web can load it as a proper URL\n            //\n            // ie we would've shipped `/Users/foo/Projects/dioxus/target/dx/project/debug/web/public/wasm/lib.wasm`\n            //    but we want to ship `/wasm/lib.wasm`\n            jump_table.lib = PathBuf::from(\n                \"/\".to_string() + base_path.unwrap_or_default().trim_start_matches('/'),\n            )\n            .join(jump_table.lib.strip_prefix(root_dir).unwrap())\n        }\n\n        Ok(jump_table)\n    }\n\n    /// Automatically detect the linker flavor based on the target triple and any custom linkers.\n    ///\n    /// This tries to replicate what rustc does when selecting the linker flavor based on the linker\n    /// and triple.\n    fn linker_flavor(&self) -> LinkerFlavor {\n        if let Some(custom) = self.custom_linker.as_ref() {\n            let name = custom.file_name().unwrap().to_ascii_lowercase();\n            match name.to_str() {\n                Some(\"lld-link\") => return LinkerFlavor::Msvc,\n                Some(\"lld-link.exe\") => return LinkerFlavor::Msvc,\n                Some(\"wasm-ld\") => return LinkerFlavor::WasmLld,\n                Some(\"ld64.lld\") => return LinkerFlavor::Darwin,\n                Some(\"ld.lld\") => return LinkerFlavor::Gnu,\n                Some(\"ld.gold\") => return LinkerFlavor::Gnu,\n                Some(\"mold\") => return LinkerFlavor::Gnu,\n                Some(\"sold\") => return LinkerFlavor::Gnu,\n                Some(\"wild\") => return LinkerFlavor::Gnu,\n                _ => {}\n            }\n        }\n\n        match self.triple.environment {\n            target_lexicon::Environment::Gnu\n            | target_lexicon::Environment::Gnuabi64\n            | target_lexicon::Environment::Gnueabi\n            | target_lexicon::Environment::Gnueabihf\n            | target_lexicon::Environment::GnuLlvm => LinkerFlavor::Gnu,\n            target_lexicon::Environment::Musl => LinkerFlavor::Gnu,\n            target_lexicon::Environment::Android => LinkerFlavor::Gnu,\n            target_lexicon::Environment::Msvc => LinkerFlavor::Msvc,\n            target_lexicon::Environment::Macabi => LinkerFlavor::Darwin,\n            _ => match self.triple.operating_system {\n                OperatingSystem::Darwin(_) => LinkerFlavor::Darwin,\n                OperatingSystem::IOS(_) => LinkerFlavor::Darwin,\n                OperatingSystem::MacOSX(_) => LinkerFlavor::Darwin,\n                OperatingSystem::Linux => LinkerFlavor::Gnu,\n                OperatingSystem::Windows => LinkerFlavor::Msvc,\n                _ => match self.triple.architecture {\n                    target_lexicon::Architecture::Wasm32 => LinkerFlavor::WasmLld,\n                    target_lexicon::Architecture::Wasm64 => LinkerFlavor::WasmLld,\n                    _ => LinkerFlavor::Unsupported,\n                },\n            },\n        }\n    }\n\n    /// Select the linker to use for this platform.\n    ///\n    /// We prefer to use the rust-lld linker when we can since it's usually there.\n    /// On macos, we use the system linker since macho files can be a bit finicky.\n    ///\n    /// This means we basically ignore the linker flavor that the user configured, which could\n    /// cause issues with a custom linker setup. In theory, rust translates most flags to the right\n    /// linker format.\n    fn select_linker(&self) -> Result<PathBuf, Error> {\n        if let Some(linker) = self.custom_linker.clone() {\n            return Ok(linker);\n        }\n\n        let cc = match self.linker_flavor() {\n            LinkerFlavor::WasmLld => self.workspace.wasm_ld(),\n\n            // On macOS, we use the system linker since it's usually there.\n            // We could also use `lld` here, but it might not be installed by default.\n            //\n            // Note that this is *clang*, not `lld`.\n            LinkerFlavor::Darwin => self.workspace.cc(),\n\n            // On Linux, we use the system linker since it's usually there.\n            LinkerFlavor::Gnu => self.workspace.cc(),\n\n            // On windows, instead of trying to find the system linker, we just go with the lld.link\n            // that rustup provides. It's faster and more stable then reyling on link.exe in path.\n            LinkerFlavor::Msvc => self.workspace.lld_link(),\n\n            // The rest of the platforms use `cc` as the linker which should be available in your path,\n            // provided you have build-tools setup. On mac/linux this is the default, but on Windows\n            // it requires msvc or gnu downloaded, which is a requirement to use rust anyways.\n            //\n            // The default linker might actually be slow though, so we could consider using lld or rust-lld\n            // since those are shipping by default on linux as of 1.86. Window's linker is the really slow one.\n            //\n            // https://blog.rust-lang.org/2024/05/17/enabling-rust-lld-on-linux.html\n            //\n            // Note that \"cc\" is *not* a linker. It's a compiler! The arguments we pass need to be in\n            // the form of `-Wl,<args>` for them to make it to the linker. This matches how rust does it\n            // which is confusing.\n            LinkerFlavor::Unsupported => self.workspace.cc(),\n        };\n\n        Ok(cc)\n    }\n\n    /// Assemble the `cargo rustc` / `rustc` command\n    ///\n    /// When building fat/base binaries, we use `cargo rustc`.\n    /// When building thin binaries, we use `rustc` directly.\n    ///\n    /// When processing the output of this command, you need to make sure to handle both cases which\n    /// both have different formats (but with json output for both).\n    fn build_command(&self, build_mode: &BuildMode) -> Result<Command> {\n        match build_mode {\n            // We're assembling rustc directly, so we need to be *very* careful. Cargo sets rustc's\n            // env up very particularly, and we want to match it 1:1 but with some changes.\n            //\n            // To do this, we reset the env completely, and then pass every env var that the original\n            // rustc process had 1:1.\n            //\n            // We need to unset a few things, like the RUSTC wrappers and then our special env var\n            // indicating that dx itself is the compiler. If we forget to do this, then the compiler\n            // ends up doing some recursive nonsense and dx is trying to link instead of compiling.\n            //\n            // todo: maybe rustc needs to be found on the FS instead of using the one in the path?\n            BuildMode::Thin {\n                workspace_rustc_args,\n                ..\n            } => {\n                let rustc_args = workspace_rustc_args\n                    .get(&format!(\"{}.bin\", self.tip_crate_name()))\n                    .context(\"Missing rustc args for tip crate\")?;\n\n                let mut cmd = Command::new(\"rustc\");\n                cmd.current_dir(self.workspace_dir());\n                cmd.env_clear();\n                cmd.args(rustc_args.args[1..].iter());\n                cmd.env_remove(\"RUSTC_WORKSPACE_WRAPPER\");\n                cmd.env_remove(\"RUSTC_WRAPPER\");\n                cmd.env_remove(DX_RUSTC_WRAPPER_ENV_VAR);\n                cmd.envs(\n                    self.cargo_build_env_vars(build_mode)?\n                        .iter()\n                        .map(|(k, v)| (k.as_ref(), v)),\n                );\n                cmd.arg(format!(\"-Clinker={}\", Workspace::path_to_dx()?.display()));\n\n                if self.is_wasm_or_wasi() {\n                    cmd.arg(\"-Crelocation-model=pic\");\n                }\n\n                cmd.envs(rustc_args.envs.iter().cloned());\n\n                Ok(cmd)\n            }\n\n            // For Base and Fat builds, we use a regular cargo setup, but we intercept rustc for\n            // workspace member crates to capture their args/envs for hot-patching.\n            //\n            // We use RUSTC_WORKSPACE_WRAPPER which wraps only workspace member crates, letting us\n            // capture per-crate args without interfering with external dependency compilation.\n            //\n            // We've also had a number of issues with incorrect canonicalization when passing paths\n            // through envs on windows, hence the frequent use of dunce::canonicalize.\n            _ => {\n                let mut cmd = Command::new(\"cargo\");\n\n                let env = self.cargo_build_env_vars(build_mode)?;\n                let args = self.cargo_build_arguments(build_mode);\n\n                tracing::trace!(\"Building with cargo rustc\");\n                for e in env.iter() {\n                    tracing::trace!(\": {}={}\", e.0, e.1.to_string_lossy());\n                }\n\n                for a in args.iter() {\n                    tracing::trace!(\": {}\", a);\n                }\n\n                cmd.arg(\"rustc\")\n                    .current_dir(self.crate_dir())\n                    .arg(\"--message-format\")\n                    .arg(\"json-diagnostic-rendered-ansi\")\n                    .args(args)\n                    .envs(env.iter().map(|(k, v)| (k.as_ref(), v)));\n\n                if matches!(build_mode, BuildMode::Fat | BuildMode::Base { run: true }) {\n                    let args_dir = self.rustc_wrapper_args_dir();\n                    std::fs::create_dir_all(&args_dir)\n                        .context(\"Failed to create rustc wrapper args directory\")?;\n                    cmd.env(\n                        DX_RUSTC_WRAPPER_ENV_VAR,\n                        dunce::canonicalize(&args_dir)\n                            .context(\"Failed to canonicalize rustc wrapper args dir\")?\n                            .display()\n                            .to_string(),\n                    );\n                    cmd.env(\n                        \"RUSTC_WORKSPACE_WRAPPER\",\n                        Workspace::path_to_dx()?.display().to_string(),\n                    );\n                }\n\n                Ok(cmd)\n            }\n        }\n    }\n\n    /// Create a list of arguments for cargo builds\n    ///\n    /// We always use `cargo rustc` *or* `rustc` directly. This means we can pass extra flags like\n    /// `-C` arguments directly to the compiler.\n    #[allow(clippy::vec_init_then_push)]\n    pub(crate) fn cargo_build_arguments(&self, build_mode: &BuildMode) -> Vec<String> {\n        let mut cargo_args = Vec::with_capacity(4);\n\n        // Set the `--config profile.{profile}.{key}={value}` flags for the profile, filling in adhoc profile\n        cargo_args.extend(self.profile_args());\n\n        // Add required profile flags. --release overrides any custom profiles.\n        cargo_args.push(\"--profile\".to_string());\n        cargo_args.push(self.profile.to_string());\n\n        // Pass the appropriate target to cargo. We *always* specify a target which is somewhat helpful for preventing thrashing\n        cargo_args.push(\"--target\".to_string());\n        cargo_args.push(self.triple.to_string());\n\n        // We always run in verbose since the CLI itself is the one doing the presentation\n        cargo_args.push(\"--verbose\".to_string());\n\n        if self.no_default_features {\n            cargo_args.push(\"--no-default-features\".to_string());\n        }\n\n        if self.all_features {\n            cargo_args.push(\"--all-features\".to_string());\n        }\n\n        if !self.features.is_empty() {\n            cargo_args.push(\"--features\".to_string());\n            cargo_args.push(self.features.join(\" \"));\n        }\n\n        // We *always* set the package since that's discovered from cargo metadata\n        cargo_args.push(String::from(\"-p\"));\n        cargo_args.push(self.package.clone());\n\n        // Set the executable\n        match self.executable_type() {\n            TargetKind::Bin => cargo_args.push(\"--bin\".to_string()),\n            TargetKind::Lib => cargo_args.push(\"--lib\".to_string()),\n            TargetKind::Example => cargo_args.push(\"--example\".to_string()),\n            _ => {}\n        };\n        cargo_args.push(self.executable_name().to_string());\n\n        // Set offline/locked/frozen\n        let lock_opts = crate::verbosity_or_default();\n        if lock_opts.frozen {\n            cargo_args.push(\"--frozen\".to_string());\n        }\n        if lock_opts.locked {\n            cargo_args.push(\"--locked\".to_string());\n        }\n        if lock_opts.offline {\n            cargo_args.push(\"--offline\".to_string());\n        }\n\n        // Merge in extra args. Order shouldn't really matter.\n        cargo_args.extend(self.extra_cargo_args.clone());\n        cargo_args.push(\"--\".to_string());\n        cargo_args.extend(self.extra_rustc_args.clone());\n\n        // On windows, we pass /SUBSYSTEM:WINDOWS to prevent a console from appearing\n        if matches!(self.bundle, BundleFormat::Windows)\n            && !self\n                .rustflags\n                .flags\n                .iter()\n                .any(|f| f.starts_with(\"-Clink-arg=/SUBSYSTEM:\"))\n        {\n            let subsystem = self\n                .windows_subsystem\n                .clone()\n                .unwrap_or_else(|| \"WINDOWS\".to_string());\n\n            cargo_args.push(format!(\"-Clink-arg=/SUBSYSTEM:{}\", subsystem));\n            // We also need to set the entry point to mainCRTStartup to avoid windows looking\n            // for a WinMain function\n            cargo_args.push(\"-Clink-arg=/ENTRY:mainCRTStartup\".to_string());\n        }\n\n        // The bundle splitter needs relocation data to create a call-graph.\n        // This will automatically be erased by wasm-opt during the optimization step.\n        if self.bundle == BundleFormat::Web && self.wasm_split {\n            cargo_args.push(\"-Clink-args=--emit-relocs\".to_string());\n        }\n\n        // dx links android, thin builds, and fat builds with a custom linker.\n        // Note: We don't intercept Darwin Base builds since Swift plugins are compiled as dynamic\n        // frameworks that load at runtime, not linked statically into the binary.\n        let use_dx_linker = self.custom_linker.is_some()\n            || matches!(build_mode, BuildMode::Thin { .. } | BuildMode::Fat);\n\n        if use_dx_linker {\n            cargo_args.push(format!(\n                \"-Clinker={}\",\n                Workspace::path_to_dx().expect(\"can't find dx\").display()\n            ));\n        }\n\n        // for debuggability, we need to make sure android studio can properly understand our build\n        // https://stackoverflow.com/questions/68481401/debugging-a-prebuilt-shared-library-in-android-studio\n        if self.bundle == BundleFormat::Android {\n            cargo_args.push(\"-Clink-arg=-Wl,--build-id=sha1\".to_string());\n        }\n\n        // Handle frameworks/dylibs by setting the rpath\n        // This is dependent on the bundle structure - iOS uses a flat structure while macOS uses nested\n        // todo: we need to figure out what to do for windows\n        match self.triple.operating_system {\n            OperatingSystem::Darwin(_) | OperatingSystem::MacOSX { .. } => {\n                // macOS: App.app/Contents/MacOS/exe -> ../Frameworks/\n                cargo_args.push(\"-Clink-arg=-Wl,-rpath,@executable_path/../Frameworks\".to_string());\n                cargo_args.push(\"-Clink-arg=-Wl,-rpath,@executable_path\".to_string());\n            }\n            OperatingSystem::IOS(_) => {\n                // iOS: App.app/exe -> Frameworks/ (flat bundle structure)\n                cargo_args.push(\"-Clink-arg=-Wl,-rpath,@executable_path/Frameworks\".to_string());\n                cargo_args.push(\"-Clink-arg=-Wl,-rpath,@executable_path\".to_string());\n            }\n            OperatingSystem::Linux => {\n                cargo_args.push(\"-Clink-arg=-Wl,-rpath,$ORIGIN/../lib\".to_string());\n                cargo_args.push(\"-Clink-arg=-Wl,-rpath,$ORIGIN\".to_string());\n            }\n            _ => {}\n        }\n\n        // Our fancy hot-patching engine needs a lot of customization to work properly.\n        //\n        // These args are mostly intended to be passed when *fat* linking but are generally fine to\n        // pass for both fat and thin linking.\n        //\n        // We need save-temps and no-dead-strip in both cases though. When we run `cargo rustc` with\n        // these args, they will be captured and re-ran for the fast compiles in the future, so whatever\n        // we set here will be set for all future hot patches too.\n        if matches!(build_mode, BuildMode::Thin { .. } | BuildMode::Fat) {\n            // rustc gives us some portable flags required:\n            // - link-dead-code: prevents rust from passing -dead_strip to the linker since that's the default.\n            // - save-temps=true: keeps the incremental object files around, which we need for manually linking.\n            cargo_args.extend_from_slice(&[\n                \"-Csave-temps=true\".to_string(),\n                \"-Clink-dead-code\".to_string(),\n            ]);\n\n            // We need to set some extra args that ensure all symbols make it into the final output\n            // and that the linker doesn't strip them out.\n            //\n            // This basically amounts of -all_load or --whole-archive, depending on the linker.\n            // We just assume an ld-like interface on macos and a gnu-ld interface elsewhere.\n            //\n            // macOS/iOS use ld64 but through the `cc` interface.\n            // cargo_args.push(\"-Clink-args=-Wl,-all_load\".to_string());\n            //\n            // Linux and Android fit under this umbrella, both with the same clang-like entrypoint\n            // and the gnu-ld interface.\n            //\n            // cargo_args.push(\"-Clink-args=-Wl,--whole-archive\".to_string());\n            //\n            // If windows -Wl,--whole-archive is required since it follows gnu-ld convention.\n            // There might be other flags on windows - we haven't tested windows thoroughly.\n            //\n            // cargo_args.push(\"-Clink-args=-Wl,--whole-archive\".to_string());\n            // https://learn.microsoft.com/en-us/cpp/build/reference/wholearchive-include-all-library-object-files?view=msvc-170\n            //\n            // ------------------------------------------------------------\n            //\n            // if web, -Wl,--whole-archive is required since it follows gnu-ld convention.\n            //\n            // We also use --no-gc-sections and --export-table and --export-memory  to push\n            // said symbols into the export table.\n            //\n            // We use --emit-relocs to build up a solid call graph.\n            //\n            // rust uses its own wasm-ld linker which can be found here (it's just gcc-ld with a `-target wasm` flag):\n            // - ~/.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/aarch64-apple-darwin/bin/gcc-ld\n            // - ~/.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/aarch64-apple-darwin/bin/gcc-ld/wasm-ld\n            //\n            // Note that we can't use --export-all, unfortunately, since some symbols are internal\n            // to wasm-bindgen and exporting them causes the JS generation to fail.\n            //\n            // We are basically replicating what emscripten does here with its dynamic linking\n            // approach where the MAIN_MODULE is very \"fat\" and exports the necessary arguments\n            // for the side modules to be linked in. This guide is really helpful:\n            //\n            // https://github.com/WebAssembly/tool-conventions/blob/main/DynamicLinking.md\n            //\n            // The tricky one is -Ctarget-cpu=mvp, which prevents rustc from generating externref\n            // entries.\n            //\n            // https://blog.rust-lang.org/2024/09/24/webassembly-targets-change-in-default-target-features/#disabling-on-by-default-webassembly-proposals\n            //\n            // It's fine that these exist in the base module but not in the patch.\n            if matches!(\n                self.triple.architecture,\n                target_lexicon::Architecture::Wasm32 | target_lexicon::Architecture::Wasm64\n            ) || self.triple.operating_system == OperatingSystem::Wasi\n            {\n                // cargo_args.push(\"-Ctarget-cpu=mvp\".into()); // disabled due to changes in wasm-bindgne\n                cargo_args.push(\"-Clink-arg=--no-gc-sections\".into());\n                cargo_args.push(\"-Clink-arg=--growable-table\".into());\n                cargo_args.push(\"-Clink-arg=--export-table\".into());\n                cargo_args.push(\"-Clink-arg=--export-memory\".into());\n                cargo_args.push(\"-Clink-arg=--emit-relocs\".into());\n                cargo_args.push(\"-Clink-arg=--export=__stack_pointer\".into());\n                cargo_args.push(\"-Clink-arg=--export=__heap_base\".into());\n                cargo_args.push(\"-Clink-arg=--export=__data_end\".into());\n            }\n        }\n\n        cargo_args\n    }\n\n    pub(crate) fn cargo_build_env_vars(\n        &self,\n        build_mode: &BuildMode,\n    ) -> Result<Vec<(Cow<'static, str>, OsString)>> {\n        let mut env_vars = vec![];\n\n        // Make sure to set all the crazy android flags. Cross-compiling is hard, man.\n        if self.bundle == BundleFormat::Android {\n            env_vars.extend(self.android_env_vars()?);\n        };\n\n        // If this is a release build, bake the base path and title into the binary with env vars.\n        // todo: should we even be doing this? might be better being a build.rs or something else.\n        if self.release {\n            if let Some(base_path) = self.trimmed_base_path() {\n                env_vars.push((ASSET_ROOT_ENV.into(), base_path.to_string().into()));\n            }\n            env_vars.push((\n                APP_TITLE_ENV.into(),\n                self.config.web.app.title.clone().into(),\n            ));\n            env_vars.push((PRODUCT_NAME_ENV.into(), self.bundled_app_name().into()));\n        }\n\n        // Assemble the rustflags by peering into the `.cargo/config.toml` file\n        let rust_flags = self.rustflags.clone();\n\n        // seems like this is fixed?\n        // // Disable reference types on wasm when using hotpatching\n        // // https://blog.rust-lang.org/2024/09/24/webassembly-targets-change-in-default-target-features/#disabling-on-by-default-webassembly-proposals\n        // if self.is_wasm_or_wasi() && matches!(build_mode, BuildMode::Thin { .. } | BuildMode::Fat) {\n        //     rust_flags.flags.push(\"-Ctarget-cpu=mvp\".to_string());\n        // }\n\n        // Set the rust flags for the build if they're not empty.\n        if !rust_flags.flags.is_empty() {\n            env_vars.push((\n                \"RUSTFLAGS\".into(),\n                rust_flags\n                    .encode_space_separated()\n                    .context(\"Failed to encode RUSTFLAGS\")?\n                    .into(),\n            ));\n        }\n\n        // If we're either zero-linking or using a custom linker, make `dx` itself do the linking.\n        // Note: We don't intercept Darwin Base builds since Swift plugins are compiled as dynamic\n        // frameworks that load at runtime, not linked statically into the binary.\n        let use_dx_linker = self.custom_linker.is_some()\n            || matches!(build_mode, BuildMode::Thin { .. } | BuildMode::Fat);\n\n        if use_dx_linker {\n            // For Android, we pass the actual linker so cargo can still link normally.\n            // For Fat/Thin builds, we use no-link mode (linker = None).\n            LinkAction {\n                triple: self.triple.clone(),\n                linker: self.custom_linker.clone(),\n                link_err_file: dunce::canonicalize(self.link_err_file())?,\n                link_args_file: dunce::canonicalize(self.link_args_file())?,\n            }\n            .write_env_vars(&mut env_vars)?;\n        }\n\n        Ok(env_vars)\n    }\n\n    /// Set the environment variables required for building on Android.\n    ///\n    /// This involves setting sysroots, CC, CXX, AR, and other environment variables along with\n    /// vars that cc-rs uses for its C/C++ compilation.\n    ///\n    /// We pulled the environment setup from `cargo ndk` and attempt to mimic its behavior to retain\n    /// compatibility with existing crates that work with `cargo ndk`.\n    ///\n    /// <https://github.com/bbqsrc/cargo-ndk/blob/1d1a6dc70a99b7f95bc71ed07bf893ef37966efc/src/cargo.rs#L97-L102>\n    ///\n    /// cargo-ndk is MIT licensed.\n    ///\n    /// <https://github.com/bbqsrc/cargo-ndk>\n    fn android_env_vars(&self) -> Result<Vec<(Cow<'static, str>, OsString)>> {\n        // Derived from getenv_with_target_prefixes in `cc` crate.\n        fn cc_env(var_base: &str, triple: &str) -> (String, Option<String>) {\n            #[inline]\n            fn env_var_with_key(key: String) -> Option<(String, String)> {\n                std::env::var(&key).map(|value| (key, value)).ok()\n            }\n\n            let triple_u = triple.replace('-', \"_\");\n            let most_specific_key = format!(\"{}_{}\", var_base, triple);\n\n            env_var_with_key(most_specific_key.to_string())\n                .or_else(|| env_var_with_key(format!(\"{}_{}\", var_base, triple_u)))\n                .or_else(|| env_var_with_key(format!(\"TARGET_{}\", var_base)))\n                .or_else(|| env_var_with_key(var_base.to_string()))\n                .map(|(key, value)| (key, Some(value)))\n                .unwrap_or_else(|| (most_specific_key, None))\n        }\n\n        fn cargo_env_target_cfg(triple: &str, key: &str) -> String {\n            format!(\"CARGO_TARGET_{}_{}\", &triple.replace('-', \"_\"), key).to_uppercase()\n        }\n\n        fn clang_target(rust_target: &str, api_level: u8) -> String {\n            let target = match rust_target {\n                \"arm-linux-androideabi\" => \"armv7a-linux-androideabi\",\n                \"armv7-linux-androideabi\" => \"armv7a-linux-androideabi\",\n                _ => rust_target,\n            };\n            format!(\"--target={target}{api_level}\")\n        }\n\n        fn sysroot_target(rust_target: &str) -> &str {\n            (match rust_target {\n                \"armv7-linux-androideabi\" => \"arm-linux-androideabi\",\n                _ => rust_target,\n            }) as _\n        }\n        fn rt_builtins(rust_target: &str) -> &str {\n            (match rust_target {\n                \"armv7-linux-androideabi\" => \"arm\",\n                \"aarch64-linux-android\" => \"aarch64\",\n                \"i686-linux-android\" => \"i686\",\n                \"x86_64-linux-android\" => \"x86_64\",\n                _ => rust_target,\n            }) as _\n        }\n\n        let mut env_vars: Vec<(Cow<'static, str>, OsString)> = vec![];\n\n        let min_sdk_version = self.min_sdk_version_or_default();\n\n        let tools = self.workspace.android_tools()?;\n        let linker = tools.android_cc(&self.triple, min_sdk_version);\n        let ar_path = tools.ar_path();\n        let target_cc = tools.target_cc();\n        let target_cxx = tools.target_cxx();\n        let java_home = tools.java_home();\n        let ndk_home = tools.ndk.clone();\n        let sdk_root = tools.sdk();\n        let artifact_dir = self.android_artifact_dir()?;\n        tracing::debug!(\n            r#\"Using android:\n            min_sdk_version: {min_sdk_version}\n            linker: {linker:?}\n            ar_path: {ar_path:?}\n            target_cc: {target_cc:?}\n            target_cxx: {target_cxx:?}\n            java_home: {java_home:?}\n            sdk_root: {sdk_root:?}\n            artifact_dir: {artifact_dir:?}\n            \"#\n        );\n\n        if let Some(java_home) = &java_home {\n            tracing::debug!(\"Setting JAVA_HOME to {java_home:?}\");\n            env_vars.push((\"JAVA_HOME\".into(), java_home.clone().into_os_string()));\n            env_vars.push((\n                \"DX_ANDROID_JAVA_HOME\".into(),\n                java_home.clone().into_os_string(),\n            ));\n        }\n\n        env_vars.push((\n            \"DX_ANDROID_ARTIFACT_DIR\".into(),\n            artifact_dir.into_os_string(),\n        ));\n        env_vars.push((\n            \"DX_ANDROID_NDK_HOME\".into(),\n            ndk_home.clone().into_os_string(),\n        ));\n        env_vars.push((\n            \"DX_ANDROID_SDK_ROOT\".into(),\n            sdk_root.clone().into_os_string(),\n        ));\n        env_vars.push((\"ANDROID_NDK_HOME\".into(), ndk_home.clone().into_os_string()));\n        env_vars.push((\"ANDROID_SDK_ROOT\".into(), sdk_root.clone().into_os_string()));\n        env_vars.push((\"ANDROID_HOME\".into(), sdk_root.into_os_string()));\n        env_vars.push((\"NDK_HOME\".into(), ndk_home.clone().into_os_string()));\n\n        let triple = self.triple.to_string();\n\n        // Environment variables for the `cc` crate\n        let (cc_key, _cc_value) = cc_env(\"CC\", &triple);\n        let (cflags_key, cflags_value) = cc_env(\"CFLAGS\", &triple);\n        let (cxx_key, _cxx_value) = cc_env(\"CXX\", &triple);\n        let (cxxflags_key, cxxflags_value) = cc_env(\"CXXFLAGS\", &triple);\n        let (ar_key, _ar_value) = cc_env(\"AR\", &triple);\n        let (ranlib_key, _ranlib_value) = cc_env(\"RANLIB\", &triple);\n\n        // Environment variables for cargo\n        let cargo_ar_key = cargo_env_target_cfg(&triple, \"ar\");\n        let cargo_rust_flags_key = cargo_env_target_cfg(&triple, \"rustflags\");\n        let bindgen_clang_args_key =\n            format!(\"BINDGEN_EXTRA_CLANG_ARGS_{}\", &triple.replace('-', \"_\"));\n\n        let clang_target = clang_target(&self.triple.to_string(), min_sdk_version as _);\n        let target_cc = tools.target_cc();\n        let target_cflags = match cflags_value {\n            Some(v) => format!(\"{clang_target} {v}\"),\n            None => clang_target.to_string(),\n        };\n        let target_cxx = tools.target_cxx();\n        let target_cxxflags = match cxxflags_value {\n            Some(v) => format!(\"{clang_target} {v}\"),\n            None => clang_target.to_string(),\n        };\n        let cargo_ndk_sysroot_path_key = \"CARGO_NDK_SYSROOT_PATH\";\n        let cargo_ndk_sysroot_path = tools.sysroot();\n        let cargo_ndk_sysroot_target_key = \"CARGO_NDK_SYSROOT_TARGET\";\n        let cargo_ndk_sysroot_target = sysroot_target(&triple);\n        let cargo_ndk_sysroot_libs_path_key = \"CARGO_NDK_SYSROOT_LIBS_PATH\";\n        let cargo_ndk_sysroot_libs_path = cargo_ndk_sysroot_path\n            .join(\"usr\")\n            .join(\"lib\")\n            .join(cargo_ndk_sysroot_target);\n        let target_ar = tools.ar_path();\n        let target_ranlib = tools.ranlib();\n        let clang_folder = tools.clang_folder();\n\n        // choose the clang target with the highest version\n        // Should we filter for only numbers?\n        let clang_rt = std::fs::read_dir(&clang_folder)\n            .map(|dir| {\n                let clang_builtins_target = dir\n                    .filter_map(|a| a.ok())\n                    .max_by(|a, b| a.file_name().cmp(&b.file_name()))\n                    .map(|s| s.path())\n                    .unwrap_or_else(|| clang_folder.join(\"clang\"));\n\n                format!(\n                    \"-L{} -lstatic=clang_rt.builtins-{}-android\",\n                    clang_builtins_target.join(\"lib\").join(\"linux\").display(),\n                    rt_builtins(&triple)\n                )\n            })\n            .unwrap_or_default();\n\n        let extra_include: String = format!(\n            \"{}/usr/include/{}\",\n            &cargo_ndk_sysroot_path.display(),\n            &cargo_ndk_sysroot_target\n        );\n\n        let bindgen_args = format!(\n            \"--sysroot={} -I{}\",\n            &cargo_ndk_sysroot_path.display(),\n            extra_include\n        );\n\n        // Load up the OpenSSL environment variables, using our defaults if not set.\n        // if the user specifies `/vendor`, then they get vendored, unless OPENSSL_NO_VENDOR is passed (implicitly...)\n        let openssl_lib_dir = std::env::var(\"OPENSSL_LIB_DIR\")\n            .map(PathBuf::from)\n            .unwrap_or_else(|_| AndroidTools::openssl_lib_dir(&self.triple));\n        let openssl_include_dir = std::env::var(\"OPENSSL_INCLUDE_DIR\")\n            .map(PathBuf::from)\n            .unwrap_or_else(|_| AndroidTools::openssl_include_dir());\n        let openssl_libs =\n            std::env::var(\"OPENSSL_LIBS\").unwrap_or_else(|_| \"ssl:crypto\".to_string());\n\n        for env in [\n            (cc_key, target_cc.clone().into_os_string()),\n            (cflags_key, target_cflags.into()),\n            (cxx_key, target_cxx.into_os_string()),\n            (cxxflags_key, target_cxxflags.into()),\n            (ar_key, target_ar.clone().into()),\n            (ranlib_key, target_ranlib.into_os_string()),\n            (cargo_ar_key, target_ar.into_os_string()),\n            (\n                cargo_ndk_sysroot_path_key.to_string(),\n                cargo_ndk_sysroot_path.clone().into_os_string(),\n            ),\n            (\n                cargo_ndk_sysroot_libs_path_key.to_string(),\n                cargo_ndk_sysroot_libs_path.into_os_string(),\n            ),\n            (\n                cargo_ndk_sysroot_target_key.to_string(),\n                cargo_ndk_sysroot_target.into(),\n            ),\n            (cargo_rust_flags_key, clang_rt.into()),\n            (bindgen_clang_args_key, bindgen_args.into()),\n            (\n                \"ANDROID_NATIVE_API_LEVEL\".to_string(),\n                min_sdk_version.to_string().into(),\n            ),\n            (\n                format!(\n                    \"CARGO_TARGET_{}_LINKER\",\n                    self.triple\n                        .to_string()\n                        .to_ascii_uppercase()\n                        .replace(\"-\", \"_\")\n                ),\n                linker.into_os_string(),\n            ),\n            (\n                \"ANDROID_NDK_ROOT\".to_string(),\n                ndk_home.clone().into_os_string(),\n            ),\n            (\n                \"OPENSSL_LIB_DIR\".to_string(),\n                openssl_lib_dir.into_os_string(),\n            ),\n            (\n                \"OPENSSL_INCLUDE_DIR\".to_string(),\n                openssl_include_dir.into_os_string(),\n            ),\n            (\"OPENSSL_LIBS\".to_string(), openssl_libs.into()),\n            // Set the wry env vars - this is where wry will dump its kotlin files.\n            // Their setup is really annoying and requires us to hardcode `dx` to specific versions of tao/wry.\n            (\n                \"WRY_ANDROID_PACKAGE\".to_string(),\n                \"dev.dioxus.main\".to_string().into(),\n            ),\n            (\n                \"WRY_ANDROID_LIBRARY\".to_string(),\n                \"dioxusmain\".to_string().into(),\n            ),\n            (\"WRY_ANDROID_KOTLIN_FILES_OUT_DIR\".to_string(), {\n                let kotlin_dir = self.wry_android_kotlin_files_out_dir();\n                // Ensure the directory exists for WRY's canonicalize check\n                if let Err(e) = std::fs::create_dir_all(&kotlin_dir) {\n                    tracing::error!(\"Failed to create kotlin directory {:?}: {}\", kotlin_dir, e);\n                    return Err(anyhow::anyhow!(\"Failed to create kotlin directory: {}\", e));\n                }\n                tracing::debug!(\"Created kotlin directory: {:?}\", kotlin_dir);\n                kotlin_dir.into_os_string()\n            }),\n            // Found this through a comment related to bindgen using the wrong clang for cross compiles\n            //\n            // https://github.com/rust-lang/rust-bindgen/issues/2962#issuecomment-2438297124\n            //\n            // https://github.com/KyleMayes/clang-sys?tab=readme-ov-file#environment-variables\n            (\"CLANG_PATH\".into(), target_cc.with_extension(\"exe\").into()),\n        ] {\n            env_vars.push((env.0.into(), env.1));\n        }\n\n        if std::env::var(\"MSYSTEM\").is_ok() || std::env::var(\"CYGWIN\").is_ok() {\n            for var in env_vars.iter_mut() {\n                // Convert windows paths to unix-style paths\n                // This is a workaround for the fact that the `cc` crate expects unix-style paths\n                // and will fail if it encounters windows-style paths.\n                var.1 = var.1.to_string_lossy().replace('\\\\', \"/\").into();\n            }\n        }\n\n        Ok(env_vars)\n    }\n\n    fn android_artifact_dir(&self) -> Result<PathBuf> {\n        let dir = self\n            .internal_out_dir()\n            .join(&self.main_target)\n            .join(if self.release { \"release\" } else { \"debug\" })\n            .join(\"android-artifacts\")\n            .join(self.triple.to_string());\n        std::fs::create_dir_all(&dir)?;\n        Ok(dir)\n    }\n\n    /// Get an estimate of the number of units in the crate. If nightly rustc is not available, this\n    /// will return an estimate of the number of units in the crate based on cargo metadata.\n    ///\n    /// TODO: always use <https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#unit-graph> once it is stable\n    async fn get_unit_count_estimate(&self, build_mode: &BuildMode) -> usize {\n        // Try to get it from nightly\n        if let Ok(count) = self.get_unit_count(build_mode).await {\n            return count;\n        }\n\n        // Otherwise, use cargo metadata\n        let units = self\n            .workspace\n            .krates\n            .krates_filtered(krates::DepKind::Dev)\n            .iter()\n            .map(|k| k.targets.len())\n            .sum::<usize>();\n\n        (units as f64 / 3.5) as usize\n    }\n\n    /// Try to get the unit graph for the crate. This is a nightly only feature which may not be\n    /// available with the current version of rustc the user has installed.\n    ///\n    /// It also might not be super reliable - I think in practice it occasionally returns 2x the units.\n    async fn get_unit_count(&self, build_mode: &BuildMode) -> crate::Result<usize> {\n        #[derive(Debug, Deserialize)]\n        struct UnitGraph {\n            units: Vec<serde_json::Value>,\n        }\n\n        let output = tokio::process::Command::new(\"cargo\")\n            .arg(\"+nightly\")\n            .arg(\"rustc\")\n            .arg(\"--unit-graph\")\n            .arg(\"-Z\")\n            .arg(\"unstable-options\")\n            .args(self.cargo_build_arguments(build_mode))\n            .envs(\n                self.cargo_build_env_vars(build_mode)?\n                    .iter()\n                    .map(|(k, v)| (k.as_ref(), v)),\n            )\n            .output()\n            .await?;\n\n        if !output.status.success() {\n            tracing::trace!(\n                \"Failed to get unit count: {}\",\n                String::from_utf8_lossy(&output.stderr)\n            );\n            bail!(\"Failed to get unit count\");\n        }\n\n        let output_text = String::from_utf8(output.stdout).context(\"Failed to get unit count\")?;\n        let graph: UnitGraph =\n            serde_json::from_str(&output_text).context(\"Failed to get unit count\")?;\n\n        Ok(graph.units.len())\n    }\n\n    pub(crate) fn all_target_features(&self) -> Vec<String> {\n        let mut features = self.features.clone();\n\n        if !self.no_default_features {\n            features.extend(\n                self.package()\n                    .features\n                    .get(\"default\")\n                    .cloned()\n                    .unwrap_or_default(),\n            );\n        }\n\n        features.dedup();\n\n        features\n    }\n\n    /// returns the path to root build folder. This will be our working directory for the build.\n    ///\n    /// we only add an extension to the folders where it sorta matters that it's named with the extension.\n    /// for example, on mac, the `.app` indicates we can `open` it and it pulls in icons, dylibs, etc.\n    ///\n    /// for our simulator-based platforms, this is less important since they need to be zipped up anyways\n    /// to run in the simulator.\n    ///\n    /// For windows/linux, it's also not important since we're just running the exe directly out of the folder\n    ///\n    /// The idea of this folder is that we can run our top-level build command against it and we'll get\n    /// a final build output somewhere. Some platforms have basically no build command, and can simply\n    /// be ran by executing the exe directly.\n    pub(crate) fn root_dir(&self) -> PathBuf {\n        let platform_dir = self.platform_dir();\n\n        match self.bundle {\n            BundleFormat::Web => platform_dir.join(\"public\"),\n            BundleFormat::Server => platform_dir.clone(), // ends up *next* to the public folder\n\n            // These might not actually need to be called `.app` but it does let us run these with `open`\n            BundleFormat::MacOS => platform_dir.join(format!(\"{}.app\", self.bundled_app_name())),\n            BundleFormat::Ios => platform_dir.join(format!(\"{}.app\", self.bundled_app_name())),\n\n            // in theory, these all could end up directly in the root dir\n            BundleFormat::Android => platform_dir.join(\"app\"), // .apk (after bundling)\n            BundleFormat::Linux => platform_dir.join(\"app\"),   // .appimage (after bundling)\n            BundleFormat::Windows => platform_dir.join(\"app\"), // .exe (after bundling)\n        }\n    }\n\n    /// Create a workdir for the given platform\n    /// This can be used as a temporary directory for the build, but in an observable way such that\n    /// you can see the files in the directory via `target`\n    ///\n    /// target/dx/build/app/web/\n    /// target/dx/build/app/web/public/\n    /// target/dx/build/app/web/server.exe\n    fn platform_dir(&self) -> PathBuf {\n        self.internal_out_dir()\n            .join(&self.main_target)\n            .join(if self.release { \"release\" } else { \"debug\" })\n            .join(self.bundle.build_folder_name())\n    }\n\n    fn platform_exe_name(&self) -> String {\n        match self.bundle {\n            // mac/ios are unixy and dont have an exe extension\n            BundleFormat::MacOS | BundleFormat::Ios => self.executable_name().to_string(),\n\n            // \"server\" and windows can be the same\n            BundleFormat::Server | BundleFormat::Windows => match self.triple.operating_system {\n                OperatingSystem::Windows => format!(\"{}.exe\", self.executable_name()),\n                _ => self.executable_name().to_string(),\n            },\n\n            // from the apk spec, the root exe is a shared library\n            // we include the user's rust code as a shared library with a fixed namespace\n            BundleFormat::Android => \"libdioxusmain.so\".to_string(),\n\n            // this will be wrong, I think, but not important?\n            BundleFormat::Web => format!(\"{}_bg.wasm\", self.executable_name()),\n\n            // todo: maybe this should be called AppRun?\n            BundleFormat::Linux => self.executable_name().to_string(),\n        }\n    }\n\n    /// Assemble the android app dir.\n    ///\n    /// This is a bit of a mess since we need to create a lot of directories and files. Other approaches\n    /// would be to unpack some zip folder or something stored via `include_dir!()`. However, we do\n    /// need to customize the whole setup a bit, so it's just simpler (though messier) to do it this way.\n    fn build_android_app_dir(&self) -> Result<()> {\n        use std::fs::{create_dir_all, write};\n        let root = self.root_dir();\n\n        // gradle\n        let wrapper = root.join(\"gradle\").join(\"wrapper\");\n        create_dir_all(&wrapper)?;\n\n        // app\n        let app = root.join(\"app\");\n        let app_main = app.join(\"src\").join(\"main\");\n        let app_kotlin = app_main.join(\"kotlin\");\n        let app_java = app_main.join(\"java\");\n        let app_jnilibs = app_main.join(\"jniLibs\");\n        let app_assets = app_main.join(\"assets\");\n        let app_kotlin_out = self.wry_android_kotlin_files_out_dir();\n        create_dir_all(&app)?;\n        create_dir_all(&app_main)?;\n        create_dir_all(&app_kotlin)?;\n        create_dir_all(&app_java)?;\n        create_dir_all(&app_jnilibs)?;\n        create_dir_all(&app_assets)?;\n        create_dir_all(&app_kotlin_out)?;\n\n        tracing::debug!(\n            r#\"Initialized android dirs:\n- gradle:              {wrapper:?}\n- app/                 {app:?}\n- app/src:             {app_main:?}\n- app/src/kotlin:      {app_kotlin:?}\n- app/src/jniLibs:     {app_jnilibs:?}\n- app/src/assets:      {app_assets:?}\n- app/src/kotlin/main: {app_kotlin_out:?}\n\"#\n        );\n\n        // handlebars\n        #[derive(Serialize)]\n        struct AndroidHandlebarsObjects {\n            application_id: String,\n            app_name: String,\n            version: String,\n            android_bundle: Option<crate::AndroidSettings>,\n            /// Android SDK version settings\n            min_sdk: u32,\n            target_sdk: u32,\n            compile_sdk: u32,\n            /// Android permission strings (e.g., \"android.permission.CAMERA\")\n            permissions: Vec<String>,\n            /// Android hardware features (e.g., \"android.hardware.location.gps\")\n            features: Vec<String>,\n            /// Raw manifest XML to inject\n            raw_manifest: String,\n            /// URL schemes for deep linking\n            url_schemes: Vec<String>,\n            /// App link hosts for auto-verified deep links\n            app_link_hosts: Vec<String>,\n            /// Pipe-joined foreground service type string (e.g., \"location|mediaPlayback\")\n            foreground_service_type: String,\n            /// Extra Gradle dependencies from [android] config\n            gradle_dependencies: Vec<String>,\n            /// Extra Gradle plugins from [android] config\n            gradle_plugins: Vec<String>,\n            /// Application-level manifest attributes from [android.application]\n            uses_cleartext_traffic: Option<bool>,\n            app_theme: Option<String>,\n            supports_rtl: Option<bool>,\n            large_heap: Option<bool>,\n        }\n\n        // Get permission mapper from config\n        let mapper = super::manifest_mapper::ManifestMapper::from_config(\n            &self.config.permissions,\n            &self.config.deep_links,\n            &self.config.background,\n            &self.config.android,\n            &self.config.ios,\n            &self.config.macos,\n        );\n\n        // Collect Android permissions\n        let permissions: Vec<String> = mapper\n            .android_permissions\n            .iter()\n            .map(|p| p.permission.clone())\n            .collect();\n\n        // Collect Android features from config\n        let features = self.config.android.features.clone();\n\n        // Get raw manifest XML\n        let raw_manifest = self.config.android.raw.manifest.clone().unwrap_or_default();\n\n        // Foreground service types as pipe-separated string\n        let foreground_service_type = mapper.android_foreground_service_types.join(\"|\");\n\n        let hbs_data = AndroidHandlebarsObjects {\n            application_id: self.bundle_identifier(),\n            app_name: self.bundled_app_name(),\n            version: self.crate_version(),\n            android_bundle: self.config.bundle.android.clone(),\n            min_sdk: self.config.android.min_sdk.unwrap_or(24),\n            target_sdk: self.config.android.target_sdk.unwrap_or(34),\n            compile_sdk: self.config.android.compile_sdk.unwrap_or(34),\n            permissions,\n            features,\n            raw_manifest,\n            url_schemes: mapper.android_url_schemes,\n            app_link_hosts: mapper.android_app_link_hosts,\n            foreground_service_type,\n            gradle_dependencies: self.config.android.gradle_dependencies.clone(),\n            gradle_plugins: self.config.android.gradle_plugins.clone(),\n            uses_cleartext_traffic: self.config.android.application.uses_cleartext_traffic,\n            app_theme: self.config.android.application.theme.clone(),\n            supports_rtl: self.config.android.application.supports_rtl,\n            large_heap: self.config.android.application.large_heap,\n        };\n        let hbs = handlebars::Handlebars::new();\n\n        // Top-level gradle config\n        write(\n            root.join(\"build.gradle.kts\"),\n            include_bytes!(\"../../assets/android/gen/build.gradle.kts\"),\n        )?;\n        write(\n            root.join(\"gradle.properties\"),\n            include_bytes!(\"../../assets/android/gen/gradle.properties\"),\n        )?;\n        write(\n            root.join(\"gradlew\"),\n            include_bytes!(\"../../assets/android/gen/gradlew\"),\n        )?;\n        write(\n            root.join(\"gradlew.bat\"),\n            include_bytes!(\"../../assets/android/gen/gradlew.bat\"),\n        )?;\n        write(\n            root.join(\"settings.gradle\"),\n            include_bytes!(\"../../assets/android/gen/settings.gradle\"),\n        )?;\n\n        // Then the wrapper and its properties\n        write(\n            wrapper.join(\"gradle-wrapper.properties\"),\n            include_bytes!(\"../../assets/android/gen/gradle/wrapper/gradle-wrapper.properties\"),\n        )?;\n        write(\n            wrapper.join(\"gradle-wrapper.jar\"),\n            include_bytes!(\"../../assets/android/gen/gradle/wrapper/gradle-wrapper.jar\"),\n        )?;\n\n        // Now the app directory\n        write(\n            app.join(\"build.gradle.kts\"),\n            hbs.render_template(\n                include_str!(\"../../assets/android/gen/app/build.gradle.kts.hbs\"),\n                &hbs_data,\n            )?,\n        )?;\n        write(\n            app.join(\"proguard-rules.pro\"),\n            include_bytes!(\"../../assets/android/gen/app/proguard-rules.pro\"),\n        )?;\n\n        // Copy additional ProGuard rule files from Dioxus.toml [android] config\n        for rule_file in &self.config.android.proguard_rules {\n            let src = self.package_manifest_dir().join(rule_file);\n            if src.exists() {\n                let dest_name = src\n                    .file_name()\n                    .unwrap_or_default()\n                    .to_string_lossy()\n                    .to_string();\n                std::fs::copy(&src, app.join(&dest_name))?;\n                tracing::debug!(\"Copied ProGuard rules: {}\", dest_name);\n            } else {\n                tracing::warn!(\"ProGuard rules file not found: {}\", src.display());\n            }\n        }\n\n        let manifest_xml = match self.config.application.android_manifest.as_deref() {\n            Some(manifest) => std::fs::read_to_string(self.package_manifest_dir().join(manifest))\n                .context(\"Failed to locate custom AndroidManifest.xml\")?,\n            _ => hbs.render_template(\n                include_str!(\"../../assets/android/gen/app/src/main/AndroidManifest.xml.hbs\"),\n                &hbs_data,\n            )?,\n        };\n\n        write(\n            app.join(\"src\").join(\"main\").join(\"AndroidManifest.xml\"),\n            manifest_xml,\n        )?;\n\n        // Write the main activity manually since tao dropped support for it\n        let main_activity = match self.config.application.android_main_activity.as_deref() {\n            Some(activity) => std::fs::read_to_string(self.package_manifest_dir().join(activity))\n                .context(\"Failed to locate custom MainActivity.kt\")?,\n            _ => hbs.render_template(\n                include_str!(\"../../assets/android/MainActivity.kt.hbs\"),\n                &hbs_data,\n            )?,\n        };\n        write(\n            self.wry_android_kotlin_files_out_dir()\n                .join(\"MainActivity.kt\"),\n            main_activity,\n        )?;\n\n        // Write the res folder, containing stuff like default icons, colors, and menubars.\n        let res = app_main.join(\"res\");\n        create_dir_all(&res)?;\n        create_dir_all(res.join(\"values\"))?;\n        write(\n            res.join(\"values\").join(\"strings.xml\"),\n            hbs.render_template(\n                include_str!(\"../../assets/android/gen/app/src/main/res/values/strings.xml.hbs\"),\n                &hbs_data,\n            )?,\n        )?;\n        write(\n            res.join(\"values\").join(\"colors.xml\"),\n            include_bytes!(\"../../assets/android/gen/app/src/main/res/values/colors.xml\"),\n        )?;\n        write(\n            res.join(\"values\").join(\"styles.xml\"),\n            include_bytes!(\"../../assets/android/gen/app/src/main/res/values/styles.xml\"),\n        )?;\n\n        create_dir_all(res.join(\"xml\"))?;\n        write(\n            res.join(\"xml\").join(\"network_security_config.xml\"),\n            include_bytes!(\n                \"../../assets/android/gen/app/src/main/res/xml/network_security_config.xml\"\n            ),\n        )?;\n\n        create_dir_all(res.join(\"drawable\"))?;\n        write(\n            res.join(\"drawable\").join(\"ic_launcher_background.xml\"),\n            include_bytes!(\n                \"../../assets/android/gen/app/src/main/res/drawable/ic_launcher_background.xml\"\n            ),\n        )?;\n        create_dir_all(res.join(\"drawable-v24\"))?;\n        write(\n            res.join(\"drawable-v24\").join(\"ic_launcher_foreground.xml\"),\n            include_bytes!(\n                \"../../assets/android/gen/app/src/main/res/drawable-v24/ic_launcher_foreground.xml\"\n            ),\n        )?;\n        create_dir_all(res.join(\"mipmap-anydpi-v26\"))?;\n        write(\n            res.join(\"mipmap-anydpi-v26\").join(\"ic_launcher.xml\"),\n            include_bytes!(\n                \"../../assets/android/gen/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml\"\n            ),\n        )?;\n        create_dir_all(res.join(\"mipmap-hdpi\"))?;\n        write(\n            res.join(\"mipmap-hdpi\").join(\"ic_launcher.webp\"),\n            include_bytes!(\n                \"../../assets/android/gen/app/src/main/res/mipmap-hdpi/ic_launcher.webp\"\n            ),\n        )?;\n        create_dir_all(res.join(\"mipmap-mdpi\"))?;\n        write(\n            res.join(\"mipmap-mdpi\").join(\"ic_launcher.webp\"),\n            include_bytes!(\n                \"../../assets/android/gen/app/src/main/res/mipmap-mdpi/ic_launcher.webp\"\n            ),\n        )?;\n        create_dir_all(res.join(\"mipmap-xhdpi\"))?;\n        write(\n            res.join(\"mipmap-xhdpi\").join(\"ic_launcher.webp\"),\n            include_bytes!(\n                \"../../assets/android/gen/app/src/main/res/mipmap-xhdpi/ic_launcher.webp\"\n            ),\n        )?;\n        create_dir_all(res.join(\"mipmap-xxhdpi\"))?;\n        write(\n            res.join(\"mipmap-xxhdpi\").join(\"ic_launcher.webp\"),\n            include_bytes!(\n                \"../../assets/android/gen/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp\"\n            ),\n        )?;\n        create_dir_all(res.join(\"mipmap-xxxhdpi\"))?;\n        write(\n            res.join(\"mipmap-xxxhdpi\").join(\"ic_launcher.webp\"),\n            include_bytes!(\n                \"../../assets/android/gen/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp\"\n            ),\n        )?;\n\n        Ok(())\n    }\n\n    fn wry_android_kotlin_files_out_dir(&self) -> PathBuf {\n        let mut kotlin_dir = self\n            .root_dir()\n            .join(\"app\")\n            .join(\"src\")\n            .join(\"main\")\n            .join(\"kotlin\");\n\n        for segment in \"dev.dioxus.main\".split('.') {\n            kotlin_dir = kotlin_dir.join(segment);\n        }\n\n        kotlin_dir\n    }\n\n    fn ensure_gradle_dependency(&self, build_gradle: &Path, dependency_line: &str) -> Result<()> {\n        use std::fs;\n\n        let mut contents = fs::read_to_string(build_gradle)?;\n        if contents.contains(dependency_line) {\n            return Ok(());\n        }\n\n        if let Some(idx) = contents.find(\"dependencies {\") {\n            let insert_pos = idx + \"dependencies {\".len();\n            contents.insert_str(insert_pos, &format!(\"\\n    {dependency_line}\"));\n        } else {\n            contents.push_str(&format!(\"\\ndependencies {{\\n    {dependency_line}\\n}}\\n\"));\n        }\n\n        fs::write(build_gradle, contents)?;\n        Ok(())\n    }\n\n    /// Get the directory where this app can write to for this session that's guaranteed to be stable\n    /// for the same app. This is useful for emitting state like window position and size.\n    ///\n    /// The directory is specific for this app and might be\n    pub(crate) fn session_cache_dir(&self) -> PathBuf {\n        self.session_cache_dir.join(self.bundle.to_string())\n    }\n\n    pub(crate) fn rustc_wrapper_args_dir(&self) -> PathBuf {\n        self.session_cache_dir().join(\"rustc_wrapper_args\")\n    }\n\n    /// The crate name that rustc uses for the tip crate (hyphens replaced with underscores).\n    fn tip_crate_name(&self) -> String {\n        self.main_target.replace('-', \"_\")\n    }\n\n    fn link_err_file(&self) -> PathBuf {\n        self.session_cache_dir().join(\"link_err.txt\")\n    }\n\n    fn link_args_file(&self) -> PathBuf {\n        self.session_cache_dir().join(\"link_args.json\")\n    }\n\n    fn windows_command_file(&self) -> PathBuf {\n        self.session_cache_dir().join(\"windows_command.txt\")\n    }\n\n    /// Get the outdir specified by the Dioxus.toml, relative to the crate directory.\n    /// We don't support workspaces yet since that would cause a collision of bundles per project.\n    pub(crate) fn crate_out_dir(&self) -> Option<PathBuf> {\n        self.config\n            .application\n            .out_dir\n            .as_ref()\n            .map(|out_dir| self.crate_dir().join(out_dir))\n    }\n\n    /// Compose an out directory. Represents the typical \"dist\" directory that\n    /// is \"distributed\" after building an application (configurable in the\n    /// `Dioxus.toml`).\n    fn internal_out_dir(&self) -> PathBuf {\n        let dir = self.target_dir.join(\"dx\");\n        std::fs::create_dir_all(&dir).unwrap();\n        dir\n    }\n\n    /// target/dx/bundle/app/\n    /// target/dx/bundle/app/blah.app\n    /// target/dx/bundle/app/blah.exe\n    /// target/dx/bundle/app/public/\n    pub(crate) fn bundle_dir(&self, bundle: BundleFormat) -> PathBuf {\n        self.internal_out_dir()\n            .join(&self.main_target)\n            .join(\"bundle\")\n            .join(bundle.build_folder_name())\n    }\n\n    /// Get the workspace directory for the crate\n    pub(crate) fn workspace_dir(&self) -> PathBuf {\n        self.workspace\n            .krates\n            .workspace_root()\n            .as_std_path()\n            .to_path_buf()\n    }\n\n    /// Get the directory of the crate\n    pub(crate) fn crate_dir(&self) -> PathBuf {\n        self.package()\n            .manifest_path\n            .parent()\n            .unwrap()\n            .as_std_path()\n            .to_path_buf()\n    }\n\n    /// Get the package we are currently in\n    pub(crate) fn package(&self) -> &krates::cm::Package {\n        &self.workspace.krates[self.crate_package]\n    }\n\n    /// Get the name of the package we are compiling\n    pub(crate) fn executable_name(&self) -> &str {\n        &self.crate_target.name\n    }\n\n    /// Get the type of executable we are compiling\n    pub(crate) fn executable_type(&self) -> TargetKind {\n        self.crate_target.kind[0].clone()\n    }\n\n    /// Get the features required to build for the given platform\n    fn feature_for_platform_and_renderer(\n        package: &krates::cm::Package,\n        triple: &Triple,\n        renderer: Renderer,\n    ) -> Option<String> {\n        // Try to find the feature that activates the dioxus feature for the given platform\n        let dioxus_feature = renderer.feature_name(triple);\n\n        let res = package.features.iter().find_map(|(key, features)| {\n            // if the feature is just the name of the platform, we use that\n            if key == dioxus_feature {\n                tracing::debug!(\"Found feature {key} for renderer {renderer}\");\n                return Some(key.clone());\n            }\n\n            // Otherwise look for the feature that starts with dioxus/ or dioxus?/ and matches just the single platform\n            // we are looking for.\n            let mut dioxus_renderers_enabled = Vec::new();\n            for feature in features {\n                if let Some((_, after_dioxus)) = feature.split_once(\"dioxus\") {\n                    if let Some(dioxus_feature_enabled) =\n                        after_dioxus.trim_start_matches('?').strip_prefix('/')\n                    {\n                        if Renderer::autodetect_from_cargo_feature(dioxus_feature_enabled).is_some()\n                        {\n                            dioxus_renderers_enabled.push(dioxus_feature_enabled.to_string());\n                        }\n                    }\n                }\n            }\n\n            // If there is exactly one renderer enabled by this feature, we can use it\n            if let [feature_name] = dioxus_renderers_enabled.as_slice() {\n                if feature_name == dioxus_feature {\n                    tracing::debug!(\n                        \"Found feature {key} for renderer {renderer} which enables dioxus/{renderer}\"\n                    );\n                    return Some(key.clone());\n                }\n            }\n\n            None\n        });\n\n        res.or_else(|| {\n            let depends_on_dioxus = package.dependencies.iter().any(|dep| dep.name == \"dioxus\");\n            if depends_on_dioxus {\n                let fallback = format!(\"dioxus/{dioxus_feature}\");\n                tracing::debug!(\n                    \"Could not find explicit feature for renderer {renderer}, passing `fallback` instead\"\n                );\n                Some(fallback)\n            } else {\n                None\n            }\n        })\n    }\n\n    /// Checks the strip setting for the package, resolving profiles recursively\n    pub(crate) fn get_strip_setting(&self) -> StripSetting {\n        let cargo_toml = &self.workspace.cargo_toml;\n        let profile = &self.profile;\n        let release = self.release;\n        let profile = match (cargo_toml.profile.custom.get(profile), release) {\n            (Some(custom_profile), _) => Some(custom_profile),\n            (_, true) => cargo_toml.profile.release.as_ref(),\n            (_, false) => cargo_toml.profile.dev.as_ref(),\n        };\n\n        let Some(profile) = profile else {\n            return StripSetting::None;\n        };\n\n        // Get the strip setting from the profile or the profile it inherits from\n        fn get_strip(profile: &Profile, profiles: &Profiles) -> Option<StripSetting> {\n            profile.strip.or_else(|| {\n                // If we can't find the strip setting, check if we inherit from another profile\n                profile.inherits.as_ref().and_then(|inherits| {\n                    let profile = match inherits.as_str() {\n                        \"dev\" => profiles.dev.as_ref(),\n                        \"release\" => profiles.release.as_ref(),\n                        \"test\" => profiles.test.as_ref(),\n                        \"bench\" => profiles.bench.as_ref(),\n                        other => profiles.custom.get(other),\n                    };\n                    profile.and_then(|p| get_strip(p, profiles))\n                })\n            })\n        }\n\n        let Some(strip) = get_strip(profile, &cargo_toml.profile) else {\n            // If the profile doesn't have a strip option, return None\n            return StripSetting::None;\n        };\n\n        strip\n    }\n\n    pub(crate) fn renderer_enabled_by_dioxus_dependency(\n        package: &krates::cm::Package,\n    ) -> Option<(Renderer, String)> {\n        let mut renderers = vec![];\n\n        // Attempt to discover the platform directly from the dioxus dependency\n        //\n        // [dependencies]\n        // dioxus = { features = [\"web\"] }\n        //\n        if let Some(dxs) = package.dependencies.iter().find(|dep| dep.name == \"dioxus\") {\n            for feature in dxs.features.iter() {\n                if let Some(renderer) = Renderer::autodetect_from_cargo_feature(feature) {\n                    renderers.push((renderer, format!(\"dioxus/{}\", feature)));\n                }\n            }\n        }\n\n        if renderers.len() != 1 {\n            return None;\n        }\n\n        Some(renderers[0].clone())\n    }\n\n    pub(crate) fn features_that_enable_renderers(\n        package: &krates::cm::Package,\n    ) -> Vec<(Renderer, String)> {\n        package\n            .features\n            .keys()\n            .filter_map(|key| {\n                Renderer::autodetect_from_cargo_feature(key).map(|v| (v, key.to_string()))\n            })\n            .collect()\n    }\n\n    /// Return the platforms that are enabled for the package only from the default features\n    ///\n    /// Ideally only one platform is enabled but we need to be able to\n    pub(crate) fn enabled_cargo_toml_default_features_renderers(\n        package: &krates::cm::Package,\n    ) -> Vec<(Renderer, String)> {\n        let mut renderers = vec![];\n\n        // Start searching through the default features\n        //\n        // [features]\n        // default = [\"dioxus/web\"]\n        //\n        // or\n        //\n        // [features]\n        // default = [\"web\"]\n        // web = [\"dioxus/web\"]\n        let Some(default) = package.features.get(\"default\") else {\n            return renderers;\n        };\n\n        // we only trace features 1 level deep..\n        // TODO: trace all enabled features, not just default features\n        for feature in default.iter() {\n            // If the user directly specified a platform we can just use that.\n            if feature.starts_with(\"dioxus/\") {\n                let dx_feature = feature.trim_start_matches(\"dioxus/\");\n                let auto = Renderer::autodetect_from_cargo_feature(dx_feature);\n                if let Some(auto) = auto {\n                    renderers.push((auto, dx_feature.to_string()));\n                }\n            }\n\n            // If the user is specifying an internal feature that points to a platform, we can use that\n            let internal_feature = package.features.get(feature);\n            if let Some(internal_feature) = internal_feature {\n                for feature in internal_feature {\n                    if feature.starts_with(\"dioxus/\") {\n                        let dx_feature = feature.trim_start_matches(\"dioxus/\");\n                        let auto = Renderer::autodetect_from_cargo_feature(dx_feature);\n                        if let Some(auto) = auto {\n                            renderers.push((auto, dx_feature.to_string()));\n                        }\n                    }\n                }\n            }\n        }\n\n        renderers.sort();\n        renderers.dedup();\n\n        renderers\n    }\n\n    /// Gather the features that are enabled for the package\n    fn rendererless_features(package: &krates::cm::Package) -> Vec<String> {\n        let Some(default) = package.features.get(\"default\") else {\n            return Vec::new();\n        };\n\n        let mut kept_features = vec![];\n\n        // Only keep the top-level features in the default list that don't point to a platform directly\n        // IE we want to drop `web` if default = [\"web\"]\n        'top: for feature in default {\n            // Don't keep features that point to a platform via dioxus/blah\n            if feature.starts_with(\"dioxus/\") {\n                let dx_feature = feature.trim_start_matches(\"dioxus/\");\n                if Renderer::autodetect_from_cargo_feature(dx_feature).is_some() {\n                    tracing::debug!(\n                        \"Dropping feature {feature} since it points to a platform renderer\"\n                    );\n                    continue 'top;\n                }\n            }\n\n            // Don't keep features that point to a platform via an internal feature\n            if let Some(internal_feature) = package.features.get(feature) {\n                for feature in internal_feature {\n                    if feature.starts_with(\"dioxus/\") {\n                        let dx_feature = feature.trim_start_matches(\"dioxus/\");\n                        if Renderer::autodetect_from_cargo_feature(dx_feature).is_some() {\n                            tracing::debug!(\n                                \"Dropping feature {feature} since it points to a platform renderer transitively\"\n                            );\n                            continue 'top;\n                        }\n                    }\n                }\n            }\n\n            // Otherwise we can keep it\n            kept_features.push(feature.to_string());\n        }\n\n        kept_features\n    }\n\n    pub(crate) fn bundled_app_name(&self) -> String {\n        use convert_case::{Case, Casing};\n        self.executable_name().to_case(Case::Pascal)\n    }\n\n    /// Get the crate version from Cargo.toml (e.g., \"0.1.0\")\n    fn crate_version(&self) -> String {\n        self.workspace.krates[self.crate_package]\n            .version\n            .to_string()\n    }\n\n    pub(crate) fn bundle_identifier(&self) -> String {\n        use crate::config::BundlePlatform;\n\n        // Check platform-specific identifier override first, then fall back to base bundle\n        let platform: BundlePlatform = self.bundle.into();\n        if let Some(identifier) = self.config.resolved_identifier(platform) {\n            let identifier = identifier.to_string();\n            if identifier.contains('.')\n                && !identifier.starts_with('.')\n                && !identifier.ends_with('.')\n                && !identifier.contains(\"..\")\n            {\n                return identifier;\n            } else {\n                tracing::error!(\n                    \"Invalid bundle identifier: {identifier:?}. Must contain at least one '.' and not start/end with '.'. E.g. `com.example.app`\"\n                );\n            }\n        }\n\n        format!(\"com.example.{}\", self.bundled_app_name())\n    }\n\n    /// The item that we'll try to run directly if we need to.\n    ///\n    /// todo(jon): we should name the app properly instead of making up the exe name. It's kinda okay for dev mode, but def not okay for prod\n    pub(crate) fn main_exe(&self) -> PathBuf {\n        self.exe_dir().join(self.platform_exe_name())\n    }\n\n    fn is_wasm_or_wasi(&self) -> bool {\n        matches!(\n            self.triple.architecture,\n            target_lexicon::Architecture::Wasm32 | target_lexicon::Architecture::Wasm64\n        ) || self.triple.operating_system == target_lexicon::OperatingSystem::Wasi\n    }\n\n    /// Does the app specify:\n    ///\n    /// - Dioxus with \"fullstack\" enabled? (access to serverfns, etc)\n    /// - An explicit \"fullstack\" feature that enables said feature?\n    ///\n    /// Note that we don't detect if dependencies enable it transitively since we want to be explicit about it.\n    ///\n    /// The intention here is to detect if \"fullstack\" is enabled in the target's features list:\n    /// ```toml\n    /// [dependencies]\n    /// dioxus = { version = \"0.4\", features = [\"fullstack\"] }\n    /// ```\n    ///\n    /// or as an explicit feature in default:\n    /// ```toml\n    /// [features]\n    /// default = [\"dioxus/fullstack\"]\n    /// ```\n    ///\n    /// or as a default feature that enables the dioxus feature:\n    /// ```toml\n    /// [features]\n    /// default = [\"fullstack\"]\n    /// fullstack = [\"dioxus/fullstack\"]\n    /// ```\n    ///\n    /// or as an explicit feature (that enables the dioxus feature):\n    /// ```\n    /// dx serve app --features \"fullstack\"\n    /// ```\n    pub(crate) fn fullstack_feature_enabled(&self) -> bool {\n        let dioxus_dep = self\n            .package()\n            .dependencies\n            .iter()\n            .find(|dep| dep.name == \"dioxus\");\n\n        // If we don't have a dioxus dependency, we can't be fullstack. This shouldn't impact non-dioxus projects\n        let Some(dioxus_dep) = dioxus_dep else {\n            return false;\n        };\n\n        // Check if the dioxus dependency has the \"fullstack\" feature enabled\n        if dioxus_dep.features.iter().any(|f| f == \"fullstack\") {\n            return true;\n        }\n\n        // Check if any of the features in our feature list enables a feature that enables \"fullstack\"\n        let transitive = self\n            .package()\n            .features\n            .iter()\n            .filter(|(_name, list)| list.iter().any(|f| f == \"dioxus/fullstack\"));\n\n        for (name, _list) in transitive {\n            if self.features.contains(name) {\n                return true;\n            }\n        }\n\n        false\n    }\n\n    /// todo(jon): use handlebars templates instead of these prebaked templates\n    async fn write_metadata(&self) -> Result<()> {\n        // write the Info.plist file\n        match self.bundle {\n            BundleFormat::MacOS => {\n                let dest = self.root_dir().join(\"Contents\").join(\"Info.plist\");\n                let plist = self.info_plist_contents(self.bundle)?;\n                std::fs::write(dest, plist)?;\n            }\n\n            BundleFormat::Ios => {\n                let dest = self.root_dir().join(\"Info.plist\");\n                let plist = self.info_plist_contents(self.bundle)?;\n                std::fs::write(dest, plist)?;\n            }\n\n            // AndroidManifest.xml\n            // er.... maybe even all the kotlin/java/gradle stuff?\n            BundleFormat::Android => {}\n\n            // Probably some custom format or a plist file (haha)\n            // When we do the proper bundle, we'll need to do something with wix templates, I think?\n            BundleFormat::Windows => {}\n\n            // eventually we'll create the .appimage file, I guess?\n            BundleFormat::Linux => {}\n\n            // These are served as folders, not appimages, so we don't need to do anything special (I think?)\n            // Eventually maybe write some secrets/.env files for the server?\n            // We could also distribute them as a deb/rpm for linux and msi for windows\n            BundleFormat::Web => {}\n            BundleFormat::Server => {}\n        }\n\n        Ok(())\n    }\n\n    /// Run the optimizers, obfuscators, minimizers, signers, etc\n    async fn optimize(&self, ctx: &BuildContext) -> Result<()> {\n        match self.bundle {\n            BundleFormat::Web => {\n                // Compress the asset dir\n                // If pre-compressing is enabled, we can pre_compress the wasm-bindgen output\n                let pre_compress = self.should_pre_compress_web_assets(self.release);\n\n                if pre_compress {\n                    ctx.status_compressing_assets();\n                    let asset_dir = self.asset_dir();\n                    tokio::task::spawn_blocking(move || {\n                        crate::fastfs::pre_compress_folder(&asset_dir, pre_compress)\n                    })\n                    .await\n                    .unwrap()?;\n                }\n            }\n            BundleFormat::MacOS\n            | BundleFormat::Windows\n            | BundleFormat::Linux\n            | BundleFormat::Ios\n            | BundleFormat::Android\n            | BundleFormat::Server => {}\n        }\n\n        Ok(())\n    }\n\n    /// Strip the final binary after extracting all assets with rustc-objcopy\n    async fn strip_binary(&self, artifacts: &BuildArtifacts) -> Result<()> {\n        // Never strip the binary if we are going to bundle split it\n        if self.wasm_split {\n            return Ok(());\n        }\n        let exe = &artifacts.exe;\n        // https://github.com/rust-lang/rust/blob/cb80ff132a0e9aa71529b701427e4e6c243b58df/compiler/rustc_codegen_ssa/src/back/linker.rs#L1433-L1443\n        let strip_arg = match self.get_strip_setting() {\n            StripSetting::Debuginfo => Some(\"--strip-debug\"),\n            StripSetting::Symbols => Some(\"--strip-all\"),\n            StripSetting::None => None,\n        };\n        if let Some(strip_arg) = strip_arg {\n            let rustc_objcopy = self.workspace.rustc_objcopy();\n            let dylib_path = self.workspace.rustc_objcopy_dylib_path();\n            let mut command = Command::new(rustc_objcopy);\n            command.env(\"LD_LIBRARY_PATH\", &dylib_path);\n            command.arg(strip_arg).arg(exe).arg(exe);\n            let output = command.output().await?;\n            if !output.status.success() {\n                if let Ok(stdout) = std::str::from_utf8(&output.stdout) {\n                    tracing::error!(\"{}\", stdout);\n                }\n                if let Ok(stderr) = std::str::from_utf8(&output.stderr) {\n                    tracing::error!(\"{}\", stderr);\n                }\n                return Err(anyhow::anyhow!(\"Failed to strip binary\"));\n            }\n        }\n        Ok(())\n    }\n\n    /// Check if assets should be pre_compressed. This will only be true in release mode if the user\n    /// has enabled pre_compress in the web config.\n    fn should_pre_compress_web_assets(&self, release: bool) -> bool {\n        self.config.web.pre_compress & release\n    }\n\n    /// Check if the wasm output should be bundled to an asset type app.\n    fn should_bundle_to_asset(&self) -> bool {\n        self.release && !self.wasm_split && self.bundle == BundleFormat::Web\n    }\n\n    /// Bundle the web app\n    /// - Run wasm-bindgen\n    /// - Bundle split\n    /// - Run wasm-opt\n    /// - Register the .wasm and .js files with the asset system\n    async fn bundle_web(\n        &self,\n        ctx: &BuildContext,\n        exe: &Path,\n        assets: &mut AssetManifest,\n    ) -> Result<()> {\n        use crate::{wasm_bindgen::WasmBindgen, wasm_opt};\n        use std::fmt::Write;\n\n        // Locate the output of the build files and the bindgen output\n        // We'll fill these in a second if they don't already exist\n        let bindgen_outdir = self.wasm_bindgen_out_dir();\n        let post_bindgen_wasm = self.wasm_bindgen_wasm_output_file();\n        let should_bundle_split: bool = self.wasm_split;\n        let bindgen_version = self\n            .workspace\n            .wasm_bindgen_version()\n            .expect(\"this should have been checked by tool verification\");\n\n        // Prepare any work dirs\n        _ = std::fs::remove_dir_all(&bindgen_outdir);\n        std::fs::create_dir_all(&bindgen_outdir)?;\n\n        // Lift the internal functions to exports\n        if ctx.mode == BuildMode::Fat {\n            let unprocessed = std::fs::read(exe)?;\n            let all_exported_bytes = crate::build::prepare_wasm_base_module(&unprocessed)?;\n            std::fs::write(exe, all_exported_bytes)?;\n        }\n\n        // Prepare our configuration\n        //\n        // we turn on debug symbols in dev mode\n        //\n        // We leave demangling to false since it's faster and these tools seem to prefer the raw symbols.\n        // todo(jon): investigate if the chrome extension needs them demangled or demangles them automatically.\n        let keep_debug = self.config.web.wasm_opt.debug\n            || self.debug_symbols\n            || self.wasm_split\n            || !self.release\n            || ctx.mode == BuildMode::Fat;\n        let keep_names = self.config.web.wasm_opt.keep_names\n            || self.keep_names\n            || self.wasm_split\n            || ctx.mode == BuildMode::Fat;\n        let demangle = false;\n        let wasm_opt_options = WasmOptConfig {\n            memory_packing: self.wasm_split,\n            debug: self.debug_symbols,\n            ..self.config.web.wasm_opt.clone()\n        };\n\n        // Run wasm-bindgen. Some of the options are not \"optimal\" but will be fixed up by wasm-opt\n        //\n        // There's performance implications here. Running with --debug is slower than without\n        // We're keeping around lld sections and names but wasm-opt will fix them\n        // todo(jon): investigate a good balance of wiping debug symbols during dev (or doing a double build?)\n        ctx.status_wasm_bindgen_start();\n        tracing::debug!(dx_src = ?TraceSrc::Bundle, \"Running wasm-bindgen\");\n        let start = std::time::Instant::now();\n        WasmBindgen::new(&bindgen_version)\n            .input_path(exe)\n            .target(\"web\")\n            .debug(keep_debug)\n            .demangle(demangle)\n            .keep_debug(keep_debug)\n            .keep_lld_sections(true)\n            .out_name(self.executable_name())\n            .out_dir(&bindgen_outdir)\n            .remove_name_section(!keep_names)\n            .remove_producers_section(!keep_names)\n            .run()\n            .await\n            .context(\"Failed to generate wasm-bindgen bindings\")?;\n        tracing::debug!(dx_src = ?TraceSrc::Bundle, \"wasm-bindgen complete in {:?}\", start.elapsed());\n\n        // Run bundle splitting if the user has requested it\n        // It's pretty expensive but because of rayon should be running separate threads, hopefully\n        // not blocking this thread. Dunno if that's true\n        if should_bundle_split {\n            ctx.status_splitting_bundle();\n\n            // Load the contents of these binaries since we need both of them\n            // We're going to use the default makeLoad glue from wasm-split\n            let original = std::fs::read(exe)?;\n            let bindgened = std::fs::read(&post_bindgen_wasm)?;\n            let mut glue = wasm_split_cli::MAKE_LOAD_JS.to_string();\n\n            // Run the emitter\n            let splitter = wasm_split_cli::Splitter::new(&original, &bindgened);\n            let modules = splitter\n                .context(\"Failed to parse wasm for splitter\")?\n                .emit()\n                .context(\"Failed to emit wasm split modules\")?;\n\n            // Write the chunks that contain shared imports\n            // These will be in the format of chunk_0_modulename.wasm - this is hardcoded in wasm-split\n            tracing::debug!(\"Writing split chunks to disk\");\n            for (idx, chunk) in modules.chunks.iter().enumerate() {\n                let path = bindgen_outdir.join(format!(\"chunk_{}_{}.wasm\", idx, chunk.module_name));\n                wasm_opt::write_wasm(&chunk.bytes, &path, &wasm_opt_options).await?;\n                writeln!(\n                    glue, \"export const __wasm_split_load_chunk_{idx} = makeLoad(\\\"/{base_path}/assets/{url}\\\", [], fusedImports);\",\n                    base_path = self.base_path_or_default(),\n                    url = assets\n                        .register_asset(&path, AssetOptions::builder().into_asset_options())?.bundled_path(),\n                )?;\n            }\n\n            // Write the modules that contain the entrypoints\n            tracing::debug!(\"Writing split modules to disk\");\n            for (idx, module) in modules.modules.iter().enumerate() {\n                let comp_name = module\n                    .component_name\n                    .as_ref()\n                    .context(\"generated bindgen module has no name?\")?;\n\n                let path = bindgen_outdir.join(format!(\"module_{idx}_{comp_name}.wasm\"));\n                wasm_opt::write_wasm(&module.bytes, &path, &wasm_opt_options).await?;\n\n                let hash_id = module\n                    .hash_id\n                    .as_ref()\n                    .context(\"generated wasm-split bindgen module has no hash id?\")?;\n\n                writeln!(\n                    glue,\n                    \"export const __wasm_split_load_{module}_{hash_id}_{comp_name} = makeLoad(\\\"/{base_path}/assets/{url}\\\", [{deps}], fusedImports);\",\n                    module = module.module_name,\n\n                    base_path = self.base_path_or_default(),\n\n                    // Again, register this wasm with the asset system\n                    url = assets\n                        .register_asset(&path, AssetOptions::builder().into_asset_options())?\n                        .bundled_path(),\n\n                    // This time, make sure to write the dependencies of this chunk\n                    // The names here are again, hardcoded in wasm-split - fix this eventually.\n                    deps = module\n                        .relies_on_chunks\n                        .iter()\n                        .map(|idx| format!(\"__wasm_split_load_chunk_{idx}\"))\n                        .collect::<Vec<_>>()\n                        .join(\", \")\n                )?;\n            }\n\n            // Write the js binding\n            // It's not registered as an asset since it will get included in the main.js file\n            let js_output_path = bindgen_outdir.join(\"__wasm_split.js\");\n            std::fs::write(&js_output_path, &glue)?;\n\n            // Make sure to write some entropy to the main.js file so it gets a new hash\n            // If we don't do this, the main.js file will be cached and never pick up the chunk names\n            let uuid = Uuid::new_v5(&Uuid::NAMESPACE_URL, glue.as_bytes());\n            std::fs::OpenOptions::new()\n                .append(true)\n                .open(self.wasm_bindgen_js_output_file())\n                .context(\"Failed to open main.js file\")?\n                .write_all(format!(\"/*{uuid}*/\").as_bytes())?;\n\n            // Write the main wasm_bindgen file and register it with the asset system\n            // This will overwrite the file in place\n            // We will wasm-opt it in just a second...\n            std::fs::write(&post_bindgen_wasm, modules.main.bytes).unwrap();\n        }\n\n        if matches!(ctx.mode, BuildMode::Fat) {\n            // add `export { __wbg_get_imports };` to the end of the wasmbindgen js file\n            let mut js = std::fs::read(self.wasm_bindgen_js_output_file())?;\n            writeln!(js, \"\\nexport {{ __wbg_get_imports }};\")?;\n            std::fs::write(self.wasm_bindgen_js_output_file(), js)?;\n        }\n\n        // Make sure to optimize the main wasm file if requested or if bundle splitting\n        if should_bundle_split || self.release {\n            ctx.status_optimizing_wasm();\n            wasm_opt::optimize(&post_bindgen_wasm, &post_bindgen_wasm, &wasm_opt_options).await?;\n        }\n\n        if self.should_bundle_to_asset() {\n            // Make sure to register the main wasm file with the asset system\n            assets.register_asset(\n                &post_bindgen_wasm,\n                AssetOptions::builder().into_asset_options(),\n            )?;\n        }\n\n        // Now that the wasm is registered as an asset, we can write the js glue shim\n        self.write_js_glue_shim(assets)?;\n\n        if self.should_bundle_to_asset() {\n            // Register the main.js with the asset system so it bundles in the snippets and optimizes\n            assets.register_asset(\n                &self.wasm_bindgen_js_output_file(),\n                AssetOptions::js()\n                    .with_minify(true)\n                    .with_preload(true)\n                    .into_asset_options(),\n            )?;\n        }\n\n        // Write the index.html file with the pre-configured contents we got from pre-rendering\n        self.write_index_html(assets)?;\n\n        Ok(())\n    }\n\n    fn write_js_glue_shim(&self, assets: &AssetManifest) -> Result<()> {\n        let wasm_path = self.bundled_wasm_path(assets);\n\n        // Load and initialize wasm without requiring a separate javascript file.\n        // This also allows using a strict Content-Security-Policy.\n        let mut js = std::fs::OpenOptions::new()\n            .append(true)\n            .open(self.wasm_bindgen_js_output_file())?;\n        let mut buf_writer = std::io::BufWriter::new(&mut js);\n        writeln!(\n            buf_writer,\n            r#\"\nglobalThis.__wasm_split_main_initSync = initSync;\n\n// Actually perform the load\n__wbg_init({{module_or_path: \"/{}/{wasm_path}\"}}).then((wasm) => {{\n    // assign this module to be accessible globally\n    globalThis.__dx_mainWasm = wasm;\n    globalThis.__dx_mainInit = __wbg_init;\n    globalThis.__dx_mainInitSync = initSync;\n    globalThis.__dx___wbg_get_imports = __wbg_get_imports;\n\n    if (wasm.__wbindgen_start == undefined) {{\n        wasm.main();\n    }}\n}});\n\"#,\n            self.base_path_or_default(),\n        )?;\n\n        Ok(())\n    }\n\n    /// Write the index.html file to the output directory. This must be called after the wasm and js\n    /// assets are registered with the asset system if this is a release build.\n    pub(crate) fn write_index_html(&self, assets: &AssetManifest) -> Result<()> {\n        let wasm_path = self.bundled_wasm_path(assets);\n        let js_path = self.bundled_js_path(assets);\n\n        // Write the index.html file with the pre-configured contents we got from pre-rendering\n        std::fs::write(\n            self.root_dir().join(\"index.html\"),\n            self.prepare_html(assets, &wasm_path, &js_path).unwrap(),\n        )?;\n\n        Ok(())\n    }\n\n    fn bundled_js_path(&self, assets: &AssetManifest) -> String {\n        let wasm_bindgen_js_out = self.wasm_bindgen_js_output_file();\n        if self.should_bundle_to_asset() {\n            let name = assets\n                .get_first_asset_for_source(&wasm_bindgen_js_out)\n                .expect(\"The js source must exist before creating index.html\");\n            format!(\"assets/{}\", name.bundled_path())\n        } else {\n            format!(\n                \"wasm/{}\",\n                wasm_bindgen_js_out.file_name().unwrap().to_str().unwrap()\n            )\n        }\n    }\n\n    /// Get the path to the wasm-bindgen output files. Either the direct file or the optimized one depending on the build mode\n    fn bundled_wasm_path(&self, assets: &AssetManifest) -> String {\n        let wasm_bindgen_wasm_out = self.wasm_bindgen_wasm_output_file();\n        if self.should_bundle_to_asset() {\n            let name = assets\n                .get_first_asset_for_source(&wasm_bindgen_wasm_out)\n                .expect(\"The wasm source must exist before creating index.html\");\n            format!(\"assets/{}\", name.bundled_path())\n        } else {\n            format!(\n                \"wasm/{}\",\n                wasm_bindgen_wasm_out.file_name().unwrap().to_str().unwrap()\n            )\n        }\n    }\n\n    fn info_plist_contents(&self, bundle: BundleFormat) -> Result<String> {\n        /// A permission entry for plist (key + description)\n        #[derive(Serialize)]\n        struct PlistPermission {\n            key: String,\n            description: String,\n        }\n\n        #[derive(Serialize)]\n        pub struct InfoPlistData {\n            pub display_name: String,\n            pub bundle_name: String,\n            pub bundle_identifier: String,\n            pub executable_name: String,\n            /// App version string (from Cargo.toml)\n            pub version: String,\n            /// Permission usage descriptions\n            pub permissions: Vec<PlistPermission>,\n            /// Additional plist entries as raw XML\n            pub plist_entries: String,\n            /// Raw plist XML to inject\n            pub raw_plist: String,\n            /// Minimum system version (macOS only)\n            pub minimum_system_version: String,\n            /// URL schemes for deep linking\n            pub url_schemes: Vec<String>,\n            /// iOS UIBackgroundModes\n            pub background_modes: Vec<String>,\n        }\n\n        // Attempt to use the user's manually specified\n        let _app = &self.config.application;\n        match bundle {\n            BundleFormat::MacOS => {\n                if let Some(macos_info_plist) = _app.macos_info_plist.as_deref() {\n                    return Ok(std::fs::read_to_string(macos_info_plist)?);\n                }\n            }\n            BundleFormat::Ios => {\n                if let Some(macos_info_plist) = _app.ios_info_plist.as_deref() {\n                    return Ok(std::fs::read_to_string(macos_info_plist)?);\n                }\n            }\n            _ => {}\n        }\n\n        // Get permission mapper from config\n        let mapper = super::manifest_mapper::ManifestMapper::from_config(\n            &self.config.permissions,\n            &self.config.deep_links,\n            &self.config.background,\n            &self.config.android,\n            &self.config.ios,\n            &self.config.macos,\n        );\n\n        match bundle {\n            BundleFormat::MacOS => {\n                // Convert macOS plist entries to permission structs\n                let permissions: Vec<PlistPermission> = mapper\n                    .macos_plist_entries\n                    .iter()\n                    .map(|p| PlistPermission {\n                        key: p.key.clone(),\n                        description: p.value.clone(),\n                    })\n                    .collect();\n\n                // Generate plist entries from config\n                let plist_entries = generate_plist_entries(&self.config.macos.plist);\n                let raw_plist = self.config.macos.raw.info_plist.clone().unwrap_or_default();\n                let minimum_system_version = self\n                    .config\n                    .macos\n                    .minimum_system_version\n                    .clone()\n                    .unwrap_or_else(|| \"10.15\".to_string());\n\n                handlebars::Handlebars::new()\n                    .render_template(\n                        include_str!(\"../../assets/macos/mac.plist.hbs\"),\n                        &InfoPlistData {\n                            display_name: self.bundled_app_name(),\n                            bundle_name: self.bundled_app_name(),\n                            executable_name: self.platform_exe_name(),\n                            bundle_identifier: self.bundle_identifier(),\n                            version: self.crate_version(),\n                            permissions,\n                            plist_entries,\n                            raw_plist,\n                            minimum_system_version,\n                            url_schemes: mapper.macos_url_schemes.clone(),\n                            background_modes: Vec::new(), // macOS doesn't use UIBackgroundModes\n                        },\n                    )\n                    .map_err(|e| e.into())\n            }\n            BundleFormat::Ios => {\n                // Convert iOS plist entries to permission structs\n                let permissions: Vec<PlistPermission> = mapper\n                    .ios_plist_entries\n                    .iter()\n                    .map(|p| PlistPermission {\n                        key: p.key.clone(),\n                        description: p.value.clone(),\n                    })\n                    .collect();\n\n                // Generate plist entries from config\n                let plist_entries = generate_plist_entries(&self.config.ios.plist);\n                let raw_plist = self.config.ios.raw.info_plist.clone().unwrap_or_default();\n\n                handlebars::Handlebars::new()\n                    .render_template(\n                        include_str!(\"../../assets/ios/ios.plist.hbs\"),\n                        &InfoPlistData {\n                            display_name: self.bundled_app_name(),\n                            bundle_name: self.bundled_app_name(),\n                            executable_name: self.platform_exe_name(),\n                            bundle_identifier: self.bundle_identifier(),\n                            version: self.crate_version(),\n                            permissions,\n                            plist_entries,\n                            raw_plist,\n                            minimum_system_version: String::new(), // Not used for iOS\n                            url_schemes: mapper.ios_url_schemes.clone(),\n                            background_modes: mapper.ios_background_modes.clone(),\n                        },\n                    )\n                    .map_err(|e| e.into())\n            }\n            _ => Err(anyhow::anyhow!(\"Unsupported platform for Info.plist\")),\n        }\n    }\n\n    /// Run any final tools to produce apks or other artifacts we might need.\n    ///\n    /// This might include codesigning, zipping, creating an appimage, etc\n    async fn assemble(&self, ctx: &BuildContext) -> Result<()> {\n        if let BundleFormat::Android = self.bundle {\n            ctx.status_running_gradle();\n\n            // When the build mode is set to release and there is an Android signature configuration, use assembleRelease\n            let build_type = if self.release && self.config.bundle.android.is_some() {\n                \"assembleRelease\"\n            } else {\n                \"assembleDebug\"\n            };\n\n            let output = Command::new(self.gradle_exe()?)\n                .arg(build_type)\n                .current_dir(self.root_dir())\n                .output()\n                .await\n                .context(\"Failed to run gradle\")?;\n\n            if !output.status.success() {\n                bail!(\n                    \"Failed to assemble apk: {}\",\n                    String::from_utf8_lossy(&output.stderr)\n                );\n            }\n        }\n\n        // if the triple is a ios or macos target, we need to codesign the binary\n        if matches!(\n            self.triple.operating_system,\n            OperatingSystem::Darwin(_) | OperatingSystem::IOS(_)\n        ) && self.should_codesign\n        {\n            self.codesign_apple(ctx).await?;\n        }\n\n        Ok(())\n    }\n\n    /// Run bundleRelease and return the path to the `.aab` file\n    ///\n    /// <https://stackoverflow.com/questions/57072558/whats-the-difference-between-gradlewassemblerelease-gradlewinstallrelease-and>\n    pub(crate) async fn android_gradle_bundle(&self) -> Result<PathBuf> {\n        let output = Command::new(self.gradle_exe()?)\n            .arg(\"bundleRelease\")\n            .current_dir(self.root_dir())\n            .output()\n            .await\n            .context(\"Failed to run gradle bundleRelease\")?;\n\n        if !output.status.success() {\n            bail!(\n                \"Failed to bundleRelease: {}\",\n                String::from_utf8_lossy(&output.stderr)\n            );\n        }\n\n        let app_release = self\n            .root_dir()\n            .join(\"app\")\n            .join(\"build\")\n            .join(\"outputs\")\n            .join(\"bundle\")\n            .join(\"release\");\n\n        // Rename it to Name-arch.aab\n        let from = app_release.join(\"app-release.aab\");\n        let to = app_release.join(format!(\"{}-{}.aab\", self.bundled_app_name(), self.triple));\n\n        std::fs::rename(from, &to).context(\"Failed to rename aab\")?;\n\n        Ok(to)\n    }\n\n    fn gradle_exe(&self) -> Result<PathBuf> {\n        // make sure we can execute the gradlew script\n        #[cfg(unix)]\n        {\n            use std::os::unix::prelude::PermissionsExt;\n            std::fs::set_permissions(\n                self.root_dir().join(\"gradlew\"),\n                std::fs::Permissions::from_mode(0o755),\n            )\n            .context(\"Failed to make gradlew executable\")?;\n        }\n\n        let gradle_exec_name = match cfg!(windows) {\n            true => \"gradlew.bat\",\n            false => \"gradlew\",\n        };\n\n        Ok(self.root_dir().join(gradle_exec_name))\n    }\n\n    pub(crate) fn debug_apk_path(&self) -> PathBuf {\n        self.root_dir()\n            .join(\"app\")\n            .join(\"build\")\n            .join(\"outputs\")\n            .join(\"apk\")\n            .join(\"debug\")\n            .join(\"app-debug.apk\")\n    }\n\n    /// We only really currently care about:\n    ///\n    /// - app dir (.app, .exe, .apk, etc)\n    /// - assetas dir\n    /// - exe dir (.exe, .app, .apk, etc)\n    /// - extra scaffolding\n    ///\n    /// It's not guaranteed that they're different from any other folder\n    pub(crate) fn prepare_build_dir(&self, ctx: &BuildContext) -> Result<()> {\n        use std::fs::{create_dir_all, remove_dir_all};\n        use std::sync::OnceLock;\n\n        static PRIMARY_INITIALIZED: OnceLock<Result<()>> = OnceLock::new();\n        static SECONDARY_INITIALIZED: OnceLock<Result<()>> = OnceLock::new();\n\n        let initializer = if ctx.is_primary_build() {\n            &PRIMARY_INITIALIZED\n        } else {\n            &SECONDARY_INITIALIZED\n        };\n\n        let success = initializer.get_or_init(|| {\n            if ctx.is_primary_build() {\n                _ = remove_dir_all(self.exe_dir());\n            }\n\n            create_dir_all(self.root_dir())?;\n            create_dir_all(self.exe_dir())?;\n            create_dir_all(self.asset_dir())?;\n\n            tracing::debug!(\n                r#\"Initialized build dirs:\n               • root dir: {:?}\n               • exe dir: {:?}\n               • asset dir: {:?}\"#,\n                self.root_dir(),\n                self.exe_dir(),\n                self.asset_dir(),\n            );\n\n            // we could download the templates from somewhere (github?) but after having banged my head against\n            // cargo-mobile2 for ages, I give up with that. We're literally just going to hardcode the templates\n            // by writing them here.\n            if self.bundle == BundleFormat::Android {\n                self.build_android_app_dir()?;\n            }\n\n            Ok(())\n        });\n\n        if let Err(e) = success.as_ref() {\n            bail!(\"Failed to initialize build directory: {e}\");\n        }\n\n        Ok(())\n    }\n\n    pub(crate) fn asset_dir(&self) -> PathBuf {\n        match self.bundle {\n            BundleFormat::MacOS => self\n                .root_dir()\n                .join(\"Contents\")\n                .join(\"Resources\")\n                .join(\"assets\"),\n\n            BundleFormat::Android => self\n                .root_dir()\n                .join(\"app\")\n                .join(\"src\")\n                .join(\"main\")\n                .join(\"assets\"),\n\n            // We put assets in public/assets for server apps\n            BundleFormat::Server => self.root_dir().join(\"public\").join(\"assets\"),\n\n            // everyone else is soooo normal, just app/assets :)\n            BundleFormat::Web | BundleFormat::Ios | BundleFormat::Windows | BundleFormat::Linux => {\n                self.root_dir().join(\"assets\")\n            }\n        }\n    }\n\n    /// The directory in which we'll put the main exe\n    ///\n    /// Mac, Android, Web are a little weird\n    /// - mac wants to be in Contents/MacOS\n    /// - android wants to be in jniLibs/arm64-v8a (or others, depending on the platform / architecture)\n    /// - web wants to be in wasm (which... we don't really need to, we could just drop the wasm into public and it would work)\n    ///\n    /// I think all others are just in the root folder\n    ///\n    /// todo(jon): investigate if we need to put .wasm in `wasm`. It kinda leaks implementation details, which ideally we don't want to do.\n    fn exe_dir(&self) -> PathBuf {\n        match self.bundle {\n            BundleFormat::MacOS => self.root_dir().join(\"Contents\").join(\"MacOS\"),\n            BundleFormat::Web => self.root_dir().join(\"wasm\"),\n\n            // Android has a whole build structure to it\n            BundleFormat::Android => self\n                .root_dir()\n                .join(\"app\")\n                .join(\"src\")\n                .join(\"main\")\n                .join(\"jniLibs\")\n                .join(AndroidTools::android_jnilib(&self.triple)),\n\n            // these are all the same, I think?\n            BundleFormat::Windows\n            | BundleFormat::Linux\n            | BundleFormat::Ios\n            | BundleFormat::Server => self.root_dir(),\n        }\n    }\n\n    /// Get the path to the wasm bindgen temporary output folder\n    fn wasm_bindgen_out_dir(&self) -> PathBuf {\n        self.root_dir().join(\"wasm\")\n    }\n\n    /// Get the path to the wasm bindgen javascript output file\n    pub(crate) fn wasm_bindgen_js_output_file(&self) -> PathBuf {\n        self.wasm_bindgen_out_dir()\n            .join(self.executable_name())\n            .with_extension(\"js\")\n    }\n\n    /// Get the path to the wasm bindgen wasm output file\n    pub(crate) fn wasm_bindgen_wasm_output_file(&self) -> PathBuf {\n        self.wasm_bindgen_out_dir()\n            .join(format!(\"{}_bg\", self.executable_name()))\n            .with_extension(\"wasm\")\n    }\n\n    /// Get the path to the app manifest file\n    ///\n    /// This includes metadata about the build such as the bundle format, target triple, features, etc.\n    /// Manifests are only written by the `PRIMARY` build.\n    pub(crate) fn app_manifest(&self) -> PathBuf {\n        self.platform_dir().join(\".manifest.json\")\n    }\n\n    pub(crate) fn load_manifest(&self) -> Result<AppManifest> {\n        let manifest_path = self.app_manifest();\n        let manifest_data = std::fs::read_to_string(&manifest_path)\n            .with_context(|| format!(\"Failed to read manifest at {:?}\", &manifest_path))?;\n        let manifest: AppManifest = serde_json::from_str(&manifest_data)\n            .with_context(|| format!(\"Failed to parse manifest at {:?}\", &manifest_path))?;\n        Ok(manifest)\n    }\n\n    /// Check for tooling that might be required for this build.\n    ///\n    /// This should generally be only called on the first build since it takes time to verify the tooling\n    /// is in place, and we don't want to slow down subsequent builds.\n    pub(crate) async fn verify_tooling(&self, ctx: &BuildContext) -> Result<()> {\n        ctx.status_installing_tooling();\n\n        self.verify_toolchain_installed().await?;\n\n        match self.bundle {\n            BundleFormat::Web => self.verify_web_tooling().await?,\n            BundleFormat::Ios => self.verify_ios_tooling().await?,\n            BundleFormat::Android => self.verify_android_tooling().await?,\n            BundleFormat::Linux => self.verify_linux_tooling().await?,\n            BundleFormat::MacOS | BundleFormat::Windows | BundleFormat::Server => {}\n        }\n\n        Ok(())\n    }\n\n    async fn verify_toolchain_installed(&self) -> Result<()> {\n        let toolchain_dir = self.workspace.sysroot.join(\"lib/rustlib\");\n        let triple = self.triple.to_string();\n\n        // Install target using rustup.\n        if !toolchain_dir.join(&triple).exists() {\n            tracing::info!(\n                \"{} platform requires {} to be installed. Installing...\",\n                self.bundle,\n                triple\n            );\n\n            let mut child = tokio::process::Command::new(\"rustup\")\n                .args([\"target\", \"add\"])\n                .arg(&triple)\n                .stdout(Stdio::piped())\n                .stderr(Stdio::piped())\n                .kill_on_drop(true)\n                .spawn()?;\n\n            let stdout = tokio::io::BufReader::new(child.stdout.take().unwrap());\n            let stderr = tokio::io::BufReader::new(child.stderr.take().unwrap());\n            let mut stdout_lines = stdout.lines();\n            let mut stderr_lines = stderr.lines();\n            loop {\n                tokio::select! {\n                    line = stdout_lines.next_line() => {\n                        match line {\n                            Ok(Some(line)) => tracing::info!(\"{}\", line),\n                            Err(err) => tracing::error!(\"{}\", err),\n                            Ok(_) => break,\n                        }\n                    }\n                    line = stderr_lines.next_line() => {\n                        match line {\n                            Ok(Some(line)) => tracing::info!(\"{}\", line),\n                            Err(err) => tracing::error!(\"{}\", err),\n                            Ok(_) => break,\n                        }\n                    }\n                }\n            }\n        }\n\n        // Ensure target is installed.\n        if !toolchain_dir.join(&triple).exists() {\n            bail!(\"Missing rust target {}\", triple);\n        }\n\n        Ok(())\n    }\n\n    async fn verify_web_tooling(&self) -> Result<()> {\n        // Wasm bindgen\n        let krate_bindgen_version =\n            self.workspace\n                .wasm_bindgen_version()\n                .ok_or(anyhow::anyhow!(\n                    \"failed to detect wasm-bindgen version, unable to proceed\"\n                ))?;\n\n        WasmBindgen::verify_install(&krate_bindgen_version).await?;\n\n        Ok(())\n    }\n\n    /// Currently does nothing, but eventually we need to check that the mobile tooling is installed.\n    ///\n    /// For ios, this would be just aarch64-apple-ios + aarch64-apple-ios-sim, as well as xcrun and xcode-select\n    ///\n    /// We don't auto-install these yet since we're not doing an architecture check. We assume most users\n    /// are running on an Apple Silicon Mac, but it would be confusing if we installed these when we actually\n    /// should be installing the x86 versions.\n    async fn verify_ios_tooling(&self) -> Result<()> {\n        // open the simulator\n        // _ = tokio::process::Command::new(\"open\")\n        //     .arg(\"/Applications/Xcode.app/Contents/Developer/Applications/Simulator.app\")\n        //     .output()\n        //     .await;\n\n        // Now xcrun to open the device\n        // todo: we should try and query the device list and/or parse it rather than hardcode this simulator\n        // _ = tokio::process::Command::new(\"xcrun\")\n        //     .args([\"simctl\", \"boot\", \"83AE3067-987F-4F85-AE3D-7079EF48C967\"])\n        //     .output()\n        //     .await;\n\n        // if !rustup\n        //     .installed_toolchains\n        //     .contains(&\"aarch64-apple-ios\".to_string())\n        // {\n        //     tracing::error!(\"You need to install aarch64-apple-ios to build for ios. Run `rustup target add aarch64-apple-ios` to install it.\");\n        // }\n\n        // if !rustup\n        //     .installed_toolchains\n        //     .contains(&\"aarch64-apple-ios-sim\".to_string())\n        // {\n        //     tracing::error!(\"You need to install aarch64-apple-ios to build for ios. Run `rustup target add aarch64-apple-ios` to install it.\");\n        // }\n\n        Ok(())\n    }\n\n    /// Check if the android tooling is installed\n    ///\n    /// looks for the android sdk + ndk\n    ///\n    /// will do its best to fill in the missing bits by exploring the sdk structure\n    /// IE will attempt to use the Java installed from android studio if possible.\n    async fn verify_android_tooling(&self) -> Result<()> {\n        let linker = self\n            .workspace\n            .android_tools()?\n            .android_cc(&self.triple, self.min_sdk_version_or_default());\n\n        tracing::debug!(\"Verifying android linker: {linker:?}\");\n\n        if linker.exists() {\n            return Ok(());\n        }\n\n        bail!(\n            \"Android linker not found at {linker:?}. Please set the `ANDROID_NDK_HOME` environment variable to the root of your NDK installation.\"\n        );\n    }\n\n    /// Ensure the right dependencies are installed for linux apps.\n    /// This varies by distro, so we just do nothing for now.\n    ///\n    /// Eventually, we want to check for the prereqs for wry/tao as outlined by tauri:\n    ///     <https://tauri.app/start/prerequisites/>\n    async fn verify_linux_tooling(&self) -> Result<()> {\n        Ok(())\n    }\n\n    /// Blow away the fingerprint for this package, forcing rustc to recompile it.\n    ///\n    /// This prevents rustc from using the cached version of the binary, which can cause issues\n    /// Find workspace crates that directly depend on the given crate.\n    ///\n    /// Returns underscore-normalized crate names of workspace members that have `crate_name`\n    /// as a dependency. Used for cascade detection — when a dep's public symbols change,\n    /// its dependents need recompilation too.\n    pub(crate) fn workspace_dependents_of(&self, crate_name: &str) -> Vec<String> {\n        let krates = &self.workspace.krates;\n\n        // Find the NodeId for the target crate\n        let target_nid = krates.workspace_members().find_map(|member| {\n            if let krates::Node::Krate { id, krate, .. } = member {\n                if krate.name.replace('-', \"_\") == crate_name {\n                    return krates.nid_for_kid(id);\n                }\n            }\n            None\n        });\n\n        let Some(target_nid) = target_nid else {\n            return Vec::new();\n        };\n\n        // Use krates' direct_dependents to find reverse deps, filter to workspace members\n        let workspace_names: HashSet<String> = krates\n            .workspace_members()\n            .filter_map(|m| {\n                if let krates::Node::Krate { krate, .. } = m {\n                    Some(krate.name.replace('-', \"_\"))\n                } else {\n                    None\n                }\n            })\n            .collect();\n\n        krates\n            .direct_dependents(target_nid)\n            .into_iter()\n            .filter_map(|dep| {\n                let name = dep.krate.name.replace('-', \"_\");\n                if workspace_names.contains(&name) {\n                    Some(name)\n                } else {\n                    None\n                }\n            })\n            .collect()\n    }\n\n    /// Compile a workspace dependency crate directly with `rustc` using its captured args.\n    ///\n    /// This produces an updated rlib at the same path cargo originally wrote to.\n    /// Used during thin builds to recompile changed workspace deps before the tip crate.\n    async fn compile_dep_crate(&self, crate_name: &str, rustc_args: &RustcArgs) -> Result<()> {\n        let mut cmd = Command::new(\"rustc\");\n        cmd.current_dir(self.workspace_dir());\n        cmd.env_clear();\n\n        // Skip args[0] which is the rustc binary path captured by the wrapper\n        cmd.args(rustc_args.args[1..].iter());\n\n        // Restore the captured environment, filtering out wrapper env vars and\n        // stale cargo jobserver vars to prevent recursive invocation and warnings.\n        let filtered_env_keys = [\n            \"RUSTC_WORKSPACE_WRAPPER\",\n            \"RUSTC_WRAPPER\",\n            DX_RUSTC_WRAPPER_ENV_VAR,\n            \"CARGO_MAKEFLAGS\",\n            \"MAKEFLAGS\",\n        ];\n        cmd.envs(\n            rustc_args\n                .envs\n                .iter()\n                .filter(|(k, _)| !filtered_env_keys.contains(&k.as_str()))\n                .cloned(),\n        );\n\n        let output = cmd.output().await?;\n        if !output.status.success() {\n            let stderr = String::from_utf8_lossy(&output.stderr);\n            bail!(\"Failed to compile workspace dep crate '{crate_name}':\\n{stderr}\");\n        }\n\n        Ok(())\n    }\n\n    /// Find the rlib path for a workspace crate from its captured rustc args.\n    ///\n    /// Extracts `--out-dir` and `-C extra-filename` from the args to construct the exact\n    /// rlib filename. This is important because multiple rlibs for the same crate can coexist\n    /// in the deps directory (e.g., from different dx builds that produce different `-C metadata`),\n    /// and globbing would return an arbitrary one.\n    fn find_rlib_for_crate(&self, crate_name: &str, rustc_args: &RustcArgs) -> Option<PathBuf> {\n        // Extract --out-dir from the captured args\n        let out_dir = rustc_args\n            .args\n            .iter()\n            .zip(rustc_args.args.iter().skip(1))\n            .find(|(flag, _)| *flag == \"--out-dir\")\n            .map(|(_, dir)| PathBuf::from(dir))?;\n\n        // Extract -C extra-filename from captured args.\n        // Cargo passes this to rustc to disambiguate output filenames via metadata hash.\n        // Handle all forms: `-Cextra-filename=X`, `-C extra-filename=X`, and `-C` `extra-filename=X`.\n        let extra_filename = rustc_args.args.iter().enumerate().find_map(|(i, arg)| {\n            arg.strip_prefix(\"-Cextra-filename=\")\n                .map(|s| s.to_string())\n                .or_else(|| {\n                    // Handle `-C` followed by `extra-filename=X` as separate args\n                    if arg == \"-C\" {\n                        rustc_args.args.get(i + 1).and_then(|next| {\n                            next.strip_prefix(\"extra-filename=\").map(|s| s.to_string())\n                        })\n                    } else {\n                        None\n                    }\n                })\n        });\n\n        // If we have an exact extra-filename, construct the precise rlib path.\n        if let Some(extra) = &extra_filename {\n            let exact = out_dir.join(format!(\"lib{crate_name}{extra}.rlib\"));\n            if exact.exists() {\n                return Some(exact);\n            }\n        }\n\n        // Fallback: glob for lib<crate_name>-<hash>.rlib in the output directory.\n        // This handles cases where -C extra-filename isn't in the captured args.\n        // Prefer the most recently modified rlib to avoid picking up stale artifacts.\n        let prefix = format!(\"lib{crate_name}-\");\n        let entries = std::fs::read_dir(&out_dir).ok()?;\n        let mut best: Option<(PathBuf, std::time::SystemTime)> = None;\n        for entry in entries.flatten() {\n            if let Some(name) = entry.file_name().to_str() {\n                if name.starts_with(&prefix) && name.ends_with(\".rlib\") {\n                    let mtime = entry.metadata().ok()?.modified().ok()?;\n                    if best.as_ref().map_or(true, |(_, t)| mtime > *t) {\n                        best = Some((entry.path(), mtime));\n                    }\n                }\n            }\n        }\n        if let Some((path, _)) = best {\n            return Some(path);\n        }\n\n        None\n    }\n\n    /// with our hotpatching setup since it uses linker interception.\n    ///\n    /// This is sadly a hack. I think there might be other ways of busting the fingerprint (rustc wrapper?)\n    /// but that would require relying on cargo internals.\n    ///\n    /// This might stop working if/when cargo stabilizes contents-based fingerprinting.\n    ///\n    /// `dx` compiles everything with `--target` which ends up with a structure like:\n    /// `target/<triple>/<profile>/.fingerprint/<package_name>-<hash>`\n    ///\n    /// Normally you can't rely on this structure (ie with `cargo build`) but the explicit\n    /// target arg guarantees this will work.\n    fn bust_fingerprint(&self, ctx: &BuildContext) -> Result<()> {\n        if matches!(ctx.mode, BuildMode::Fat) {\n            let fingerprint_dir = self\n                .target_dir\n                .join(self.triple.to_string())\n                .join(&self.profile)\n                .join(\".fingerprint\");\n\n            // Bust fingerprints for ALL workspace member crates during Fat builds.\n            // This ensures cargo recompiles them through RUSTC_WORKSPACE_WRAPPER\n            // so we capture their rustc args for later thin builds.\n            let mut busted = HashSet::new();\n            for member in self.workspace.krates.workspace_members() {\n                if let krates::Node::Krate { krate, .. } = member {\n                    busted.insert(krate.name.as_str());\n                }\n            }\n\n            // split at the last `-` used to separate the hash from the name\n            // This causes to more aggressively bust hashes for all combinations of features\n            // and fingerprints for this package since we're just ignoring the hash\n            if let Ok(entries) = std::fs::read_dir(&fingerprint_dir) {\n                for entry in entries.flatten() {\n                    if let Some(fname) = entry.file_name().to_str() {\n                        if let Some((name, _)) = fname.rsplit_once('-') {\n                            if busted.contains(name) {\n                                _ = std::fs::remove_dir_all(entry.path());\n                            }\n                        }\n                    }\n                }\n            }\n        }\n\n        Ok(())\n    }\n\n    pub(crate) fn patch_cache_exe(&self, exe: &Path) -> PathBuf {\n        match self.bundle {\n            BundleFormat::Web => self.wasm_bindgen_wasm_output_file(),\n            _ => exe.to_path_buf(),\n        }\n    }\n\n    pub(crate) fn create_patch_cache(&self, exe: &Path) -> Result<HotpatchModuleCache> {\n        Ok(HotpatchModuleCache::new(\n            &self.patch_cache_exe(exe),\n            &self.triple,\n        )?)\n    }\n\n    /// Users create an index.html for their SPA if they want it\n    ///\n    /// We always write our wasm as main.js and main_bg.wasm\n    ///\n    /// In prod we run the optimizer which bundles everything together properly\n    ///\n    /// So their index.html needs to include main.js in the scripts otherwise nothing happens?\n    ///\n    /// Seems like every platform has a weird file that declares a bunch of stuff\n    /// - web: index.html\n    /// - ios: info.plist\n    /// - macos: info.plist\n    /// - linux: appimage root thing?\n    /// - android: androidmanifest.xml\n    ///\n    /// You also might different variants of these files (staging / prod) and different flavors (eu/us)\n    ///\n    /// web's index.html is weird since it's not just a bundle format but also a *content* format\n    pub(crate) fn prepare_html(\n        &self,\n        assets: &AssetManifest,\n        wasm_path: &str,\n        js_path: &str,\n    ) -> Result<String> {\n        let mut html = {\n            const DEV_DEFAULT_HTML: &str = include_str!(\"../../assets/web/dev.index.html\");\n            const PROD_DEFAULT_HTML: &str = include_str!(\"../../assets/web/prod.index.html\");\n\n            let crate_root: &Path = &self.crate_dir();\n            let custom_html_file = crate_root.join(\"index.html\");\n            let default_html = match self.release {\n                true => PROD_DEFAULT_HTML,\n                false => DEV_DEFAULT_HTML,\n            };\n            std::fs::read_to_string(custom_html_file).unwrap_or_else(|_| String::from(default_html))\n        };\n\n        // Inject any resources from the config into the html\n        self.inject_resources(assets, &mut html)?;\n\n        // Inject loading scripts if they are not already present\n        self.inject_loading_scripts(assets, &mut html);\n\n        // Replace any special placeholders in the HTML with resolved values\n        self.replace_template_placeholders(&mut html, wasm_path, js_path);\n\n        let title = self.config.web.app.title.clone();\n        Self::replace_or_insert_before(\"{app_title}\", \"</title\", &title, &mut html);\n\n        Ok(html)\n    }\n\n    fn is_dev_build(&self) -> bool {\n        !self.release\n    }\n\n    // Inject any resources from the config into the html\n    fn inject_resources(&self, assets: &AssetManifest, html: &mut String) -> Result<()> {\n        use std::fmt::Write;\n\n        // Collect all resources into a list of styles and scripts\n        let resources = &self.config.web.resource;\n        let mut style_list = resources.style.clone().unwrap_or_default();\n        let mut script_list = resources.script.clone().unwrap_or_default();\n\n        if self.is_dev_build() {\n            style_list.extend(resources.dev.style.iter().cloned());\n            script_list.extend(resources.dev.script.iter().cloned());\n        }\n\n        let mut head_resources = String::new();\n\n        // Add all styles to the head\n        for style in &style_list {\n            writeln!(\n                &mut head_resources,\n                \"<link rel=\\\"stylesheet\\\" href=\\\"{}\\\">\",\n                &style.to_str().unwrap(),\n            )?;\n        }\n\n        // Add all scripts to the head\n        for script in &script_list {\n            writeln!(\n                &mut head_resources,\n                \"<script src=\\\"{}\\\"></script>\",\n                &script.to_str().unwrap(),\n            )?;\n        }\n\n        // Add the base path to the head if this is a debug build\n        if self.is_dev_build() {\n            if let Some(base_path) = &self.trimmed_base_path() {\n                head_resources.push_str(&format_base_path_meta_element(base_path));\n            }\n        }\n\n        // Inject any resources from manganis into the head\n        for asset in assets.unique_assets() {\n            let asset_path = asset.bundled_path();\n            match asset.options().variant() {\n                AssetVariant::Css(css_options) => {\n                    if css_options.preloaded() {\n                        _ = write!(\n                            head_resources,\n                            r#\"<link rel=\"preload\" as=\"style\" href=\"/{{base_path}}/assets/{asset_path}\" crossorigin>\"#\n                        );\n                    }\n                    if css_options.static_head() {\n                        _ = write!(\n                            head_resources,\n                            r#\"<link rel=\"stylesheet\" href=\"/{{base_path}}/assets/{asset_path}\" type=\"text/css\">\"#\n                        );\n                    }\n                }\n                AssetVariant::Image(image_options) => {\n                    if image_options.preloaded() {\n                        _ = write!(\n                            head_resources,\n                            r#\"<link rel=\"preload\" as=\"image\" href=\"/{{base_path}}/assets/{asset_path}\" crossorigin>\"#\n                        );\n                    }\n                }\n                AssetVariant::Js(js_options) => {\n                    if js_options.preloaded() {\n                        _ = write!(\n                            head_resources,\n                            r#\"<link rel=\"preload\" as=\"script\" href=\"/{{base_path}}/assets/{asset_path}\" crossorigin>\"#\n                        );\n                    }\n                    if js_options.static_head() {\n                        _ = write!(\n                            head_resources,\n                            r#\"<script src=\"/{{base_path}}/assets/{asset_path}\"></script>\"#\n                        );\n                    }\n                }\n                _ => {}\n            }\n        }\n\n        // Do not preload the wasm file, because in Safari, preload as=fetch requires additional fetch() options to exactly match the network request\n        // And if they do not match then Safari downloads the wasm file twice.\n        // See https://github.com/wasm-bindgen/wasm-bindgen/blob/ac51055a4c39fa0affe02f7b63fb1d4c9b3ddfaf/crates/cli-support/src/js/mod.rs#L967\n        Self::replace_or_insert_before(\"{style_include}\", \"</head\", &head_resources, html);\n\n        Ok(())\n    }\n\n    /// Inject loading scripts if they are not already present\n    fn inject_loading_scripts(&self, assets: &AssetManifest, html: &mut String) {\n        // If the current build opted out of injecting loading scripts, don't inject anything\n        if !self.inject_loading_scripts {\n            return;\n        }\n\n        // If not, insert the script\n        *html = html.replace(\n            \"</body\",\n            &format!(\n                r#\"<script type=\"module\" async src=\"/{}/{}\"></script>\n            </body\"#,\n                self.base_path_or_default(),\n                self.bundled_js_path(assets)\n            ),\n        );\n    }\n\n    /// Replace any special placeholders in the HTML with resolved values\n    fn replace_template_placeholders(&self, html: &mut String, wasm_path: &str, js_path: &str) {\n        let base_path = self.base_path_or_default();\n        *html = html.replace(\"{base_path}\", base_path);\n\n        let app_name = &self.executable_name();\n\n        // If the html contains the old `{app_name}` placeholder, replace {app_name}_bg.wasm and {app_name}.js\n        // with the new paths\n        *html = html.replace(\"wasm/{app_name}_bg.wasm\", wasm_path);\n        *html = html.replace(\"wasm/{app_name}.js\", js_path);\n\n        // Otherwise replace the new placeholders\n        *html = html.replace(\"{wasm_path}\", wasm_path);\n        *html = html.replace(\"{js_path}\", js_path);\n\n        // Replace the app_name if we find it anywhere standalone\n        *html = html.replace(\"{app_name}\", app_name);\n    }\n\n    /// Replace a string or insert the new contents before a marker\n    fn replace_or_insert_before(\n        replace: &str,\n        or_insert_before: &str,\n        with: &str,\n        content: &mut String,\n    ) {\n        if content.contains(replace) {\n            *content = content.replace(replace, with);\n        } else if let Some(pos) = content.find(or_insert_before) {\n            content.insert_str(pos, with);\n        }\n    }\n\n    /// Resolve the configured public directory relative to the crate, if any.\n    pub(crate) fn user_public_dir(&self) -> Option<PathBuf> {\n        let path = self.config.application.public_dir.as_ref()?;\n\n        if path.as_os_str().is_empty() {\n            return None;\n        }\n\n        Some(if path.is_absolute() {\n            path.clone()\n        } else {\n            self.crate_dir().join(path)\n        })\n    }\n\n    pub(crate) fn path_is_in_public_dir(&self, path: &Path) -> bool {\n        let Some(static_dir) = self.user_public_dir() else {\n            return false;\n        };\n\n        // Canonicalize when possible so we work with editors that use tmp files\n        let canonical_static =\n            dunce::canonicalize(&static_dir).unwrap_or_else(|_| static_dir.clone());\n        let canonical_path = dunce::canonicalize(path).unwrap_or_else(|_| path.to_path_buf());\n\n        canonical_path.starts_with(&canonical_static)\n    }\n\n    /// Get the base path from the config or None if this is not a web or server build\n    pub(crate) fn base_path(&self) -> Option<&str> {\n        self.base_path\n            .as_deref()\n            .or(self.config.web.app.base_path.as_deref())\n            .filter(|_| matches!(self.bundle, BundleFormat::Web | BundleFormat::Server))\n    }\n\n    /// Get the normalized base path for the application with `/` trimmed from both ends.\n    pub(crate) fn trimmed_base_path(&self) -> Option<&str> {\n        self.base_path()\n            .map(|p| p.trim_matches('/'))\n            .filter(|p| !p.is_empty())\n    }\n\n    /// Get the trimmed base path or `.` if no base path is set\n    pub(crate) fn base_path_or_default(&self) -> &str {\n        self.trimmed_base_path().unwrap_or(\".\")\n    }\n\n    /// Get the path to the package manifest directory\n    pub(crate) fn package_manifest_dir(&self) -> PathBuf {\n        self.workspace.krates[self.crate_package]\n            .manifest_path\n            .parent()\n            .unwrap()\n            .to_path_buf()\n            .into()\n    }\n\n    /// Returns the min sdk version set in config. If not set 24 is returned as a default.\n    pub(crate) fn min_sdk_version_or_default(&self) -> u32 {\n        self.config\n            .application\n            .android_min_sdk_version\n            .unwrap_or(28)\n    }\n\n    pub(crate) async fn start_simulators(&self) -> Result<()> {\n        if self.device_name.is_some() {\n            return Ok(());\n        }\n\n        match self.bundle {\n            // Boot an iOS simulator if one is not already running.\n            //\n            // We always choose the most recently opened simulator based on the xcrun list.\n            // Note that simulators can be running but the simulator app itself is not open.\n            // Calling `open::that` is always fine, even on running apps, since apps are singletons.\n            BundleFormat::Ios => {\n                #[derive(Deserialize, Debug)]\n                struct XcrunListJson {\n                    // \"com.apple.CoreSimulator.SimRuntime.iOS-18-4\": [{}, {}, {}]\n                    devices: BTreeMap<String, Vec<XcrunDevice>>,\n                }\n\n                #[derive(Deserialize, Debug)]\n                struct XcrunDevice {\n                    #[serde(rename = \"lastBootedAt\")]\n                    last_booted_at: Option<String>,\n                    udid: String,\n                    name: String,\n                    state: String,\n                }\n                let xcrun_list = Command::new(\"xcrun\")\n                    .arg(\"simctl\")\n                    .arg(\"list\")\n                    .arg(\"-j\")\n                    .output()\n                    .await?;\n\n                let as_str = String::from_utf8_lossy(&xcrun_list.stdout);\n                let xcrun_list_json = serde_json::from_str::<XcrunListJson>(as_str.trim());\n                if let Ok(xcrun_list_json) = xcrun_list_json {\n                    if xcrun_list_json.devices.is_empty() {\n                        tracing::warn!(\n                            \"No iOS sdks installed found. Please install the iOS SDK in Xcode.\"\n                        );\n                    }\n\n                    if let Some((_rt, devices)) = xcrun_list_json.devices.iter().next() {\n                        if devices.iter().all(|device| device.state != \"Booted\") {\n                            let last_booted =\n                                devices\n                                    .iter()\n                                    .max_by_key(|device| match device.last_booted_at {\n                                        Some(ref last_booted) => last_booted,\n                                        None => \"2000-01-01T01:01:01Z\",\n                                    });\n\n                            if let Some(device) = last_booted {\n                                tracing::info!(\"Booting iOS simulator: \\\"{}\\\"\", device.name);\n                                Command::new(\"xcrun\")\n                                    .arg(\"simctl\")\n                                    .arg(\"boot\")\n                                    .arg(&device.udid)\n                                    .output()\n                                    .await?;\n                            }\n                        }\n                    }\n                }\n                let path_to_xcode = Command::new(\"xcode-select\")\n                    .arg(\"--print-path\")\n                    .output()\n                    .await?;\n                let path_to_xcode: PathBuf = String::from_utf8_lossy(&path_to_xcode.stdout)\n                    .as_ref()\n                    .trim()\n                    .into();\n                let path_to_sim = path_to_xcode.join(\"Applications\").join(\"Simulator.app\");\n                open::that_detached(path_to_sim)?;\n            }\n\n            BundleFormat::Android => {\n                let tools = self.workspace.android_tools()?;\n                tokio::spawn(async move {\n                    let emulator = tools.emulator();\n                    let avds = Command::new(&emulator)\n                        .arg(\"-list-avds\")\n                        .output()\n                        .await\n                        .unwrap();\n                    let avds = String::from_utf8_lossy(&avds.stdout);\n                    let avd = avds.trim().lines().next().map(|s| s.trim().to_string());\n                    if let Some(avd) = avd {\n                        tracing::info!(\"Booting Android emulator: \\\"{avd}\\\"\");\n                        Command::new(&emulator)\n                            .arg(\"-avd\")\n                            .arg(avd)\n                            .args([\"-netdelay\", \"none\", \"-netspeed\", \"full\"])\n                            .stdout(std::process::Stdio::null()) // prevent accumulating huge amounts of mem usage\n                            .stderr(std::process::Stdio::null()) // prevent accumulating huge amounts of mem usage\n                            .output()\n                            .await\n                            .unwrap();\n                    } else {\n                        tracing::warn!(\"No Android emulators found. Please create one using `emulator -avd <name>`\");\n                    }\n                });\n            }\n\n            _ => {\n                // nothing - maybe on the web we should open the browser?\n            }\n        };\n\n        Ok(())\n    }\n\n    /// Assemble a series of `--config key=value` arguments for the build command.\n    ///\n    /// This adds adhoc profiles that dx uses to isolate builds from each other. Normally if you ran\n    /// `cargo build --feature desktop` and `cargo build --feature server`, then both binaries get\n    /// the same name and overwrite each other, causing thrashing and locking issues.\n    ///\n    /// By creating adhoc profiles, we can ensure that each build is isolated and doesn't interfere with each other.\n    ///\n    /// The user can also define custom profiles in their `Cargo.toml` file, which will be used instead\n    /// of the adhoc profiles.\n    ///\n    /// The names of the profiles are:\n    /// - web-dev\n    /// - web-release\n    /// - desktop-dev\n    /// - desktop-release\n    /// - server-dev\n    /// - server-release\n    /// - ios-dev\n    /// - ios-release\n    /// - android-dev\n    /// - android-release\n    /// - liveview-dev\n    /// - liveview-release\n    ///\n    /// Note how every platform gets its own profile, and each platform has a dev and release profile.\n    fn profile_args(&self) -> Vec<String> {\n        // Always disable stripping so symbols still exist for the asset system. We will apply strip manually\n        // after assets are built\n        let profile = self.profile.as_str();\n        let mut args = Vec::new();\n        args.push(format!(r#\"profile.{profile}.strip=false\"#));\n\n        // If the user defined the profile in the Cargo.toml, we don't need to add it to our adhoc list\n        if !self\n            .workspace\n            .cargo_toml\n            .profile\n            .custom\n            .contains_key(&self.profile)\n        {\n            // Otherwise, we need to add the profile arguments to make it adhoc\n            let inherits = if self.release { \"release\" } else { \"dev\" };\n\n            // Add the profile definition first.\n            args.push(format!(r#\"profile.{profile}.inherits=\"{inherits}\"\"#));\n\n            // The default dioxus experience is to lightly optimize the web build, both in debug and release\n            // Note that typically in release builds, you would strip debuginfo, but we actually choose to do\n            // that with wasm-opt tooling instead.\n            if matches!(self.bundle, BundleFormat::Web) {\n                if self.release {\n                    args.push(format!(r#\"profile.{profile}.opt-level=\"s\"\"#));\n                }\n\n                if self.wasm_split {\n                    args.push(format!(r#\"profile.{profile}.lto=true\"#));\n                    args.push(format!(r#\"profile.{profile}.debug=true\"#));\n                }\n            }\n        }\n\n        // Prepend --config to each argument\n        args.into_iter()\n            .flat_map(|arg| [\"--config\".to_string(), arg])\n            .collect()\n    }\n\n    pub async fn codesign_apple(&self, ctx: &BuildContext) -> Result<()> {\n        ctx.status_codesigning();\n\n        // We don't want to drop the entitlements file, until the end of the block, so we hoist it to this temporary.\n        let mut _saved_entitlements = None;\n\n        let mut app_dev_name = self.apple_team_id.clone();\n        if app_dev_name.is_none() {\n            app_dev_name = Some(Self::auto_provision_signing_name().await.context(\n                \"Failed to automatically provision signing name for Apple codesigning.\",\n            )?);\n        }\n\n        let mut entitlements_file = self.apple_entitlements.clone();\n        let mut provisioning_profile_path = None;\n        if entitlements_file.is_none() {\n            let bundle_id = self.bundle_identifier();\n            let (entitlements_xml, profile_path) = Self::auto_provision_entitlements(&bundle_id)\n                .await\n                .context(\"Failed to auto-provision entitlements for Apple codesigning.\")?;\n\n            // Enrich with entitlements from Dioxus.toml config\n            let entitlements_xml = self.enrich_entitlements_from_config(entitlements_xml)?;\n\n            let entitlements_temp_file = tempfile::NamedTempFile::new()?;\n            std::fs::write(entitlements_temp_file.path(), entitlements_xml)?;\n            entitlements_file = Some(entitlements_temp_file.path().to_path_buf());\n            provisioning_profile_path = Some(profile_path);\n            _saved_entitlements = Some(entitlements_temp_file);\n        }\n\n        let entitlements_file = entitlements_file.as_ref().context(\n            \"No entitlements file provided and could not provision entitlements to sign app.\",\n        )?;\n        let app_dev_name = app_dev_name.as_ref().context(\n            \"No Apple Development signing name provided and could not auto-provision one.\",\n        )?;\n\n        tracing::debug!(\n            \"Codesigning Apple app with entitlements: {} and dev name: {}\",\n            entitlements_file.display(),\n            app_dev_name\n        );\n\n        // determine the target exe - the server and macos bundles are different\n        let target_exe = match self.bundle {\n            BundleFormat::MacOS => self.root_dir(),\n            BundleFormat::Ios => self.root_dir(),\n            BundleFormat::Server => self.main_exe(),\n            _ => bail!(\"Codesigning is only supported for MacOS and iOS bundles\"),\n        };\n\n        // iOS devices require the provisioning profile to be embedded in the .app bundle\n        if self.bundle == BundleFormat::Ios {\n            if let Some(profile_path) = &provisioning_profile_path {\n                let dest = target_exe.join(\"embedded.mobileprovision\");\n                std::fs::copy(profile_path, &dest)\n                    .context(\"Failed to embed provisioning profile into .app bundle\")?;\n            }\n        }\n\n        // codesign the app\n        let output = Command::new(\"codesign\")\n            .args([\n                \"--force\",\n                \"--entitlements\",\n                entitlements_file.to_str().unwrap(),\n                \"--sign\",\n                app_dev_name,\n            ])\n            .arg(target_exe)\n            .output()\n            .await\n            .context(\"Failed to codesign the app - is `codesign` in your path?\")?;\n\n        if !output.status.success() {\n            bail!(\n                \"Failed to codesign the app: {}\",\n                String::from_utf8(output.stderr).unwrap_or_default()\n            );\n        }\n\n        Ok(())\n    }\n\n    async fn auto_provision_signing_name() -> Result<String> {\n        let identities = Command::new(\"security\")\n            .args([\"find-identity\", \"-v\", \"-p\", \"codesigning\"])\n            .output()\n            .await\n            .context(\"Failed to run `security find-identity -v -p codesigning` - is `security` in your path?\")\n            .map(|e| {\n                String::from_utf8(e.stdout)\n                    .context(\"Failed to parse `security find-identity -v -p codesigning`\")\n            })??;\n\n        // Parsing this:\n        // 1231231231231asdasdads123123 \"Apple Development: foo@gmail.com (XYZYZY)\"\n        let app_dev_name = regex::Regex::new(r#\"\"Apple Development: (.+)\"\"#)\n            .unwrap()\n            .captures(&identities)\n            .and_then(|caps| caps.get(1))\n            .map(|m| m.as_str())\n            .context(\n                \"Failed to find Apple Development in `security find-identity -v -p codesigning`\",\n            )?;\n\n        Ok(app_dev_name.to_string())\n    }\n\n    /// Enrich auto-provisioned entitlements XML with config from Dioxus.toml.\n    ///\n    /// Injects entitlements from `[ios.entitlements]` or `[macos.entitlements]` sections\n    /// and associated domains from `[deep_links]` into the base entitlements XML.\n    fn enrich_entitlements_from_config(&self, base_xml: String) -> Result<String> {\n        let mut extra_entries = String::new();\n\n        match self.bundle {\n            BundleFormat::Ios => {\n                let ent = &self.config.ios.entitlements;\n\n                // Associated domains (from deep_links.hosts + ios.entitlements.associated-domains)\n                let mapper = super::manifest_mapper::ManifestMapper::from_config(\n                    &self.config.permissions,\n                    &self.config.deep_links,\n                    &self.config.background,\n                    &self.config.android,\n                    &self.config.ios,\n                    &self.config.macos,\n                );\n                let mut domains: Vec<String> = mapper.ios_associated_domains;\n                domains.extend(ent.associated_domains.clone());\n                domains.dedup();\n                if !domains.is_empty() {\n                    extra_entries.push_str(\n                        \"    <key>com.apple.developer.associated-domains</key>\\n    <array>\\n\",\n                    );\n                    for domain in &domains {\n                        extra_entries.push_str(&format!(\"        <string>{domain}</string>\\n\"));\n                    }\n                    extra_entries.push_str(\"    </array>\\n\");\n                }\n\n                // App groups\n                if !ent.app_groups.is_empty() {\n                    extra_entries.push_str(\n                        \"    <key>com.apple.security.application-groups</key>\\n    <array>\\n\",\n                    );\n                    for group in &ent.app_groups {\n                        extra_entries.push_str(&format!(\"        <string>{group}</string>\\n\"));\n                    }\n                    extra_entries.push_str(\"    </array>\\n\");\n                }\n\n                // APS environment (push notifications)\n                if let Some(env) = &ent.aps_environment {\n                    extra_entries.push_str(&format!(\n                        \"    <key>aps-environment</key>\\n    <string>{env}</string>\\n\"\n                    ));\n                }\n\n                // iCloud\n                if ent.icloud {\n                    extra_entries.push_str(\n                        \"    <key>com.apple.developer.icloud-container-identifiers</key>\\n    <array/>\\n\\\n                         <key>com.apple.developer.icloud-services</key>\\n    <array>\\n        <string>CloudDocuments</string>\\n    </array>\\n\"\n                    );\n                }\n\n                // Keychain access groups\n                // (base entitlements already include one from provisioning profile, only add extras)\n                if !ent.keychain_access_groups.is_empty() {\n                    extra_entries.push_str(\"    <key>keychain-access-groups</key>\\n    <array>\\n\");\n                    for group in &ent.keychain_access_groups {\n                        extra_entries.push_str(&format!(\"        <string>{group}</string>\\n\"));\n                    }\n                    extra_entries.push_str(\"    </array>\\n\");\n                }\n\n                // Apple Pay\n                if ent.apple_pay {\n                    extra_entries.push_str(\n                        \"    <key>com.apple.developer.in-app-payments</key>\\n    <array>\\n        <string>merchant.*</string>\\n    </array>\\n\"\n                    );\n                }\n\n                // HealthKit\n                if ent.healthkit {\n                    extra_entries\n                        .push_str(\"    <key>com.apple.developer.healthkit</key>\\n    <true/>\\n\");\n                }\n\n                // HomeKit\n                if ent.homekit {\n                    extra_entries\n                        .push_str(\"    <key>com.apple.developer.homekit</key>\\n    <true/>\\n\");\n                }\n\n                // Additional entitlements from the flat map\n                for (key, value) in &ent.additional {\n                    extra_entries.push_str(&format!(\n                        \"    <key>{key}</key>\\n    {}\\n\",\n                        value_to_plist_xml(value, 1)\n                    ));\n                }\n\n                // Raw entitlements XML\n                if let Some(raw) = &self.config.ios.raw.entitlements {\n                    extra_entries.push_str(raw);\n                    extra_entries.push('\\n');\n                }\n            }\n            BundleFormat::MacOS => {\n                let ent = &self.config.macos.entitlements;\n\n                // App Sandbox\n                if let Some(v) = ent.app_sandbox {\n                    extra_entries.push_str(&format!(\n                        \"    <key>com.apple.security.app-sandbox</key>\\n    <{v}/>\\n\"\n                    ));\n                }\n\n                // File access\n                if let Some(true) = ent.files_user_selected {\n                    extra_entries.push_str(\n                        \"    <key>com.apple.security.files.user-selected.read-write</key>\\n    <true/>\\n\"\n                    );\n                }\n                if let Some(true) = ent.files_user_selected_readonly {\n                    extra_entries.push_str(\n                        \"    <key>com.apple.security.files.user-selected.read-only</key>\\n    <true/>\\n\"\n                    );\n                }\n\n                // Network\n                if let Some(true) = ent.network_client {\n                    extra_entries.push_str(\n                        \"    <key>com.apple.security.network.client</key>\\n    <true/>\\n\",\n                    );\n                }\n                if let Some(true) = ent.network_server {\n                    extra_entries.push_str(\n                        \"    <key>com.apple.security.network.server</key>\\n    <true/>\\n\",\n                    );\n                }\n\n                // Device access\n                if let Some(true) = ent.camera {\n                    extra_entries\n                        .push_str(\"    <key>com.apple.security.device.camera</key>\\n    <true/>\\n\");\n                }\n                if let Some(true) = ent.microphone {\n                    extra_entries.push_str(\n                        \"    <key>com.apple.security.device.microphone</key>\\n    <true/>\\n\",\n                    );\n                }\n                if let Some(true) = ent.usb {\n                    extra_entries\n                        .push_str(\"    <key>com.apple.security.device.usb</key>\\n    <true/>\\n\");\n                }\n                if let Some(true) = ent.bluetooth {\n                    extra_entries.push_str(\n                        \"    <key>com.apple.security.device.bluetooth</key>\\n    <true/>\\n\",\n                    );\n                }\n                if let Some(true) = ent.print {\n                    extra_entries\n                        .push_str(\"    <key>com.apple.security.print</key>\\n    <true/>\\n\");\n                }\n\n                // Personal information\n                if let Some(true) = ent.location {\n                    extra_entries.push_str(\n                        \"    <key>com.apple.security.personal-information.location</key>\\n    <true/>\\n\"\n                    );\n                }\n                if let Some(true) = ent.addressbook {\n                    extra_entries.push_str(\n                        \"    <key>com.apple.security.personal-information.addressbook</key>\\n    <true/>\\n\"\n                    );\n                }\n                if let Some(true) = ent.calendars {\n                    extra_entries.push_str(\n                        \"    <key>com.apple.security.personal-information.calendars</key>\\n    <true/>\\n\"\n                    );\n                }\n\n                // Runtime exceptions\n                if let Some(true) = ent.disable_library_validation {\n                    extra_entries.push_str(\n                        \"    <key>com.apple.security.cs.disable-library-validation</key>\\n    <true/>\\n\"\n                    );\n                }\n                if let Some(true) = ent.allow_jit {\n                    extra_entries\n                        .push_str(\"    <key>com.apple.security.cs.allow-jit</key>\\n    <true/>\\n\");\n                }\n                if let Some(true) = ent.allow_unsigned_executable_memory {\n                    extra_entries.push_str(\n                        \"    <key>com.apple.security.cs.allow-unsigned-executable-memory</key>\\n    <true/>\\n\"\n                    );\n                }\n\n                // Additional entitlements from the flat map\n                for (key, value) in &ent.additional {\n                    extra_entries.push_str(&format!(\n                        \"    <key>{key}</key>\\n    {}\\n\",\n                        value_to_plist_xml(value, 1)\n                    ));\n                }\n\n                // Raw entitlements XML\n                if let Some(raw) = &self.config.macos.raw.entitlements {\n                    extra_entries.push_str(raw);\n                    extra_entries.push('\\n');\n                }\n            }\n            _ => {}\n        }\n\n        if extra_entries.is_empty() {\n            return Ok(base_xml);\n        }\n\n        // Insert before closing </dict></plist>\n        if let Some(pos) = base_xml.rfind(\"</dict>\") {\n            let mut enriched = base_xml[..pos].to_string();\n            enriched.push_str(&extra_entries);\n            enriched.push_str(&base_xml[pos..]);\n            Ok(enriched)\n        } else {\n            tracing::warn!(\"Could not find </dict> in entitlements XML to inject config entries\");\n            Ok(base_xml)\n        }\n    }\n\n    async fn auto_provision_entitlements(bundle_id: &str) -> Result<(String, PathBuf)> {\n        const CODESIGN_ERROR: &str = r#\"This is likely because you haven't\n- Created a provisioning profile before\n- Accepted the Apple Developer Program License Agreement\n\nThe agreement changes frequently and might need to be accepted again.\nTo accept the agreement, go to https://developer.apple.com/account\n\nTo create a provisioning profile, follow the instructions here:\nhttps://developer.apple.com/documentation/xcode/sharing-your-teams-signing-certificates\"#;\n\n        // Check the xcode 16 location first\n        let mut profiles_folder = dirs::home_dir()\n            .context(\"Your machine has no home-dir\")?\n            .join(\"Library/Developer/Xcode/UserData/Provisioning Profiles\");\n\n        // If it doesn't exist, check the old location\n        if !profiles_folder.exists() {\n            profiles_folder = dirs::home_dir()\n                .context(\"Your machine has no home-dir\")?\n                .join(\"Library/MobileDevice/Provisioning Profiles\");\n        }\n\n        if !profiles_folder.exists() || profiles_folder.read_dir()?.next().is_none() {\n            tracing::error!(\n                r#\"No provisioning profiles found when trying to codesign the app.\nWe checked the folders:\n- XCode16: ~/Library/Developer/Xcode/UserData/Provisioning Profiles\n- XCode15: ~/Library/MobileDevice/Provisioning Profiles\n\n{CODESIGN_ERROR}\n\"#\n            )\n        }\n\n        #[derive(serde::Deserialize, Debug)]\n        struct ProvisioningProfile {\n            #[serde(rename = \"TeamIdentifier\")]\n            team_identifier: Vec<String>,\n            #[serde(rename = \"Entitlements\")]\n            entitlements: ProfileEntitlements,\n            #[allow(dead_code)]\n            #[serde(rename = \"ApplicationIdentifierPrefix\")]\n            application_identifier_prefix: Vec<String>,\n            #[serde(rename = \"ProvisionedDevices\", default)]\n            provisioned_devices: Vec<String>,\n        }\n\n        #[derive(serde::Deserialize, Debug)]\n        struct ProfileEntitlements {\n            #[serde(rename = \"application-identifier\")]\n            application_identifier: String,\n            #[serde(rename = \"keychain-access-groups\")]\n            keychain_access_groups: Vec<String>,\n        }\n\n        // The .mobileprovision file has some random binary thrown into it, but it's still basically a plist\n        // Let's use the plist markers to find the start and end of the plist\n        fn cut_plist(bytes: &[u8], byte_match: &[u8]) -> Option<usize> {\n            bytes\n                .windows(byte_match.len())\n                .enumerate()\n                .rev()\n                .find(|(_, slice)| *slice == byte_match)\n                .map(|(i, _)| i + byte_match.len())\n        }\n\n        fn parse_profile(path: &Path) -> Result<ProvisioningProfile> {\n            let bytes = std::fs::read(path)?;\n            let cut1 =\n                cut_plist(&bytes, b\"<plist\").context(\"Failed to parse .mobileprovision file\")?;\n            let cut2 = cut_plist(&bytes, r#\"</dict>\"#.as_bytes())\n                .context(\"Failed to parse .mobileprovision file\")?;\n            let sub_bytes = &bytes[(cut1 - 6)..cut2];\n            plist::from_bytes(sub_bytes).context(\"Failed to parse .mobileprovision file\")\n        }\n\n        /// Check if a provisioning profile's application-identifier matches the given bundle ID.\n        /// The app ID is in the format \"TEAMID.com.example.app\" or \"TEAMID.*\" for wildcard profiles.\n        fn profile_matches_bundle_id(app_identifier: &str, bundle_id: &str) -> bool {\n            // Strip the team ID prefix (everything before and including the first dot)\n            let app_id_suffix = match app_identifier.split_once('.') {\n                Some((_, suffix)) => suffix,\n                None => return false,\n            };\n\n            // Wildcard profile matches everything\n            if app_id_suffix == \"*\" {\n                return true;\n            }\n\n            // Check exact match\n            if app_id_suffix == bundle_id {\n                return true;\n            }\n\n            // Check wildcard prefix match (e.g. \"com.example.*\" matches \"com.example.app\")\n            if let Some(prefix) = app_id_suffix.strip_suffix(\".*\") {\n                return bundle_id.starts_with(prefix);\n            }\n\n            false\n        }\n\n        // Collect all provisioning profiles and find the best match for the bundle ID.\n        // Priority: exact app ID match > more provisioned devices > newer file.\n        let mut best_match: Option<(PathBuf, ProvisioningProfile, bool, usize)> = None;\n\n        for entry in profiles_folder.read_dir()?.flatten() {\n            let path = entry.path();\n            let is_mobileprovision = path\n                .extension()\n                .map(|e| e == \"mobileprovision\")\n                .unwrap_or(false);\n\n            if !is_mobileprovision {\n                continue;\n            }\n\n            let profile = match parse_profile(&path) {\n                Ok(p) => p,\n                Err(e) => {\n                    tracing::debug!(\"Skipping profile {}: {e}\", path.display());\n                    continue;\n                }\n            };\n\n            let app_id = &profile.entitlements.application_identifier;\n            if !profile_matches_bundle_id(app_id, bundle_id) {\n                tracing::debug!(\n                    \"Skipping profile {} (app ID {app_id} does not match bundle ID {bundle_id})\",\n                    path.display()\n                );\n                continue;\n            }\n\n            let is_exact = !app_id.ends_with(\".*\") && !app_id.ends_with(\"*\");\n            let num_devices = profile.provisioned_devices.len();\n\n            tracing::debug!(\n                \"Found matching profile {} (app ID: {app_id}, exact: {is_exact}, devices: {num_devices})\",\n                path.display()\n            );\n\n            // Prefer: exact match > more provisioned devices (newer profiles have more devices)\n            let dominated = match &best_match {\n                Some((_, _, prev_exact, prev_devices)) => {\n                    if *prev_exact && !is_exact {\n                        true // existing exact match beats wildcard\n                    } else if is_exact && !*prev_exact {\n                        false // new exact match beats existing wildcard\n                    } else {\n                        // same specificity — prefer more provisioned devices\n                        num_devices <= *prev_devices\n                    }\n                }\n                None => false,\n            };\n\n            if !dominated {\n                best_match = Some((path, profile, is_exact, num_devices));\n            }\n        }\n\n        let (profile_path, mbfile) = match best_match {\n            Some((path, profile, _, _)) => {\n                tracing::info!(\n                    \"Using provisioning profile: {} (app ID: {})\",\n                    path.display(),\n                    profile.entitlements.application_identifier\n                );\n                (path, profile)\n            }\n            None => {\n                bail!(\n                    \"No provisioning profile found matching bundle identifier \\\"{bundle_id}\\\".\\n\\\n                     \\n\\\n                     Your provisioning profiles are in: {}\\n\\\n                     \\n\\\n                     To fix this, either:\\n  \\\n                     1. Set `bundle.identifier` in Dioxus.toml to match an existing profile\\n  \\\n                     2. Create a wildcard provisioning profile in your Apple Developer account\\n  \\\n                     3. Open the project in Xcode and let it auto-provision\\n\\\n                     \\n\\\n                     {CODESIGN_ERROR}\",\n                    profiles_folder.display()\n                );\n            }\n        };\n\n        let entitlements_xml = format!(\n            r#\"\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\"><dict>\n    <key>application-identifier</key>\n    <string>{APPLICATION_IDENTIFIER}</string>\n    <key>keychain-access-groups</key>\n    <array>\n        <string>{APP_ID_ACCESS_GROUP}.*</string>\n    </array>\n    <key>get-task-allow</key>\n    <true/>\n    <key>com.apple.developer.team-identifier</key>\n    <string>{TEAM_IDENTIFIER}</string>\n</dict></plist>\n        \"#,\n            APPLICATION_IDENTIFIER = mbfile.entitlements.application_identifier,\n            APP_ID_ACCESS_GROUP = mbfile.entitlements.keychain_access_groups[0],\n            TEAM_IDENTIFIER = mbfile.team_identifier[0],\n        );\n\n        Ok((entitlements_xml, profile_path))\n    }\n\n    async fn write_app_manifest(&self, assets: &AssetManifest) -> Result<()> {\n        let manifest = AppManifest {\n            assets: assets.clone(),\n            cli_version: crate::VERSION.to_string(),\n            rust_version: self.workspace.rustc_version.clone(),\n        };\n\n        let manifest_path = self.app_manifest();\n        std::fs::write(&manifest_path, serde_json::to_string_pretty(&manifest)?)?;\n\n        Ok(())\n    }\n\n    /// Log the build duration and some metadata about the build, saving a telemetry event.\n    fn record_build_duration(&self, time_start: SystemTime, ctx: &BuildContext) {\n        // Calculate some final metadata for logging\n        let time_taken = SystemTime::now()\n            .duration_since(time_start)\n            .map(|d| d.as_millis())\n            .unwrap_or_default();\n\n        tracing::debug!(\n            telemetry = %serde_json::json!({\n                \"event\": \"build_and_bundle_complete\",\n                \"time_taken\": time_taken,\n                \"mode\": match ctx.mode {\n                    BuildMode::Base { .. } => \"base\",\n                    BuildMode::Fat => \"fat\",\n                    BuildMode::Thin { .. } => \"thin\",\n                },\n                \"blah\": 123,\n                \"triple\": self.triple.to_string(),\n                \"format\": self.bundle.to_string(),\n                \"num_dependencies\": self.workspace.krates.len(),\n            }),\n            \"Build completed in {time_taken}ms\",\n        );\n    }\n}\n\n/// Generate plist XML entries from a HashMap of key-value pairs\n///\n/// Converts a HashMap like `{ \"UIBackgroundModes\" = [\"location\", \"fetch\"] }` to plist XML:\n/// ```xml\n/// <key>UIBackgroundModes</key>\n/// <array>\n///     <string>location</string>\n///     <string>fetch</string>\n/// </array>\n/// ```\nfn generate_plist_entries(plist: &std::collections::HashMap<String, serde_json::Value>) -> String {\n    let mut output = String::new();\n\n    for (key, value) in plist {\n        output.push_str(&format!(\"\\t<key>{}</key>\\n\", key));\n        output.push_str(&value_to_plist_xml(value, 1));\n    }\n\n    output\n}\n\n/// Convert a serde_json::Value to plist XML format\nfn value_to_plist_xml(value: &serde_json::Value, indent: usize) -> String {\n    let tabs = \"\\t\".repeat(indent);\n\n    match value {\n        serde_json::Value::String(s) => format!(\"{}<string>{}</string>\\n\", tabs, s),\n        serde_json::Value::Bool(b) => {\n            if *b {\n                format!(\"{}<true/>\\n\", tabs)\n            } else {\n                format!(\"{}<false/>\\n\", tabs)\n            }\n        }\n        serde_json::Value::Number(n) => {\n            if n.is_i64() {\n                format!(\"{}<integer>{}</integer>\\n\", tabs, n)\n            } else {\n                format!(\"{}<real>{}</real>\\n\", tabs, n)\n            }\n        }\n        serde_json::Value::Array(arr) => {\n            let mut output = format!(\"{}<array>\\n\", tabs);\n            for item in arr {\n                output.push_str(&value_to_plist_xml(item, indent + 1));\n            }\n            output.push_str(&format!(\"{}</array>\\n\", tabs));\n            output\n        }\n        serde_json::Value::Object(obj) => {\n            let mut output = format!(\"{}<dict>\\n\", tabs);\n            for (k, v) in obj {\n                output.push_str(&format!(\"{}\\t<key>{}</key>\\n\", tabs, k));\n                output.push_str(&value_to_plist_xml(v, indent + 1));\n            }\n            output.push_str(&format!(\"{}</dict>\\n\", tabs));\n            output\n        }\n        serde_json::Value::Null => String::new(),\n    }\n}\n"
  },
  {
    "path": "packages/cli/src/build/tools.rs",
    "content": "use crate::Result;\nuse anyhow::Context;\nuse itertools::Itertools;\nuse std::{path::PathBuf, sync::Arc};\nuse target_lexicon::{\n    Aarch64Architecture, Architecture, ArmArchitecture, Triple, X86_32Architecture,\n};\nuse tokio::process::Command;\n\n/// The tools for Android (ndk, sdk, etc)\n///\n/// <https://gist.github.com/Pulimet/5013acf2cd5b28e55036c82c91bd56d8?permalink_comment_id=3678614>\n#[derive(Debug, Clone)]\npub(crate) struct AndroidTools {\n    pub(crate) sdk: Option<PathBuf>,\n    pub(crate) ndk: PathBuf,\n    pub(crate) adb: PathBuf,\n    pub(crate) java_home: Option<PathBuf>,\n}\n\npub fn get_android_tools() -> Option<Arc<AndroidTools>> {\n    // We check for SDK first since users might install Android Studio and then install the SDK\n    // After that they might install the NDK, so the SDK drives the source of truth.\n    let sdk = var_or_debug(\"ANDROID_SDK_ROOT\")\n        .or_else(|| var_or_debug(\"ANDROID_SDK\"))\n        .or_else(|| var_or_debug(\"ANDROID_HOME\"));\n\n    // Check the ndk. We look for users's overrides first and then look into the SDK.\n    // Sometimes users set only the NDK (especially if they're somewhat advanced) so we need to look for it manually\n    //\n    // Might look like this, typically under \"sdk\":\n    // \"/Users/jonkelley/Library/Android/sdk/ndk/25.2.9519653/toolchains/llvm/prebuilt/darwin-x86_64/bin/aarch64-linux-android24-clang\"\n    let ndk = var_or_debug(\"NDK_HOME\")\n        .or_else(|| var_or_debug(\"ANDROID_NDK_HOME\"))\n        .or_else(|| {\n            // Look for the most recent NDK in the event the user has installed multiple NDK\n            // Eventually we might need to drive this from Dioxus.toml\n            let sdk = sdk.as_ref()?;\n            let ndk_dir = sdk.join(\"ndk\").read_dir().ok()?;\n            ndk_dir\n                .flatten()\n                .map(|dir| (dir.file_name(), dir.path()))\n                .sorted()\n                .next_back()\n                .map(|(_, path)| path.to_path_buf())\n        })?;\n\n    // Look for ADB in the SDK. If it's not there we'll use `adb` from the PATH\n    let adb = sdk\n        .as_ref()\n        .and_then(|sdk| {\n            let tools = sdk.join(\"platform-tools\");\n            if tools.join(\"adb\").exists() {\n                return Some(tools.join(\"adb\"));\n            }\n            if tools.join(\"adb.exe\").exists() {\n                return Some(tools.join(\"adb.exe\"));\n            }\n            None\n        })\n        .unwrap_or_else(|| PathBuf::from(\"adb\"));\n\n    // https://stackoverflow.com/questions/71381050/java-home-is-set-to-an-invalid-directory-android-studio-flutter\n    // always respect the user's JAVA_HOME env var above all other options\n    //\n    // we only attempt autodetection if java_home is not set\n    //\n    // this is a better fallback than falling onto the users' system java home since many users might\n    // not even know which java that is - they just know they have android studio installed\n    let java_home = std::env::var_os(\"JAVA_HOME\")\n        .map(PathBuf::from)\n        .or_else(|| {\n            // Attempt to autodetect java home from the android studio path or jdk path on macos\n            #[cfg(target_os = \"macos\")]\n            {\n                let jbr_home =\n                    PathBuf::from(\"/Applications/Android Studio.app/Contents/jbr/Contents/Home/\");\n                if jbr_home.exists() {\n                    return Some(jbr_home);\n                }\n\n                let jre_home =\n                    PathBuf::from(\"/Applications/Android Studio.app/Contents/jre/Contents/Home\");\n                if jre_home.exists() {\n                    return Some(jre_home);\n                }\n\n                let jdk_home =\n                    PathBuf::from(\"/Library/Java/JavaVirtualMachines/openjdk.jdk/Contents/Home/\");\n                if jdk_home.exists() {\n                    return Some(jdk_home);\n                }\n            }\n\n            #[cfg(target_os = \"windows\")]\n            {\n                let jbr_home = PathBuf::from(\"C:\\\\Program Files\\\\Android\\\\Android Studio\\\\jbr\");\n                if jbr_home.exists() {\n                    return Some(jbr_home);\n                }\n            }\n\n            // todo(jon): how do we detect java home on linux?\n            #[cfg(target_os = \"linux\")]\n            {\n                let jbr_home = PathBuf::from(\"/usr/lib/jvm/java-11-openjdk-amd64\");\n                if jbr_home.exists() {\n                    return Some(jbr_home);\n                }\n            }\n\n            None\n        });\n\n    Some(Arc::new(AndroidTools {\n        ndk,\n        adb,\n        java_home,\n        sdk,\n    }))\n}\n\nimpl AndroidTools {\n    pub(crate) fn android_tools_dir(&self) -> PathBuf {\n        let prebuilt = self.ndk.join(\"toolchains\").join(\"llvm\").join(\"prebuilt\");\n\n        if cfg!(target_os = \"macos\") {\n            // for whatever reason, even on aarch64 macos, the linker is under darwin-x86_64\n            return prebuilt.join(\"darwin-x86_64\").join(\"bin\");\n        }\n\n        if cfg!(target_os = \"linux\") {\n            return prebuilt.join(\"linux-x86_64\").join(\"bin\");\n        }\n\n        if cfg!(target_os = \"windows\") {\n            return prebuilt.join(\"windows-x86_64\").join(\"bin\");\n        }\n\n        // Otherwise return the first entry in the prebuilt directory\n        prebuilt\n            .read_dir()\n            .expect(\"Failed to read android toolchains directory\")\n            .next()\n            .expect(\"Failed to find android toolchains directory\")\n            .expect(\"Failed to read android toolchain file\")\n            .path()\n    }\n\n    /// Return the location of the clang toolchain for the given target triple.\n    ///\n    /// Note that we use clang:\n    /// \"~/Library/Android/sdk/ndk/25.2.9519653/toolchains/llvm/prebuilt/darwin-x86_64/bin/aarch64-linux-android24-clang\"\n    ///\n    /// But if we needed the linker, we would use:\n    /// \"~/Library/Android/sdk/ndk/25.2.9519653/toolchains/llvm/prebuilt/darwin-x86_64/bin/ld\"\n    ///\n    /// However, for our purposes, we only go through the cc driver and not the linker directly.\n    pub(crate) fn android_cc(&self, triple: &Triple, sdk_version: u32) -> PathBuf {\n        let suffix = if cfg!(target_os = \"windows\") {\n            \".cmd\"\n        } else {\n            \"\"\n        };\n\n        let target = match triple.architecture {\n            Architecture::Arm(_) => \"armv7a-linux-androideabi\",\n            _ => &triple.to_string(),\n        };\n\n        self.android_tools_dir()\n            .join(format!(\"{}{}-clang{}\", target, sdk_version, suffix))\n    }\n\n    pub(crate) fn sysroot(&self) -> PathBuf {\n        // The sysroot is usually located in the NDK under:\n        // \"~/Library/Android/sdk/ndk/25.2.9519653/toolchains/llvm/prebuilt/darwin-x86_64/sysroot\"\n        // or similar, depending on the platform.\n        self.android_tools_dir().parent().unwrap().join(\"sysroot\")\n    }\n\n    pub(crate) fn sdk(&self) -> PathBuf {\n        // /Users/jonathankelley/Library/Android/sdk/ndk/25.2/... (25.2 is the ndk here)\n        // /Users/jonathankelley/Library/Android/sdk/\n        self.sdk\n            .clone()\n            .unwrap_or_else(|| self.ndk.parent().unwrap().parent().unwrap().to_path_buf())\n    }\n\n    pub(crate) fn emulator(&self) -> PathBuf {\n        self.sdk().join(\"emulator\").join(\"emulator\")\n    }\n\n    pub(crate) fn clang_folder(&self) -> PathBuf {\n        // The clang folder is usually located in the NDK under:\n        // \"~/Library/Android/sdk/ndk/25.2.9519653/toolchains/llvm/prebuilt/darwin-x86_64/lib/clang/<version>\"\n        // or similar, depending on the platform.\n        self.android_tools_dir()\n            .parent()\n            .unwrap()\n            .join(\"lib\")\n            .join(\"clang\")\n    }\n\n    pub(crate) fn ranlib(&self) -> PathBuf {\n        self.android_tools_dir().join(\"llvm-ranlib\")\n    }\n\n    pub(crate) fn ar_path(&self) -> PathBuf {\n        self.android_tools_dir().join(\"llvm-ar\")\n    }\n\n    pub(crate) fn target_cc(&self) -> PathBuf {\n        self.android_tools_dir().join(\"clang\")\n    }\n\n    pub(crate) fn target_cxx(&self) -> PathBuf {\n        self.android_tools_dir().join(\"clang++\")\n    }\n\n    pub(crate) fn java_home(&self) -> Option<PathBuf> {\n        self.java_home.clone()\n    }\n\n    pub(crate) fn android_jnilib(triple: &Triple) -> &'static str {\n        use target_lexicon::Architecture;\n        match triple.architecture {\n            Architecture::Arm(_) => \"armeabi-v7a\",\n            Architecture::Aarch64(_) => \"arm64-v8a\",\n            Architecture::X86_32(_) => \"x86\",\n            Architecture::X86_64 => \"x86_64\",\n            _ => unimplemented!(\"Unsupported architecture\"),\n        }\n    }\n\n    pub(crate) async fn autodetect_android_device_triple(&self) -> Triple {\n        // Use the host's triple and then convert field by field\n        // ie, the \"best\" emulator for an m1 mac would be: \"aarch64-linux-android\"\n        //  - We assume android is always \"linux\"\n        //  - We try to match the architecture unless otherwise specified. This is because\n        //    emulators that match the host arch are usually faster.\n        let mut triple = \"aarch64-linux-android\".parse::<Triple>().unwrap();\n\n        // TODO: Wire this up with --device flag. (add `-s serial`` flag before `shell` arg)\n        let output = Command::new(&self.adb)\n            .arg(\"shell\")\n            .arg(\"uname\")\n            .arg(\"-m\")\n            .output()\n            .await\n            .map(|out| String::from_utf8(out.stdout));\n\n        match output {\n            Ok(Ok(out)) => match out.trim() {\n                \"armv7l\" | \"armv8l\" => {\n                    triple.architecture = Architecture::Arm(ArmArchitecture::Arm)\n                }\n                \"aarch64\" => {\n                    triple.architecture = Architecture::Aarch64(Aarch64Architecture::Aarch64)\n                }\n                \"i386\" => triple.architecture = Architecture::X86_32(X86_32Architecture::I386),\n                \"x86_64\" => {\n                    triple.architecture = Architecture::X86_64;\n                }\n                \"\" => {\n                    tracing::debug!(\"No device running - probably waiting for emulator\");\n                }\n                other => {\n                    tracing::debug!(\"Unknown architecture from adb: {other}\");\n                }\n            },\n            Ok(Err(err)) => {\n                tracing::debug!(\"Failed to parse adb output: {err}\");\n            }\n            Err(err) => {\n                tracing::debug!(\"ADB command failed: {:?}\", err);\n            }\n        };\n\n        triple\n    }\n\n    pub(crate) fn libcpp_shared(&self, triple: &Triple) -> PathBuf {\n        // The libc++_shared.so is usually located in the sysroot under:\n        // \"~/Library/Android/sdk/ndk/25.2.9519653/toolchains/llvm/prebuilt/darwin-x86_64/sysroot/usr/lib/<arch>/libc++_shared.so\"\n        // or similar, depending on the platform.\n        self.sysroot()\n            .join(\"usr\")\n            .join(\"lib\")\n            .join(Self::sysroot_target(&triple.to_string()))\n            .join(\"libc++_shared.so\")\n    }\n\n    pub(crate) fn sysroot_target(rust_target: &str) -> &str {\n        (match rust_target {\n            \"armv7-linux-androideabi\" => \"arm-linux-androideabi\",\n            _ => rust_target,\n        }) as _\n    }\n\n    pub(crate) fn openssl_prebuilt_aar() -> &'static [u8] {\n        include_bytes!(\"../../assets/android/prebuilt/openssl-1.1.1q-beta-1.tar.gz\")\n    }\n\n    pub(crate) fn openssl_prebuilt_dest() -> PathBuf {\n        crate::Workspace::dioxus_data_dir()\n            .join(\"prebuilt\")\n            .join(\"openssl-1.1.1q-beta-1\")\n    }\n\n    pub(crate) fn openssl_lib_dir(arch: &Triple) -> PathBuf {\n        let libs_dir = Self::openssl_prebuilt_dest().join(\"ssl\").join(\"libs\");\n\n        match arch.architecture {\n            Architecture::Arm(_) => libs_dir.join(\"android.armeabi-v7a\"),\n            Architecture::Aarch64(_) => libs_dir.join(\"android.arm64-v8a\"),\n            Architecture::X86_32(_) => libs_dir.join(\"android.x86\"),\n            Architecture::X86_64 => libs_dir.join(\"android.x86_64\"),\n            _ => libs_dir.join(\"android.arm64-v8a\"), // Default to arm64-v8a\n        }\n    }\n\n    pub(crate) fn openssl_include_dir() -> PathBuf {\n        Self::openssl_prebuilt_dest().join(\"ssl\").join(\"include\")\n    }\n\n    /// Unzip the prebuilt OpenSSL AAR file into the `.dx/prebuilt/openssl-<version>` directory\n    pub(crate) fn unpack_prebuilt_openssl() -> Result<()> {\n        let raw_aar = AndroidTools::openssl_prebuilt_aar();\n        let aar_dest = AndroidTools::openssl_prebuilt_dest();\n\n        if aar_dest.exists() {\n            tracing::trace!(\"Prebuilt OpenSSL already exists at {:?}\", aar_dest);\n            return Ok(());\n        }\n\n        std::fs::create_dir_all(aar_dest.parent().context(\"no parent for aar\")?)\n            .context(\"failed to create prebuilt OpenSSL directory\")?;\n\n        // Unpack the entire tar.gz file into the destination directory\n        let mut archive = tar::Archive::new(flate2::read::GzDecoder::new(raw_aar as &[u8]));\n        archive\n            .unpack(aar_dest.parent().context(\"no parent for aar dest\")?)\n            .context(\"failed to unpack prebuilt OpenSSL archive\")?;\n\n        tracing::debug!(\"Unpacked prebuilt OpenSSL to {:?}\", aar_dest);\n\n        Ok(())\n    }\n}\n\nfn var_or_debug(name: &str) -> Option<PathBuf> {\n    use std::env::var;\n\n    var(name)\n        .inspect_err(|_| tracing::trace!(\"{name} not set\"))\n        .ok()\n        .map(PathBuf::from)\n}\n"
  },
  {
    "path": "packages/cli/src/bundle_utils.rs",
    "content": "use crate::{\n    config::BundleConfig, CustomSignCommandSettings, DebianSettings, MacOsSettings,\n    NSISInstallerMode, NsisSettings, PackageType, WebviewInstallMode, WindowsSettings, WixSettings,\n};\n\nimpl From<NsisSettings> for tauri_bundler::NsisSettings {\n    fn from(val: NsisSettings) -> Self {\n        tauri_bundler::NsisSettings {\n            header_image: val.header_image,\n            sidebar_image: val.sidebar_image,\n            installer_icon: val.installer_icon,\n            install_mode: val.install_mode.into(),\n            languages: val.languages,\n            display_language_selector: val.display_language_selector,\n            custom_language_files: None,\n            template: None,\n            compression: tauri_utils::config::NsisCompression::None,\n            start_menu_folder: val.start_menu_folder,\n            installer_hooks: val.installer_hooks,\n            minimum_webview2_version: val.minimum_webview2_version,\n        }\n    }\n}\n\nimpl From<BundleConfig> for tauri_bundler::BundleSettings {\n    fn from(val: BundleConfig) -> Self {\n        tauri_bundler::BundleSettings {\n            identifier: val.identifier,\n            publisher: val.publisher,\n            icon: val.icon,\n            resources: val.resources,\n            copyright: val.copyright,\n            category: val.category.and_then(|c| c.parse().ok()),\n            short_description: val.short_description,\n            long_description: val.long_description,\n            external_bin: val.external_bin,\n            deb: val.deb.map(Into::into).unwrap_or_default(),\n            macos: val.macos.map(Into::into).unwrap_or_default(),\n            windows: val.windows.map(Into::into).unwrap_or_default(),\n            ..Default::default()\n        }\n    }\n}\n\nimpl From<DebianSettings> for tauri_bundler::DebianSettings {\n    fn from(val: DebianSettings) -> Self {\n        tauri_bundler::DebianSettings {\n            depends: val.depends,\n            files: val.files,\n            desktop_template: val.desktop_template,\n            provides: val.provides,\n            conflicts: val.conflicts,\n            replaces: val.replaces,\n            section: val.section,\n            priority: val.priority,\n            changelog: val.changelog,\n            pre_install_script: val.pre_install_script,\n            post_install_script: val.post_install_script,\n            pre_remove_script: val.pre_remove_script,\n            post_remove_script: val.post_remove_script,\n            recommends: val.recommends,\n        }\n    }\n}\n\nimpl From<WixSettings> for tauri_bundler::WixSettings {\n    fn from(val: WixSettings) -> Self {\n        tauri_bundler::WixSettings {\n            language: tauri_bundler::bundle::WixLanguage({\n                let mut languages: Vec<_> = val\n                    .language\n                    .iter()\n                    .map(|l| {\n                        (\n                            l.0.clone(),\n                            tauri_bundler::bundle::WixLanguageConfig {\n                                locale_path: l.1.clone(),\n                            },\n                        )\n                    })\n                    .collect();\n                if languages.is_empty() {\n                    languages.push((\"en-US\".into(), Default::default()));\n                }\n                languages\n            }),\n            template: val.template,\n            fragment_paths: val.fragment_paths,\n            component_group_refs: val.component_group_refs,\n            component_refs: val.component_refs,\n            feature_group_refs: val.feature_group_refs,\n            feature_refs: val.feature_refs,\n            merge_refs: val.merge_refs,\n            enable_elevated_update_task: val.enable_elevated_update_task,\n            banner_path: val.banner_path,\n            dialog_image_path: val.dialog_image_path,\n            fips_compliant: val.fips_compliant,\n            version: val.version,\n            upgrade_code: val.upgrade_code,\n        }\n    }\n}\n\nimpl From<MacOsSettings> for tauri_bundler::MacOsSettings {\n    fn from(val: MacOsSettings) -> Self {\n        tauri_bundler::MacOsSettings {\n            frameworks: val.frameworks,\n            minimum_system_version: val.minimum_system_version,\n            exception_domain: val.exception_domain,\n            signing_identity: val.signing_identity,\n            provider_short_name: val.provider_short_name,\n            entitlements: val.entitlements,\n            info_plist_path: val.info_plist_path,\n            files: val.files,\n            hardened_runtime: val.hardened_runtime,\n            bundle_version: val.bundle_version,\n            bundle_name: val.bundle_name,\n        }\n    }\n}\n\n#[allow(deprecated)]\nimpl From<WindowsSettings> for tauri_bundler::WindowsSettings {\n    fn from(val: WindowsSettings) -> Self {\n        tauri_bundler::WindowsSettings {\n            digest_algorithm: val.digest_algorithm,\n            certificate_thumbprint: val.certificate_thumbprint,\n            timestamp_url: val.timestamp_url,\n            tsp: val.tsp,\n            wix: val.wix.map(Into::into),\n            webview_install_mode: val.webview_install_mode.into(),\n            allow_downgrades: val.allow_downgrades,\n            nsis: val.nsis.map(Into::into),\n            sign_command: val.sign_command.map(Into::into),\n\n            icon_path: val.icon_path.unwrap_or(\"./icons/icon.ico\".into()),\n        }\n    }\n}\n\nimpl From<NSISInstallerMode> for tauri_utils::config::NSISInstallerMode {\n    fn from(val: NSISInstallerMode) -> Self {\n        match val {\n            NSISInstallerMode::CurrentUser => tauri_utils::config::NSISInstallerMode::CurrentUser,\n            NSISInstallerMode::PerMachine => tauri_utils::config::NSISInstallerMode::PerMachine,\n            NSISInstallerMode::Both => tauri_utils::config::NSISInstallerMode::Both,\n        }\n    }\n}\n\nimpl From<PackageType> for tauri_bundler::PackageType {\n    fn from(value: PackageType) -> Self {\n        match value {\n            PackageType::MacOsBundle => Self::MacOsBundle,\n            PackageType::IosBundle => Self::IosBundle,\n            PackageType::WindowsMsi => Self::WindowsMsi,\n            PackageType::Deb => Self::Deb,\n            PackageType::Rpm => Self::Rpm,\n            PackageType::AppImage => Self::AppImage,\n            PackageType::Dmg => Self::Dmg,\n            PackageType::Updater => Self::Updater,\n            PackageType::Nsis => Self::Nsis,\n        }\n    }\n}\n\nimpl WebviewInstallMode {\n    fn into(self) -> tauri_utils::config::WebviewInstallMode {\n        match self {\n            Self::Skip => tauri_utils::config::WebviewInstallMode::Skip,\n            Self::DownloadBootstrapper { silent } => {\n                tauri_utils::config::WebviewInstallMode::DownloadBootstrapper { silent }\n            }\n            Self::EmbedBootstrapper { silent } => {\n                tauri_utils::config::WebviewInstallMode::EmbedBootstrapper { silent }\n            }\n            Self::OfflineInstaller { silent } => {\n                tauri_utils::config::WebviewInstallMode::OfflineInstaller { silent }\n            }\n            Self::FixedRuntime { path } => {\n                tauri_utils::config::WebviewInstallMode::FixedRuntime { path }\n            }\n        }\n    }\n}\n\nimpl From<CustomSignCommandSettings> for tauri_bundler::CustomSignCommandSettings {\n    fn from(val: CustomSignCommandSettings) -> Self {\n        tauri_bundler::CustomSignCommandSettings {\n            cmd: val.cmd,\n            args: val.args,\n        }\n    }\n}\n"
  },
  {
    "path": "packages/cli/src/cargo_toml.rs",
    "content": "//! The cargo_toml crate contains some logic for resolving Cargo.toml files with workspace inheritance, but it\n//! doesn't handle global configs like ~/.cargo/config.toml. This module handles extending the manifest with those\n//! settings if they exist.\n\nuse std::path::{Path, PathBuf};\n\nuse cargo_toml::{Manifest, Profile, Profiles};\n\n/// Load the manifest from a path inheriting from the global config where needed\npub fn load_manifest_from_path(path: &Path) -> Result<Manifest, cargo_toml::Error> {\n    let mut original = Manifest::from_path(path)?;\n\n    // Merge the .cargo/config.toml if it exists\n    extend_manifest_config_toml(&mut original, &path.join(\".cargo\").join(\"config.toml\"));\n\n    // Merge the global cargo config if it exists\n    if let Some(global_config) = global_cargo_config_path() {\n        extend_manifest_config_toml(&mut original, &global_config);\n    }\n\n    Ok(original)\n}\n\n/// Get the path to cargo home\nfn cargo_home() -> Option<PathBuf> {\n    // If the cargo home env var is set, use that\n    if let Some(cargo_home) = std::env::var_os(\"CARGO_HOME\") {\n        return Some(PathBuf::from(cargo_home));\n    }\n\n    // Otherwise, use the default location\n    if cfg!(windows) {\n        std::env::var_os(\"USERPROFILE\")\n            .map(|user_profile| PathBuf::from(user_profile).join(\".cargo\"))\n    } else if cfg!(unix) {\n        dirs::home_dir().map(|home_dir| home_dir.join(\".cargo\"))\n    } else {\n        None\n    }\n}\n\n/// Get the global cargo config path if it exists\nfn global_cargo_config_path() -> Option<PathBuf> {\n    cargo_home().map(|cargo_home| cargo_home.join(\"config.toml\"))\n}\n\n// Extend a manifest with a config.toml if it exists\nfn extend_manifest_config_toml(manifest: &mut Manifest, path: &Path) {\n    // Read the config.toml if it exists\n    let Ok(config) = std::fs::read_to_string(path) else {\n        return;\n    };\n\n    let Ok(config) = config.parse::<toml::Value>() else {\n        return;\n    };\n\n    // Try to parse profiles\n    if let Some(profiles) = config.get(\"profile\").and_then(|p| p.as_table()) {\n        merge_profiles(\n            &mut manifest.profile,\n            toml::from_str::<cargo_toml::Profiles>(&profiles.to_string()).unwrap_or_default(),\n        );\n    }\n}\n\n/// Merge the new profiles into the target profiles. Keep the existing values if they exist.\nfn merge_profiles(target: &mut Profiles, new: Profiles) {\n    if let Some(new_release) = new.release {\n        if target.release.is_none() {\n            target.release = Some(new_release);\n        } else {\n            merge_profile(target.release.as_mut().unwrap(), new_release);\n        }\n    }\n\n    if let Some(new_dev) = new.dev {\n        if target.dev.is_none() {\n            target.dev = Some(new_dev);\n        } else {\n            merge_profile(target.dev.as_mut().unwrap(), new_dev);\n        }\n    }\n\n    if let Some(new_test) = new.test {\n        if target.test.is_none() {\n            target.test = Some(new_test);\n        } else {\n            merge_profile(target.test.as_mut().unwrap(), new_test);\n        }\n    }\n\n    if let Some(new_bench) = new.bench {\n        if target.bench.is_none() {\n            target.bench = Some(new_bench);\n        } else {\n            merge_profile(target.bench.as_mut().unwrap(), new_bench);\n        }\n    }\n\n    #[allow(deprecated)]\n    if let Some(new_doc) = new.doc {\n        if target.doc.is_none() {\n            target.doc = Some(new_doc);\n        } else {\n            merge_profile(target.doc.as_mut().unwrap(), new_doc);\n        }\n    }\n\n    for (profile_name, profile) in new.custom {\n        if let Some(target_profile) = target.custom.get_mut(&profile_name) {\n            merge_profile(target_profile, profile);\n        } else {\n            target.custom.insert(profile_name, profile);\n        }\n    }\n}\n\n/// Merge the new profile into the target profile. Keep the existing values if they exist.\nfn merge_profile(target: &mut Profile, new: Profile) {\n    if target.opt_level.is_none() {\n        target.opt_level = new.opt_level;\n    }\n    if target.debug.is_none() {\n        target.debug = new.debug;\n    }\n    if target.split_debuginfo.is_none() {\n        target.split_debuginfo = new.split_debuginfo;\n    }\n    if target.rpath.is_none() {\n        target.rpath = new.rpath;\n    }\n    if target.lto.is_none() {\n        target.lto = new.lto;\n    }\n    if target.debug_assertions.is_none() {\n        target.debug_assertions = new.debug_assertions;\n    }\n    if target.codegen_units.is_none() {\n        target.codegen_units = new.codegen_units;\n    }\n    if target.panic.is_none() {\n        target.panic = new.panic;\n    }\n    if target.incremental.is_none() {\n        target.incremental = new.incremental;\n    }\n    if target.overflow_checks.is_none() {\n        target.overflow_checks = new.overflow_checks;\n    }\n    if target.strip.is_none() {\n        target.strip = new.strip;\n    }\n    if target.build_override.is_none() {\n        target.build_override = new.build_override;\n    }\n    if target.inherits.is_none() {\n        target.inherits = new.inherits;\n    }\n    target.package.extend(new.package);\n}\n"
  },
  {
    "path": "packages/cli/src/cli/autoformat.rs",
    "content": "use super::{check::collect_rs_files, *};\nuse crate::Workspace;\nuse anyhow::{bail, Context};\nuse dioxus_autofmt::{IndentOptions, IndentType};\nuse rayon::prelude::*;\nuse std::{borrow::Cow, fs, path::Path};\n\n// For reference, the rustfmt main.rs file\n// https://github.com/rust-lang/rustfmt/blob/master/src/bin/main.rs\n\n/// Format some rsx\n#[derive(Clone, Debug, Parser)]\npub(crate) struct Autoformat {\n    /// Format rust code before the formatting the rsx macros\n    #[clap(long)]\n    pub(crate) all_code: bool,\n\n    /// Run in 'check' mode. Exits with 0 if input is formatted correctly. Exits\n    /// with 1 and prints a diff if formatting is required.\n    #[clap(short, long)]\n    pub(crate) check: bool,\n\n    /// Input rsx (selection)\n    #[clap(short, long)]\n    pub(crate) raw: Option<String>,\n\n    /// Input file at path (set to \"-\" to read file from stdin, and output formatted file to stdout)\n    #[clap(short, long)]\n    pub(crate) file: Option<String>,\n\n    /// Split attributes in lines or not\n    #[clap(short, long, default_value = \"false\")]\n    pub(crate) split_line_attributes: bool,\n\n    /// The package to build\n    #[clap(short, long)]\n    pub(crate) package: Option<String>,\n}\n\nimpl Autoformat {\n    pub(crate) async fn autoformat(self) -> Result<StructuredOutput> {\n        let Autoformat {\n            check,\n            raw,\n            file,\n            split_line_attributes,\n            all_code: format_rust_code,\n            ..\n        } = self;\n\n        if let Some(file) = file {\n            // Format a single file\n            refactor_file(file, split_line_attributes, format_rust_code)?;\n        } else if let Some(raw) = raw {\n            // Format raw text.\n            let indent = indentation_for(\".\", self.split_line_attributes)?;\n            let formatted =\n                dioxus_autofmt::fmt_block(&raw, 0, indent).context(\"error formatting codeblock\")?;\n            println!(\"{}\", formatted);\n        } else {\n            // Default to formatting the project.\n            let crate_dir = if let Some(package) = self.package {\n                let workspace = Workspace::current().await?;\n                let dx_crate = workspace\n                    .find_main_package(Some(package))\n                    .context(\"Failed to find package\")?;\n                workspace.krates[dx_crate]\n                    .manifest_path\n                    .parent()\n                    .unwrap()\n                    .to_path_buf()\n                    .into()\n            } else {\n                Cow::Borrowed(Path::new(\".\"))\n            };\n\n            autoformat_project(check, split_line_attributes, format_rust_code, crate_dir)\n                .context(\"error autoformatting project\")?;\n        }\n\n        Ok(StructuredOutput::Success)\n    }\n}\n\nfn refactor_file(\n    file: String,\n    split_line_attributes: bool,\n    format_rust_code: bool,\n) -> Result<(), Error> {\n    let indent = indentation_for(\".\", split_line_attributes)?;\n    let file_content = if file == \"-\" {\n        let mut contents = String::new();\n        std::io::stdin().read_to_string(&mut contents)?;\n        Ok(contents)\n    } else {\n        fs::read_to_string(&file)\n    };\n    let mut s = file_content.context(\"failed to open file\")?;\n\n    if format_rust_code {\n        s = format_rust(&s)?;\n    }\n\n    let parsed = syn::parse_file(&s).context(\"failed to parse file\")?;\n    let edits =\n        dioxus_autofmt::try_fmt_file(&s, &parsed, indent).context(\"failed to format file\")?;\n\n    let out = dioxus_autofmt::apply_formats(&s, edits);\n\n    if file == \"-\" {\n        print!(\"{out}\");\n    } else if let Err(e) = fs::write(&file, out) {\n        tracing::error!(\"failed to write formatted content to file: {e}\",);\n    } else {\n        println!(\"formatted {file}\");\n    }\n\n    Ok(())\n}\n\nfn format_file(\n    path: impl AsRef<Path>,\n    indent: IndentOptions,\n    format_rust_code: bool,\n) -> Result<usize> {\n    let mut contents = fs::read_to_string(&path)?;\n    let mut if_write = false;\n    if format_rust_code {\n        let formatted = format_rust(&contents).context(\"Syntax Error\")?;\n        if contents != formatted {\n            if_write = true;\n            contents = formatted;\n        }\n    }\n\n    let parsed = syn::parse_file(&contents).context(\"Failed to parse file\")?;\n    let edits = dioxus_autofmt::try_fmt_file(&contents, &parsed, indent)\n        .context(\"Failed to format file\")?;\n    let len = edits.len();\n\n    if !edits.is_empty() {\n        if_write = true;\n    }\n\n    if if_write {\n        let out = dioxus_autofmt::apply_formats(&contents, edits);\n        fs::write(path, out)?;\n    }\n\n    Ok(len)\n}\n\n/// Read every .rs file accessible when considering the .gitignore and try to format it\n///\n/// Runs using rayon for multithreading, so it should be really really fast\n///\n/// Doesn't do mod-descending, so it will still try to format unreachable files. TODO.\nfn autoformat_project(\n    check: bool,\n    split_line_attributes: bool,\n    format_rust_code: bool,\n    dir: impl AsRef<Path>,\n) -> Result<()> {\n    let mut files_to_format = vec![];\n    collect_rs_files(dir.as_ref(), &mut files_to_format);\n\n    if files_to_format.is_empty() {\n        return Ok(());\n    }\n\n    if files_to_format.is_empty() {\n        return Ok(());\n    }\n\n    let indent = indentation_for(&files_to_format[0], split_line_attributes)?;\n\n    let counts = files_to_format\n        .into_par_iter()\n        .map(|path| {\n            let res = format_file(&path, indent.clone(), format_rust_code);\n            match res {\n                Ok(cnt) => Some(cnt),\n                Err(err) => {\n                    tracing::error!(\"error formatting file : {}\\n{:#?}\", path.display(), err);\n                    None\n                }\n            }\n        })\n        .collect::<Vec<_>>();\n\n    let files_formatted: usize = counts.into_iter().flatten().sum();\n\n    if files_formatted > 0 && check {\n        bail!(\"{files_formatted} files needed formatting\");\n    }\n\n    Ok(())\n}\n\nfn indentation_for(\n    file_or_dir: impl AsRef<Path>,\n    split_line_attributes: bool,\n) -> Result<IndentOptions> {\n    let out = std::process::Command::new(\"cargo\")\n        .args([\"fmt\", \"--\", \"--print-config\", \"current\"])\n        .arg(file_or_dir.as_ref())\n        .stdout(std::process::Stdio::piped())\n        .stderr(std::process::Stdio::inherit())\n        .output()?;\n\n    if !out.status.success() {\n        bail!(\"cargo fmt failed with status: {out:?}\");\n    }\n\n    let config = String::from_utf8_lossy(&out.stdout);\n\n    let hard_tabs = config\n        .lines()\n        .find(|line| line.starts_with(\"hard_tabs \"))\n        .and_then(|line| line.split_once('='))\n        .map(|(_, value)| value.trim() == \"true\")\n        .context(\"Could not find hard_tabs option in rustfmt config\")?;\n    let tab_spaces = config\n        .lines()\n        .find(|line| line.starts_with(\"tab_spaces \"))\n        .and_then(|line| line.split_once('='))\n        .map(|(_, value)| value.trim().parse::<usize>())\n        .context(\"Could not find tab_spaces option in rustfmt config\")?\n        .context(\"Could not parse tab_spaces option in rustfmt config\")?;\n\n    Ok(IndentOptions::new(\n        if hard_tabs {\n            IndentType::Tabs\n        } else {\n            IndentType::Spaces\n        },\n        tab_spaces,\n        split_line_attributes,\n    ))\n}\n\n/// Format rust code using prettyplease\nfn format_rust(input: &str) -> Result<String> {\n    let syntax_tree = syn::parse_file(input)\n        .map_err(format_syn_error)\n        .context(\"Failed to parse Rust syntax\")?;\n    let output = prettyplease::unparse(&syntax_tree);\n    Ok(output)\n}\n\nfn format_syn_error(err: syn::Error) -> Error {\n    let start = err.span().start();\n    let line = start.line;\n    let column = start.column;\n    anyhow::anyhow!(\"Syntax Error in line {line} column {column}:\\n{err}\")\n}\n\n#[tokio::test]\nasync fn test_auto_fmt() {\n    let test_rsx = r#\"\n                    //\n\n\n\n                        div {}\n\n\n                    //\n                    //\n                    //\n\n                    \"#\n    .to_string();\n\n    let fmt = Autoformat {\n        all_code: false,\n        check: false,\n        raw: Some(test_rsx),\n        file: None,\n        split_line_attributes: false,\n        package: None,\n    };\n\n    fmt.autoformat().await.unwrap();\n}\n"
  },
  {
    "path": "packages/cli/src/cli/build.rs",
    "content": "use dioxus_dx_wire_format::StructuredBuildArtifacts;\n\nuse crate::{\n    cli::*, Anonymized, AppBuilder, BuildArtifacts, BuildId, BuildMode, BuildRequest, BundleFormat,\n    Platform, TargetArgs, Workspace,\n};\n\n/// Build the Rust Dioxus app and all of its assets.\n///\n/// Produces a final output build. If a \"server\" feature is present in the package's Cargo.toml, it will\n/// be considered a fullstack app and the server will be built as well.\n#[derive(Clone, Debug, Default, Parser)]\npub struct BuildArgs {\n    /// Enable fullstack mode [default: false]\n    ///\n    /// This is automatically detected from `dx serve` if the \"fullstack\" feature is enabled by default.\n    #[arg(\n        long,\n        default_missing_value = \"true\",\n        num_args = 0..=1,\n    )]\n    pub(crate) fullstack: Option<bool>,\n\n    /// Pre-render all routes returned from the app's `/static_routes` endpoint [default: false]\n    #[clap(long)]\n    pub(crate) ssg: bool,\n\n    /// Force a \"fat\" binary, required to use `dx build-tools hotpatch`\n    #[clap(long)]\n    pub(crate) fat_binary: bool,\n\n    /// This flag only applies to fullstack builds. By default fullstack builds will run the server\n    /// and client builds in parallel. This flag will force the build to run the server build first, then the client build. [default: false]\n    ///\n    /// If CI is enabled, this will be set to true by default.\n    ///\n    #[clap(\n        long, default_missing_value = \"true\",\n        num_args = 0..=1,\n    )]\n    pub(crate) force_sequential: Option<bool>,\n\n    /// Arguments for the build itself\n    #[clap(flatten)]\n    pub(crate) build_arguments: TargetArgs,\n}\n\nimpl BuildArgs {\n    pub(crate) fn force_sequential_build(&self) -> bool {\n        self.force_sequential\n            .unwrap_or_else(|| std::env::var(\"CI\").is_ok())\n    }\n}\n\nimpl Anonymized for BuildArgs {\n    fn anonymized(&self) -> Value {\n        json! {{\n            \"fullstack\": self.fullstack,\n            \"ssg\": self.ssg,\n            \"build_arguments\": self.build_arguments.anonymized(),\n        }}\n    }\n}\n\npub struct BuildTargets {\n    pub client: BuildRequest,\n    pub server: Option<BuildRequest>,\n}\n\nimpl CommandWithPlatformOverrides<BuildArgs> {\n    /// We need to decompose the combined `BuildArgs` into the individual targets that we need to build.\n    ///\n    /// Only in a few cases do we spin out an additional server binary:\n    /// - the fullstack feature is passed\n    /// - the fullstack flag is enabled\n    /// - the server flag is enabled\n    ///\n    /// The buildtargets configuration comes in two flavors:\n    /// - implied via the `fullstack` feature\n    /// - explicit when using `@server and @client`\n    ///\n    /// We use the client arguments to build the client target, and then make a few changes to make\n    /// the server target.\n    ///\n    /// The `--fullstack` feature is basically the same as passing `--features fullstack`\n    ///\n    /// Some examples:\n    /// ```shell, ignore\n    /// dx serve --target wasm32-unknown-unknown --fullstack            # serves both client and server\n    /// dx serve --target wasm32-unknown-unknown --features fullstack   # serves both client and server\n    /// dx serve --target wasm32-unknown-unknown                        # only serves the client\n    /// dx serve --target wasm32-unknown-unknown                        # servers both if `fullstack` is enabled on dioxus\n    /// dx serve @client --target wasm32-unknown-unknown                # only serves the client\n    /// dx serve @client --target wasm32-unknown-unknown --fullstack    # serves both client and server\n    /// ```\n    ///\n    /// Currently it is not possible to serve the server without the client, but this could be added in the future.\n    pub async fn into_targets(mut self) -> Result<BuildTargets> {\n        let workspace = Workspace::current().await?;\n\n        // do some logging to ensure dx matches the dioxus version since we're not always API compatible\n        workspace.check_dioxus_version_against_cli();\n\n        // The client args are the `@client` arguments, or the shared build arguments if @client is not specified.\n        let client_args = &self.client.as_ref().unwrap_or(&self.shared).build_arguments;\n\n        // Create the client build request\n        let client = BuildRequest::new(client_args, workspace.clone()).await?;\n\n        // Create the server build request if needed\n        let mut server = None;\n        if matches!(self.shared.fullstack, Some(true))\n            || client.fullstack_feature_enabled()\n            || self.server.is_some()\n        {\n            match self.server.as_mut() {\n                Some(server_args) => {\n                    // Make sure we set the client target here so @server knows to place its output into the @client target directory.\n                    server_args.build_arguments.client_target = Some(client.main_target.clone());\n\n                    // We don't override anything except the bundle format since @server usually implies a server output\n                    server_args.build_arguments.bundle = server_args\n                        .build_arguments\n                        .bundle\n                        .or(Some(BundleFormat::Server));\n\n                    server = Some(\n                        BuildRequest::new(&server_args.build_arguments, workspace.clone()).await?,\n                    );\n                }\n                None if client_args.platform == Platform::Server => {\n                    // If the user requests a server build with `--server`, then we don't need to build a separate server binary.\n                    // There's no client to use, so even though fullstack is true, we only build the server.\n                }\n                None => {\n                    let mut args = self.shared.build_arguments.clone();\n                    args.platform = crate::Platform::Server;\n                    args.renderer = Some(crate::Renderer::Server);\n                    args.bundle = Some(crate::BundleFormat::Server);\n                    args.target = Some(target_lexicon::Triple::host());\n                    server = Some(BuildRequest::new(&args, workspace.clone()).await?);\n                }\n            }\n        }\n\n        Ok(BuildTargets { client, server })\n    }\n\n    pub async fn build(self) -> Result<StructuredOutput> {\n        tracing::info!(\"Building project...\");\n\n        let force_sequential = self.shared.force_sequential_build();\n        let ssg = self.shared.ssg;\n        let mode = match self.shared.fat_binary {\n            true => BuildMode::Fat,\n            false => BuildMode::Base { run: false },\n        };\n        let targets = self.into_targets().await?;\n\n        let build_client = Self::build_client_inner(&targets.client, mode.clone());\n        let build_server = Self::build_server_inner(&targets.server, mode.clone(), ssg);\n\n        let (client, server) = match force_sequential {\n            true => (build_client.await, build_server.await),\n            false => tokio::join!(build_client, build_server),\n        };\n\n        Ok(StructuredOutput::BuildsFinished {\n            client: client?.into_structured_output(),\n            server: server?.map(|s| s.into_structured_output()),\n        })\n    }\n\n    pub(crate) async fn build_client_inner(\n        request: &BuildRequest,\n        mode: BuildMode,\n    ) -> Result<BuildArtifacts> {\n        AppBuilder::started(request, mode, BuildId::PRIMARY)?\n            .finish_build()\n            .await\n            .inspect(|_| {\n                tracing::info!(path = ?request.root_dir(), \"Client build completed successfully! 🚀\");\n            })\n    }\n\n    pub(crate) async fn build_server_inner(\n        request: &Option<BuildRequest>,\n        mode: BuildMode,\n        ssg: bool,\n    ) -> Result<Option<BuildArtifacts>> {\n        let Some(server) = request.as_ref() else {\n            return Ok(None);\n        };\n\n        // If the server is present, we need to build it as well\n        let mut server_build = AppBuilder::started(server, mode, BuildId::SECONDARY)?;\n        let server_artifacts = server_build.finish_build().await?;\n\n        // Run SSG and cache static routes\n        if ssg {\n            crate::pre_render_static_routes(None, &mut server_build, None).await?;\n        }\n\n        tracing::info!(path = ?server.root_dir(), \"Server build completed successfully! 🚀\");\n\n        Ok(Some(server_artifacts))\n    }\n}\n\nimpl BuildArtifacts {\n    pub(crate) fn into_structured_output(self) -> StructuredBuildArtifacts {\n        // Extract the tip crate's args for the structured output.\n        // The tip crate is identified by replacing hyphens with underscores in the target name,\n        // but since we don't have the BuildRequest here, we look for the entry with link_args\n        // (only the tip crate has link_args attached) or fall back to any entry.\n        let tip_args = self\n            .workspace_rustc_args\n            .values()\n            .find(|a| !a.link_args.is_empty())\n            .or_else(|| self.workspace_rustc_args.values().next())\n            .cloned()\n            .unwrap_or_default();\n\n        StructuredBuildArtifacts {\n            path: self.root_dir,\n            exe: self.exe,\n            rustc_args: tip_args.args,\n            rustc_envs: tip_args.envs,\n            link_args: tip_args.link_args,\n            assets: self.assets.unique_assets().cloned().collect(),\n        }\n    }\n}\n"
  },
  {
    "path": "packages/cli/src/cli/build_assets.rs",
    "content": "use std::{fs::create_dir_all, path::PathBuf};\n\nuse crate::{extract_assets_from_file, Result, StructuredOutput};\nuse clap::Parser;\nuse dioxus_cli_opt::process_file_to;\nuse tracing::debug;\n\n#[derive(Clone, Debug, Parser)]\npub struct BuildAssets {\n    /// The source executable to build assets for.\n    pub(crate) executable: PathBuf,\n\n    /// The destination directory for the assets.\n    pub(crate) destination: PathBuf,\n}\n\nimpl BuildAssets {\n    pub async fn run(self) -> Result<StructuredOutput> {\n        let manifest = extract_assets_from_file(&self.executable).await?;\n\n        create_dir_all(&self.destination)?;\n        for asset in manifest.unique_assets() {\n            let source_path = PathBuf::from(asset.absolute_source_path());\n            let destination_path = self.destination.join(asset.bundled_path());\n            debug!(\n                \"Processing asset {} --> {} {:#?}\",\n                source_path.display(),\n                destination_path.display(),\n                asset\n            );\n            process_file_to(asset.options(), &source_path, &destination_path)?;\n        }\n\n        Ok(StructuredOutput::Success)\n    }\n}\n"
  },
  {
    "path": "packages/cli/src/cli/bundle.rs",
    "content": "use crate::{AppBuilder, BuildArgs, BuildId, BuildMode, BuildRequest, BundleFormat};\nuse anyhow::{bail, Context};\nuse path_absolutize::Absolutize;\nuse std::collections::HashMap;\nuse tauri_bundler::{BundleBinary, BundleSettings, PackageSettings, SettingsBuilder};\n\nuse walkdir::WalkDir;\n\nuse super::*;\n\n/// Bundle an app and its assets.\n///\n/// This will produce a client `public` folder and the associated server executable in the output folder.\n#[derive(Clone, Debug, Parser)]\npub struct Bundle {\n    /// The package types to bundle\n    #[clap(long)]\n    pub package_types: Option<Vec<crate::PackageType>>,\n\n    /// The directory in which the final bundle will be placed.\n    ///\n    /// Relative paths will be placed relative to the current working directory if specified.\n    /// Otherwise, the out_dir path specified in Dioxus.toml will be used (relative to the crate root).\n    ///\n    /// We will flatten the artifacts into this directory - there will be no differentiation between\n    /// artifacts produced by different platforms.\n    #[clap(long)]\n    pub out_dir: Option<PathBuf>,\n\n    /// The arguments for the dioxus build\n    #[clap(flatten)]\n    pub(crate) args: CommandWithPlatformOverrides<BuildArgs>,\n}\n\nimpl Bundle {\n    // todo: make sure to run pre-render static routes! we removed this from the other bundling step\n    pub(crate) async fn bundle(mut self) -> Result<StructuredOutput> {\n        tracing::info!(\"Bundling project...\");\n\n        let BuildTargets { client, server } = self.args.into_targets().await?;\n\n        let mut server_artifacts = None;\n        let client_artifacts =\n            AppBuilder::started(&client, BuildMode::Base { run: false }, BuildId::PRIMARY)?\n                .finish_build()\n                .await?;\n\n        tracing::info!(path = ?client.root_dir(), \"Client build completed successfully! 🚀\");\n\n        if let Some(server) = server.as_ref() {\n            // If the server is present, we need to build it as well\n            server_artifacts = Some(\n                AppBuilder::started(server, BuildMode::Base { run: false }, BuildId::SECONDARY)?\n                    .finish_build()\n                    .await?,\n            );\n\n            tracing::info!(path = ?client.root_dir(), \"Server build completed successfully! 🚀\");\n        }\n\n        // If we're building for iOS, we need to bundle the iOS bundle\n        if client.bundle == BundleFormat::Ios && self.package_types.is_none() {\n            self.package_types = Some(vec![crate::PackageType::IosBundle]);\n        }\n\n        let mut bundles = vec![];\n\n        // Copy the server over if it exists\n        if let Some(server) = server.as_ref() {\n            bundles.push(server.main_exe());\n        }\n\n        // Create a list of bundles that we might need to copy\n        match client.bundle {\n            // By default, mac/win/linux work with tauri bundle\n            BundleFormat::MacOS | BundleFormat::Linux | BundleFormat::Windows => {\n                tracing::info!(\"Running desktop bundler...\");\n                for bundle in Self::bundle_desktop(&client, &self.package_types)? {\n                    bundles.extend(bundle.bundle_paths);\n                }\n            }\n\n            // Web/ios can just use their root_dir\n            BundleFormat::Web => bundles.push(client.root_dir()),\n            BundleFormat::Ios => {\n                tracing::warn!(\"iOS bundles are not currently codesigned! You will need to codesign the app before distributing.\");\n                bundles.push(client.root_dir())\n            }\n            BundleFormat::Server => bundles.push(client.root_dir()),\n\n            BundleFormat::Android => {\n                let aab = client\n                    .android_gradle_bundle()\n                    .await\n                    .context(\"Failed to run gradle bundleRelease\")?;\n                bundles.push(aab);\n            }\n        };\n\n        // Copy the bundles to the output directory if one was specified\n        let crate_outdir = client.crate_out_dir();\n        if let Some(outdir) = self.out_dir.clone().or(crate_outdir) {\n            let outdir = outdir\n                .absolutize()\n                .context(\"Failed to absolutize output directory\")?;\n\n            tracing::info!(\"Copying bundles to output directory: {}\", outdir.display());\n\n            std::fs::create_dir_all(&outdir)?;\n\n            for bundle_path in bundles.iter_mut() {\n                let destination = outdir.join(bundle_path.file_name().unwrap());\n\n                tracing::debug!(\n                    \"Copying from {} to {}\",\n                    bundle_path.display(),\n                    destination.display()\n                );\n\n                if bundle_path.is_dir() {\n                    dircpy::CopyBuilder::new(&bundle_path, &destination)\n                        .overwrite(true)\n                        .run_par()\n                        .context(\"Failed to copy the app to output directory\")?;\n                } else {\n                    std::fs::copy(&bundle_path, &destination)\n                        .context(\"Failed to copy the app to output directory\")?;\n                }\n\n                *bundle_path = destination;\n            }\n        }\n\n        for bundle_path in bundles.iter() {\n            tracing::info!(\n                \"Bundled app at: {}\",\n                bundle_path.absolutize().unwrap().display()\n            );\n        }\n\n        let client = client_artifacts.into_structured_output();\n        let server = server_artifacts.map(|s| s.into_structured_output());\n\n        Ok(StructuredOutput::BundleOutput {\n            bundles,\n            client,\n            server,\n        })\n    }\n\n    fn bundle_desktop(\n        build: &BuildRequest,\n        package_types: &Option<Vec<crate::PackageType>>,\n    ) -> Result<Vec<tauri_bundler::Bundle>, Error> {\n        let krate = &build;\n        let exe = build.main_exe();\n\n        _ = std::fs::remove_dir_all(krate.bundle_dir(build.bundle));\n\n        let package = krate.package();\n        let mut name: PathBuf = krate.executable_name().into();\n        if cfg!(windows) {\n            name.set_extension(\"exe\");\n        }\n        std::fs::create_dir_all(krate.bundle_dir(build.bundle))\n            .context(\"Failed to create bundle directory\")?;\n        std::fs::copy(&exe, krate.bundle_dir(build.bundle).join(&name))\n            .with_context(|| \"Failed to copy the output executable into the bundle directory\")?;\n\n        let binaries = vec![\n            // We use the name of the exe but it has to be in the same directory\n            BundleBinary::new(krate.executable_name().to_string(), true)\n                .set_src_path(Some(exe.display().to_string())),\n        ];\n\n        let mut bundle_settings: BundleSettings = krate.config.bundle.clone().into();\n\n        // Check if required fields are provided instead of failing silently.\n        if bundle_settings.identifier.is_none() {\n            bail!(\"\\n\\nBundle identifier was not provided in `Dioxus.toml`. Add it as:\\n\\n[bundle]\\nidentifier = \\\"com.mycompany\\\"\\n\\n\");\n        }\n        if bundle_settings.publisher.is_none() {\n            bail!(\"\\n\\nBundle publisher was not provided in `Dioxus.toml`. Add it as:\\n\\n[bundle]\\npublisher = \\\"MyCompany\\\"\\n\\n\");\n        }\n\n        /// Resolve an icon path relative to the crate dir\n        fn canonicalize_icon_path(build: &BuildRequest, icon: &mut String) -> Result<(), Error> {\n            let icon_path = build\n                .crate_dir()\n                .join(&icon)\n                .canonicalize()\n                .with_context(|| format!(\"Failed to canonicalize path to icon {icon:?}\"))?;\n            *icon = icon_path.to_string_lossy().to_string();\n            Ok(())\n        }\n\n        // Resolve bundle.icon relative to the crate dir\n        if let Some(icons) = bundle_settings.icon.as_mut() {\n            for icon in icons.iter_mut() {\n                canonicalize_icon_path(build, icon)?;\n            }\n        }\n\n        #[allow(deprecated)]\n        if cfg!(windows) {\n            // Resolve bundle.windows.icon_path relative to the crate dir\n            let mut windows_icon_path = bundle_settings\n                .windows\n                .icon_path\n                .to_string_lossy()\n                .to_string();\n            canonicalize_icon_path(build, &mut windows_icon_path)?;\n            bundle_settings.windows.icon_path = PathBuf::from(&windows_icon_path);\n\n            let windows_icon_override = krate.config.bundle.windows.as_ref().map(|w| &w.icon_path);\n            if windows_icon_override.is_none() {\n                let icon_path = bundle_settings\n                    .icon\n                    .as_ref()\n                    .and_then(|icons| icons.first());\n\n                if let Some(icon_path) = icon_path {\n                    bundle_settings.icon = Some(vec![icon_path.into()]);\n                };\n            }\n        }\n\n        if bundle_settings.resources_map.is_none() {\n            bundle_settings.resources_map = Some(HashMap::new());\n        }\n\n        let asset_dir = build.asset_dir();\n        if asset_dir.exists() {\n            for entry in WalkDir::new(&asset_dir) {\n                let entry = entry.unwrap();\n                let path = entry.path();\n\n                if path.is_file() {\n                    let old = path\n                        .canonicalize()\n                        .with_context(|| format!(\"Failed to canonicalize {entry:?}\"))?;\n                    let new =\n                        PathBuf::from(\"assets\").join(path.strip_prefix(&asset_dir).unwrap_or(path));\n\n                    tracing::debug!(\"Bundled asset: {old:?} -> {new:?}\");\n                    bundle_settings\n                        .resources_map\n                        .as_mut()\n                        .expect(\"to be set\")\n                        .insert(old.display().to_string(), new.display().to_string());\n                }\n            }\n        }\n\n        for resource_path in bundle_settings.resources.take().into_iter().flatten() {\n            bundle_settings\n                .resources_map\n                .as_mut()\n                .expect(\"to be set\")\n                .insert(resource_path, \"\".to_string());\n        }\n\n        let mut settings = SettingsBuilder::new()\n            .project_out_directory(krate.bundle_dir(build.bundle))\n            .package_settings(PackageSettings {\n                product_name: krate.bundled_app_name(),\n                version: package.version.to_string(),\n                description: package.description.clone().unwrap_or_default(),\n                homepage: Some(package.homepage.clone().unwrap_or_default()),\n                authors: Some(package.authors.clone()),\n                default_run: Some(name.display().to_string()),\n            })\n            .log_level(log::Level::Debug)\n            .binaries(binaries)\n            .bundle_settings(bundle_settings);\n\n        if let Some(packages) = &package_types {\n            settings = settings.package_types(packages.iter().map(|p| (*p).into()).collect());\n        }\n\n        settings = settings.target(build.triple.to_string());\n\n        let settings = settings\n            .build()\n            .context(\"failed to bundle tauri bundle settings\")?;\n        tracing::debug!(\"Bundling project with settings: {:#?}\", settings);\n        if cfg!(target_os = \"macos\") {\n            std::env::set_var(\"CI\", \"true\");\n        }\n\n        let bundles = tauri_bundler::bundle::bundle_project(&settings).inspect_err(|err| {\n            tracing::error!(\"Failed to bundle project: {:#?}\", err);\n            if cfg!(target_os = \"macos\") {\n                tracing::error!(\"Make sure you have automation enabled in your terminal (https://github.com/tauri-apps/tauri/issues/3055#issuecomment-1624389208) and full disk access enabled for your terminal (https://github.com/tauri-apps/tauri/issues/3055#issuecomment-1624389208)\");\n            }\n        })?;\n\n        Ok(bundles)\n    }\n}\n"
  },
  {
    "path": "packages/cli/src/cli/check.rs",
    "content": "//! Run linting against the user's codebase.\n//!\n//! For reference, the rustfmt main.rs file\n//! <https://github.com/rust-lang/rustfmt/blob/master/src/bin/main.rs>\n\nuse super::*;\nuse crate::BuildRequest;\nuse anyhow::{anyhow, Context};\nuse futures_util::{stream::FuturesUnordered, StreamExt};\nuse std::path::Path;\n\n/// Check the Rust files in the project for issues.\n#[derive(Clone, Debug, Parser)]\npub(crate) struct Check {\n    /// Input file\n    #[clap(short, long)]\n    pub(crate) file: Option<PathBuf>,\n\n    /// Information about the target to check\n    #[clap(flatten)]\n    pub(crate) build_args: CommandWithPlatformOverrides<BuildArgs>,\n}\n\nimpl Check {\n    // Todo: check the entire crate\n    pub(crate) async fn check(self) -> Result<StructuredOutput> {\n        let BuildTargets { client, server } = self.build_args.into_targets().await?;\n\n        match self.file {\n            // Default to checking the project\n            None => {\n                check_project_and_report(&client)\n                    .await\n                    .context(\"error checking project\")?;\n\n                if let Some(server) = server {\n                    if server.package != client.package {\n                        check_project_and_report(&server)\n                            .await\n                            .context(\"error checking project\")?;\n                    }\n                }\n            }\n            Some(file) => {\n                check_file_and_report(file)\n                    .await\n                    .context(\"error checking file\")?;\n            }\n        }\n\n        Ok(StructuredOutput::Success)\n    }\n}\n\nasync fn check_file_and_report(path: PathBuf) -> Result<()> {\n    check_files_and_report(vec![path]).await\n}\n\n/// Read every .rs file accessible when considering the .gitignore and check it\n///\n/// Runs using Tokio for multithreading, so it should be really really fast\n///\n/// Doesn't do mod-descending, so it will still try to check unreachable files. TODO.\nasync fn check_project_and_report(build: &BuildRequest) -> Result<()> {\n    let dioxus_crate = build\n        .workspace\n        .find_main_package(Some(build.package.clone()))?;\n    let dioxus_crate = &build.workspace.krates[dioxus_crate];\n    let mut files_to_check = vec![];\n    collect_rs_files(\n        dioxus_crate.manifest_path.parent().unwrap().as_std_path(),\n        &mut files_to_check,\n    );\n    check_files_and_report(files_to_check).await\n}\n\n/// Check a list of files and report the issues.\nasync fn check_files_and_report(files_to_check: Vec<PathBuf>) -> Result<()> {\n    let issue_reports = files_to_check\n        .into_iter()\n        .filter(|file| file.components().all(|f| f.as_os_str() != \"target\"))\n        .map(|path| async move {\n            let _path = path.clone();\n            let res = tokio::spawn(async move {\n                tokio::fs::read_to_string(&_path)\n                    .await\n                    .map(|contents| dioxus_check::check_file(_path, &contents))\n            })\n            .await;\n\n            if res.is_err() {\n                tracing::error!(\"error checking file: {}\", path.display());\n            }\n\n            res\n        })\n        .collect::<FuturesUnordered<_>>()\n        .collect::<Vec<_>>()\n        .await;\n\n    // remove error results which we've already printed\n    let issue_reports = issue_reports\n        .into_iter()\n        .flatten()\n        .flatten()\n        .collect::<Vec<_>>();\n\n    let total_issues = issue_reports.iter().map(|r| r.issues.len()).sum::<usize>();\n\n    for report in issue_reports.into_iter() {\n        if !report.issues.is_empty() {\n            tracing::info!(\"{}\", report);\n        }\n    }\n\n    match total_issues {\n        0 => {\n            tracing::info!(\"No issues found.\");\n            Ok(())\n        }\n        1 => Err(anyhow!(\"1 issue found.\")),\n        _ => Err(anyhow!(\"{total_issues} issues found.\")),\n    }\n}\n\npub(crate) fn collect_rs_files(folder: &Path, files: &mut Vec<PathBuf>) {\n    for entry in ignore::Walk::new(folder).flatten() {\n        if entry.path().extension() == Some(\"rs\".as_ref()) {\n            files.push(entry.path().to_path_buf());\n        }\n    }\n}\n"
  },
  {
    "path": "packages/cli/src/cli/component.rs",
    "content": "use std::{\n    collections::{HashMap, HashSet},\n    ops::Deref,\n    path::{Path, PathBuf},\n};\n\nuse crate::{verbosity_or_default, DioxusConfig, Result, StructuredOutput, Workspace};\nuse anyhow::{bail, Context};\nuse clap::Parser;\nuse dioxus_component_manifest::{\n    component_manifest_schema, CargoDependency, Component, ComponentDependency,\n};\nuse git2::Repository;\nuse schemars::JsonSchema;\nuse serde::{Deserialize, Serialize};\nuse tokio::{process::Command, task::JoinSet};\nuse tracing::debug;\n\n#[derive(Clone, Debug, Parser)]\npub enum ComponentCommand {\n    /// Add a component from a registry\n    Add {\n        #[clap(flatten)]\n        component: ComponentArgs,\n\n        /// The registry to use\n        #[clap(flatten)]\n        registry: ComponentRegistry,\n\n        /// Overwrite the component if it already exists\n        #[clap(long)]\n        force: bool,\n    },\n\n    /// Remove a component\n    Remove {\n        #[clap(flatten)]\n        component: ComponentArgs,\n\n        /// The registry to use\n        #[clap(flatten)]\n        registry: ComponentRegistry,\n    },\n\n    /// Update a component registry\n    Update {\n        /// The registry to update\n        #[clap(flatten)]\n        registry: Option<RemoteComponentRegistry>,\n    },\n\n    /// List available components in a registry\n    List {\n        /// The registry to list components in\n        #[clap(flatten)]\n        registry: ComponentRegistry,\n    },\n\n    /// Clear the component registry cache\n    Clean,\n\n    /// Print the schema for component manifests\n    Schema,\n}\n\n/// Arguments for a component and component module location\n#[derive(Clone, Debug, Parser, Serialize)]\npub struct ComponentArgs {\n    /// The components to add or remove\n    #[clap(required_unless_present = \"all\", value_delimiter = ',')]\n    components: Vec<String>,\n\n    /// The location of the component module in your project (default: src/components)\n    #[clap(long)]\n    module_path: Option<PathBuf>,\n\n    /// The location of the global assets in your project (default: assets)\n    #[clap(long)]\n    global_assets_path: Option<PathBuf>,\n\n    /// Include all components in the registry\n    #[clap(long)]\n    all: bool,\n}\n\nimpl ComponentCommand {\n    /// Run the component command\n    pub async fn run(self) -> Result<StructuredOutput> {\n        match self {\n            // List all components in the registry\n            Self::List { registry } => {\n                let config = Self::resolve_config().await?;\n                let registry = Self::resolve_registry(registry, &config)?;\n                let mut components = registry.read_components().await?;\n                components.sort_by_key(|c| c.name.clone());\n                for component in components {\n                    println!(\"- {}: {}\", component.name, component.description);\n                }\n            }\n\n            // Add a component to the managed component module\n            Self::Add {\n                component: component_args,\n                registry,\n                force,\n            } => {\n                // Resolve the config\n                let config = Self::resolve_config().await?;\n\n                // Resolve the registry\n                let registry = Self::resolve_registry(registry, &config)?;\n\n                // Get the registry root. Components can't copy files outside of this path\n                let registry_root = registry.resolve().await?;\n\n                // Read all components from the registry\n                let components = registry.read_components().await?;\n                let mode = if force {\n                    ComponentExistsBehavior::Overwrite\n                } else {\n                    ComponentExistsBehavior::Error\n                };\n\n                // Find the requested components\n                let components = if component_args.all {\n                    components\n                } else {\n                    component_args\n                        .components\n                        .iter()\n                        .map(|component| find_component(&components, component))\n                        .collect::<Result<Vec<_>>>()?\n                };\n\n                // Find and initialize the components module if it doesn't exist\n                let components_root =\n                    components_root(component_args.module_path.as_deref(), &config)?;\n                let new_components_module =\n                    ensure_components_module_exists(&components_root).await?;\n\n                // Recursively add dependencies\n                // A map of the components that have been added or are queued to be added\n                let mut required_components = HashMap::new();\n                required_components.extend(components.iter().cloned().map(|c| (c, mode)));\n                // A stack of components to process\n                let mut queued_components = components;\n                while let Some(queued_component) = queued_components.pop() {\n                    for dependency in &queued_component.component_dependencies {\n                        let (registry, name) = match dependency {\n                            ComponentDependency::Builtin(name) => {\n                                (ComponentRegistry::default(), name)\n                            }\n                            ComponentDependency::ThirdParty { name, git, rev } => (\n                                ComponentRegistry {\n                                    remote: RemoteComponentRegistry {\n                                        git: Some(git.clone()),\n                                        rev: rev.clone(),\n                                    },\n                                    path: None,\n                                },\n                                name,\n                            ),\n                        };\n                        let registry_components = registry.read_components().await?;\n                        let dependency_component = find_component(&registry_components, name)?;\n                        if required_components\n                            .insert(\n                                dependency_component.clone(),\n                                ComponentExistsBehavior::Return,\n                            )\n                            .is_none()\n                        {\n                            queued_components.push(dependency_component);\n                        }\n                    }\n                }\n\n                // Then collect all required rust dependencies\n                let mut rust_dependencies = HashSet::new();\n                for component in required_components.keys() {\n                    rust_dependencies.extend(component.cargo_dependencies.iter().cloned());\n                }\n\n                // And add them to Cargo.toml\n                Self::add_rust_dependencies(&rust_dependencies).await?;\n\n                // Once we have all required components, add them\n                for (component, mode) in required_components {\n                    add_component(\n                        &registry_root,\n                        component_args.global_assets_path.as_deref(),\n                        component_args.module_path.as_deref(),\n                        &component,\n                        mode,\n                        &config,\n                    )\n                    .await?;\n                }\n\n                // If we created a new components module, print instructions about the final setup steps required\n                if new_components_module {\n                    println!(\n                        \"Created new components module at {}.\",\n                        components_root.display()\n                    );\n                    println!(\"To finish setting up components, you will need to:\");\n                    println!(\"- manually reference the module by adding `mod components;` to your `main.rs` file\");\n                    if registry.is_default() {\n                        println!(\"- add a reference to `asset!(\\\"/assets/dx-components-theme.css\\\")` as a stylesheet in your app\");\n                    }\n                }\n            }\n\n            // Update the remote component registry\n            Self::Update { registry } => {\n                let config = Self::resolve_config().await?;\n                registry\n                    .unwrap_or(config.components.registry.remote)\n                    .update()\n                    .await?;\n            }\n\n            // Remove a component from the managed component module\n            Self::Remove {\n                component,\n                registry,\n            } => {\n                Self::remove_component(&component, registry).await?;\n            }\n\n            // Clear the component registry cache\n            Self::Clean => {\n                _ = tokio::fs::remove_dir_all(&Workspace::component_cache_dir()).await;\n            }\n\n            // Print the schema for component manifests\n            Self::Schema => {\n                let schema = component_manifest_schema();\n                println!(\n                    \"{}\",\n                    serde_json::to_string_pretty(&schema).unwrap_or_default()\n                );\n            }\n        }\n\n        Ok(StructuredOutput::Success)\n    }\n\n    /// Remove a component from the managed component module\n    async fn remove_component(\n        component_args: &ComponentArgs,\n        registry: ComponentRegistry,\n    ) -> Result<()> {\n        let config = Self::resolve_config().await?;\n        let registry = Self::resolve_registry(registry, &config)?;\n\n        let components_root = components_root(component_args.module_path.as_deref(), &config)?;\n\n        // Find the requested components\n        let components = if component_args.all {\n            registry\n                .read_components()\n                .await?\n                .into_iter()\n                .map(|c| c.component.name)\n                .collect()\n        } else {\n            component_args.components.clone()\n        };\n\n        for component_name in components {\n            // Remove the component module\n            _ = tokio::fs::remove_dir_all(&components_root.join(&component_name)).await;\n\n            // Remove the module from the components mod.rs\n            let mod_rs_path = components_root.join(\"mod.rs\");\n            let mod_rs_content = tokio::fs::read_to_string(&mod_rs_path)\n                .await\n                .with_context(|| format!(\"Failed to read {}\", mod_rs_path.display()))?;\n            let mod_line = format!(\"pub mod {};\\n\", component_name);\n            let new_mod_rs_content = mod_rs_content.replace(&mod_line, \"\");\n            tokio::fs::write(&mod_rs_path, new_mod_rs_content)\n                .await\n                .with_context(|| format!(\"Failed to write to {}\", mod_rs_path.display()))?;\n        }\n        Ok(())\n    }\n\n    /// Load the config\n    async fn resolve_config() -> Result<DioxusConfig> {\n        let workspace = Workspace::current().await?;\n\n        let crate_package = workspace.find_main_package(None)?;\n\n        Ok(workspace\n            .load_dioxus_config(crate_package, None)?\n            .unwrap_or_default())\n    }\n\n    /// Resolve a registry from the config if none is provided\n    fn resolve_registry(\n        registry: ComponentRegistry,\n        config: &DioxusConfig,\n    ) -> Result<ComponentRegistry> {\n        if !registry.is_default() {\n            return Ok(registry);\n        }\n\n        Ok(config.components.registry.clone())\n    }\n\n    /// Add any rust dependencies required for a component\n    async fn add_rust_dependencies(dependencies: &HashSet<CargoDependency>) -> Result<()> {\n        for dep in dependencies {\n            let status = Command::from(dep.add_command())\n                .status()\n                .await\n                .with_context(|| {\n                    format!(\n                        \"Failed to run command to add dependency {} to Cargo.toml\",\n                        dep.name()\n                    )\n                })?;\n            if !status.success() {\n                bail!(\"Failed to add dependency {} to Cargo.toml\", dep.name());\n            }\n        }\n\n        Ok(())\n    }\n}\n\n/// Arguments for the default or custom remote registry\n/// If both values are None, the default registry will be used\n#[derive(Clone, Debug, Parser, Default, Serialize, Deserialize, JsonSchema)]\npub struct RemoteComponentRegistry {\n    /// The url of the component registry\n    #[arg(long)]\n    git: Option<String>,\n\n    /// The revision of the component registry\n    #[arg(long)]\n    rev: Option<String>,\n}\n\nimpl RemoteComponentRegistry {\n    /// Resolve the path to the component registry, downloading the remote registry if needed\n    async fn resolve(&self) -> Result<PathBuf> {\n        // If a git url is provided use that (plus optional rev)\n        // Otherwise use the built-in registry\n        let (git, rev) = self.resolve_or_default();\n\n        let repo_dir = Workspace::component_cache_path(&git, rev.as_deref());\n\n        // If the repo already exists, use it otherwise clone it\n        if !repo_dir.exists() {\n            // If offline, we cannot download the registry\n            if verbosity_or_default().offline {\n                bail!(\"Cannot download component registry '{}' while offline\", git);\n            }\n\n            // Make sure the parent directory exists\n            tokio::fs::create_dir_all(&repo_dir).await?;\n            tokio::task::spawn_blocking({\n                let git = git.clone();\n                let repo_dir = repo_dir.clone();\n                move || {\n                    println!(\"Downloading {git}...\");\n\n                    // Clone the repo\n                    let repo = Repository::clone(&git, repo_dir)?;\n\n                    // If a rev is provided, checkout that rev\n                    if let Some(rev) = &rev {\n                        Self::checkout_rev(&repo, &git, rev)?;\n                    }\n\n                    anyhow::Ok(())\n                }\n            })\n            .await??;\n        }\n\n        Ok(repo_dir)\n    }\n\n    /// Update the component registry by fetching the latest changes from the remote\n    async fn update(&self) -> Result<()> {\n        let (git, rev) = self.resolve_or_default();\n\n        // Make sure the repo is cloned\n        let path = self.resolve().await?;\n\n        // Open the repo and update it\n        tokio::task::spawn_blocking({\n            let path = path.clone();\n            move || {\n                let repo = Repository::open(path)?;\n                let mut remote = repo.find_remote(\"origin\")?;\n                // Fetch all remote branches with the same name as local branches\n                remote.fetch(&[\"refs/heads/*:refs/heads/*\"], None, None)?;\n                // If a rev is provided, checkout that rev\n                if let Some(rev) = &rev {\n                    Self::checkout_rev(&repo, &git, rev)?;\n                }\n                // Otherwise, just checkout the latest commit on the default branch\n                else {\n                    let head = repo.head()?;\n                    let branch = head.shorthand().unwrap_or(\"main\");\n                    let oid = repo.refname_to_id(&format!(\"refs/remotes/origin/{branch}\"))?;\n                    let object = repo.find_object(oid, None).unwrap();\n                    repo.reset(&object, git2::ResetType::Hard, None)?;\n                }\n                anyhow::Ok(())\n            }\n        })\n        .await??;\n\n        Ok(())\n    }\n\n    /// If a git url is provided use that (plus optional rev)\n    /// Otherwise use the built-in registry\n    fn resolve_or_default(&self) -> (String, Option<String>) {\n        if let Some(git) = &self.git {\n            (git.clone(), self.rev.clone())\n        } else {\n            (\"https://github.com/dioxuslabs/components\".into(), None)\n        }\n    }\n\n    /// Checkout the given rev in the given repo\n    fn checkout_rev(repo: &Repository, git: &str, rev: &str) -> Result<()> {\n        let (object, reference) = repo\n            .revparse_ext(rev)\n            .with_context(|| format!(\"Failed to find revision '{}' in '{}'\", rev, git))?;\n        repo.checkout_tree(&object, None)?;\n\n        if let Some(gref) = reference {\n            if let Some(name) = gref.name() {\n                repo.set_head(name)?;\n            }\n        } else {\n            repo.set_head_detached(object.id())?;\n        }\n\n        Ok(())\n    }\n}\n\n/// Arguments for a component registry\n/// Either a path to a local directory or a remote git repo (with optional rev)\n#[derive(Clone, Debug, Parser, Default, Serialize, Deserialize, JsonSchema)]\npub struct ComponentRegistry {\n    /// The remote repo args\n    #[clap(flatten)]\n    #[serde(flatten)]\n    remote: RemoteComponentRegistry,\n\n    /// The path to the components directory\n    #[arg(long)]\n    path: Option<String>,\n}\n\nimpl ComponentRegistry {\n    /// Resolve the path to the component registry, downloading the remote registry if needed\n    async fn resolve(&self) -> Result<PathBuf> {\n        // If a path is provided, use that\n        if let Some(path) = &self.path {\n            return Ok(PathBuf::from(path));\n        }\n\n        // Otherwise use the remote/default registry\n        self.remote.resolve().await\n    }\n\n    /// Read all components that are part of this registry\n    async fn read_components(&self) -> Result<Vec<ResolvedComponent>> {\n        let path = self.resolve().await?;\n\n        let root = read_component(&path).await?;\n        let mut components = discover_components(root).await?;\n\n        // Filter out any virtual components with members\n        components.retain(|c| c.members.is_empty());\n\n        Ok(components)\n    }\n\n    /// Check if this is the default registry\n    fn is_default(&self) -> bool {\n        self.path.is_none() && self.remote.git.is_none() && self.remote.rev.is_none()\n    }\n}\n\n/// A component that has been downloaded and resolved at a specific path\n#[derive(Clone, Debug, PartialEq, Eq, Hash)]\nstruct ResolvedComponent {\n    path: PathBuf,\n    component: Component,\n}\n\nimpl ResolvedComponent {\n    /// Get the absolute paths to members of this component\n    fn member_paths(&self) -> Vec<PathBuf> {\n        self.component\n            .members\n            .iter()\n            .map(|m| self.path.join(m))\n            .collect()\n    }\n}\n\nimpl Deref for ResolvedComponent {\n    type Target = Component;\n\n    fn deref(&self) -> &Self::Target {\n        &self.component\n    }\n}\n\n// Find a component by name in a list of components\nfn find_component(components: &[ResolvedComponent], component: &str) -> Result<ResolvedComponent> {\n    components\n        .iter()\n        .find(|c| c.name == component)\n        .cloned()\n        .ok_or_else(|| anyhow::anyhow!(\"Component '{}' not found in registry\", component))\n}\n\n/// Get the path to the components module, defaulting to src/components\nfn components_root(module_path: Option<&Path>, config: &DioxusConfig) -> Result<PathBuf> {\n    if let Some(module_path) = module_path {\n        return Ok(PathBuf::from(module_path));\n    }\n\n    let root = Workspace::crate_root_from_path()?;\n\n    if let Some(component_path) = &config.components.components_dir {\n        return Ok(root.join(component_path));\n    }\n\n    Ok(root.join(\"src\").join(\"components\"))\n}\n\n/// Get the path to the global assets directory, defaulting to assets\nasync fn global_assets_root(assets_path: Option<&Path>, config: &DioxusConfig) -> Result<PathBuf> {\n    if let Some(assets_path) = assets_path {\n        return Ok(PathBuf::from(assets_path));\n    }\n\n    if let Some(asset_dir) = &config.application.asset_dir {\n        return Ok(asset_dir.clone());\n    }\n\n    let root = Workspace::crate_root_from_path()?;\n\n    Ok(root.join(\"assets\"))\n}\n\n/// How should we handle the component if it already exists\n#[derive(Clone, Copy, Debug)]\nenum ComponentExistsBehavior {\n    /// Return an error (default)\n    Error,\n\n    /// Return early for component dependencies\n    Return,\n\n    /// Overwrite the existing component\n    Overwrite,\n}\n\n/// Add a component to the managed component module\nasync fn add_component(\n    registry_root: &Path,\n    assets_path: Option<&Path>,\n    component_path: Option<&Path>,\n    component: &ResolvedComponent,\n    behavior: ComponentExistsBehavior,\n    config: &DioxusConfig,\n) -> Result<()> {\n    // Copy the folder content to the components directory\n    let components_root = components_root(component_path, config)?;\n    let copied = copy_component_files(\n        &component.path,\n        &components_root.join(&component.name),\n        &component.exclude,\n        behavior,\n    )\n    .await?;\n    if !copied {\n        debug!(\n            \"Component '{}' already exists, skipping copy\",\n            component.name\n        );\n        return Ok(());\n    }\n\n    // Copy any global assets\n    let assets_root = global_assets_root(assets_path, config).await?;\n    copy_global_assets(registry_root, &assets_root, component).await?;\n\n    // Add the module to the components mod.rs\n    let mod_rs_path = components_root.join(\"mod.rs\");\n    let mut mod_rs = tokio::fs::OpenOptions::new()\n        .append(true)\n        .read(true)\n        .open(&mod_rs_path)\n        .await\n        .with_context(|| format!(\"Failed to open {}\", mod_rs_path.display()))?;\n\n    // Check if the module already exists\n    let mod_rs_content = tokio::fs::read_to_string(&mod_rs_path)\n        .await\n        .with_context(|| format!(\"Failed to read {}\", mod_rs_path.display()))?;\n    if !mod_rs_content.contains(&format!(\"mod {};\", component.name)) {\n        let mod_line = format!(\"pub mod {};\\n\", component.name);\n        tokio::io::AsyncWriteExt::write_all(&mut mod_rs, mod_line.as_bytes())\n            .await\n            .with_context(|| format!(\"Failed to write to {}\", mod_rs_path.display()))?;\n    }\n\n    Ok(())\n}\n\n/// Copy the component files. Returns true if the component was copied, false if it was skipped.\nasync fn copy_component_files(\n    src: &Path,\n    dest: &Path,\n    exclude: &[String],\n    behavior: ComponentExistsBehavior,\n) -> Result<bool> {\n    async fn read_dir_paths(src: &Path) -> Result<Vec<PathBuf>> {\n        let mut entries = tokio::fs::read_dir(src).await?;\n        let mut paths = vec![];\n        while let Some(entry) = entries.next_entry().await? {\n            paths.push(entry.path());\n        }\n        Ok(paths)\n    }\n\n    // If the directory already exists, return an error, return silently or overwrite it depending on the behavior\n    if dest.exists() {\n        match behavior {\n            // The default behavior is to return an error\n            ComponentExistsBehavior::Error => {\n                bail!(\"Destination directory '{}' already exists\", dest.display());\n            }\n            // For dependencies, we return early\n            ComponentExistsBehavior::Return => {\n                debug!(\n                    \"Destination directory '{}' already exists, returning early\",\n                    dest.display()\n                );\n                return Ok(false);\n            }\n            // If the force flag is set, we overwrite the existing component\n            ComponentExistsBehavior::Overwrite => {\n                debug!(\n                    \"Destination directory '{}' already exists, overwriting\",\n                    dest.display()\n                );\n                tokio::fs::remove_dir_all(dest).await?;\n            }\n        }\n    }\n\n    tokio::fs::create_dir_all(dest).await?;\n\n    let exclude = exclude\n        .iter()\n        .map(|exclude| dunce::canonicalize(src.join(exclude)))\n        .collect::<Result<Vec<_>, _>>()?;\n\n    // Set set of tasks to read directories\n    let mut read_folder_tasks = JoinSet::new();\n    // Set set of tasks to copy files\n    let mut copy_tasks = JoinSet::new();\n\n    // Start by reading the source directory\n    let src = src.to_path_buf();\n    read_folder_tasks.spawn({\n        let src = src.clone();\n        async move { read_dir_paths(&src).await }\n    });\n\n    // Continue while there are read tasks\n    while let Some(res) = read_folder_tasks.join_next().await {\n        let paths = res??;\n        for path in paths {\n            let path = dunce::canonicalize(path)?;\n\n            // Skip excluded paths\n            if exclude.iter().any(|e| *e == path || path.starts_with(e)) {\n                debug!(\"Excluding path {}\", path.display());\n                continue;\n            }\n\n            // Find the path in the destination directory\n            let Ok(path_relative_to_src) = path.strip_prefix(&src) else {\n                continue;\n            };\n            let dest = dest.join(path_relative_to_src);\n\n            // If it's a directory, read it, otherwise copy the file\n            if path.is_dir() {\n                read_folder_tasks.spawn(async move { read_dir_paths(&path).await });\n            } else {\n                copy_tasks.spawn(async move {\n                    if let Some(parent) = dest.parent() {\n                        if !parent.exists() {\n                            tokio::fs::create_dir_all(parent).await?;\n                        }\n                    }\n                    tokio::fs::copy(&path, &dest).await\n                });\n            }\n        }\n    }\n\n    // Wait for all copy tasks to finish\n    while let Some(res) = copy_tasks.join_next().await {\n        res??;\n    }\n\n    Ok(true)\n}\n\n/// Make sure the components directory and a mod.rs file exists. Returns true if the directory was created, false if it already existed.\nasync fn ensure_components_module_exists(components_dir: &Path) -> Result<bool> {\n    if components_dir.exists() {\n        return Ok(false);\n    }\n    tokio::fs::create_dir_all(&components_dir).await?;\n    let mod_rs_path = components_dir.join(\"mod.rs\");\n    if mod_rs_path.exists() {\n        return Ok(false);\n    }\n    tokio::fs::write(&mod_rs_path, \"// AUTOGENERATED Components module\\n\").await?;\n\n    Ok(true)\n}\n\n/// Read a component from the given path\nasync fn read_component(path: &Path) -> Result<ResolvedComponent> {\n    let json_path = path.join(\"component.json\");\n    let bytes = tokio::fs::read(&json_path).await.with_context(|| {\n        format!(\n            \"Failed to open component manifest at {}\",\n            json_path.display()\n        )\n    })?;\n\n    let component = serde_json::from_slice(&bytes)?;\n    let absolute_path = dunce::canonicalize(path)?;\n    Ok(ResolvedComponent {\n        path: absolute_path,\n        component,\n    })\n}\n\n/// Recursively discover all components starting from the root component\nasync fn discover_components(root: ResolvedComponent) -> Result<Vec<ResolvedComponent>> {\n    // Create a queue of members to read\n    let mut queue = root.member_paths();\n    // The list of discovered components\n    let mut components = vec![root];\n    // The set of pending read tasks\n    let mut pending = JoinSet::new();\n    loop {\n        // First, spawn tasks for all queued paths\n        while let Some(root_path) = queue.pop() {\n            pending.spawn(async move { read_component(&root_path).await });\n        }\n        // Then try to join the next task\n        let Some(component) = pending.join_next().await else {\n            break;\n        };\n        let component = component??;\n        // And add the result to the queue and list\n        queue.extend(component.member_paths());\n        components.push(component);\n    }\n    Ok(components)\n}\n\n/// Copy any global assets for the component\nasync fn copy_global_assets(\n    registry_root: &Path,\n    assets_root: &Path,\n    component: &ResolvedComponent,\n) -> Result<()> {\n    let canonical_registry_root = dunce::canonicalize(registry_root)?;\n    for path in &component.global_assets {\n        let src = component.path.join(path);\n        let absolute_source = dunce::canonicalize(&src).with_context(|| {\n            format!(\n                \"Failed to find global asset '{}' for component '{}'\",\n                src.display(),\n                component.name\n            )\n        })?;\n\n        // Make sure the source is inside the component registry somewhere\n        if !absolute_source.starts_with(&canonical_registry_root) {\n            bail!(\n                \"Cannot copy global asset '{}' for component '{}' because it is outside of the component registry '{}'\",\n                absolute_source.display(),\n                component.name,\n                canonical_registry_root.display()\n            );\n        }\n\n        // Copy the file into the assets directory, preserving the file name and extension\n        let dest = assets_root.join(\n            absolute_source\n                .components()\n                .next_back()\n                .context(\"Global assets must have at least one file component\")?,\n        );\n\n        // Make sure the asset dir exists\n        if let Some(parent) = dest.parent() {\n            if !parent.exists() {\n                tokio::fs::create_dir_all(parent).await?;\n            }\n        }\n\n        tokio::fs::copy(&src, &dest).await.with_context(|| {\n            format!(\n                \"Failed to copy global asset from {} to {}\",\n                src.display(),\n                dest.display()\n            )\n        })?;\n    }\n\n    Ok(())\n}\n"
  },
  {
    "path": "packages/cli/src/cli/config.rs",
    "content": "use super::*;\nuse crate::{CliSettings, TraceSrc, Workspace};\n\n/// Dioxus config file controls\n#[derive(Clone, Debug, Deserialize, Subcommand)]\npub(crate) enum Config {\n    /// Init `Dioxus.toml` for project/folder.\n    Init {\n        /// Init project name\n        name: String,\n\n        /// Cover old config\n        #[clap(long)]\n        #[serde(default)]\n        force: bool,\n    },\n\n    /// Format print Dioxus config.\n    FormatPrint {},\n\n    /// Create a custom html file.\n    CustomHtml {},\n\n    /// Set CLI settings.\n    #[command(subcommand)]\n    Set(Setting),\n\n    /// Generate JSON schema for Dioxus.toml configuration.\n    /// Useful for IDE autocomplete and validation.\n    Schema {\n        /// Output file path. If not provided, prints to stdout.\n        #[clap(long, short)]\n        out: Option<PathBuf>,\n    },\n}\n\n#[derive(Debug, Clone, Copy, Deserialize, Subcommand)]\npub(crate) enum Setting {\n    /// Set the value of the always-hot-reload setting.\n    AlwaysHotReload { value: BoolValue },\n    /// Set the value of the always-open-browser setting.\n    AlwaysOpenBrowser { value: BoolValue },\n    /// Set the value of the always-on-top desktop setting.\n    AlwaysOnTop { value: BoolValue },\n    /// Set the interval that file changes are polled on WSL for hot reloading.\n    WSLFilePollInterval { value: u16 },\n    /// Disable the built-in telemetry for the CLI\n    DisableTelemetry { value: BoolValue },\n}\n\nimpl Display for Setting {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        match self {\n            Self::AlwaysHotReload { value: _ } => write!(f, \"always-hot-reload\"),\n            Self::AlwaysOpenBrowser { value: _ } => write!(f, \"always-open-browser\"),\n            Self::AlwaysOnTop { value: _ } => write!(f, \"always-on-top\"),\n            Self::WSLFilePollInterval { value: _ } => write!(f, \"wsl-file-poll-interval\"),\n            Self::DisableTelemetry { value: _ } => write!(f, \"disable-telemetry\"),\n        }\n    }\n}\n\n// Clap complains if we use a bool directly and I can't find much info about it.\n// \"Argument 'value` is positional and it must take a value but action is SetTrue\"\n#[derive(Debug, Clone, Copy, serde::Serialize, Deserialize, clap::ValueEnum)]\npub(crate) enum BoolValue {\n    True,\n    False,\n}\n\nimpl From<BoolValue> for bool {\n    fn from(value: BoolValue) -> Self {\n        match value {\n            BoolValue::True => true,\n            BoolValue::False => false,\n        }\n    }\n}\n\nimpl Config {\n    pub(crate) async fn config(self) -> Result<StructuredOutput> {\n        let crate_root = Workspace::crate_root_from_path()?;\n        match self {\n            Config::Init { name, force } => {\n                let conf_path = crate_root.join(\"Dioxus.toml\");\n                if conf_path.is_file() && !force {\n                    tracing::warn!(\n                        \"config file `Dioxus.toml` already exist, use `--force` to overwrite it.\"\n                    );\n                    return Ok(StructuredOutput::Success);\n                }\n                let mut file = File::create(conf_path)?;\n                let content = String::from(include_str!(\"../../assets/dioxus.toml\"))\n                    .replace(\"{{project-name}}\", &name);\n                file.write_all(content.as_bytes())?;\n                tracing::info!(dx_src = ?TraceSrc::Dev, \"🚩 Init config file completed.\");\n            }\n            Config::FormatPrint {} => {\n                let workspace = Workspace::current().await?;\n                tracing::info!(\"{:#?}\", workspace.settings);\n            }\n            Config::CustomHtml {} => {\n                let html_path = crate_root.join(\"index.html\");\n                let mut file = File::create(html_path)?;\n                let content = include_str!(\"../../assets/web/dev.index.html\");\n                file.write_all(content.as_bytes())?;\n                tracing::info!(dx_src = ?TraceSrc::Dev, \"🚩 Create custom html file done.\");\n            }\n            // Handle CLI settings.\n            Config::Set(setting) => {\n                CliSettings::modify_settings(|settings| match setting {\n                    Setting::AlwaysOnTop { value } => settings.always_on_top = Some(value.into()),\n                    Setting::AlwaysHotReload { value } => {\n                        settings.always_hot_reload = Some(value.into())\n                    }\n                    Setting::AlwaysOpenBrowser { value } => {\n                        settings.always_open_browser = Some(value.into())\n                    }\n                    Setting::WSLFilePollInterval { value } => {\n                        settings.wsl_file_poll_interval = Some(value)\n                    }\n                    Setting::DisableTelemetry { value } => {\n                        settings.disable_telemetry = Some(value.into());\n                    }\n                })?;\n                tracing::info!(dx_src = ?TraceSrc::Dev, \"🚩 CLI setting `{setting}` has been set.\");\n            }\n            Config::Schema { out } => {\n                let schema = crate::config::generate_manifest_schema();\n                let json = serde_json::to_string_pretty(&schema)?;\n                match out {\n                    Some(path) => {\n                        std::fs::write(&path, format!(\"{json}\\n\"))?;\n                        tracing::info!(dx_src = ?TraceSrc::Dev, \"Schema written to {}\", path.display());\n                    }\n                    None => println!(\"{json}\"),\n                }\n            }\n        }\n\n        Ok(StructuredOutput::Success)\n    }\n}\n"
  },
  {
    "path": "packages/cli/src/cli/create.rs",
    "content": "use super::*;\nuse crate::TraceSrc;\nuse anyhow::{bail, Context};\nuse cargo_generate::{GenerateArgs, TemplatePath, Vcs};\nuse std::{fs, path::Path};\n\npub(crate) static DEFAULT_TEMPLATE: &str = \"gh:dioxuslabs/dioxus-template\";\n\n#[derive(Clone, Debug, Default, Deserialize, Parser)]\n#[clap(name = \"new\")]\npub struct Create {\n    /// Create a new Dioxus project at PATH\n    pub path: PathBuf,\n\n    /// Project name. Defaults to directory name\n    #[arg(short, long)]\n    pub name: Option<String>,\n\n    /// Template path\n    #[clap(short, long)]\n    pub template: Option<String>,\n\n    /// Branch to select when using `template` from a git repository.\n    /// Mutually exclusive with: `--revision`, `--tag`.\n    #[clap(long, conflicts_with_all([\"revision\", \"tag\"]))]\n    pub branch: Option<String>,\n\n    /// A commit hash to select when using `template` from a git repository.\n    /// Mutually exclusive with: `--branch`, `--tag`.\n    #[clap(long, conflicts_with_all([\"branch\", \"tag\"]))]\n    pub revision: Option<String>,\n\n    /// Tag to select when using `template` from a git repository.\n    /// Mutually exclusive with: `--branch`, `--revision`.\n    #[clap(long, conflicts_with_all([\"branch\", \"revision\"]))]\n    pub tag: Option<String>,\n\n    /// Specify a sub-template within the template repository to be used as the actual template\n    #[clap(long)]\n    pub subtemplate: Option<String>,\n\n    /// Pass `<option>=<value>` for the used template (e.g., `foo=bar`)\n    #[clap(short, long)]\n    pub option: Vec<String>,\n\n    /// Skip user interaction by using the default values for the used template.\n    /// Default values can be overridden with `--option`\n    #[clap(short, long)]\n    pub yes: bool,\n\n    /// Specify the VCS used to initialize the generated template.\n    /// Options: `git`, `none`.\n    #[arg(long, value_parser)]\n    pub vcs: Option<Vcs>,\n}\n\nimpl Create {\n    pub async fn create(mut self) -> Result<StructuredOutput> {\n        // Project name defaults to directory name.\n        if self.name.is_none() {\n            self.name = Some(create::name_from_path(&self.path)?);\n        }\n\n        check_path(&self.path).await?;\n\n        // Perform a connectivity check so we just don't it around doing nothing if there's a network error\n        if self.template.is_none() {\n            check_connectivity().await?;\n        }\n\n        // If no template is specified, use the default one and set the branch to the latest release.\n        resolve_template_and_branch(&mut self.template, &mut self.branch);\n\n        // cargo-generate requires the path to be created first.\n        std::fs::create_dir_all(&self.path)?;\n\n        let args = GenerateArgs {\n            define: self.option,\n            destination: Some(self.path),\n            // NOTE: destination without init means base_dir + name, with —\n            // means dest_dir. So use `init: true` and always handle\n            // the dest_dir manually and carefully.\n            // Cargo never adds name to the path. Name is solely for project name.\n            // https://github.com/cargo-generate/cargo-generate/issues/1250\n            init: true,\n            name: self.name,\n            silent: self.yes,\n            vcs: self.vcs,\n            template_path: TemplatePath {\n                auto_path: self.template,\n                branch: self.branch,\n                revision: self.revision,\n                subfolder: self.subtemplate,\n                tag: self.tag,\n                ..Default::default()\n            },\n            verbose: crate::logging::VERBOSITY\n                .get()\n                .map(|f| f.verbose)\n                .unwrap_or(false),\n            ..Default::default()\n        };\n\n        tracing::debug!(dx_src = ?TraceSrc::Dev, \"Creating new project with args: {args:#?}\");\n        let path = cargo_generate::generate(args)?;\n\n        _ = post_create(&path, &self.vcs.unwrap_or(Vcs::Git));\n\n        Ok(StructuredOutput::Success)\n    }\n}\n\n/// If no template is specified, use the default one and set the branch to the latest release.\n///\n/// Allows us to version templates under the v0.5/v0.6 scheme on the templates repo.\npub(crate) fn resolve_template_and_branch(\n    template: &mut Option<String>,\n    branch: &mut Option<String>,\n) {\n    if template.is_none() {\n        use crate::dx_build_info::{PKG_VERSION_MAJOR, PKG_VERSION_MINOR};\n        *template = Some(DEFAULT_TEMPLATE.to_string());\n\n        if branch.is_none() {\n            *branch = Some(format!(\"v{PKG_VERSION_MAJOR}.{PKG_VERSION_MINOR}\"));\n        }\n    };\n}\n\n/// Extracts the last directory name from the `path`.\npub(crate) fn name_from_path(path: &Path) -> Result<String> {\n    use path_absolutize::Absolutize;\n\n    Ok(path\n        .absolutize()?\n        .to_path_buf()\n        .file_name()\n        .context(\"Current path does not include directory name\".to_string())?\n        .to_str()\n        .context(\"Current directory name is not a valid UTF-8 string\".to_string())?\n        .to_string())\n}\n\n/// Post-creation actions for newly setup crates.\npub(crate) fn post_create(path: &Path, vcs: &Vcs) -> Result<()> {\n    let metadata = if let Some(parent_dir) = path.parent() {\n        match cargo_metadata::MetadataCommand::new()\n            .current_dir(parent_dir)\n            .exec()\n        {\n            Ok(v) => Some(v),\n            // Only 1 error means that CWD isn't a cargo project.\n            Err(cargo_metadata::Error::CargoMetadata { .. }) => None,\n            Err(err) => {\n                anyhow::bail!(\"Couldn't retrieve cargo metadata: {:?}\", err)\n            }\n        }\n    } else {\n        None\n    };\n\n    // 1. Add the new project to the workspace, if it exists.\n    //    This must be executed first in order to run `cargo fmt` on the new project.\n    let is_workspace = metadata.is_some();\n    metadata.and_then(|metadata| {\n        let cargo_toml_path = &metadata.workspace_root.join(\"Cargo.toml\");\n        let cargo_toml_str = std::fs::read_to_string(cargo_toml_path).ok()?;\n        let relative_path = path.strip_prefix(metadata.workspace_root).ok()?;\n\n        let mut cargo_toml: toml_edit::DocumentMut = cargo_toml_str.parse().ok()?;\n        cargo_toml\n            .get_mut(\"workspace\")?\n            .get_mut(\"members\")?\n            .as_array_mut()?\n            .push(relative_path.display().to_string());\n\n        std::fs::write(cargo_toml_path, cargo_toml.to_string()).ok()\n    });\n\n    // 2. Run `cargo fmt` on the new project.\n    let mut cmd = Command::new(\"cargo\");\n    let cmd = cmd.arg(\"fmt\").current_dir(path);\n    let output = cmd.output().expect(\"failed to execute process\");\n    if !output.status.success() {\n        tracing::error!(dx_src = ?TraceSrc::Dev, \"cargo fmt failed\");\n        tracing::error!(dx_src = ?TraceSrc::Build, \"stdout: {}\", String::from_utf8_lossy(&output.stdout));\n        tracing::error!(dx_src = ?TraceSrc::Build, \"stderr: {}\", String::from_utf8_lossy(&output.stderr));\n    }\n\n    // 3. Format the `Cargo.toml` and `Dioxus.toml` files.\n    let toml_paths = [path.join(\"Cargo.toml\"), path.join(\"Dioxus.toml\")];\n    for toml_path in &toml_paths {\n        let Ok(toml) = std::fs::read_to_string(toml_path) else {\n            continue;\n        };\n\n        let mut toml = toml.parse::<toml_edit::DocumentMut>().map_err(|e| {\n            anyhow::anyhow!(\"failed to parse toml at {}: {}\", toml_path.display(), e)\n        })?;\n\n        toml.as_table_mut().fmt();\n\n        let as_string = toml.to_string();\n        let new_string = remove_triple_newlines(&as_string);\n        let mut file = std::fs::File::create(toml_path)?;\n        file.write_all(new_string.as_bytes())?;\n    }\n\n    // 4. Remove any triple newlines from the readme.\n    let readme_path = path.join(\"README.md\");\n    let readme = std::fs::read_to_string(&readme_path)?;\n    let new_readme = remove_triple_newlines(&readme);\n    let mut file = std::fs::File::create(readme_path)?;\n    file.write_all(new_readme.as_bytes())?;\n\n    // 5. Run git init\n    if !is_workspace {\n        vcs.initialize(path, Some(\"main\"), true)?;\n    }\n\n    tracing::info!(dx_src = ?TraceSrc::Dev, \"Generated project at {}\\n\\n`cd` to your project and run `dx serve` to start developing.\\nMore information is available in the generated `README.md`.\\n\\nBuild cool things! ✌️\", path.display());\n\n    Ok(())\n}\n\nfn remove_triple_newlines(string: &str) -> String {\n    let mut new_string = String::new();\n    for char in string.chars() {\n        if char == '\\n' && new_string.ends_with(\"\\n\\n\") {\n            continue;\n        }\n        new_string.push(char);\n    }\n    new_string\n}\n\n/// Check if the requested project can be created in the filesystem\npub(crate) async fn check_path(path: &std::path::PathBuf) -> Result<()> {\n    match fs::metadata(path) {\n        Ok(_metadata) => {\n            bail!(\n                \"A file or directory with the given project name \\\"{}\\\" already exists.\",\n                path.to_string_lossy()\n            )\n        }\n        Err(_err) => Ok(()),\n    }\n}\n\n/// Perform a health check against github itself before we attempt to download any templates hosted\n/// on github.\npub(crate) async fn check_connectivity() -> Result<()> {\n    if crate::verbosity_or_default().offline {\n        return Ok(());\n    }\n\n    use crate::styles::{GLOW_STYLE, LINK_STYLE};\n    let client = reqwest::Client::new();\n    for x in 0..=5 {\n        tokio::select! {\n            res = client.head(\"https://github.com/DioxusLabs/\").header(\"User-Agent\", \"dioxus-cli\").send() => {\n                if res.is_ok() {\n                    return Ok(());\n                }\n                tokio::time::sleep(std::time::Duration::from_millis(2000)).await;\n            },\n            _ = tokio::time::sleep(std::time::Duration::from_millis(if x == 1 { 500 } else { 2000 })) => {}\n        }\n        if x == 0 {\n            eprintln!(\"{GLOW_STYLE}warning{GLOW_STYLE:#}: Waiting for {LINK_STYLE}https://github.com/dioxuslabs{LINK_STYLE:#}...\")\n        } else {\n            eprintln!(\n                \"{GLOW_STYLE}warning{GLOW_STYLE:#}: ({x}/5) Taking a while, maybe your internet is down?\"\n            );\n        }\n    }\n\n    bail!(\n        \"Error connecting to template repository. Try cloning the template manually or add `dioxus` to a `cargo new` project.\"\n    )\n}\n\n// todo: re-enable these tests with better parallelization\n//\n// #[cfg(test)]\n// pub(crate) mod tests {\n//     use escargot::{CargoBuild, CargoRun};\n//     use std::sync::LazyLock;\n//     use std::fs::{create_dir_all, read_to_string};\n//     use std::path::{Path, PathBuf};\n//     use std::process::Command;\n//     use tempfile::tempdir;\n//     use toml::Value;\n\n//     static BINARY: LazyLock<CargoRun> = LazyLock::new(|| {\n//         CargoBuild::new()\n//             .bin(env!(\"CARGO_BIN_NAME\"))\n//             .current_release()\n//             .run()\n//             .expect(\"Couldn't build the binary for tests.\")\n//     });\n\n//     // Note: tests below (at least 6 of them) were written to mainly test\n//     // correctness of project's directory and its name, because previously it\n//     // was broken and tests bring a peace of mind. And also so that I don't have\n//     // to run my local hand-made tests every time.\n\n//     pub(crate) type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;\n\n//     pub(crate) fn subcommand(name: &str) -> Command {\n//         let mut command = BINARY.command();\n//         command.arg(name).arg(\"--yes\"); // Skip any questions by choosing default answers.\n//         command\n//     }\n\n//     pub(crate) fn get_cargo_toml_path(project_path: &Path) -> PathBuf {\n//         project_path.join(\"Cargo.toml\")\n//     }\n\n//     pub(crate) fn get_project_name(cargo_toml_path: &Path) -> Result<String> {\n//         Ok(toml::from_str::<Value>(&read_to_string(cargo_toml_path)?)?\n//             .get(\"package\")\n//             .unwrap()\n//             .get(\"name\")\n//             .unwrap()\n//             .as_str()\n//             .unwrap()\n//             .to_string())\n//     }\n\n//     fn subcommand_new() -> Command {\n//         subcommand(\"new\")\n//     }\n\n//     #[test]\n//     fn test_subcommand_new_with_dot_path() -> Result<()> {\n//         let project_dir = \"dir\";\n//         let project_name = project_dir;\n\n//         let temp_dir = tempdir()?;\n//         // Make current dir's name deterministic.\n//         let current_dir = temp_dir.path().join(project_dir);\n//         create_dir_all(&current_dir)?;\n//         let project_path = &current_dir;\n//         assert!(project_path.exists());\n\n//         assert!(subcommand_new()\n//             .arg(\".\")\n//             .current_dir(&current_dir)\n//             .status()\n//             .is_ok());\n\n//         let cargo_toml_path = get_cargo_toml_path(project_path);\n//         assert!(cargo_toml_path.exists());\n//         assert_eq!(get_project_name(&cargo_toml_path)?, project_name);\n//         Ok(())\n//     }\n\n//     #[test]\n//     fn test_subcommand_new_with_1_dir_path() -> Result<()> {\n//         let project_dir = \"dir\";\n//         let project_name = project_dir;\n\n//         let current_dir = tempdir()?;\n\n//         assert!(subcommand_new()\n//             .arg(project_dir)\n//             .current_dir(&current_dir)\n//             .status()\n//             .is_ok());\n\n//         let project_path = current_dir.path().join(project_dir);\n//         let cargo_toml_path = get_cargo_toml_path(&project_path);\n//         assert!(project_path.exists());\n//         assert!(cargo_toml_path.exists());\n//         assert_eq!(get_project_name(&cargo_toml_path)?, project_name);\n//         Ok(())\n//     }\n\n//     #[test]\n//     fn test_subcommand_new_with_2_dir_path() -> Result<()> {\n//         let project_dir = \"a/b\";\n//         let project_name = \"b\";\n\n//         let current_dir = tempdir()?;\n\n//         assert!(subcommand_new()\n//             .arg(project_dir)\n//             .current_dir(&current_dir)\n//             .status()\n//             .is_ok());\n\n//         let project_path = current_dir.path().join(project_dir);\n//         let cargo_toml_path = get_cargo_toml_path(&project_path);\n//         assert!(project_path.exists());\n//         assert!(cargo_toml_path.exists());\n//         assert_eq!(get_project_name(&cargo_toml_path)?, project_name);\n//         Ok(())\n//     }\n\n//     #[test]\n//     fn test_subcommand_new_with_dot_path_and_custom_name() -> Result<()> {\n//         let project_dir = \"dir\";\n//         let project_name = \"project\";\n\n//         let temp_dir = tempdir()?;\n//         // Make current dir's name deterministic.\n//         let current_dir = temp_dir.path().join(project_dir);\n//         create_dir_all(&current_dir)?;\n//         let project_path = &current_dir;\n//         assert!(project_path.exists());\n\n//         assert!(subcommand_new()\n//             .arg(\"--name\")\n//             .arg(project_name)\n//             .arg(\".\")\n//             .current_dir(&current_dir)\n//             .status()\n//             .is_ok());\n\n//         let cargo_toml_path = get_cargo_toml_path(project_path);\n//         assert!(cargo_toml_path.exists());\n//         assert_eq!(get_project_name(&cargo_toml_path)?, project_name);\n//         Ok(())\n//     }\n\n//     #[test]\n//     fn test_subcommand_new_with_1_dir_path_and_custom_name() -> Result<()> {\n//         let project_dir = \"dir\";\n//         let project_name = \"project\";\n\n//         let current_dir = tempdir()?;\n\n//         assert!(subcommand_new()\n//             .arg(project_dir)\n//             .arg(\"--name\")\n//             .arg(project_name)\n//             .current_dir(&current_dir)\n//             .status()\n//             .is_ok());\n\n//         let project_path = current_dir.path().join(project_dir);\n//         let cargo_toml_path = get_cargo_toml_path(&project_path);\n//         assert!(project_path.exists());\n//         assert!(cargo_toml_path.exists());\n//         assert_eq!(get_project_name(&cargo_toml_path)?, project_name);\n//         Ok(())\n//     }\n\n//     #[test]\n//     fn test_subcommand_new_with_2_dir_path_and_custom_name() -> Result<()> {\n//         let project_dir = \"a/b\";\n//         let project_name = \"project\";\n\n//         let current_dir = tempdir()?;\n\n//         assert!(subcommand_new()\n//             .arg(project_dir)\n//             .arg(\"--name\")\n//             .arg(project_name)\n//             .current_dir(&current_dir)\n//             .status()\n//             .is_ok());\n\n//         let project_path = current_dir.path().join(project_dir);\n//         let cargo_toml_path = get_cargo_toml_path(&project_path);\n//         assert!(project_path.exists());\n//         assert!(cargo_toml_path.exists());\n//         assert_eq!(get_project_name(&cargo_toml_path)?, project_name);\n//         Ok(())\n//     }\n// }\n"
  },
  {
    "path": "packages/cli/src/cli/doctor.rs",
    "content": "use super::*;\nuse crate::{Result, Workspace};\nuse anyhow::{bail, Context};\nuse itertools::Itertools;\n\n/// Perform a system analysis to verify the system install is working correctly.\n#[derive(Clone, Debug, Parser)]\npub(crate) struct Doctor {}\n\nimpl Doctor {\n    pub async fn doctor(self) -> Result<StructuredOutput> {\n        let mut rustc_version = \"not found\".to_string();\n        let mut rustc_sysroot = \"not found\".to_string();\n        let mut rustlib = PathBuf::from(\".\");\n        if let Ok(r) = Workspace::get_rustc_sysroot().await {\n            rustlib = PathBuf::from(r.as_str()).join(\"lib\").join(\"rustlib\");\n            rustc_sysroot = r;\n        }\n        if let Ok(r) = Workspace::get_rustc_version().await {\n            rustc_version = r;\n        }\n\n        // wasm-opt\n        let wasm_opt_location = crate::wasm_opt::installed_location();\n        let wasm_opt_message = match wasm_opt_location.clone() {\n            Some(path) => path.to_string_lossy().to_string(),\n            None => \"not installed\".into(),\n        };\n\n        // wasm-bindgen\n        let mut wbg_version_msg = \"automatically managed\".to_string();\n        let mut wasm_bindgen_location = \"automatically managed\".to_string();\n        if let Ok(workspace) = Workspace::current().await {\n            let wbg_version = workspace.wasm_bindgen_version();\n            if let Some(vers) = &wbg_version {\n                wbg_version_msg = vers.to_string();\n\n                wasm_bindgen_location =\n                    match crate::wasm_bindgen::WasmBindgen::new(vers).get_binary_path() {\n                        Ok(path) => path.to_string_lossy().to_string(),\n                        Err(err) => err.to_string().lines().join(\"\"),\n                    };\n            }\n        }\n\n        // extensions\n        fn has_dioxus_ext(editor_dir: &str) -> anyhow::Result<PathBuf> {\n            let home = dirs::home_dir().context(\"no home dir\")?;\n            let exts = home.join(editor_dir).join(\"extensions\");\n            for dir in exts.read_dir()?.flatten() {\n                if dir\n                    .file_name()\n                    .to_string_lossy()\n                    .contains(\"dioxuslabs.dioxus-\")\n                {\n                    return Ok(dir.path());\n                }\n            }\n\n            bail!(\"not found\")\n        }\n\n        // Editors\n        let vscode_ext = has_dioxus_ext(\".vscode\");\n        let vscode_ext_msg = match vscode_ext.as_ref() {\n            Ok(path) => path.to_string_lossy().to_string(),\n            Err(_) => \"not found\".to_string(),\n        };\n        let vscode_insiders_ext = has_dioxus_ext(\".vscode-insiders\");\n        let vscode_insiders_ext_msg = match vscode_insiders_ext.as_ref() {\n            Ok(path) => path.to_string_lossy().to_string(),\n            Err(_) => \"not found\".to_string(),\n        };\n        let cursor_ext = has_dioxus_ext(\".cursor\");\n        let cursor_ext_msg = match cursor_ext.as_ref() {\n            Ok(path) => path.to_string_lossy().to_string(),\n            Err(_) => \"not found\".to_string(),\n        };\n\n        // Tailwind\n        let mut tailwindcss = \"not found\".to_string();\n        if let Ok(path) = crate::tailwind::TailwindCli::v3().get_binary_path() {\n            tailwindcss = path.display().to_string();\n        }\n        if let Ok(path) = crate::tailwind::TailwindCli::v4().get_binary_path() {\n            tailwindcss = path.display().to_string();\n        }\n\n        let mut adb = \"not found\".to_string();\n        let mut ndk = \"not found\".to_string();\n        let mut sdk = \"not found\".to_string();\n        let mut java_home = \"not found\".to_string();\n        let mut emulator = \"not found\".to_string();\n        if let Some(rf) = crate::build::get_android_tools() {\n            if rf.adb.exists() {\n                adb = rf.adb.display().to_string();\n            }\n            if rf.ndk.exists() {\n                ndk = rf.ndk.display().to_string();\n            }\n            if let Some(jh) = rf.java_home.as_ref() {\n                java_home = jh.display().to_string();\n            }\n            if rf.sdk().exists() {\n                sdk = rf.sdk().display().to_string();\n            }\n            if let Some(jh) = rf.java_home.as_ref() {\n                java_home = jh.display().to_string();\n            }\n            if rf.emulator().exists() {\n                emulator = rf.emulator().display().to_string();\n            }\n        };\n\n        let mut simulator_location = \"not found\".to_string();\n        let mut xcode_install = \"not found\".to_string();\n        if let Some(xcode) = Workspace::get_xcode_path().await {\n            let sim_location = xcode.join(\"Applications\").join(\"Simulator.app\");\n            if sim_location.exists() {\n                simulator_location = sim_location.display().to_string();\n            }\n            if xcode.exists() {\n                xcode_install = xcode.display().to_string();\n            }\n        }\n\n        let mut security_cli_path = \"not found\".to_string();\n        let mut codesign_path = \"not found\".to_string();\n        let mut xcode_select_path = \"not found\".to_string();\n        let mut xcrun_path = \"not found\".to_string();\n        let mut ranlib_path = \"not found\".to_string();\n        if let Ok(path) = which::which(\"security\") {\n            security_cli_path = path.display().to_string();\n        }\n        if let Ok(path) = which::which(\"codesign\") {\n            codesign_path = path.display().to_string();\n        }\n        if let Ok(path) = which::which(\"xcode-select\") {\n            xcode_select_path = path.display().to_string();\n        }\n        if let Ok(path) = which::which(\"xcrun\") {\n            xcrun_path = path.display().to_string();\n        }\n        if let Some(path) = Workspace::select_ranlib() {\n            ranlib_path = path.display().to_string();\n        }\n\n        // toolchains\n        let mut has_wasm32_unknown_unknown = \"❌\";\n        let mut has_aarch64_linux_android = \"❌\";\n        let mut has_i686_linux_android = \"❌\";\n        let mut has_armv7_linux_androideabi = \"❌\";\n        let mut has_x86_64_linux_android = \"❌\";\n        let mut has_x86_64_apple_ios = \"❌\";\n        let mut has_aarch64_apple_ios = \"❌\";\n        let mut has_aarch64_apple_ios_sim = \"❌\";\n        let mut has_aarch64_apple_darwin = \"❌\";\n        if rustlib.join(\"wasm32-unknown-unknown\").exists() {\n            has_wasm32_unknown_unknown = \"✅\";\n        }\n        if rustlib.join(\"aarch64-linux-android\").exists() {\n            has_aarch64_linux_android = \"✅\";\n        }\n        if rustlib.join(\"i686-linux-android\").exists() {\n            has_i686_linux_android = \"✅\";\n        }\n        if rustlib.join(\"armv7-linux-androideabi\").exists() {\n            has_armv7_linux_androideabi = \"✅\";\n        }\n        if rustlib.join(\"x86_64-linux-android\").exists() {\n            has_x86_64_linux_android = \"✅\";\n        }\n        if rustlib.join(\"x86_64-apple-ios\").exists() {\n            has_x86_64_apple_ios = \"✅\";\n        }\n        if rustlib.join(\"aarch64-apple-ios\").exists() {\n            has_aarch64_apple_ios = \"✅\";\n        }\n        if rustlib.join(\"aarch64-apple-ios-sim\").exists() {\n            has_aarch64_apple_ios_sim = \"✅\";\n        }\n        if rustlib.join(\"aarch64-apple-darwin\").exists() {\n            has_aarch64_apple_darwin = \"✅\";\n        }\n\n        // Rust tool paths\n        let mut rustc_path = \"not found\".to_string();\n        let mut cargo_path = \"not found\".to_string();\n        let mut cc_path = \"not found\".to_string();\n        if let Ok(path) = which::which(\"rustc\") {\n            rustc_path = path.display().to_string();\n        }\n        if let Ok(path) = which::which(\"cargo\") {\n            cargo_path = path.display().to_string();\n        }\n        if let Ok(path) = which::which(\"cc\") {\n            cc_path = path.display().to_string();\n        }\n\n        // Things to know\n        // - current rust version and rust-related things\n        // - installed toolchains\n        // -\n        use crate::styles::*;\n        println!(\n            r#\"{LINK_STYLE}Setup{LINK_STYLE:#}\n {GLOW_STYLE}Web{GLOW_STYLE:#}: wasm-bindgen, wasm-opt, and TailwindCSS are downloaded automatically\n {GLOW_STYLE}iOS{GLOW_STYLE:#}: Install iOS SDK and developer tools and through XCode\n {GLOW_STYLE}Android{GLOW_STYLE:#}: Install Android Studio, NDK, and then set ANDROID_HOME and ANDROID_NDK_HOME\n {GLOW_STYLE}macOS{GLOW_STYLE:#}: all tools should be installed by default\n {GLOW_STYLE}Windows{GLOW_STYLE:#}: install the webview2 binary\n {GLOW_STYLE}Linux{GLOW_STYLE:#}: Install libwebkit2gtk-4.1-dev libgtk-3-dev libasound2-dev libudev-dev libayatana-appindicator3-dev libxdo-dev libglib2.0-dev\n {GLOW_STYLE}nix{GLOW_STYLE:#}: Make sure all tools are in your path (codesign, ld, etc.)\n\n{LINK_STYLE}Rust{LINK_STYLE:#}\n Rustc version: {HINT_STYLE}{rustc_version}{HINT_STYLE:#}\n Rustc sysroot: {HINT_STYLE}{rustc_sysroot}{HINT_STYLE:#}\n Rustc path: {HINT_STYLE}{rustc_path}{HINT_STYLE:#}\n Cargo path: {HINT_STYLE}{cargo_path}{HINT_STYLE:#}\n cc path: {HINT_STYLE}{cc_path}{HINT_STYLE:#}\n\n{LINK_STYLE}Devtools{LINK_STYLE:#}\n VSCode Extension: {HINT_STYLE}{vscode_ext_msg}{HINT_STYLE:#}\n VSCode-Insiders Extension: {HINT_STYLE}{vscode_insiders_ext_msg}{HINT_STYLE:#}\n Cursor Extension: {HINT_STYLE}{cursor_ext_msg}{HINT_STYLE:#}\n TailwindCSS: {HINT_STYLE}{tailwindcss}{HINT_STYLE:#}\n\n{LINK_STYLE}Web{LINK_STYLE:#}\n wasm-opt: {HINT_STYLE}{wasm_opt_message}{HINT_STYLE:#}\n wasm-bindgen: {HINT_STYLE}{wasm_bindgen_location}{HINT_STYLE:#}\n wasm-bindgen version: {HINT_STYLE}{wbg_version_msg}{HINT_STYLE:#}\n\n{LINK_STYLE}iOS/macOS{LINK_STYLE:#}\n XCode: {HINT_STYLE}{xcode_install}{HINT_STYLE:#}\n Simulator: {HINT_STYLE}{simulator_location}{HINT_STYLE:#}\n Security CLI: {HINT_STYLE}{security_cli_path}{HINT_STYLE:#}\n Codesign CII: {HINT_STYLE}{codesign_path}{HINT_STYLE:#}\n xcode-select: {HINT_STYLE}{xcode_select_path}{HINT_STYLE:#}\n xcrun: {HINT_STYLE}{xcrun_path}{HINT_STYLE:#}\n ranlib: {HINT_STYLE}{ranlib_path}{HINT_STYLE:#}\n\n{LINK_STYLE}Android{LINK_STYLE:#}\n sdk: {HINT_STYLE}{sdk}{HINT_STYLE:#}\n ndk: {HINT_STYLE}{ndk}{HINT_STYLE:#}\n adb: {HINT_STYLE}{adb}{HINT_STYLE:#}\n emulator: {HINT_STYLE}{emulator}{HINT_STYLE:#}\n java_home: {HINT_STYLE}{java_home}{HINT_STYLE:#}\n\n{LINK_STYLE}Toolchains{LINK_STYLE:#}\n {HINT_STYLE}{has_wasm32_unknown_unknown}{HINT_STYLE:#} wasm32-unknown-unknown {HINT_STYLE}(web){HINT_STYLE:#}\n {HINT_STYLE}{has_aarch64_linux_android}{HINT_STYLE:#} aarch64-linux-android {HINT_STYLE}(android){HINT_STYLE:#}\n {HINT_STYLE}{has_i686_linux_android}{HINT_STYLE:#} i686-linux-android {HINT_STYLE}(android){HINT_STYLE:#}\n {HINT_STYLE}{has_armv7_linux_androideabi}{HINT_STYLE:#} armv7-linux-androideabi {HINT_STYLE}(android){HINT_STYLE:#}\n {HINT_STYLE}{has_x86_64_linux_android}{HINT_STYLE:#} x86_64-linux-android {HINT_STYLE}(android){HINT_STYLE:#}\n {HINT_STYLE}{has_x86_64_apple_ios}{HINT_STYLE:#} x86_64-apple-ios {HINT_STYLE}(iOS){HINT_STYLE:#}\n {HINT_STYLE}{has_aarch64_apple_ios}{HINT_STYLE:#} aarch64-apple-ios {HINT_STYLE}(iOS){HINT_STYLE:#}\n {HINT_STYLE}{has_aarch64_apple_ios_sim}{HINT_STYLE:#} aarch64-apple-ios-sim {HINT_STYLE}(iOS){HINT_STYLE:#}\n {HINT_STYLE}{has_aarch64_apple_darwin}{HINT_STYLE:#} aarch64-apple-darwin {HINT_STYLE}(iOS){HINT_STYLE:#}\n\nGet help: {LINK_STYLE}https://discord.gg/XgGxMSkvUM{LINK_STYLE:#}\nMore info: {LINK_STYLE}https://dioxuslabs.com/learn/0.7/{LINK_STYLE:#}\n\"#\n        );\n\n        Ok(StructuredOutput::Success)\n    }\n}\n"
  },
  {
    "path": "packages/cli/src/cli/hotpatch.rs",
    "content": "use crate::{\n    platform_override::CommandWithPlatformOverrides, AppBuilder, BuildArgs, BuildId, BuildMode,\n    HotpatchModuleCache, Result, StructuredOutput,\n};\nuse anyhow::Context;\nuse clap::Parser;\nuse dioxus_dx_wire_format::StructuredBuildArtifacts;\nuse std::sync::Arc;\nuse std::{collections::HashMap, io::Read};\n\nconst HELP_HEADING: &str = \"Hotpatching a binary\";\n\n/// Patches a single binary, but takes the same arguments as a `cargo build` and the serialized output from `dx build --fat-binary` as input.\n///\n/// This is intended to be used with something like `dx build --fat-binary >> output.json` and then\n/// `cat output.json | dx hotpatch --aslr-reference 0x12345678` to produce a hotpatched binary.\n///\n/// By default, patches the client, but you can set patch_server to true to patch the server instead.\n#[derive(Clone, Debug, Parser)]\npub struct HotpatchTip {\n    /// Should we patch the server or the client? False = client, True = server\n    #[clap(long, num_args = 0..=1, default_missing_value=\"true\", help_heading = HELP_HEADING)]\n    pub patch_server: Option<bool>,\n\n    /// The ASLR reference of the running app being patched. Used to generate sensible offsets for patched code.\n    #[clap(long, help_heading = HELP_HEADING)]\n    pub aslr_reference: u64,\n\n    #[clap(flatten)]\n    pub build_args: CommandWithPlatformOverrides<BuildArgs>,\n}\n\nimpl HotpatchTip {\n    pub async fn run(self) -> Result<StructuredOutput> {\n        let targets = self.build_args.into_targets().await?;\n\n        let patch_server = self.patch_server.unwrap_or(false);\n\n        let build_id = if patch_server {\n            BuildId::SECONDARY\n        } else {\n            BuildId::PRIMARY\n        };\n\n        // Select which target to patch\n        let request = if patch_server {\n            targets.server.as_ref().context(\"No server to patch!\")?\n        } else {\n            &targets.client\n        };\n\n        let mut serialized_artifacts = String::new();\n        std::io::stdin()\n            .lock()\n            .read_to_string(&mut serialized_artifacts)\n            .context(\"Failed to read serialized build artifacts from stdin\")?;\n        let structured_build_artifacts =\n            serde_json::from_str::<StructuredBuildArtifacts>(&serialized_artifacts)\n                .context(\"Failed to parse structured build artifacts\")?;\n\n        let StructuredBuildArtifacts {\n            exe,\n            rustc_args,\n            rustc_envs,\n            link_args,\n            ..\n        } = structured_build_artifacts;\n\n        // todo: loading this cache over and over defeats the purpose of a cache\n        //       consider a shared-mem approach or a binary serializer? something like arrow / parquet / bincode?\n        let cache = Arc::new(HotpatchModuleCache::new(&exe, &request.triple)?);\n\n        let tip_crate_name = request.main_target.replace('-', \"_\");\n        let mut workspace_rustc_args = HashMap::new();\n        workspace_rustc_args.insert(\n            format!(\"{tip_crate_name}.bin\"),\n            crate::RustcArgs {\n                args: rustc_args,\n                envs: rustc_envs,\n                link_args,\n            },\n        );\n\n        let mode = BuildMode::Thin {\n            workspace_rustc_args,\n            changed_files: vec![],\n            changed_crates: vec![],\n            modified_crates: std::collections::HashSet::new(),\n            aslr_reference: self.aslr_reference,\n            cache: cache.clone(),\n            object_cache: crate::ObjectCache::new(&request.session_cache_dir()),\n        };\n\n        let artifacts = AppBuilder::started(request, mode, build_id)?\n            .finish_build()\n            .await?;\n        let patch_exe = request.patch_exe(artifacts.time_start);\n\n        Ok(StructuredOutput::Hotpatch {\n            jump_table: request.create_jump_table(&patch_exe, &cache)?,\n            artifacts: artifacts.into_structured_output(),\n        })\n    }\n}\n"
  },
  {
    "path": "packages/cli/src/cli/init.rs",
    "content": "use super::*;\nuse cargo_generate::{GenerateArgs, TemplatePath, Vcs};\n\n#[derive(Clone, Debug, Default, Deserialize, Parser)]\n#[clap(name = \"init\")]\npub struct Init {\n    /// Create a new Dioxus project at PATH\n    #[arg(default_value = \".\")]\n    pub path: PathBuf,\n\n    /// Project name. Defaults to directory name\n    #[arg(short, long)]\n    pub name: Option<String>,\n\n    /// Template path\n    #[clap(short, long)]\n    pub template: Option<String>,\n\n    /// Branch to select when using `template` from a git repository.\n    /// Mutually exclusive with: `--revision`, `--tag`.\n    #[clap(long, conflicts_with_all([\"revision\", \"tag\"]))]\n    pub branch: Option<String>,\n\n    /// A commit hash to select when using `template` from a git repository.\n    /// Mutually exclusive with: `--branch`, `--tag`.\n    #[clap(long, conflicts_with_all([\"branch\", \"tag\"]))]\n    pub revision: Option<String>,\n\n    /// Tag to select when using `template` from a git repository.\n    /// Mutually exclusive with: `--branch`, `--revision`.\n    #[clap(long, conflicts_with_all([\"branch\", \"revision\"]))]\n    pub tag: Option<String>,\n\n    /// Specify a sub-template within the template repository to be used as the actual template\n    #[clap(long)]\n    pub subtemplate: Option<String>,\n\n    /// Pass `<option>=<value>` for the used template (e.g., `foo=bar`)\n    #[clap(short, long)]\n    pub option: Vec<String>,\n\n    /// Skip user interaction by using the default values for the used template.\n    /// Default values can be overridden with `--option`\n    #[clap(short, long)]\n    pub yes: bool,\n\n    /// Specify the VCS used to initialize the generated template.\n    /// Options: `git`, `none`.\n    #[arg(long, value_parser)]\n    pub vcs: Option<Vcs>,\n}\n\nimpl Init {\n    pub async fn init(mut self) -> Result<StructuredOutput> {\n        // Project name defaults to directory name.\n        if self.name.is_none() {\n            self.name = Some(create::name_from_path(&self.path)?);\n        }\n\n        // Perform a connectivity check so we just don't it around doing nothing if there's a network error\n        if self.template.is_none() {\n            create::check_connectivity().await?;\n        }\n\n        // If no template is specified, use the default one and set the branch to the latest release.\n        create::resolve_template_and_branch(&mut self.template, &mut self.branch);\n\n        // cargo-generate requires the path to be created first.\n        std::fs::create_dir_all(&self.path)?;\n\n        let args = GenerateArgs {\n            define: self.option,\n            destination: Some(self.path),\n            init: true,\n            name: self.name,\n            silent: self.yes,\n            vcs: self.vcs,\n            template_path: TemplatePath {\n                auto_path: self.template,\n                branch: self.branch,\n                revision: self.revision,\n                subfolder: self.subtemplate,\n                tag: self.tag,\n                ..Default::default()\n            },\n            ..Default::default()\n        };\n\n        let path = cargo_generate::generate(args)?;\n        _ = create::post_create(&path, &self.vcs.unwrap_or(Vcs::Git));\n        Ok(StructuredOutput::Success)\n    }\n}\n\n// todo: re-enable these tests with better parallelization\n//\n// #[cfg(test)]\n// mod tests {\n//     use std::{fs::create_dir_all, process::Command};\n//     use tempfile::tempdir;\n\n//     use super::create::tests::*;\n\n//     // Note: tests below (at least 6 of them) were written to mainly test\n//     // correctness of project's directory and its name, because previously it\n//     // was broken and tests bring a peace of mind. And also so that I don't have\n//     // to run my local hand-made tests every time.\n\n//     fn subcommand_init() -> Command {\n//         subcommand(\"init\")\n//     }\n\n//     #[test]\n//     fn test_subcommand_init_with_default_path() -> Result<()> {\n//         let project_dir = \"dir\";\n//         let project_name = project_dir;\n\n//         let temp_dir = tempdir()?;\n//         // Make current dir's name deterministic.\n//         let current_dir = temp_dir.path().join(project_dir);\n//         create_dir_all(&current_dir)?;\n//         let project_path = &current_dir;\n//         assert!(project_path.exists());\n\n//         assert!(subcommand_init().current_dir(&current_dir).status().is_ok());\n\n//         let cargo_toml_path = get_cargo_toml_path(project_path);\n//         assert!(cargo_toml_path.exists());\n//         assert_eq!(get_project_name(&cargo_toml_path)?, project_name);\n//         Ok(())\n//     }\n\n//     #[test]\n//     fn test_subcommand_init_with_1_dir_path() -> Result<()> {\n//         let project_dir = \"dir\";\n//         let project_name = project_dir;\n\n//         let current_dir = tempdir()?;\n\n//         assert!(subcommand_init()\n//             .arg(project_dir)\n//             .current_dir(&current_dir)\n//             .status()\n//             .is_ok());\n\n//         let project_path = current_dir.path().join(project_dir);\n//         let cargo_toml_path = get_cargo_toml_path(&project_path);\n//         assert!(project_path.exists());\n//         assert!(cargo_toml_path.exists());\n//         assert_eq!(get_project_name(&cargo_toml_path)?, project_name);\n//         Ok(())\n//     }\n\n//     #[test]\n//     fn test_subcommand_init_with_2_dir_path() -> Result<()> {\n//         let project_dir = \"a/b\";\n//         let project_name = \"b\";\n\n//         let current_dir = tempdir()?;\n\n//         assert!(subcommand_init()\n//             .arg(project_dir)\n//             .current_dir(&current_dir)\n//             .status()\n//             .is_ok());\n\n//         let project_path = current_dir.path().join(project_dir);\n//         let cargo_toml_path = get_cargo_toml_path(&project_path);\n//         assert!(project_path.exists());\n//         assert!(cargo_toml_path.exists());\n//         assert_eq!(get_project_name(&cargo_toml_path)?, project_name);\n//         Ok(())\n//     }\n\n//     #[test]\n//     fn test_subcommand_init_with_default_path_and_custom_name() -> Result<()> {\n//         let project_dir = \"dir\";\n//         let project_name = \"project\";\n\n//         let temp_dir = tempdir()?;\n//         // Make current dir's name deterministic.\n//         let current_dir = temp_dir.path().join(project_dir);\n//         create_dir_all(&current_dir)?;\n//         let project_path = &current_dir;\n//         assert!(project_path.exists());\n\n//         assert!(subcommand_init()\n//             .arg(\"--name\")\n//             .arg(project_name)\n//             .current_dir(&current_dir)\n//             .status()\n//             .is_ok());\n\n//         let cargo_toml_path = get_cargo_toml_path(project_path);\n//         assert!(cargo_toml_path.exists());\n//         assert_eq!(get_project_name(&cargo_toml_path)?, project_name);\n//         Ok(())\n//     }\n\n//     #[test]\n//     fn test_subcommand_init_with_1_dir_path_and_custom_name() -> Result<()> {\n//         let project_dir = \"dir\";\n//         let project_name = \"project\";\n\n//         let current_dir = tempdir()?;\n\n//         assert!(subcommand_init()\n//             .arg(project_dir)\n//             .arg(\"--name\")\n//             .arg(project_name)\n//             .current_dir(&current_dir)\n//             .status()\n//             .is_ok());\n\n//         let project_path = current_dir.path().join(project_dir);\n//         let cargo_toml_path = get_cargo_toml_path(&project_path);\n//         assert!(project_path.exists());\n//         assert!(cargo_toml_path.exists());\n//         assert_eq!(get_project_name(&cargo_toml_path)?, project_name);\n//         Ok(())\n//     }\n\n//     #[test]\n//     fn test_subcommand_init_with_2_dir_path_and_custom_name() -> Result<()> {\n//         let project_dir = \"a/b\";\n//         let project_name = \"project\";\n\n//         let current_dir = tempdir()?;\n\n//         assert!(subcommand_init()\n//             .arg(project_dir)\n//             .arg(\"--name\")\n//             .arg(project_name)\n//             .current_dir(&current_dir)\n//             .status()\n//             .is_ok());\n\n//         let project_path = current_dir.path().join(project_dir);\n//         let cargo_toml_path = get_cargo_toml_path(&project_path);\n//         assert!(project_path.exists());\n//         assert!(cargo_toml_path.exists());\n//         assert_eq!(get_project_name(&cargo_toml_path)?, project_name);\n//         Ok(())\n//     }\n// }\n"
  },
  {
    "path": "packages/cli/src/cli/link.rs",
    "content": "use crate::Result;\nuse anyhow::{bail, Context};\nuse serde::{Deserialize, Serialize};\nuse std::{borrow::Cow, ffi::OsString, path::PathBuf, process::ExitCode};\nuse target_lexicon::Triple;\n\n/// `dx` can act as a linker in a few scenarios. Note that we don't *actually* implement the linker logic,\n/// instead just proxying to a specified linker (or not linking at all!).\n///\n/// This comes in two flavors:\n/// --------------------------\n/// - `BaseLink`: We are linking dependencies and want to dynamically select the linker from the environment.\n///               This is mostly implemented for Android where the linker is selected in part by the\n///               device connected over ADB which can not be determined by .cargo/Config.toml.\n///               We implemented this because previous setups like cargo mobile required a hard-coded\n///               linker path in your project which does not work in team-based setups.\n///\n/// - `NoLink`: We are not linking at all, and instead deferring our linking to the driving process,\n///             usually being `dx` itself. In this case, we are just writing the linker args to a file\n///             and then outputting a dummy object file to satisfy the linker. This is generally used\n///             by the binary patching engine since we need to actually do \"real linker logic\" like\n///             traversing object files and satisfying missing symbols. That process is *much* easier\n///             to do in the driving host process when we have all the information available. Unfortunately,\n///             rustc doesn't provide a \"real\" way of granularly stepping through the compile process\n///             so this is basically a hack.\n///\n/// We use \"BaseLink\" when a linker is specified, and \"NoLink\" when it is not. Both generate a resulting\n/// object file.\n\n#[derive(Debug)]\npub struct LinkAction {\n    pub linker: Option<PathBuf>,\n    pub triple: Triple,\n    pub link_args_file: PathBuf,\n    pub link_err_file: PathBuf,\n}\n\n/// The linker flavor to use. This influences the argument style that gets passed to the linker.\n/// We're imitating the rustc linker flavors here.\n///\n/// <https://doc.rust-lang.org/beta/nightly-rustc/rustc_target/spec/enum.LinkerFlavor.html>\n#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]\npub enum LinkerFlavor {\n    Gnu,\n    Darwin,\n    WasmLld,\n    Msvc,\n    Unsupported, // a catch-all for unsupported linkers, usually the stripped-down unix ones\n}\n\nimpl LinkAction {\n    const DX_LINK_ARG: &str = \"DX_LINK\";\n    const DX_ARGS_FILE: &str = \"DX_LINK_ARGS_FILE\";\n    const DX_ERR_FILE: &str = \"DX_LINK_ERR_FILE\";\n    const DX_LINK_TRIPLE: &str = \"DX_LINK_TRIPLE\";\n    const DX_LINK_CUSTOM_LINKER: &str = \"DX_LINK_CUSTOM_LINKER\";\n\n    /// Should we write the input arguments to a file (aka act as a linker subprocess)?\n    ///\n    /// Just check if the magic env var is set\n    pub(crate) fn from_env() -> Option<Self> {\n        if std::env::var(Self::DX_LINK_ARG).is_err() {\n            return None;\n        }\n\n        Some(Self {\n            linker: std::env::var(Self::DX_LINK_CUSTOM_LINKER)\n                .ok()\n                .map(PathBuf::from),\n            link_args_file: std::env::var(Self::DX_ARGS_FILE)\n                .expect(\"Linker args file not set\")\n                .into(),\n            link_err_file: std::env::var(Self::DX_ERR_FILE)\n                .expect(\"Linker error file not set\")\n                .into(),\n            triple: std::env::var(Self::DX_LINK_TRIPLE)\n                .expect(\"Linker triple not set\")\n                .parse()\n                .expect(\"Failed to parse linker triple\"),\n        })\n    }\n\n    pub(crate) fn write_env_vars(\n        &self,\n        env_vars: &mut Vec<(Cow<'static, str>, OsString)>,\n    ) -> Result<()> {\n        env_vars.push((Self::DX_LINK_ARG.into(), \"1\".into()));\n        env_vars.push((\n            Self::DX_ARGS_FILE.into(),\n            dunce::canonicalize(&self.link_args_file)?.into_os_string(),\n        ));\n        env_vars.push((\n            Self::DX_ERR_FILE.into(),\n            dunce::canonicalize(&self.link_err_file)?.into_os_string(),\n        ));\n        env_vars.push((Self::DX_LINK_TRIPLE.into(), self.triple.to_string().into()));\n        if let Some(linker) = &self.linker {\n            env_vars.push((\n                Self::DX_LINK_CUSTOM_LINKER.into(),\n                dunce::canonicalize(linker)\n                    .unwrap_or(linker.clone())\n                    .into_os_string(),\n            ));\n        }\n\n        Ok(())\n    }\n\n    pub(crate) fn run_link(self) -> ExitCode {\n        let link_err_file = self.link_err_file.clone();\n        if let Err(err) = self.run_link_inner() {\n            eprintln!(\"Linker error: {err}\");\n\n            // If we failed to run the linker, we need to write the error to the file\n            // so that the main process can read it.\n            _ = std::fs::create_dir_all(link_err_file.parent().unwrap());\n            _ = std::fs::write(link_err_file, format!(\"Linker error: {err}\"));\n\n            return ExitCode::FAILURE;\n        }\n\n        ExitCode::SUCCESS\n    }\n\n    /// Write the incoming linker args to a file\n    ///\n    /// The file will be given by the dx-magic-link-arg env var itself, so we use\n    /// it both for determining if we should act as a linker and the for the file name itself.\n    fn run_link_inner(self) -> Result<()> {\n        let args: Vec<_> = std::env::args().collect();\n        if args.is_empty() {\n            return Ok(());\n        }\n\n        let mut args = get_actual_linker_args_excluding_program_name(args);\n\n        if self.triple.environment == target_lexicon::Environment::Android {\n            args.retain(|arg| !arg.ends_with(\".lib\"));\n        }\n\n        // Write the linker args to a file for the main process to read\n        // todo: we might need to encode these as escaped shell words in case newlines are passed\n        std::fs::write(&self.link_args_file, args.join(\"\\n\"))?;\n\n        // If there's a linker specified, we use that. Otherwise, we write a dummy object file to satisfy\n        // any post-processing steps that rustc does.\n        match self.linker {\n            Some(linker) => {\n                let mut cmd = std::process::Command::new(linker);\n                match cfg!(target_os = \"windows\") {\n                    true => cmd.arg(format!(\"@{}\", &self.link_args_file.display())),\n                    false => cmd.args(args),\n                };\n                let res = cmd.output().expect(\"Failed to run linker\");\n\n                if !res.status.success() {\n                    bail!(\n                        \"{}\\n{}\",\n                        String::from_utf8_lossy(&res.stdout),\n                        String::from_utf8_lossy(&res.stderr)\n                    );\n                }\n                if !res.stderr.is_empty() || !res.stdout.is_empty() {\n                    // Write linker warnings to file so that the main process can read them.\n                    _ = std::fs::create_dir_all(self.link_err_file.parent().unwrap());\n                    _ = std::fs::write(\n                        self.link_err_file,\n                        format!(\n                            \"Linker warnings: {}\\n{}\",\n                            String::from_utf8_lossy(&res.stdout),\n                            String::from_utf8_lossy(&res.stderr)\n                        ),\n                    );\n                }\n            }\n            None => {\n                // Extract the out path - we're going to write a dummy object file to satisfy the linker\n                let out_file: PathBuf = match self.triple.operating_system {\n                    target_lexicon::OperatingSystem::Windows => {\n                        let out_arg = args.iter().find(|arg| arg.starts_with(\"/OUT\")).unwrap();\n                        out_arg.trim_start_matches(\"/OUT:\").to_string().into()\n                    }\n                    _ => {\n                        let out = args.iter().position(|arg| arg == \"-o\").unwrap();\n                        args[out + 1].clone().into()\n                    }\n                };\n\n                // This creates an object file that satisfies rust's use of llvm-objcopy\n                //\n                // I'd rather we *not* do this and instead generate a truly linked file (and then delete it) but\n                // this at least lets us delay linking until the host compiler is ready.\n                //\n                // This is because our host compiler is a stateful server and not a stateless linker.\n                //\n                // todo(jon): do we use Triple::host or the target triple? I think I ran into issues\n                // using the target triple, hence the use of \"host\" but it might not even matter?\n                let triple = Triple::host();\n                let format = match triple.binary_format {\n                    target_lexicon::BinaryFormat::Elf => object::BinaryFormat::Elf,\n                    target_lexicon::BinaryFormat::Coff => object::BinaryFormat::Coff,\n                    target_lexicon::BinaryFormat::Macho => object::BinaryFormat::MachO,\n                    target_lexicon::BinaryFormat::Wasm => object::BinaryFormat::Wasm,\n                    target_lexicon::BinaryFormat::Xcoff => object::BinaryFormat::Xcoff,\n                    target_lexicon::BinaryFormat::Unknown => unimplemented!(),\n                    _ => unimplemented!(\"Binary format not supported\"),\n                };\n\n                let arch = match triple.architecture {\n                    target_lexicon::Architecture::Wasm32 => object::Architecture::Wasm32,\n                    target_lexicon::Architecture::Wasm64 => object::Architecture::Wasm64,\n                    target_lexicon::Architecture::X86_64 => object::Architecture::X86_64,\n                    target_lexicon::Architecture::Arm(_) => object::Architecture::Arm,\n                    target_lexicon::Architecture::Aarch64(_) => object::Architecture::Aarch64,\n                    target_lexicon::Architecture::LoongArch64 => object::Architecture::LoongArch64,\n                    target_lexicon::Architecture::Unknown => object::Architecture::Unknown,\n                    _ => unimplemented!(\"Architecture not supported\"),\n                };\n\n                let endian = match triple.endianness() {\n                    Ok(target_lexicon::Endianness::Little) => object::Endianness::Little,\n                    Ok(target_lexicon::Endianness::Big) => object::Endianness::Big,\n                    Err(_) => unimplemented!(\"Endianness not supported\"),\n                };\n\n                let bytes = object::write::Object::new(format, arch, endian)\n                    .write()\n                    .context(\"Failed to emit stub link file\")?;\n\n                // Write a dummy object file to satisfy rust/linker since it'll run llvm-objcopy\n                // ... I wish it *didn't* do that but I can't tell how to disable the linker without\n                // using --emit=obj which is not exactly what we want since that will still pull in\n                // the dependencies.\n                std::fs::create_dir_all(out_file.parent().unwrap())?;\n                std::fs::write(out_file, bytes)?;\n            }\n        }\n\n        Ok(())\n    }\n}\n\npub fn get_actual_linker_args_excluding_program_name(args: Vec<String>) -> Vec<String> {\n    args.into_iter()\n        .skip(1) // the first arg is program name\n        .flat_map(|arg| handle_linker_arg_response_file(arg).into_iter())\n        .collect()\n}\n\n// handle Windows linker response file. It's designed to workaround Windows command length limit.\n// https://learn.microsoft.com/en-us/cpp/build/reference/at-specify-a-linker-response-file?view=msvc-170\npub fn handle_linker_arg_response_file(arg: String) -> Vec<String> {\n    if arg.starts_with('@') {\n        let path = arg.trim().trim_start_matches('@');\n        let file_binary = std::fs::read(path).unwrap();\n\n        // This may be a utf-16le file. Let's try utf-8 first.\n        let mut content = String::from_utf8(file_binary.clone()).unwrap_or_else(|_| {\n            // Convert Vec<u8> to Vec<u16> to convert into a String\n            let binary_u16le: Vec<u16> = file_binary\n                .chunks_exact(2)\n                .map(|a| u16::from_le_bytes([a[0], a[1]]))\n                .collect();\n\n            String::from_utf16_lossy(&binary_u16le)\n        });\n\n        // Remove byte order mark in the beginning\n        if content.starts_with('\\u{FEFF}') {\n            content.remove(0);\n        }\n\n        // Gather linker args, and reset the args to be just the linker args\n        content\n            .lines()\n            .map(|line| {\n                let line_parsed = line.trim().to_string();\n                let line_parsed = line_parsed.trim_end_matches('\"').to_string();\n                let line_parsed = line_parsed.trim_start_matches('\"').to_string();\n                line_parsed\n            })\n            .collect()\n    } else {\n        vec![arg]\n    }\n}\n"
  },
  {
    "path": "packages/cli/src/cli/mod.rs",
    "content": "pub(crate) mod autoformat;\npub(crate) mod build;\npub(crate) mod build_assets;\npub(crate) mod bundle;\npub(crate) mod check;\npub(crate) mod component;\npub(crate) mod config;\npub(crate) mod create;\npub(crate) mod doctor;\npub(crate) mod hotpatch;\npub(crate) mod init;\npub(crate) mod link;\npub(crate) mod platform_override;\npub(crate) mod print;\npub(crate) mod run;\npub(crate) mod serve;\npub(crate) mod target;\npub(crate) mod translate;\npub(crate) mod update;\npub(crate) mod verbosity;\n\npub(crate) use build::*;\npub(crate) use serve::*;\npub(crate) use target::*;\npub(crate) use verbosity::*;\n\nuse crate::platform_override::CommandWithPlatformOverrides;\nuse crate::Anonymized;\nuse crate::{error::Result, Error, StructuredOutput};\nuse clap::builder::styling::{AnsiColor, Effects, Style, Styles};\nuse clap::{Parser, Subcommand};\nuse html_parser::Dom;\nuse serde::Deserialize;\nuse serde_json::{json, Value};\nuse std::sync::LazyLock;\nuse std::{\n    fmt::Display,\n    fs::File,\n    io::{Read, Write},\n    path::PathBuf,\n    process::Command,\n};\n\n/// Dioxus: build web, desktop, and mobile apps with a single codebase.\n#[derive(Parser)]\n#[clap(name = \"dioxus\", version = VERSION.as_str())]\n#[clap(styles = CARGO_STYLING)]\npub(crate) struct Cli {\n    #[command(subcommand)]\n    pub(crate) action: Commands,\n\n    #[command(flatten)]\n    pub(crate) verbosity: Verbosity,\n}\n\n#[derive(Subcommand)]\npub(crate) enum Commands {\n    /// Create a new Dioxus project.\n    #[clap(name = \"new\")]\n    New(create::Create),\n\n    /// Build, watch, and serve the project.\n    #[clap(name = \"serve\")]\n    Serve(serve::ServeArgs),\n\n    /// Bundle the Dioxus app into a shippable object.\n    #[clap(name = \"bundle\")]\n    Bundle(bundle::Bundle),\n\n    /// Build the Dioxus project and all of its assets.\n    #[clap(name = \"build\")]\n    Build(CommandWithPlatformOverrides<build::BuildArgs>),\n\n    /// Run the project without any hotreloading.\n    #[clap(name = \"run\")]\n    Run(run::RunArgs),\n\n    /// Init a new project for Dioxus in the current directory (by default).\n    /// Will attempt to keep your project in a good state.\n    #[clap(name = \"init\")]\n    Init(init::Init),\n\n    /// Diagnose installed tools and system configuration.\n    #[clap(name = \"doctor\")]\n    Doctor(doctor::Doctor),\n\n    /// Print project information in a structured format, like cargo args, linker args, and other\n    /// flags DX sets that might be useful in third-party tools.\n    #[clap(name = \"print\")]\n    #[clap(subcommand)]\n    Print(print::Print),\n\n    /// Translate a source file into Dioxus code.\n    #[clap(name = \"translate\")]\n    Translate(translate::Translate),\n\n    /// Automatically format RSX.\n    #[clap(name = \"fmt\")]\n    Autoformat(autoformat::Autoformat),\n\n    /// Check the project for any issues.\n    #[clap(name = \"check\")]\n    Check(check::Check),\n\n    /// Dioxus config file controls.\n    #[clap(subcommand)]\n    #[clap(name = \"config\")]\n    Config(config::Config),\n\n    /// Update the Dioxus CLI to the latest version.\n    #[clap(name = \"self-update\")]\n    SelfUpdate(update::SelfUpdate),\n\n    /// Run a dioxus build tool. IE `build-assets`, `hotpatch`, etc\n    #[clap(name = \"tools\")]\n    #[clap(subcommand)]\n    Tools(BuildTools),\n\n    /// Manage components from the `dioxus-component` registry.\n    #[clap(name = \"components\")]\n    #[clap(subcommand)]\n    Components(component::ComponentCommand),\n}\n\n#[allow(clippy::large_enum_variant)]\n#[derive(Subcommand)]\npub enum BuildTools {\n    /// Build the assets for a specific target.\n    #[clap(name = \"assets\")]\n    BuildAssets(build_assets::BuildAssets),\n\n    /// Hotpatch the \"tip\" of a given \"fat\" binary. The output here must be from the `dx build` command with \"fat\" enabled\n    #[clap(name = \"hotpatch\")]\n    HotpatchTip(hotpatch::HotpatchTip),\n}\n\npub(crate) static VERSION: LazyLock<String> = LazyLock::new(|| {\n    format!(\n        \"{} ({})\",\n        crate::dx_build_info::PKG_VERSION,\n        crate::dx_build_info::GIT_COMMIT_HASH_SHORT.unwrap_or(\"was built without git repository\")\n    )\n});\n\n/// Cargo's color style\n/// [source](https://github.com/crate-ci/clap-cargo/blob/master/src/style.rs)\npub(crate) const CARGO_STYLING: Styles = Styles::styled()\n    .header(styles::HEADER)\n    .usage(styles::USAGE)\n    .literal(styles::LITERAL)\n    .placeholder(styles::PLACEHOLDER)\n    .error(styles::ERROR)\n    .valid(styles::VALID)\n    .invalid(styles::INVALID);\n\npub mod styles {\n    use super::*;\n    pub(crate) const HEADER: Style = AnsiColor::Green.on_default().effects(Effects::BOLD);\n    pub(crate) const USAGE: Style = AnsiColor::Green.on_default().effects(Effects::BOLD);\n    pub(crate) const LITERAL: Style = AnsiColor::Cyan.on_default().effects(Effects::BOLD);\n    pub(crate) const PLACEHOLDER: Style = AnsiColor::Cyan.on_default();\n    pub(crate) const ERROR: Style = AnsiColor::Red.on_default().effects(Effects::BOLD);\n\n    pub(crate) const VALID: Style = AnsiColor::Cyan.on_default().effects(Effects::BOLD);\n    pub(crate) const INVALID: Style = AnsiColor::Yellow.on_default().effects(Effects::BOLD);\n\n    // extra styles for styling logs\n    // we can style stuff using the ansi sequences like: \"hotpatched in {GLOW_STYLE}{}{GLOW_STYLE:#}ms\"\n    pub(crate) const GLOW_STYLE: Style = AnsiColor::Yellow.on_default();\n    pub(crate) const NOTE_STYLE: Style = AnsiColor::Green.on_default();\n    pub(crate) const LINK_STYLE: Style = AnsiColor::Blue.on_default();\n    pub(crate) const ERROR_STYLE: Style = AnsiColor::Red.on_default();\n    pub(crate) const HINT_STYLE: Style = clap::builder::styling::Ansi256Color(244).on_default();\n}\n"
  },
  {
    "path": "packages/cli/src/cli/platform_override.rs",
    "content": "#![allow(dead_code)]\nuse crate::Anonymized;\nuse clap::parser::ValueSource;\nuse clap::{ArgMatches, Args, CommandFactory, FromArgMatches, Parser, Subcommand};\nuse serde_json::{json, Value};\n\n/// Wraps a component with the subcommands `@server` and `@client` which will let you override the\n/// base arguments for the client and server instances.\n#[derive(Debug, Clone, Default)]\npub struct CommandWithPlatformOverrides<T> {\n    /// The arguments that are shared between the client and server\n    pub shared: T,\n    /// The merged arguments for the server\n    pub server: Option<T>,\n    /// The merged arguments for the client\n    pub client: Option<T>,\n}\n\nimpl<T> CommandWithPlatformOverrides<T> {\n    pub(crate) fn with_client_or_shared<'a, O>(&'a self, f: impl FnOnce(&'a T) -> O) -> O {\n        match &self.client {\n            Some(client) => f(client),\n            None => f(&self.shared),\n        }\n    }\n\n    pub(crate) fn with_server_or_shared<'a, O>(&'a self, f: impl FnOnce(&'a T) -> O) -> O {\n        match &self.server {\n            Some(server) => f(server),\n            None => f(&self.shared),\n        }\n    }\n}\n\nimpl<T> Anonymized for CommandWithPlatformOverrides<T>\nwhere\n    T: Anonymized,\n{\n    fn anonymized(&self) -> Value {\n        json!({\n            \"shared\": self.shared.anonymized(),\n            \"server\": self.server.as_ref().map(|s| s.anonymized()),\n            \"client\": self.client.as_ref().map(|c| c.anonymized()),\n        })\n    }\n}\n\nimpl<T: CommandFactory + Args> Parser for CommandWithPlatformOverrides<T> {}\n\nimpl<T: CommandFactory + Args> CommandFactory for CommandWithPlatformOverrides<T> {\n    fn command() -> clap::Command {\n        T::command()\n    }\n\n    fn command_for_update() -> clap::Command {\n        T::command_for_update()\n    }\n}\n\nimpl<T> Args for CommandWithPlatformOverrides<T>\nwhere\n    T: Args,\n{\n    fn augment_args(cmd: clap::Command) -> clap::Command {\n        T::augment_args(cmd).defer(|cmd| {\n            PlatformOverrides::<Self>::augment_subcommands(cmd.disable_help_subcommand(true))\n        })\n    }\n\n    fn augment_args_for_update(_cmd: clap::Command) -> clap::Command {\n        unimplemented!()\n    }\n}\n\nfn merge_matches<T: Args>(base: &ArgMatches, platform: &ArgMatches) -> Result<T, clap::Error> {\n    let mut base = T::from_arg_matches(base)?;\n\n    let mut platform = platform.clone();\n    let original_ids: Vec<_> = platform.ids().cloned().collect();\n    for arg_id in original_ids {\n        let arg_name = arg_id.as_str();\n        // Remove any default values from the platform matches\n        if platform.value_source(arg_name) == Some(ValueSource::DefaultValue) {\n            _ = platform.try_clear_id(arg_name);\n        }\n    }\n\n    // Then merge the stripped platform matches into the base matches\n    base.update_from_arg_matches(&platform)?;\n\n    Ok(base)\n}\n\nimpl<T> FromArgMatches for CommandWithPlatformOverrides<T>\nwhere\n    T: Args,\n{\n    fn from_arg_matches(matches: &ArgMatches) -> Result<Self, clap::Error> {\n        let mut client = None;\n        let mut server = None;\n        let mut subcommand = matches.subcommand();\n        while let Some((name, sub_matches)) = subcommand {\n            match name {\n                \"@client\" => client = Some(sub_matches),\n                \"@server\" => server = Some(sub_matches),\n                _ => {}\n            }\n            subcommand = sub_matches.subcommand();\n        }\n\n        let shared = T::from_arg_matches(matches)?;\n        let client = client\n            .map(|client| merge_matches::<T>(matches, client))\n            .transpose()?;\n        let server = server\n            .map(|server| merge_matches::<T>(matches, server))\n            .transpose()?;\n\n        Ok(Self {\n            shared,\n            server,\n            client,\n        })\n    }\n\n    fn update_from_arg_matches(&mut self, _matches: &ArgMatches) -> Result<(), clap::Error> {\n        unimplemented!()\n    }\n}\n\n#[derive(Debug, Subcommand, Clone)]\n#[command(subcommand_precedence_over_arg = true)]\npub(crate) enum PlatformOverrides<T: Args> {\n    /// Specify the arguments for the client build\n    #[clap(name = \"@client\")]\n    Client(ChainedCommand<T, PlatformOverrides<T>>),\n\n    /// Specify the arguments for the server build\n    #[clap(name = \"@server\")]\n    Server(ChainedCommand<T, PlatformOverrides<T>>),\n}\n\n// https://github.com/clap-rs/clap/issues/2222#issuecomment-2524152894\n//\n//\n/// `[Args]` wrapper to match `T` variants recursively in `U`.\n#[derive(Debug, Clone)]\npub struct ChainedCommand<T, U> {\n    /// Specific Variant.\n    pub inner: T,\n\n    /// Enum containing `Self<T>` variants, in other words possible follow-up commands.\n    pub next: Option<Box<U>>,\n}\n\nimpl<T, U> Args for ChainedCommand<T, U>\nwhere\n    T: Args,\n    U: Subcommand,\n{\n    fn augment_args(cmd: clap::Command) -> clap::Command {\n        // We use the special `defer` method which lets us recursively call `augment_args` on the inner command\n        // and thus `from_arg_matches`\n        T::augment_args(cmd).defer(|cmd| U::augment_subcommands(cmd.disable_help_subcommand(true)))\n    }\n\n    fn augment_args_for_update(_cmd: clap::Command) -> clap::Command {\n        unimplemented!()\n    }\n}\n\nimpl<T, U> FromArgMatches for ChainedCommand<T, U>\nwhere\n    T: Args,\n    U: Subcommand,\n{\n    fn from_arg_matches(_: &ArgMatches) -> Result<Self, clap::Error> {\n        unimplemented!()\n    }\n\n    fn update_from_arg_matches(&mut self, _matches: &ArgMatches) -> Result<(), clap::Error> {\n        unimplemented!()\n    }\n}\n"
  },
  {
    "path": "packages/cli/src/cli/print.rs",
    "content": "use std::{borrow::Cow, ffi::OsString};\n\nuse super::*;\nuse crate::{BuildMode, Result};\nuse anyhow::Context;\n\n/// Perform a system analysis to verify the system install is working correctly.\n#[derive(Clone, Debug, Subcommand)]\npub(crate) enum Print {\n    /// Print the cargo args dioxus uses to build the server app.\n    /// Environment variables will be set with the `env` command.\n    #[clap(name = \"client-args\")]\n    ClientArgs(PrintCargoArgs),\n\n    /// Print the cargo args dioxus uses to build the client app.\n    /// Environment variables will be set with the `env` command.\n    #[clap(name = \"server-args\")]\n    ServerArgs(PrintCargoArgs),\n}\n\n#[derive(Clone, Debug, Parser)]\npub(crate) struct PrintCargoArgs {\n    #[clap(flatten)]\n    pub(crate) args: CommandWithPlatformOverrides<build::BuildArgs>,\n\n    /// The print output style to use. By default, this uses the current-platform's best fit,\n    /// though you can customize it in the case you might be driving an external build system.\n    /// - Unix style uses the `env` command\n    /// - Windows style uses the `set` command\n    /// - JSON style prints the arguments as JSON\n    /// - Pretty JSON style prints the arguments as pretty JSON\n    /// - Args style prints only the arguments, one per line, without any environment variables.\n    /// - Env style prints only the environment variables, one key-pair per line, without any arguments.\n    #[clap(long)]\n    pub(crate) style: Option<PrintStyle>,\n}\n\n#[derive(Clone, Debug, clap::ValueEnum)]\npub(crate) enum PrintStyle {\n    /// Print the arguments as a list of arguments, one per line.\n    /// Does not include the `cargo rustc` command itself\n    Args,\n\n    /// Print the environment variables as a list of key=value pairs, one per line.\n    Env,\n\n    /// Print the arguments using the Unix `env` command.\n    Unix,\n\n    /// Print the arguments using the Windows `set` command.\n    Cmd,\n\n    /// Print the arguments as JSON. Does not include the `cargo rustc` command itself\n    Json,\n\n    /// Print the arguments as pretty JSON. Does not include the `cargo rustc` command itself\n    PrettyJson,\n}\n\nimpl Print {\n    pub(crate) async fn print(self) -> Result<StructuredOutput> {\n        match self {\n            Self::ClientArgs(opts) => {\n                let targets = opts.args.into_targets().await?;\n                let mode = BuildMode::Base { run: false };\n                let args = targets.client.cargo_build_arguments(&mode);\n                let env = targets.client.cargo_build_env_vars(&mode)?;\n                Self::print_as_unified_command(&env, &args, &opts.style);\n                Ok(StructuredOutput::PrintCargoArgs {\n                    args,\n                    env: env\n                        .into_iter()\n                        .map(|(k, v)| (k, v.to_string_lossy().to_string()))\n                        .collect::<Vec<_>>(),\n                })\n            }\n            Self::ServerArgs(print_cargo_args) => {\n                let targets = print_cargo_args.args.into_targets().await?;\n                let mode = BuildMode::Base { run: false };\n                let server = targets\n                    .server\n                    .context(\"No server target found, cannot print server args\")?;\n                let args = server.cargo_build_arguments(&mode);\n                let env = server.cargo_build_env_vars(&mode)?;\n                Self::print_as_unified_command(&env, &args, &print_cargo_args.style);\n                Ok(StructuredOutput::PrintCargoArgs {\n                    args,\n                    env: env\n                        .into_iter()\n                        .map(|(k, v)| (k, v.to_string_lossy().to_string()))\n                        .collect::<Vec<_>>(),\n                })\n            }\n        }\n    }\n\n    /// Prints the given env and args as a unified command.\n    /// - Uses `env` on unix systems\n    /// - Uses `set VAR=value &&` on windows systems\n    /// - Prints structured JSON on json style\n    fn print_as_unified_command(\n        env: &[(Cow<'static, str>, OsString)],\n        args: &[String],\n        style: &Option<PrintStyle>,\n    ) {\n        let style = style.clone().unwrap_or({\n            if cfg!(unix) || std::env::var(\"MSYSTEM\").is_ok() || std::env::var(\"CYGWIN\").is_ok() {\n                PrintStyle::Unix\n            } else {\n                PrintStyle::Cmd\n            }\n        });\n\n        match style {\n            PrintStyle::Args => {\n                for arg in args {\n                    println!(\"{}\", arg);\n                }\n            }\n\n            PrintStyle::Env => {\n                for (key, value) in env {\n                    println!(\"{}={}\", key, value.to_string_lossy());\n                }\n            }\n\n            PrintStyle::Unix => {\n                let mut cmd = String::from(\"env\");\n                for (key, value) in env {\n                    cmd.push_str(&format!(\n                        \" {}={}\",\n                        key,\n                        shell_words::quote(&value.to_string_lossy())\n                    ));\n                }\n                cmd.push_str(\" cargo rustc\");\n                for arg in args {\n                    cmd.push_str(&format!(\" {}\", shell_words::quote(arg)));\n                }\n                println!(\"{}\", cmd);\n            }\n            PrintStyle::Cmd => {\n                let mut cmd = String::new();\n                for (key, value) in env {\n                    cmd.push_str(&format!(\n                        \"set {}={} && \",\n                        key,\n                        Self::escape_windows(value.to_string_lossy())\n                    ));\n                }\n                cmd.push_str(\"cargo rustc\");\n                for arg in args {\n                    cmd.push_str(&format!(\n                        \" {}\",\n                        Self::escape_windows(Cow::Borrowed(arg.as_str()))\n                    ));\n                }\n                println!(\"{}\", cmd);\n            }\n            PrintStyle::Json | PrintStyle::PrettyJson => {\n                let output = serde_json::json!({\n                    \"env\": env.iter().map(|(k, v)| (k.as_ref(), v)).collect::<std::collections::HashMap<_, _>>(),\n                    \"args\": args\n                });\n                if matches!(style, PrintStyle::PrettyJson) {\n                    println!(\"{}\", serde_json::to_string_pretty(&output).unwrap());\n                } else {\n                    println!(\"{}\", serde_json::to_string(&output).unwrap());\n                }\n            }\n        }\n    }\n    /// Escape for the windows cmd.exe shell.\n    ///\n    /// See [here][msdn] for more information.\n    ///\n    /// [msdn]: http://blogs.msdn.com/b/twistylittlepassagesallalike/archive/2011/04/23/everyone-quotes-arguments-the-wrong-way.aspx\n    ///\n    /// This function comes from shell-escape\n    fn escape_windows(s: Cow<str>) -> Cow<str> {\n        use std::iter::repeat;\n\n        let mut needs_escape = s.is_empty();\n        for ch in s.chars() {\n            match ch {\n                '\"' | '\\t' | '\\n' | ' ' => needs_escape = true,\n                _ => {}\n            }\n        }\n        if !needs_escape {\n            return s;\n        }\n        let mut es = String::with_capacity(s.len());\n        es.push('\"');\n        let mut chars = s.chars().peekable();\n        loop {\n            let mut nslashes = 0;\n            while let Some(&'\\\\') = chars.peek() {\n                chars.next();\n                nslashes += 1;\n            }\n\n            match chars.next() {\n                Some('\"') => {\n                    es.extend(repeat('\\\\').take(nslashes * 2 + 1));\n                    es.push('\"');\n                }\n                Some(c) => {\n                    es.extend(repeat('\\\\').take(nslashes));\n                    es.push(c);\n                }\n                None => {\n                    es.extend(repeat('\\\\').take(nslashes * 2));\n                    break;\n                }\n            }\n        }\n        es.push('\"');\n        es.into()\n    }\n}\n"
  },
  {
    "path": "packages/cli/src/cli/run.rs",
    "content": "use super::*;\nuse crate::{\n    serve::{AppServer, ServeUpdate, WebServer},\n    BuilderUpdate, BundleFormat, Result,\n};\nuse anyhow::bail;\nuse dioxus_dx_wire_format::BuildStage;\n\n/// Run the project with the given arguments\n///\n/// This is a shorthand for `dx serve` with interactive mode and hot-reload disabled.\n///\n/// Unlike `dx serve`, errors during build and run will cascade out as an error, rather than being\n/// handled by the TUI, making it more suitable for scripting, automation, or CI/CD pipelines.\n#[derive(Clone, Debug, Parser)]\npub(crate) struct RunArgs {\n    /// Information about the target to build\n    #[clap(flatten)]\n    pub(crate) args: ServeArgs,\n}\n\nimpl RunArgs {\n    pub(crate) async fn run(mut self) -> Result<StructuredOutput> {\n        // Override the build arguments, leveraging our serve infrastructure.\n        //\n        // We want to turn off the fancy stuff like the TUI, watcher, and hot-reload, but leave logging\n        // and other things like the devserver on.\n        self.args.hot_patch = false;\n        self.args.interactive = Some(false);\n        self.args.hot_reload = Some(false);\n        self.args.watch = Some(false);\n\n        let mut builder = AppServer::new(self.args).await?;\n        let mut devserver = WebServer::start(&builder)?;\n\n        builder.initialize();\n\n        loop {\n            let msg = tokio::select! {\n                msg = builder.wait() => msg,\n                msg = devserver.wait() => msg,\n            };\n\n            match msg {\n                ServeUpdate::BuilderUpdate { id, update } => {\n                    let bundle_format = builder.get_build(id).unwrap().build.bundle;\n\n                    // And then update the websocketed clients with the new build status in case they want it\n                    devserver.new_build_update(&update).await;\n\n                    // Finally, we also want to update the builder with the new update\n                    builder.new_build_update(&update, &devserver).await;\n\n                    // And then open the app if it's ready\n                    match update {\n                        BuilderUpdate::BuildReady { bundle } => {\n                            _ = builder\n                                .open(&bundle, &mut devserver)\n                                .await\n                                .inspect_err(|e| {\n                                    tracing::error!(\n                                        telemetry = %serde_json::json!({ \"event\": \"failed_to_open_app_run\" }),\n                                        \"Failed to open app: {e}\"\n                                    );\n                                });\n\n                            if bundle_format == BundleFormat::Web {\n                                tracing::info!(\n                                    \"Serving app at http://{}:{}\",\n                                    builder.devserver_bind_ip,\n                                    builder.devserver_port\n                                );\n                            }\n                        }\n                        BuilderUpdate::Progress { stage } => match stage {\n                            BuildStage::Initializing => {\n                                tracing::info!(\"[{bundle_format}] Initializing build\")\n                            }\n                            BuildStage::Starting { .. } => {}\n                            BuildStage::InstallingTooling => {}\n                            BuildStage::Compiling {\n                                current,\n                                total,\n                                krate,\n                            } => {\n                                tracing::debug!(\n                                    \"[{bundle_format}] ({current}/{total}) Compiling {krate} \",\n                                )\n                            }\n                            BuildStage::RunningBindgen => {\n                                tracing::info!(\"[{bundle_format}] Running WASM bindgen\")\n                            }\n                            BuildStage::SplittingBundle => {}\n                            BuildStage::OptimizingWasm => {\n                                tracing::info!(\"[{bundle_format}] Optimizing WASM with `wasm-opt`\")\n                            }\n                            BuildStage::Linking => tracing::info!(\"Linking app\"),\n                            BuildStage::Hotpatching => {}\n                            BuildStage::CopyingAssets {\n                                current,\n                                total,\n                                path,\n                            } => tracing::info!(\n                                \"[{bundle_format}] Copying asset {} ({current}/{total})\",\n                                path.file_name()\n                                    .map(|f| f.to_string_lossy())\n                                    .unwrap_or_default(),\n                            ),\n                            BuildStage::Bundling => {\n                                tracing::info!(\"[{bundle_format}] Bundling app\")\n                            }\n                            BuildStage::RunningGradle => {\n                                tracing::info!(\"[{bundle_format}] Running Gradle\")\n                            }\n                            BuildStage::CodeSigning => {\n                                tracing::info!(\"[{bundle_format}] Code signing app\")\n                            }\n                            BuildStage::Success => {}\n                            BuildStage::Restarting => {}\n                            BuildStage::CompressingAssets => {}\n                            BuildStage::ExtractingAssets => {}\n                            BuildStage::Prerendering => {\n                                tracing::info!(\"[{bundle_format}] Prerendering app\")\n                            }\n                            BuildStage::Failed => {\n                                tracing::error!(\"[{bundle_format}] Build failed\");\n                                bail!(\"Build failed for platform: {bundle_format}\");\n                            }\n                            BuildStage::Aborted => {\n                                tracing::error!(\"[{bundle_format}] Build aborted\");\n                                bail!(\"Build aborted for platform: {bundle_format}\");\n                            }\n                            _ => {}\n                        },\n                        BuilderUpdate::CompilerMessage { message } => {\n                            print!(\"{message}\");\n                        }\n                        BuilderUpdate::BuildFailed { err } => {\n                            tracing::error!(\"❌ Build failed: {}\", err);\n                            return Err(err);\n                        }\n                        BuilderUpdate::StdoutReceived { msg } => {\n                            tracing::info!(\"[{bundle_format}] {msg}\");\n                        }\n                        BuilderUpdate::StderrReceived { msg } => {\n                            tracing::error!(\"[{bundle_format}] {msg}\");\n                        }\n                        BuilderUpdate::ProcessExited { status } => {\n                            if !status.success() {\n                                tracing::error!(\n                                    \"Application [{bundle_format}] exited with error: {status}\"\n                                );\n                                bail!(\"Application [{bundle_format}] exited with error: {status}\");\n                            }\n\n                            break;\n                        }\n                        BuilderUpdate::ProcessWaitFailed { err } => {\n                            return Err(err.into());\n                        }\n                    }\n                }\n                ServeUpdate::Exit { .. } => break,\n                ServeUpdate::NewConnection { .. } => {}\n                ServeUpdate::WsMessage { .. } => {}\n                ServeUpdate::FilesChanged { .. } => {}\n                ServeUpdate::OpenApp => {}\n                ServeUpdate::RequestRebuild => {}\n                ServeUpdate::ToggleShouldRebuild => {}\n                ServeUpdate::OpenDebugger { .. } => {}\n                ServeUpdate::Redraw => {}\n                ServeUpdate::TracingLog { .. } => {}\n            }\n        }\n\n        Ok(StructuredOutput::Success)\n    }\n}\n"
  },
  {
    "path": "packages/cli/src/cli/serve.rs",
    "content": "use super::*;\nuse crate::{AddressArguments, Anonymized, BuildArgs, TraceController};\n/// Serve the project\n///\n/// `dx serve` takes cargo args by default with additional renderer args like `--web`, `--webview`, and `--native`:\n///\n/// ```sh\n/// dx serve --example blah --target blah --android\n/// ```\n///\n/// A simple serve:\n/// ```sh\n/// dx serve --web\n/// ```\n///\n/// As of dioxus 0.7, `dx serve` allows independent customization of the client and server builds,\n/// allowing workspaces and removing any \"magic\" done to support ergonomic fullstack serving with\n/// an plain `dx serve`. These require specifying more arguments like features since they won't be autodetected.\n///\n/// ```sh\n/// dx serve \\\n///     client --package frontend \\\n///     server --package backend\n/// ```\n#[derive(Clone, Debug, Default, Parser)]\n#[command(group = clap::ArgGroup::new(\"release-incompatible\").multiple(true).conflicts_with(\"release\"))]\npub(crate) struct ServeArgs {\n    /// The arguments for the address the server will run on\n    #[clap(flatten)]\n    pub(crate) address: AddressArguments,\n\n    /// Open the app in the default browser [default: true - unless cli settings are set]\n    #[arg(long, default_missing_value=\"true\", num_args=0..=1)]\n    pub(crate) open: Option<bool>,\n\n    /// Enable full hot reloading for the app [default: true - unless cli settings are set]\n    #[clap(long, group = \"release-incompatible\")]\n    pub(crate) hot_reload: Option<bool>,\n\n    /// Configure always-on-top for desktop apps [default: true - unless cli settings are set]\n    #[clap(long, default_missing_value = \"true\")]\n    pub(crate) always_on_top: Option<bool>,\n\n    /// Set cross-origin-policy to same-origin [default: false]\n    #[clap(name = \"cross-origin-policy\")]\n    #[clap(long)]\n    pub(crate) cross_origin_policy: bool,\n\n    /// Sets the interval in seconds that the CLI will poll for file changes on WSL.\n    #[clap(long, default_missing_value = \"2\")]\n    pub(crate) wsl_file_poll_interval: Option<u16>,\n\n    /// Run the server in interactive mode\n    #[arg(long, default_missing_value=\"true\", num_args=0..=1, short = 'i')]\n    pub(crate) interactive: Option<bool>,\n\n    /// Enable Rust hot-patching instead of full rebuilds [default: false]\n    ///\n    /// This is quite experimental and may lead to unexpected segfaults or crashes in development.\n    #[arg(long, default_value_t = false, alias = \"hotpatch\")]\n    pub(crate) hot_patch: bool,\n\n    /// Watch the filesystem for changes and trigger a rebuild [default: true]\n    #[clap(long, default_missing_value = \"true\", num_args=0..=1)]\n    pub(crate) watch: Option<bool>,\n\n    /// Exit the CLI after running into an error. This is mainly used to test hot patching internally\n    #[clap(long)]\n    #[clap(hide = true)]\n    pub(crate) exit_on_error: bool,\n\n    /// Platform-specific arguments for the build\n    #[clap(flatten)]\n    pub(crate) platform_args: CommandWithPlatformOverrides<PlatformServeArgs>,\n}\n\n#[derive(Clone, Debug, Default, Parser)]\npub(crate) struct PlatformServeArgs {\n    #[clap(flatten)]\n    pub(crate) targets: BuildArgs,\n\n    /// Additional arguments to pass to the executable\n    #[clap(long, default_value = \"\")]\n    pub(crate) args: String,\n}\n\nimpl ServeArgs {\n    /// Start the tui, builder, etc by resolving the arguments and then running the actual top-level serve function\n    ///\n    /// Make sure not to do any intermediate logging since our tracing infra has now enabled much\n    /// higher log levels\n    ///\n    /// We also set up proper panic handling since the TUI has a tendency to corrupt the terminal.\n    pub(crate) async fn serve(self, tracer: &TraceController) -> Result<StructuredOutput> {\n        // Redirect all logging the cli logger - if there's any pending after a panic, we flush it\n        let is_interactive_tty = self.is_interactive_tty();\n        if is_interactive_tty {\n            tracer.redirect_to_tui();\n        }\n\n        crate::serve::serve_all(self, tracer)\n            .await\n            .map(|_| StructuredOutput::Success)\n    }\n\n    /// Check if the server is running in interactive mode. This involves checking the terminal as well\n    pub(crate) fn is_interactive_tty(&self) -> bool {\n        use std::io::IsTerminal;\n        std::io::stdout().is_terminal() && self.interactive.unwrap_or(true)\n    }\n}\n\nimpl Anonymized for ServeArgs {\n    fn anonymized(&self) -> Value {\n        json! {{\n            \"address\": self.address.anonymized(),\n            \"open\": self.open,\n            \"hot_reload\": self.hot_reload,\n            \"always_on_top\": self.always_on_top,\n            \"cross_origin_policy\": self.cross_origin_policy,\n            \"wsl_file_poll_interval\": self.wsl_file_poll_interval,\n            \"interactive\": self.interactive,\n            \"hot_patch\": self.hot_patch,\n            \"watch\": self.watch,\n            \"exit_on_error\": self.exit_on_error,\n            \"platform_args\": self.platform_args.anonymized(),\n        }}\n    }\n}\n\nimpl Anonymized for PlatformServeArgs {\n    fn anonymized(&self) -> Value {\n        json! {{\n            \"targets\": self.targets.anonymized(),\n            \"args\": !self.args.is_empty(),\n        }}\n    }\n}\n"
  },
  {
    "path": "packages/cli/src/cli/target.rs",
    "content": "use crate::BundleFormat;\nuse crate::Platform;\nuse crate::{cli::*, Renderer};\n// use crate::RendererArg;\n// use crate::PlatformAlias;\nuse target_lexicon::Triple;\n\nconst HELP_HEADING: &str = \"Target Options\";\n\n/// A single target to build for\n#[derive(Clone, Debug, Default, Deserialize, Parser)]\npub(crate) struct TargetArgs {\n    /// Build platform: supports Web, MacOS, Windows, Linux, iOS, Android, and Server\n    ///\n    /// The platform implies a combination of the target alias, renderer, and bundle format flags.\n    ///\n    /// You should generally prefer to use the `--web`, `--webview`, or `--native` flags to set the renderer\n    /// or the `--wasm`, `--macos`, `--windows`, `--linux`, `--ios`, or `--android` flags to set the target alias\n    /// instead of this flag. The renderer, target alias, and bundle format will be inferred if you only pass one.\n    #[clap(flatten)]\n    pub(crate) platform: Platform,\n\n    /// Which renderer to use? By default, this is usually inferred from the platform.\n    #[clap(long, value_enum, help_heading = HELP_HEADING)]\n    pub(crate) renderer: Option<Renderer>,\n\n    /// The bundle format to target for the build: supports web, macos, windows, linux, ios, android, and server\n    #[clap(long, value_enum, help_heading = HELP_HEADING)]\n    pub(crate) bundle: Option<BundleFormat>,\n\n    /// Build in release mode [default: false]\n    #[clap(long, short, help_heading = HELP_HEADING)]\n    #[serde(default)]\n    pub(crate) release: bool,\n\n    /// The package to build\n    #[clap(short, long, help_heading = HELP_HEADING)]\n    pub(crate) package: Option<String>,\n\n    /// Build a specific binary [default: \"\"]\n    #[clap(long, help_heading = HELP_HEADING)]\n    pub(crate) bin: Option<String>,\n\n    /// Build a specific example [default: \"\"]\n    #[clap(long, help_heading = HELP_HEADING)]\n    pub(crate) example: Option<String>,\n\n    /// Build the app with custom a profile\n    #[clap(long, help_heading = HELP_HEADING)]\n    pub(crate) profile: Option<String>,\n\n    /// Space separated list of features to activate\n    #[clap(long, help_heading = HELP_HEADING)]\n    pub(crate) features: Vec<String>,\n\n    /// Don't include the default features in the build\n    #[clap(long, help_heading = HELP_HEADING)]\n    pub(crate) no_default_features: bool,\n\n    /// Include all features in the build\n    #[clap(long, help_heading = HELP_HEADING)]\n    pub(crate) all_features: bool,\n\n    /// Rustc platform triple\n    #[clap(long, help_heading = HELP_HEADING)]\n    pub(crate) target: Option<Triple>,\n\n    /// Extra arguments passed to `cargo`\n    ///\n    /// To see a list of args, run `cargo rustc --help`\n    ///\n    /// This can include stuff like, \"--locked\", \"--frozen\", etc. Note that `dx` sets many of these\n    /// args directly from other args in this command.\n    #[clap(long, help_heading = HELP_HEADING)]\n    pub(crate) cargo_args: Option<String>,\n\n    /// Extra arguments passed to `rustc`. This can be used to customize the linker, or other flags.\n    ///\n    /// For example, specifign `dx build --rustc-args \"-Clink-arg=-Wl,-blah\"` will pass \"-Clink-arg=-Wl,-blah\"\n    /// to the underlying the `cargo rustc` command:\n    ///\n    /// cargo rustc -- -Clink-arg=-Wl,-blah\n    ///\n    #[clap(long, help_heading = HELP_HEADING)]\n    pub(crate) rustc_args: Option<String>,\n\n    /// Skip collecting assets from dependencies [default: false]\n    #[clap(long, help_heading = HELP_HEADING)]\n    #[serde(default)]\n    pub(crate) skip_assets: bool,\n\n    /// Inject scripts to load the wasm and js files for your dioxus app if they are not already present [default: true]\n    #[clap(long, default_value_t = true, help_heading = HELP_HEADING, num_args = 0..=1)]\n    pub(crate) inject_loading_scripts: bool,\n\n    /// Experimental: Bundle split the wasm binary into multiple chunks based on `#[wasm_split]` annotations [default: false]\n    #[clap(long, default_value_t = false, help_heading = HELP_HEADING)]\n    pub(crate) wasm_split: bool,\n\n    /// Generate debug symbols for the wasm binary [default: true]\n    ///\n    /// This will make the binary larger and take longer to compile, but will allow you to debug the\n    /// wasm binary\n    #[clap(long, default_value_t = true, help_heading = HELP_HEADING, num_args = 0..=1)]\n    pub(crate) debug_symbols: bool,\n\n    /// Keep the name section in the wasm binary, useful for profiling and debugging [default: false]\n    ///\n    /// Unlike --debug-symbols which preserves DWARF debug info (requiring a browser extension to\n    /// read), the name section allows tools like console_error_panic_hook to print backtraces with\n    /// human-readable function names without any browser extension.\n    #[clap(long, default_value_t = false, help_heading = HELP_HEADING)]\n    pub(crate) keep_names: bool,\n\n    /// The name of the device we are hoping to upload to. By default, dx tries to upload to the active\n    /// simulator. If the device name is passed, we will upload to that device instead.\n    ///\n    /// This performs a search among devices, and fuzzy matches might be found.\n    #[arg(long, default_missing_value=Some(\"\".into()), num_args=0..=1)]\n    pub(crate) device: Option<String>,\n\n    /// The base path the build will fetch assets relative to. This will override the\n    /// base path set in the `dioxus` config.\n    #[clap(long, help_heading = HELP_HEADING)]\n    pub(crate) base_path: Option<String>,\n\n    /// Should dx attempt to codesign the app bundle?\n    #[clap(long, default_value_t = false, help_heading = HELP_HEADING, num_args = 0..=1)]\n    pub(crate) codesign: bool,\n\n    /// The path to the Apple entitlements file to used to sign the resulting app bundle.\n    ///\n    /// On iOS, this is required for deploy to a device and some configurations in the simulator.\n    #[clap(long, help_heading = HELP_HEADING)]\n    pub(crate) apple_entitlements: Option<PathBuf>,\n\n    /// The Apple team ID to use when signing the app bundle.\n    ///\n    /// Usually this is an email or name associated with your Apple Developer account, usually in the\n    /// format `Signing Name (GXTEAMID123)`.\n    ///\n    /// This is passed directly to the `codesign` tool.\n    ///\n    /// ```\n    /// codesign --force --entitlements <entitlements_file> --sign <apple_team_id> <app_bundle>\n    /// ```\n    #[clap(long, help_heading = HELP_HEADING)]\n    pub(crate) apple_team_id: Option<String>,\n\n    /// The folder where DX stores its temporary artifacts for things like hotpatching, build caches,\n    /// window position, etc. This is meant to be stable within an invocation of the CLI, but you can\n    /// persist it by setting this flag.\n    #[clap(long, help_heading = HELP_HEADING)]\n    pub(crate) session_cache_dir: Option<PathBuf>,\n\n    /// The target for the client build, used for specifying which target the server should end up in\n    /// when merging `@client and @server` targets together.\n    #[clap(long, help_heading = HELP_HEADING)]\n    pub(crate) client_target: Option<String>,\n\n    /// Automatically pass `--features=js_cfg` when building for wasm targets. This is enabled by default.\n    #[clap(long, default_value_t = true, help_heading = HELP_HEADING, num_args = 0..=1)]\n    pub(crate) wasm_js_cfg: bool,\n\n    /// The Windows subsystem to use when building for Windows targets. This can be either `CONSOLE` or `WINDOWS`.\n    ///\n    /// By default, DX uses `WINDOWS` since it assumes a GUI application, but you can override this behavior with this flag.\n    ///\n    /// See <https://learn.microsoft.com/en-us/cpp/build/reference/subsystem-specify-subsystem?view=msvc-170> for more information.\n    #[clap(long, help_heading = HELP_HEADING)]\n    pub(crate) windows_subsystem: Option<String>,\n\n    /// Output raw JSON diagnostics from cargo instead of processing them [default: false]\n    ///\n    /// When enabled, cargo's JSON output will be relayed directly to stdout without any processing or formatting by DX.\n    /// This is useful for integration with other tools that expect cargo's raw JSON format.\n    #[clap(long, help_heading = HELP_HEADING)]\n    #[serde(default)]\n    pub(crate) raw_json_diagnostics: bool,\n}\n\nimpl Anonymized for TargetArgs {\n    fn anonymized(&self) -> Value {\n        json! {{\n            \"renderer\": self.renderer,\n            \"bundle\": self.bundle,\n            \"platform\": self.platform,\n            \"release\": self.release,\n            \"package\": self.package,\n            \"bin\": self.bin,\n            \"example\": self.example.is_some(),\n            \"profile\": self.profile.is_some(),\n            \"features\": !self.features.is_empty(),\n            \"no_default_features\": self.no_default_features,\n            \"all_features\": self.all_features,\n            \"target\": self.target.as_ref().map(|t| t.to_string()),\n            \"skip_assets\": self.skip_assets,\n            \"inject_loading_scripts\": self.inject_loading_scripts,\n            \"wasm_split\": self.wasm_split,\n            \"debug_symbols\": self.debug_symbols,\n            \"keep_names\": self.keep_names,\n            \"device\": self.device,\n            \"base_path\": self.base_path.is_some(),\n            \"cargo_args\": self.cargo_args.is_some(),\n            \"rustc_args\": self.rustc_args.is_some(),\n            \"raw_json_diagnostics\": self.raw_json_diagnostics,\n        }}\n    }\n}\n"
  },
  {
    "path": "packages/cli/src/cli/translate.rs",
    "content": "use super::*;\nuse crate::{Result, StructuredOutput};\nuse anyhow::bail;\nuse dioxus_rsx::{BodyNode, CallBody, TemplateBody};\n\n/// Translate some source file into Dioxus code\n#[derive(Clone, Debug, Parser)]\npub(crate) struct Translate {\n    /// Activate debug mode\n    // short and long flags (-d, --debug) will be deduced from the field's name\n    #[clap(short, long)]\n    pub(crate) component: bool,\n\n    /// Input file\n    #[clap(short, long)]\n    pub(crate) file: Option<String>,\n\n    /// Input file\n    #[clap(short, long)]\n    pub(crate) raw: Option<String>,\n\n    /// Output file, stdout if not present\n    #[arg(short, long)]\n    pub(crate) output: Option<PathBuf>,\n}\n\nimpl Translate {\n    pub(crate) fn translate(self) -> Result<StructuredOutput> {\n        // Get the right input for the translation\n        let contents = determine_input(self.file, self.raw)?;\n\n        // Ensure we're loading valid HTML\n        let dom = html_parser::Dom::parse(&contents)?;\n\n        // Convert the HTML to RSX\n        let html = convert_html_to_formatted_rsx(&dom, self.component);\n\n        // Write the output\n        // todo(jon): we should probably use tracing out a different output format\n        // right now we're just printing to stdout since some tools rely on that, but likely we don't want that\n        // instead we should be printing as json (or maybe even a different format) if we're not interactive\n        match self.output {\n            Some(output) => std::fs::write(output, &html)?,\n            None => print!(\"{html}\"),\n        }\n\n        Ok(StructuredOutput::HtmlTranslate { html })\n    }\n}\n\npub fn convert_html_to_formatted_rsx(dom: &Dom, component: bool) -> String {\n    let callbody = dioxus_rsx_rosetta::rsx_from_html(dom);\n\n    match component {\n        true => write_callbody_with_icon_section(callbody),\n        false => dioxus_autofmt::write_block_out(&callbody).unwrap(),\n    }\n}\n\nfn write_callbody_with_icon_section(mut callbody: CallBody) -> String {\n    let mut svgs = vec![];\n\n    dioxus_rsx_rosetta::collect_svgs(&mut callbody.body.roots, &mut svgs);\n\n    let mut out = write_component_body(dioxus_autofmt::write_block_out(&callbody).unwrap());\n\n    if !svgs.is_empty() {\n        write_svg_section(&mut out, svgs);\n    }\n\n    out\n}\n\nfn write_component_body(raw: String) -> String {\n    let mut out = String::from(\"fn component() -> Element {\\n    rsx! {\");\n    indent_and_write(&raw, 1, &mut out);\n    out.push_str(\"    })\\n}\");\n    out\n}\n\nfn write_svg_section(out: &mut String, svgs: Vec<BodyNode>) {\n    out.push_str(\"\\n\\nmod icons {\");\n    out.push_str(\"\\n    use super::*;\");\n    for (idx, icon) in svgs.into_iter().enumerate() {\n        let raw =\n            dioxus_autofmt::write_block_out(&CallBody::new(TemplateBody::new(vec![icon]))).unwrap();\n        out.push_str(\"\\n\\n    pub(crate) fn icon_\");\n        out.push_str(&idx.to_string());\n        out.push_str(\"() -> Element {\\n        rsx! {\");\n        indent_and_write(&raw, 2, out);\n        out.push_str(\"        })\\n    }\");\n    }\n\n    out.push_str(\"\\n}\");\n}\n\nfn indent_and_write(raw: &str, idx: usize, out: &mut String) {\n    for line in raw.lines() {\n        for _ in 0..idx {\n            out.push_str(\"    \");\n        }\n        out.push_str(line);\n        out.push('\\n');\n    }\n}\n\nfn determine_input(file: Option<String>, raw: Option<String>) -> Result<String> {\n    use std::io::IsTerminal as _;\n\n    // Make sure not both are specified\n    if file.is_some() && raw.is_some() {\n        bail!(\"Only one of --file or --raw should be specified.\");\n    }\n\n    if let Some(raw) = raw {\n        return Ok(raw);\n    }\n\n    if let Some(file) = file {\n        return Ok(std::fs::read_to_string(file)?);\n    }\n\n    // If neither exist, we try to read from stdin\n    if std::io::stdin().is_terminal() {\n        bail!(\"No input file, source, or stdin to translate from.\");\n    }\n\n    let mut buffer = String::new();\n    std::io::stdin().read_to_string(&mut buffer).unwrap();\n\n    Ok(buffer.trim().to_string())\n}\n"
  },
  {
    "path": "packages/cli/src/cli/update.rs",
    "content": "use super::*;\nuse crate::{Result, Workspace};\nuse anyhow::{bail, Context};\nuse itertools::Itertools;\nuse self_update::cargo_crate_version;\n\n/// Run the project with the given arguments\n///\n/// This is a shorthand for `dx serve` with interactive mode and hot-reload disabled.\n#[derive(Clone, Debug, Parser)]\npub(crate) struct SelfUpdate {\n    /// Use the latest nightly build.\n    #[clap(long, default_value = \"false\")]\n    pub nightly: bool,\n\n    /// Specify a version to install.\n    #[clap(long)]\n    pub version: Option<String>,\n\n    /// Install the update.\n    #[clap(long, default_value = \"true\", num_args = 0..=1)]\n    pub install: bool,\n\n    /// List available versions.\n    #[clap(long, default_value = \"false\")]\n    pub list: bool,\n\n    /// Force the update even if the current version is up to date.\n    #[clap(long, default_value = \"false\")]\n    pub force: bool,\n}\n\nimpl SelfUpdate {\n    pub async fn self_update(self) -> Result<StructuredOutput> {\n        tokio::task::spawn_blocking(move || {\n            let start = std::time::Instant::now();\n            if self.list {\n                let res = self_update::backends::github::Update::configure()\n                    .repo_owner(\"dioxuslabs\")\n                    .repo_name(\"dioxus\")\n                    .bin_name(\"dx\")\n                    .current_version(cargo_crate_version!())\n                    .build()\n                    .unwrap()\n                    .get_latest_releases(cargo_crate_version!())\n                    .context(\"Failed to fetch latest version\")?;\n\n                if res.is_empty() {\n                    tracing::info!(\"Your version {} is up to date!\", cargo_crate_version!());\n                } else {\n                    tracing::info!(\"Your version {} is out of date!\", cargo_crate_version!());\n                    tracing::info!(\n                        \"Available versions: [{}]\",\n                        res.iter()\n                            .map(|r| r.version.clone())\n                            .collect::<Vec<_>>()\n                            .join(\", \")\n                    );\n                }\n\n                return Ok(StructuredOutput::Success);\n            }\n\n            let repo = self_update::backends::github::Update::configure()\n                .repo_owner(\"dioxuslabs\")\n                .repo_name(\"dioxus\")\n                .bin_name(\"dx\")\n                .current_version(cargo_crate_version!())\n                .build()\n                .unwrap();\n\n            let force = self.force || self.version.is_some();\n            let latest = match self.version {\n                Some(version) => repo\n                    .get_release_version(&version)\n                    .context(\"Failed to fetch release by tag\")?,\n                None => repo\n                    .get_latest_release()\n                    .context(\"Failed to fetch latest version\")?,\n            };\n\n            if latest.version == cargo_crate_version!() && !force {\n                tracing::info!(\"Your version {} is up to date!\", cargo_crate_version!());\n                return Ok(StructuredOutput::Success);\n            }\n\n            tracing::info!(\"Your version is out of date!\");\n            tracing::info!(\"- Yours:  {}\", cargo_crate_version!());\n            tracing::info!(\"- Latest: {}\", latest.version);\n\n            let cur_arch = if cfg!(target_arch = \"x86_64\") {\n                \"x86_64\"\n            } else if cfg!(target_arch = \"aarch64\") {\n                \"aarch64\"\n            } else {\n                bail!(\"Unsupported architecture\");\n            };\n\n            let cur_os = if cfg!(target_os = \"windows\") {\n                \"windows\"\n            } else if cfg!(target_os = \"linux\") {\n                \"linux\"\n            } else if cfg!(target_os = \"macos\") {\n                \"darwin\"\n            } else {\n                bail!(\"Unsupported OS\");\n            };\n\n            let zip_ext = \"zip\";\n\n            tracing::debug!(\"Available assets: {:?}\", latest.assets);\n\n            let asset = latest\n                .assets\n                .iter()\n                .find(|a| {\n                    a.name.contains(cur_os)\n                        && a.name.contains(cur_arch)\n                        && a.name.ends_with(zip_ext)\n                })\n                .context(\"No suitable asset found\")?;\n\n            let install_dir = Workspace::dioxus_data_dir().join(\"self-update\");\n            std::fs::create_dir_all(&install_dir).context(\"Failed to create install directory\")?;\n\n            tracing::info!(\"Downloading update from Github\");\n            tracing::debug!(\"Download URL: {}\", asset.download_url);\n            let body = latest.body.unwrap_or_default();\n            let brief = vec![\n                latest.name.to_string(),\n                \"\".to_string(),\n                latest.date.to_string(),\n                asset.download_url.to_string(),\n                \"\".to_string(),\n            ]\n            .into_iter()\n            .chain(body.lines().map(ToString::to_string).take(7))\n            .chain(std::iter::once(\" ...\".to_string()))\n            .map(|line| format!(\"                | {line}\"))\n            .join(\"\\n\");\n\n            tracing::info!(\"{}\", brief.trim());\n\n            let archive_path = install_dir.join(&asset.name);\n            _ = std::fs::remove_file(&archive_path).ok();\n            let archive_file = std::fs::File::create(&archive_path)?;\n            let download_url = asset.download_url.clone();\n            self_update::Download::from_url(&download_url)\n                .set_header(\n                    hyper::http::header::ACCEPT,\n                    \"application/octet-stream\".parse().unwrap(),\n                )\n                .download_to(archive_file)\n                .context(\"Failed to download update\")?;\n\n            let install_dir = install_dir.join(\"dx\");\n            _ = std::fs::remove_dir_all(&install_dir);\n            self_update::Extract::from_source(&archive_path)\n                .extract_into(&install_dir)\n                .context(\"Failed to extract update\")?;\n\n            let exe = if cfg!(target_os = \"windows\") {\n                \"dx.exe\"\n            } else {\n                \"dx\"\n            };\n            let executable = install_dir.join(exe);\n            if !executable.exists() {\n                bail!(\"Executable not found in {}\", install_dir.display());\n            }\n\n            tracing::info!(\n                \"Successfully downloaded update in {}ms! 👍\",\n                start.elapsed().as_millis()\n            );\n\n            if self.install {\n                tracing::info!(\n                    \"Installing dx v{} to {}\",\n                    latest.version,\n                    std::env::current_exe()?.display()\n                );\n\n                if !self.force {\n                    tracing::warn!(\"Continue? (y/n)\");\n                    print!(\"                > \");\n                    std::io::stdout()\n                        .flush()\n                        .context(\"Failed to flush stdout\")?;\n                    let mut input = String::new();\n                    std::io::stdin()\n                        .read_line(&mut input)\n                        .context(\"Failed to read input\")?;\n                    if !input.trim().to_ascii_lowercase().starts_with('y') {\n                        tracing::info!(\"Aborting update\");\n                        return Ok(StructuredOutput::Success);\n                    }\n                }\n\n                self_update::self_replace::self_replace(executable)?;\n                let time_taken = start.elapsed().as_millis();\n                tracing::info!(\"Done in {} ms! 💫\", time_taken)\n            } else {\n                tracing::info!(\"Update downloaded to {}\", install_dir.display());\n                tracing::info!(\"Run `dx self-update --install` to install the update\");\n            }\n\n            Ok(StructuredOutput::Success)\n        })\n        .await\n        .context(\"Failed to run self-update\")?\n    }\n}\n\n/// Check against the github release list to see if the currently released `dx` version is\n/// more up-to-date than our own.\n///\n/// We only toss out this warning once and then save to the settings file to ignore this version\n/// in the future.\npub fn log_if_cli_could_update() {\n    tokio::task::spawn_blocking(|| {\n        let release = self_update::backends::github::Update::configure()\n            .repo_owner(\"dioxuslabs\")\n            .repo_name(\"dioxus\")\n            .bin_name(\"dx\")\n            .current_version(cargo_crate_version!())\n            .build()\n            .unwrap()\n            .get_latest_release();\n\n        if let Ok(release) = release {\n            let old = krates::semver::Version::parse(cargo_crate_version!());\n            let new = krates::semver::Version::parse(&release.version);\n\n            if let (Ok(old), Ok(new)) = (old, new) {\n                if old < new {\n                    _ = crate::CliSettings::modify_settings(|f| {\n                        let ignored = f.ignore_version_update.as_deref().unwrap_or_default();\n                        if release.version != ignored {\n                            use crate::styles::GLOW_STYLE;\n                            tracing::warn!(\"A new dx version is available: {new}! Run {GLOW_STYLE}dx self-update{GLOW_STYLE:#} to update.\");\n                            f.ignore_version_update = Some(new.to_string());\n                        }\n                    });\n                }\n            }\n        }\n    });\n}\n"
  },
  {
    "path": "packages/cli/src/cli/verbosity.rs",
    "content": "use clap::Parser;\nuse std::path::PathBuf;\n\n#[derive(Parser, Clone, Debug, Default)]\npub struct Verbosity {\n    /// Use verbose output [default: false]\n    #[clap(long, global = true)]\n    pub(crate) verbose: bool,\n\n    /// Use trace output [default: false]\n    #[clap(long, global = true)]\n    pub(crate) trace: bool,\n\n    /// Output logs in JSON format\n    #[clap(long, global = true)]\n    pub(crate) json_output: bool,\n\n    /// Write *all* logs to a file\n    #[clap(long, global = true, help_heading = \"Logging Options\")]\n    pub(crate) log_to_file: Option<PathBuf>,\n\n    /// Assert that `Cargo.lock` will remain unchanged\n    #[clap(long, global = true, help_heading = \"Manifest Options\")]\n    pub(crate) locked: bool,\n\n    /// Run without accessing the network\n    #[clap(long, global = true, help_heading = \"Manifest Options\")]\n    pub(crate) offline: bool,\n\n    /// Equivalent to specifying both --locked and --offline\n    #[clap(long, global = true, help_heading = \"Manifest Options\")]\n    pub(crate) frozen: bool,\n}\n"
  },
  {
    "path": "packages/cli/src/config/app.rs",
    "content": "use schemars::JsonSchema;\nuse serde::{Deserialize, Serialize};\nuse std::path::PathBuf;\n\n#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema)]\npub(crate) struct ApplicationConfig {\n    /// The path where global assets will be added when components are added with `dx components add`\n    #[serde(default)]\n    pub(crate) asset_dir: Option<PathBuf>,\n\n    #[serde(default)]\n    pub(crate) out_dir: Option<PathBuf>,\n\n    #[serde(default = \"public_dir_default\")]\n    #[serde(deserialize_with = \"empty_string_is_none\")]\n    pub(crate) public_dir: Option<PathBuf>,\n\n    #[serde(default)]\n    pub(crate) tailwind_input: Option<PathBuf>,\n\n    #[serde(default)]\n    pub(crate) tailwind_output: Option<PathBuf>,\n\n    /// Use this file for the info.plist associated with the iOS app.\n    /// `dx` will merge any required settings into this file required to build the app\n    #[serde(default)]\n    pub(crate) ios_info_plist: Option<PathBuf>,\n\n    /// Use this file for the info.plist associated with the macOS app.\n    /// `dx` will merge any required settings into this file required to build the app\n    #[serde(default)]\n    pub(crate) macos_info_plist: Option<PathBuf>,\n\n    /// Use this file for the entitlements.plist associated with the iOS app.\n    #[serde(default)]\n    pub(crate) ios_entitlements: Option<PathBuf>,\n\n    /// Use this file for the entitlements.plist associated with the macOS app.\n    #[serde(default)]\n    pub(crate) macos_entitlements: Option<PathBuf>,\n\n    /// Use this file for the AndroidManifest.xml associated with the Android app.\n    /// `dx` will merge any required settings into this file required to build the app\n    #[serde(default)]\n    pub(crate) android_manifest: Option<PathBuf>,\n\n    /// Use this file for the MainActivity.kt associated with the Android app.\n    #[serde(default)]\n    pub(crate) android_main_activity: Option<PathBuf>,\n\n    /// Specified minimum sdk version for gradle to build the app with.\n    #[serde(default)]\n    pub(crate) android_min_sdk_version: Option<u32>,\n}\n\nfn public_dir_default() -> Option<PathBuf> {\n    Some(\"public\".into())\n}\n\nfn empty_string_is_none<'de, D>(deserializer: D) -> Result<Option<PathBuf>, D::Error>\nwhere\n    D: serde::Deserializer<'de>,\n{\n    let opt: Option<String> = Option::deserialize(deserializer)?;\n    match opt {\n        Some(s) if s.is_empty() => Ok(None),\n        Some(s) => Ok(Some(PathBuf::from(s))),\n        None => Ok(None),\n    }\n}\n"
  },
  {
    "path": "packages/cli/src/config/bundle.rs",
    "content": "use schemars::JsonSchema;\nuse serde::{Deserialize, Serialize};\nuse std::path::PathBuf;\nuse std::{collections::HashMap, str::FromStr};\n\n#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]\npub(crate) struct BundleConfig {\n    #[serde(default)]\n    pub(crate) identifier: Option<String>,\n    #[serde(default)]\n    pub(crate) publisher: Option<String>,\n    #[serde(default)]\n    pub(crate) icon: Option<Vec<String>>,\n    #[serde(default)]\n    pub(crate) resources: Option<Vec<String>>,\n    #[serde(default)]\n    pub(crate) copyright: Option<String>,\n    #[serde(default)]\n    pub(crate) category: Option<String>,\n    #[serde(default)]\n    pub(crate) short_description: Option<String>,\n    #[serde(default)]\n    pub(crate) long_description: Option<String>,\n    #[serde(default)]\n    pub(crate) external_bin: Option<Vec<String>>,\n    #[serde(default)]\n    pub(crate) deb: Option<DebianSettings>,\n    #[serde(default)]\n    pub(crate) macos: Option<MacOsSettings>,\n    #[serde(default)]\n    pub(crate) windows: Option<WindowsSettings>,\n    #[serde(default)]\n    pub(crate) android: Option<AndroidSettings>,\n}\n\n#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]\npub(crate) struct DebianSettings {\n    // OS-specific settings:\n    /// the list of debian dependencies.\n    #[serde(default)]\n    pub depends: Option<Vec<String>>,\n    /// the list of recommended debian dependencies.\n    #[serde(default)]\n    pub recommends: Option<Vec<String>>,\n    /// the list of dependencies the package provides.\n    #[serde(default)]\n    pub provides: Option<Vec<String>>,\n    /// the list of package conflicts.\n    #[serde(default)]\n    pub conflicts: Option<Vec<String>>,\n    /// the list of package replaces.\n    #[serde(default)]\n    pub replaces: Option<Vec<String>>,\n    /// List of custom files to add to the deb package.\n    /// Maps the path on the debian package to the path of the file to include (relative to the current working directory).\n    #[serde(default)]\n    pub files: HashMap<PathBuf, PathBuf>,\n    /// Path to a custom desktop file Handlebars template.\n    ///\n    /// Available variables: `categories`, `comment` (optional), `exec`, `icon` and `name`.\n    #[serde(default)]\n    pub desktop_template: Option<PathBuf>,\n    /// Define the section in Debian Control file. See : <https://www.debian.org/doc/debian-policy/ch-archive.html#s-subsections>\n    #[serde(default)]\n    pub section: Option<String>,\n    /// Change the priority of the Debian Package. By default, it is set to `optional`.\n    /// Recognized Priorities as of now are :  `required`, `important`, `standard`, `optional`, `extra`\n    #[serde(default)]\n    pub priority: Option<String>,\n    /// Path of the uncompressed Changelog file, to be stored at /usr/share/doc/package-name/changelog.gz. See\n    /// <https://www.debian.org/doc/debian-policy/ch-docs.html#changelog-files-and-release-notes>\n    #[serde(default)]\n    pub changelog: Option<PathBuf>,\n    /// Path to script that will be executed before the package is unpacked. See\n    /// <https://www.debian.org/doc/debian-policy/ch-maintainerscripts.html>\n    #[serde(default)]\n    pub pre_install_script: Option<PathBuf>,\n    /// Path to script that will be executed after the package is unpacked. See\n    /// <https://www.debian.org/doc/debian-policy/ch-maintainerscripts.html>\n    #[serde(default)]\n    pub post_install_script: Option<PathBuf>,\n    /// Path to script that will be executed before the package is removed. See\n    /// <https://www.debian.org/doc/debian-policy/ch-maintainerscripts.html>\n    #[serde(default)]\n    pub pre_remove_script: Option<PathBuf>,\n    /// Path to script that will be executed after the package is removed. See\n    /// <https://www.debian.org/doc/debian-policy/ch-maintainerscripts.html>\n    #[serde(default)]\n    pub post_remove_script: Option<PathBuf>,\n}\n\n#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]\npub(crate) struct WixSettings {\n    #[serde(default)]\n    pub(crate) language: Vec<(String, Option<PathBuf>)>,\n    #[serde(default)]\n    pub(crate) template: Option<PathBuf>,\n    #[serde(default)]\n    pub(crate) fragment_paths: Vec<PathBuf>,\n    #[serde(default)]\n    pub(crate) component_group_refs: Vec<String>,\n    #[serde(default)]\n    pub(crate) component_refs: Vec<String>,\n    #[serde(default)]\n    pub(crate) feature_group_refs: Vec<String>,\n    #[serde(default)]\n    pub(crate) feature_refs: Vec<String>,\n    #[serde(default)]\n    pub(crate) merge_refs: Vec<String>,\n    #[serde(default)]\n    pub(crate) skip_webview_install: bool,\n    #[serde(default)]\n    pub(crate) license: Option<PathBuf>,\n    #[serde(default)]\n    pub(crate) enable_elevated_update_task: bool,\n    #[serde(default)]\n    pub(crate) banner_path: Option<PathBuf>,\n    #[serde(default)]\n    pub(crate) dialog_image_path: Option<PathBuf>,\n    #[serde(default)]\n    pub(crate) fips_compliant: bool,\n    /// MSI installer version in the format `major.minor.patch.build` (build is optional).\n    ///\n    /// Because a valid version is required for MSI installer, it will be derived from [`tauri_bundler::PackageSettings::version`] if this field is not set.\n    ///\n    /// The first field is the major version and has a maximum value of 255. The second field is the minor version and has a maximum value of 255.\n    /// The third and fourth fields have a maximum value of 65,535.\n    ///\n    /// See <https://learn.microsoft.com/en-us/windows/win32/msi/productversion> for more info.\n    #[serde(default)]\n    pub version: Option<String>,\n    /// A GUID upgrade code for MSI installer. This code **_must stay the same across all of your updates_**,\n    /// otherwise, Windows will treat your update as a different app and your users will have duplicate versions of your app.\n    ///\n    /// By default, tauri generates this code by generating a Uuid v5 using the string `<productName>.exe.app.x64` in the DNS namespace.\n    /// You can use Tauri's CLI to generate and print this code for you by running `tauri inspect wix-upgrade-code`.\n    ///\n    /// It is recommended that you set this value in your tauri config file to avoid accidental changes in your upgrade code\n    /// whenever you want to change your product name.\n    #[serde(default)]\n    #[schemars(with = \"Option<String>\")]\n    pub upgrade_code: Option<uuid::Uuid>,\n}\n\n#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]\npub(crate) struct MacOsSettings {\n    #[serde(default)]\n    pub(crate) bundle_version: Option<String>,\n    #[serde(default)]\n    pub(crate) frameworks: Option<Vec<String>>,\n    #[serde(default)]\n    pub(crate) minimum_system_version: Option<String>,\n    #[serde(default)]\n    pub(crate) license: Option<String>,\n    #[serde(default)]\n    pub(crate) exception_domain: Option<String>,\n    #[serde(default)]\n    pub(crate) signing_identity: Option<String>,\n    #[serde(default)]\n    pub(crate) provider_short_name: Option<String>,\n    #[serde(default)]\n    pub(crate) entitlements: Option<String>,\n    #[serde(default)]\n    pub(crate) info_plist_path: Option<PathBuf>,\n    #[serde(default)]\n    pub(crate) bundle_name: Option<String>,\n    /// List of custom files to add to the application bundle.\n    /// Maps the path in the Contents directory in the app to the path of the file to include (relative to the current working directory).\n    #[serde(default)]\n    pub files: HashMap<PathBuf, PathBuf>,\n    /// Preserve the hardened runtime version flag, see <https://developer.apple.com/documentation/security/hardened_runtime>\n    ///\n    /// Settings this to `false` is useful when using an ad-hoc signature, making it less strict.\n    #[serde(default = \"default_hardened_runtime\")]\n    pub hardened_runtime: bool,\n}\n\nfn default_hardened_runtime() -> bool {\n    true\n}\n\n#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]\npub(crate) struct WindowsSettings {\n    #[serde(default)]\n    pub(crate) digest_algorithm: Option<String>,\n    #[serde(default)]\n    pub(crate) certificate_thumbprint: Option<String>,\n    #[serde(default)]\n    pub(crate) timestamp_url: Option<String>,\n    #[serde(default)]\n    pub(crate) tsp: bool,\n    #[serde(default)]\n    pub(crate) wix: Option<WixSettings>,\n    #[serde(default)]\n    pub(crate) icon_path: Option<PathBuf>,\n    #[serde(default)]\n    pub(crate) webview_install_mode: WebviewInstallMode,\n    #[serde(default)]\n    pub(crate) webview_fixed_runtime_path: Option<PathBuf>,\n    #[serde(default)]\n    pub(crate) allow_downgrades: bool,\n    #[serde(default)]\n    pub(crate) nsis: Option<NsisSettings>,\n    /// Specify a custom command to sign the binaries.\n    /// This command needs to have a `%1` in it which is just a placeholder for the binary path,\n    /// which we will detect and replace before calling the command.\n    ///\n    /// Example:\n    /// ```text\n    /// sign-cli --arg1 --arg2 %1\n    /// ```\n    ///\n    /// By Default we use `signtool.exe` which can be found only on Windows so\n    /// if you are on another platform and want to cross-compile and sign you will\n    /// need to use another tool like `osslsigncode`.\n    #[serde(default)]\n    pub sign_command: Option<CustomSignCommandSettings>,\n}\n\n#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema)]\npub(crate) struct NsisSettings {\n    #[serde(default)]\n    pub(crate) template: Option<PathBuf>,\n    #[serde(default)]\n    pub(crate) license: Option<PathBuf>,\n    #[serde(default)]\n    pub(crate) header_image: Option<PathBuf>,\n    #[serde(default)]\n    pub(crate) sidebar_image: Option<PathBuf>,\n    #[serde(default)]\n    pub(crate) installer_icon: Option<PathBuf>,\n    #[serde(default)]\n    pub(crate) install_mode: NSISInstallerMode,\n    #[serde(default)]\n    pub(crate) languages: Option<Vec<String>>,\n    #[serde(default)]\n    pub(crate) custom_language_files: Option<HashMap<String, PathBuf>>,\n    #[serde(default)]\n    pub(crate) display_language_selector: bool,\n    #[serde(default)]\n    pub(crate) start_menu_folder: Option<String>,\n    #[serde(default)]\n    pub(crate) installer_hooks: Option<PathBuf>,\n    /// Try to ensure that the WebView2 version is equal to or newer than this version,\n    /// if the user's WebView2 is older than this version,\n    /// the installer will try to trigger a WebView2 update.\n    #[serde(default)]\n    pub minimum_webview2_version: Option<String>,\n}\n\n#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema)]\npub(crate) enum NSISInstallerMode {\n    #[default]\n    CurrentUser,\n    PerMachine,\n    Both,\n}\n\n#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]\npub(crate) enum WebviewInstallMode {\n    Skip,\n    DownloadBootstrapper { silent: bool },\n    EmbedBootstrapper { silent: bool },\n    OfflineInstaller { silent: bool },\n    FixedRuntime { path: PathBuf },\n}\n\nimpl Default for WebviewInstallMode {\n    fn default() -> Self {\n        Self::OfflineInstaller { silent: false }\n    }\n}\n\n// Because all four fields must appear at the same time, there is no need for an Option\n#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]\npub(crate) struct AndroidSettings {\n    pub(crate) jks_file: PathBuf,\n    pub(crate) jks_password: String,\n    pub(crate) key_alias: String,\n    pub(crate) key_password: String,\n}\n\n#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]\npub struct CustomSignCommandSettings {\n    /// The command to run to sign the binary.\n    pub cmd: String,\n    /// The arguments to pass to the command.\n    ///\n    /// \"%1\" will be replaced with the path to the binary to be signed.\n    pub args: Vec<String>,\n}\n\n#[derive(Clone, Copy, Debug, clap::ValueEnum, Serialize)]\npub(crate) enum PackageType {\n    /// The macOS application bundle (.app).\n    #[clap(name = \"macos\")]\n    MacOsBundle,\n\n    /// The iOS app bundle.\n    #[clap(name = \"ios\")]\n    IosBundle,\n\n    /// The Windows bundle (.msi).\n    #[clap(name = \"msi\")]\n    WindowsMsi,\n\n    /// The NSIS bundle (.exe).\n    #[clap(name = \"nsis\")]\n    Nsis,\n\n    /// The Linux Debian package bundle (.deb).\n    #[clap(name = \"deb\")]\n    Deb,\n\n    /// The Linux RPM bundle (.rpm).\n    #[clap(name = \"rpm\")]\n    Rpm,\n\n    /// The Linux AppImage bundle (.AppImage).\n    #[clap(name = \"appimage\")]\n    AppImage,\n\n    /// The macOS DMG bundle (.dmg).\n    #[clap(name = \"dmg\")]\n    Dmg,\n\n    /// The Updater bundle (a patch of an existing app)\n    #[clap(name = \"updater\")]\n    Updater,\n}\n\nimpl FromStr for PackageType {\n    type Err = String;\n\n    fn from_str(s: &str) -> Result<Self, Self::Err> {\n        match s {\n            \"macos\" => Ok(PackageType::MacOsBundle),\n            \"ios\" => Ok(PackageType::IosBundle),\n            \"msi\" => Ok(PackageType::WindowsMsi),\n            \"nsis\" => Ok(PackageType::Nsis),\n            \"deb\" => Ok(PackageType::Deb),\n            \"rpm\" => Ok(PackageType::Rpm),\n            \"appimage\" => Ok(PackageType::AppImage),\n            \"dmg\" => Ok(PackageType::Dmg),\n            \"updater\" => Ok(PackageType::Updater),\n            _ => Err(format!(\"{s} is not a valid package type\")),\n        }\n    }\n}\n"
  },
  {
    "path": "packages/cli/src/config/component.rs",
    "content": "use crate::component::ComponentRegistry;\nuse schemars::JsonSchema;\nuse serde::{Deserialize, Serialize};\nuse std::path::PathBuf;\n\n/// Configuration for the `dioxus component` commands\n#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]\npub(crate) struct ComponentConfig {\n    /// The component registry to default to when adding components\n    #[serde(default)]\n    pub(crate) registry: ComponentRegistry,\n\n    /// The path where components are stored when adding or removing components\n    #[serde(default)]\n    pub(crate) components_dir: Option<PathBuf>,\n}\n"
  },
  {
    "path": "packages/cli/src/config/dioxus_config.rs",
    "content": "use crate::config::component::ComponentConfig;\n\nuse super::*;\nuse schemars::JsonSchema;\nuse serde::{Deserialize, Serialize};\n\n#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]\npub(crate) struct DioxusConfig {\n    #[serde(default)]\n    pub(crate) application: ApplicationConfig,\n\n    #[serde(default)]\n    pub(crate) web: WebConfig,\n\n    #[serde(default)]\n    pub(crate) bundle: BundleConfig,\n\n    #[serde(default)]\n    pub(crate) components: ComponentConfig,\n\n    /// Unified permissions configuration.\n    /// Permissions declared here are automatically mapped to platform-specific\n    /// identifiers (AndroidManifest.xml, Info.plist, etc.)\n    #[serde(default)]\n    pub(crate) permissions: PermissionsConfig,\n\n    /// Unified deep linking configuration.\n    /// URL schemes and universal links declared here are mapped to platform-specific\n    /// configurations. Use `[ios]`, `[android]`, `[macos]` sections for overrides.\n    #[serde(default)]\n    pub(crate) deep_links: DeepLinkConfig,\n\n    /// Unified background mode configuration.\n    /// Background capabilities declared here are mapped to platform-specific\n    /// configurations. Use `[ios]`, `[android]` sections for overrides.\n    #[serde(default)]\n    pub(crate) background: BackgroundConfig,\n\n    /// iOS-specific configuration.\n    #[serde(default)]\n    pub(crate) ios: IosConfig,\n\n    /// Android-specific configuration.\n    #[serde(default)]\n    pub(crate) android: AndroidConfig,\n\n    /// macOS-specific configuration.\n    #[serde(default)]\n    pub(crate) macos: MacosConfig,\n\n    /// Windows-specific configuration.\n    #[serde(default)]\n    pub(crate) windows: WindowsConfig,\n\n    /// Linux-specific configuration.\n    #[serde(default)]\n    pub(crate) linux: LinuxConfig,\n}\n\n/// Platform identifier for bundle resolution.\n/// This is separate from the CLI's Platform enum which includes Server and Unknown variants.\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\npub enum BundlePlatform {\n    Ios,\n    Android,\n    MacOS,\n    Windows,\n    Linux,\n    Web,\n}\n\nimpl From<crate::BundleFormat> for BundlePlatform {\n    fn from(format: crate::BundleFormat) -> Self {\n        match format {\n            crate::BundleFormat::Ios => BundlePlatform::Ios,\n            crate::BundleFormat::Android => BundlePlatform::Android,\n            crate::BundleFormat::MacOS => BundlePlatform::MacOS,\n            crate::BundleFormat::Windows => BundlePlatform::Windows,\n            crate::BundleFormat::Linux => BundlePlatform::Linux,\n            crate::BundleFormat::Web | crate::BundleFormat::Server => BundlePlatform::Web,\n        }\n    }\n}\n\nimpl DioxusConfig {\n    /// Get the resolved bundle identifier for a specific platform.\n    /// Platform-specific identifiers override the base bundle identifier.\n    pub fn resolved_identifier(&self, platform: BundlePlatform) -> Option<&str> {\n        let platform_override = match platform {\n            BundlePlatform::Ios => self.ios.identifier.as_deref(),\n            BundlePlatform::Android => self.android.identifier.as_deref(),\n            BundlePlatform::MacOS => self.macos.identifier.as_deref(),\n            BundlePlatform::Windows => self.windows.identifier.as_deref(),\n            BundlePlatform::Linux => self.linux.identifier.as_deref(),\n            BundlePlatform::Web => None,\n        };\n        platform_override.or(self.bundle.identifier.as_deref())\n    }\n}\n\nimpl Default for DioxusConfig {\n    fn default() -> Self {\n        Self {\n            application: ApplicationConfig {\n                asset_dir: None,\n                out_dir: None,\n                public_dir: Some(\"public\".into()),\n                tailwind_input: None,\n                tailwind_output: None,\n                ios_info_plist: None,\n                android_manifest: None,\n                android_main_activity: None,\n                android_min_sdk_version: None,\n                macos_info_plist: None,\n                ios_entitlements: None,\n                macos_entitlements: None,\n            },\n            web: WebConfig {\n                app: WebAppConfig {\n                    title: default_title(),\n                    base_path: None,\n                },\n                proxy: vec![],\n                watcher: Default::default(),\n                resource: WebResourceConfig {\n                    dev: WebDevResourceConfig {\n                        style: vec![],\n                        script: vec![],\n                    },\n                    style: Some(vec![]),\n                    script: Some(vec![]),\n                },\n                https: WebHttpsConfig {\n                    enabled: None,\n                    mkcert: None,\n                    key_path: None,\n                    cert_path: None,\n                },\n                pre_compress: false,\n                wasm_opt: Default::default(),\n            },\n            bundle: BundleConfig::default(),\n            components: ComponentConfig::default(),\n            permissions: PermissionsConfig::default(),\n            deep_links: DeepLinkConfig::default(),\n            background: BackgroundConfig::default(),\n            ios: IosConfig::default(),\n            android: AndroidConfig::default(),\n            macos: MacosConfig::default(),\n            windows: WindowsConfig::default(),\n            linux: LinuxConfig::default(),\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn static_dir_defaults_to_public() {\n        let config = DioxusConfig::default();\n        assert_eq!(\n            config.application.public_dir,\n            Some(std::path::PathBuf::from(\"public\"))\n        );\n    }\n\n    #[test]\n    fn static_dir_can_be_overridden() {\n        let source = r#\"\n            [application]\n            public_dir = \"public2\"\n        \"#;\n\n        let config: DioxusConfig = toml::from_str(source).expect(\"parse config\");\n        assert_eq!(\n            config.application.public_dir.as_deref(),\n            Some(std::path::Path::new(\"public2\"))\n        );\n    }\n\n    #[test]\n    fn static_dir_can_be_disabled() {\n        let source = r#\"\n            [application]\n            public_dir = \"\"\n        \"#;\n\n        let config: DioxusConfig = toml::from_str(source).expect(\"parse config\");\n        assert_eq!(config.application.public_dir.as_deref(), None);\n    }\n}\n"
  },
  {
    "path": "packages/cli/src/config/inline_config.rs",
    "content": "//! Extract and merge inline Dioxus.toml configuration from Rust source files.\n//!\n//! This module allows examples and tests to embed configuration in their doc comments:\n//!\n//! ```rust\n//! #![allow(unused)]\n//! //! This example demonstrates iOS geolocation\n//! //!\n//! //! ```dioxus.toml\n//! //! [bundle]\n//! //! identifier = \"com.example.geolocation\"\n//! //!\n//! //! [permissions]\n//! //! location = { precision = \"fine\", description = \"Track location\" }\n//! //! ```\n//! ```\n//!\n//! The embedded config is deep-merged with the workspace/app Dioxus.toml,\n//! with inline values taking precedence.\n\nuse std::path::Path;\n\n/// Extract embedded Dioxus.toml configuration from a Rust source file.\n///\n/// Parses the file's module-level doc comments (`//!`) looking for a fenced\n/// code block marked as `dioxus.toml`. Returns the parsed TOML as a generic\n/// Value for merging.\n///\n/// Handles inner attributes (`#![...]`) that may appear before doc comments.\n///\n/// # Example\n///\n/// ```rust,ignore\n/// let source = r#\"\n/// #![allow(unused)]\n/// //! Example showing permissions\n/// //!\n/// //! ```dioxus.toml\n/// //! [permissions]\n/// //! camera = { description = \"Take photos\" }\n/// //! ```\n///\n/// fn main() {}\n/// \"#;\n///\n/// let config = extract_inline_config(source).unwrap();\n/// ```\npub fn extract_inline_config(source: &str) -> Option<toml::Value> {\n    let doc_content = extract_module_doc_comments(source)?;\n    let toml_content = extract_toml_block(&doc_content)?;\n    toml::from_str(&toml_content).ok()\n}\n\n/// Extract inline config from a file path.\npub fn extract_inline_config_from_file(path: &Path) -> Option<toml::Value> {\n    let source = std::fs::read_to_string(path).ok()?;\n    extract_inline_config(&source)\n}\n\n/// Extract module-level doc comments (`//!`) from the beginning of a source file.\n///\n/// Handles:\n/// - Inner attributes (`#![...]`) that may precede doc comments\n/// - Empty lines between attributes and doc comments\n/// - Multi-line inner attributes\nfn extract_module_doc_comments(source: &str) -> Option<String> {\n    let mut doc_lines = Vec::new();\n    let mut in_doc_section = false;\n    let mut in_multiline_attr = false;\n\n    for line in source.lines() {\n        let trimmed = line.trim();\n\n        // Handle multi-line inner attributes (tracking bracket depth would be\n        // more robust, but this handles the common case)\n        if in_multiline_attr {\n            if trimmed.ends_with(']') || trimmed.contains(\")]\") {\n                in_multiline_attr = false;\n            }\n            continue;\n        }\n\n        // Skip empty lines before we've started collecting doc comments\n        if !in_doc_section && trimmed.is_empty() {\n            continue;\n        }\n\n        // Skip inner attributes (#![...])\n        if trimmed.starts_with(\"#![\") {\n            // Check if it's a multi-line attribute (doesn't close on same line)\n            if !trimmed.ends_with(']') {\n                in_multiline_attr = true;\n            }\n            continue;\n        }\n\n        // Also handle inner attributes with the #! on one line and [ on another (rare but possible)\n        // and block-style attributes like #![cfg(...)]\n        if trimmed.starts_with(\"#!\") {\n            continue;\n        }\n\n        // Check for module doc comment\n        if let Some(content) = trimmed.strip_prefix(\"//!\") {\n            in_doc_section = true;\n            // Remove the leading space if present\n            let content = content.strip_prefix(' ').unwrap_or(content);\n            doc_lines.push(content.to_string());\n        } else if in_doc_section {\n            // We've hit a non-doc-comment line after starting, stop collecting\n            break;\n        } else if !trimmed.is_empty() {\n            // Hit actual code before any doc comments\n            break;\n        }\n    }\n\n    if doc_lines.is_empty() {\n        None\n    } else {\n        Some(doc_lines.join(\"\\n\"))\n    }\n}\n\n/// Extract the content of a ```dioxus.toml fenced code block.\nfn extract_toml_block(doc_content: &str) -> Option<String> {\n    let mut in_toml_block = false;\n    let mut toml_lines = Vec::new();\n\n    for line in doc_content.lines() {\n        if !in_toml_block {\n            // Look for opening fence with dioxus.toml marker\n            let trimmed = line.trim();\n            if trimmed == \"```dioxus.toml\" || trimmed == \"```toml dioxus.toml\" {\n                in_toml_block = true;\n                continue;\n            }\n        } else {\n            // Check for closing fence\n            if line.trim() == \"```\" {\n                break;\n            }\n            toml_lines.push(line);\n        }\n    }\n\n    if toml_lines.is_empty() {\n        None\n    } else {\n        Some(toml_lines.join(\"\\n\"))\n    }\n}\n\n/// Deep merge two TOML values, with `override_val` taking precedence.\n///\n/// - For tables: recursively merge, with override values winning\n/// - For arrays: override replaces base entirely\n/// - For other values: override replaces base\npub fn merge_toml_values(base: toml::Value, override_val: toml::Value) -> toml::Value {\n    match (base, override_val) {\n        // Both are tables - deep merge\n        (toml::Value::Table(mut base_table), toml::Value::Table(override_table)) => {\n            for (key, override_value) in override_table {\n                match base_table.remove(&key) {\n                    Some(base_value) => {\n                        // Recursively merge\n                        base_table.insert(key, merge_toml_values(base_value, override_value));\n                    }\n                    None => {\n                        // Key only in override, just insert\n                        base_table.insert(key, override_value);\n                    }\n                }\n            }\n            toml::Value::Table(base_table)\n        }\n        // Override is not a table, or base is not a table - override wins\n        (_, override_val) => override_val,\n    }\n}\n\n/// Merge an inline config with a base DioxusConfig.\n///\n/// The inline config (from doc comments) takes precedence over the base config.\npub fn merge_with_inline_config(\n    base: &super::DioxusConfig,\n    inline: toml::Value,\n) -> Result<super::DioxusConfig, toml::de::Error> {\n    // Convert base to toml::Value\n    let base_str = toml::to_string(base).expect(\"DioxusConfig should serialize\");\n    let base_value: toml::Value = toml::from_str(&base_str).expect(\"Should parse back\");\n\n    // Merge\n    let merged = merge_toml_values(base_value, inline);\n\n    // Convert back to DioxusConfig\n    merged.try_into()\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn test_extract_module_doc_comments() {\n        let source = r#\"//! This is an example\n//! with multiple lines\n//!\n//! And a blank line\n\nfn main() {}\n\"#;\n\n        let docs = extract_module_doc_comments(source).unwrap();\n        assert!(docs.contains(\"This is an example\"));\n        assert!(docs.contains(\"with multiple lines\"));\n        assert!(docs.contains(\"And a blank line\"));\n    }\n\n    #[test]\n    fn test_extract_doc_comments_with_inner_attrs() {\n        let source = r#\"#![allow(unused)]\n#![cfg(feature = \"something\")]\n//! This is an example\n//! with inner attributes before it\n\nfn main() {}\n\"#;\n\n        let docs = extract_module_doc_comments(source).unwrap();\n        assert!(docs.contains(\"This is an example\"));\n        assert!(docs.contains(\"with inner attributes before it\"));\n        assert!(!docs.contains(\"allow\"));\n        assert!(!docs.contains(\"cfg\"));\n    }\n\n    #[test]\n    fn test_extract_doc_comments_with_multiline_attr() {\n        let source = r#\"#![cfg(\n    all(\n        feature = \"something\",\n        target_os = \"ios\"\n    )\n)]\n//! This is an example\n\nfn main() {}\n\"#;\n\n        let docs = extract_module_doc_comments(source).unwrap();\n        assert!(docs.contains(\"This is an example\"));\n        assert!(!docs.contains(\"cfg\"));\n        assert!(!docs.contains(\"feature\"));\n    }\n\n    #[test]\n    fn test_extract_toml_block() {\n        let doc = r#\"This is an example\n\n```dioxus.toml\n[bundle]\nidentifier = \"com.test\"\n```\n\nMore text here\"#;\n\n        let toml = extract_toml_block(doc).unwrap();\n        assert!(toml.contains(\"[bundle]\"));\n        assert!(toml.contains(\"identifier\"));\n    }\n\n    #[test]\n    fn test_extract_inline_config() {\n        let source = r#\"//! Example app\n//!\n//! ```dioxus.toml\n//! [bundle]\n//! identifier = \"com.example.test\"\n//!\n//! [permissions]\n//! camera = { description = \"Take photos\" }\n//! ```\n\nfn main() {}\n\"#;\n\n        let config = extract_inline_config(source).unwrap();\n        let table = config.as_table().unwrap();\n\n        assert!(table.contains_key(\"bundle\"));\n        assert!(table.contains_key(\"permissions\"));\n\n        let bundle = table[\"bundle\"].as_table().unwrap();\n        assert_eq!(bundle[\"identifier\"].as_str().unwrap(), \"com.example.test\");\n    }\n\n    #[test]\n    fn test_extract_inline_config_with_attrs() {\n        let source = r#\"#![allow(unused)]\n#![cfg(target_os = \"ios\")]\n//! iOS Example\n//!\n//! ```dioxus.toml\n//! [bundle]\n//! identifier = \"com.example.ios\"\n//! ```\n\nfn main() {}\n\"#;\n\n        let config = extract_inline_config(source).unwrap();\n        let table = config.as_table().unwrap();\n        let bundle = table[\"bundle\"].as_table().unwrap();\n        assert_eq!(bundle[\"identifier\"].as_str().unwrap(), \"com.example.ios\");\n    }\n\n    #[test]\n    fn test_no_inline_config() {\n        let source = r#\"//! Just a regular example\n//! No config here\n\nfn main() {}\n\"#;\n\n        assert!(extract_inline_config(source).is_none());\n    }\n\n    #[test]\n    fn test_merge_toml_values_simple() {\n        let base: toml::Value = toml::from_str(\n            r#\"\n            [bundle]\n            identifier = \"com.base\"\n            publisher = \"Base Publisher\"\n            \"#,\n        )\n        .unwrap();\n\n        let override_val: toml::Value = toml::from_str(\n            r#\"\n            [bundle]\n            identifier = \"com.override\"\n            \"#,\n        )\n        .unwrap();\n\n        let merged = merge_toml_values(base, override_val);\n        let table = merged.as_table().unwrap();\n        let bundle = table[\"bundle\"].as_table().unwrap();\n\n        // Override wins for identifier\n        assert_eq!(bundle[\"identifier\"].as_str().unwrap(), \"com.override\");\n        // Base value preserved for publisher\n        assert_eq!(bundle[\"publisher\"].as_str().unwrap(), \"Base Publisher\");\n    }\n\n    #[test]\n    fn test_merge_toml_values_deep() {\n        let base: toml::Value = toml::from_str(\n            r#\"\n            [permissions]\n            camera = { description = \"Base camera\" }\n            location = { precision = \"coarse\", description = \"Base location\" }\n            \"#,\n        )\n        .unwrap();\n\n        let override_val: toml::Value = toml::from_str(\n            r#\"\n            [permissions]\n            location = { precision = \"fine\" }\n            microphone = { description = \"Record audio\" }\n            \"#,\n        )\n        .unwrap();\n\n        let merged = merge_toml_values(base, override_val);\n        let perms = merged[\"permissions\"].as_table().unwrap();\n\n        // camera untouched\n        assert_eq!(\n            perms[\"camera\"][\"description\"].as_str().unwrap(),\n            \"Base camera\"\n        );\n\n        // location: precision overridden, description preserved\n        assert_eq!(perms[\"location\"][\"precision\"].as_str().unwrap(), \"fine\");\n        assert_eq!(\n            perms[\"location\"][\"description\"].as_str().unwrap(),\n            \"Base location\"\n        );\n\n        // microphone added\n        assert_eq!(\n            perms[\"microphone\"][\"description\"].as_str().unwrap(),\n            \"Record audio\"\n        );\n    }\n\n    #[test]\n    fn test_merge_adds_new_sections() {\n        let base: toml::Value = toml::from_str(\n            r#\"\n            [bundle]\n            identifier = \"com.base\"\n            \"#,\n        )\n        .unwrap();\n\n        let override_val: toml::Value = toml::from_str(\n            r#\"\n            [permissions]\n            camera = { description = \"Photos\" }\n            \"#,\n        )\n        .unwrap();\n\n        let merged = merge_toml_values(base, override_val);\n        let table = merged.as_table().unwrap();\n\n        // Both sections present\n        assert!(table.contains_key(\"bundle\"));\n        assert!(table.contains_key(\"permissions\"));\n    }\n}\n"
  },
  {
    "path": "packages/cli/src/config/manifest.rs",
    "content": "//! Unified manifest configuration for cross-platform app packaging.\n//!\n//! This module provides configuration structs for permissions and platform-specific\n//! manifest customization. Permissions declared here are automatically mapped to\n//! platform-specific identifiers (AndroidManifest.xml, Info.plist, etc.)\n//!\n//! ## JSON Schema Generation\n//!\n//! Generate a JSON schema for IDE autocomplete:\n//! ```bash\n//! dx config --schema > dioxus-schema.json\n//! ```\n\nuse schemars::JsonSchema;\nuse serde::{Deserialize, Serialize};\nuse std::collections::HashMap;\nuse std::path::PathBuf;\n\n// ============================================================================\n// Unified Permissions\n// ============================================================================\n\n/// Unified permission configuration that maps to platform-specific identifiers.\n///\n/// Example:\n/// ```toml\n/// [permissions]\n/// location = { precision = \"fine\", description = \"Track your runs\" }\n/// camera = { description = \"Take photos for your profile\" }\n/// ```\n#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]\npub struct PermissionsConfig {\n    /// Location permission with precision level.\n    /// Maps to ACCESS_FINE_LOCATION/ACCESS_COARSE_LOCATION on Android,\n    /// NSLocationWhenInUseUsageDescription on iOS/macOS.\n    #[serde(default)]\n    pub location: Option<LocationPermission>,\n\n    /// Camera access permission.\n    #[serde(default)]\n    pub camera: Option<SimplePermission>,\n\n    /// Microphone access permission.\n    #[serde(default)]\n    pub microphone: Option<SimplePermission>,\n\n    /// Push notifications permission.\n    #[serde(default)]\n    pub notifications: Option<SimplePermission>,\n\n    /// Photo library access.\n    #[serde(default)]\n    pub photos: Option<StoragePermission>,\n\n    /// Bluetooth connectivity.\n    #[serde(default)]\n    pub bluetooth: Option<SimplePermission>,\n\n    /// Background location updates.\n    #[serde(default, rename = \"background-location\")]\n    pub background_location: Option<SimplePermission>,\n\n    /// Contacts access.\n    #[serde(default)]\n    pub contacts: Option<StoragePermission>,\n\n    /// Calendar access.\n    #[serde(default)]\n    pub calendar: Option<StoragePermission>,\n\n    /// Biometric authentication (Face ID, fingerprint).\n    #[serde(default)]\n    pub biometrics: Option<SimplePermission>,\n\n    /// NFC access.\n    #[serde(default)]\n    pub nfc: Option<SimplePermission>,\n\n    /// Motion and fitness data.\n    #[serde(default)]\n    pub motion: Option<SimplePermission>,\n\n    /// Health data access.\n    #[serde(default)]\n    pub health: Option<StoragePermission>,\n\n    /// Speech recognition.\n    #[serde(default)]\n    pub speech: Option<SimplePermission>,\n\n    /// Media library access.\n    #[serde(default, rename = \"media-library\")]\n    pub media_library: Option<SimplePermission>,\n\n    /// Siri integration (iOS only).\n    #[serde(default)]\n    pub siri: Option<SimplePermission>,\n\n    /// HomeKit integration (iOS only).\n    #[serde(default)]\n    pub homekit: Option<SimplePermission>,\n\n    /// Local network access.\n    #[serde(default, rename = \"local-network\")]\n    pub local_network: Option<SimplePermission>,\n\n    /// Nearby Wi-Fi devices (Android).\n    #[serde(default, rename = \"nearby-wifi\")]\n    pub nearby_wifi: Option<SimplePermission>,\n}\n\n/// Simple permission with just a description.\n#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]\npub struct SimplePermission {\n    /// User-facing description shown in permission dialogs.\n    pub description: String,\n}\n\n// ============================================================================\n// Unified Deep Linking\n// ============================================================================\n\n/// Unified deep linking configuration.\n///\n/// This provides a cross-platform interface for URL schemes and universal/app links.\n/// Platform-specific overrides can be configured in `[ios]` and `[android]` sections.\n///\n/// Example:\n/// ```toml\n/// [deep_links]\n/// schemes = [\"myapp\", \"com.example.myapp\"]\n/// hosts = [\"example.com\", \"*.example.com\"]\n/// ```\n#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]\npub struct DeepLinkConfig {\n    /// Custom URL schemes (e.g., \"myapp\" for myapp://path).\n    /// Maps to CFBundleURLSchemes on iOS/macOS and intent-filter on Android.\n    #[serde(default)]\n    pub schemes: Vec<String>,\n\n    /// Universal link / App link hosts (e.g., \"example.com\").\n    /// Maps to Associated Domains on iOS and App Links on Android.\n    /// Supports wildcards like \"*.example.com\".\n    #[serde(default)]\n    pub hosts: Vec<String>,\n\n    /// Path patterns for universal/app links (e.g., \"/app/*\", \"/share/*\").\n    /// If empty, all paths are matched.\n    #[serde(default)]\n    pub paths: Vec<String>,\n}\n\n// ============================================================================\n// Unified Background Modes\n// ============================================================================\n\n/// Unified background execution configuration.\n///\n/// This provides a cross-platform interface for background capabilities.\n/// Platform-specific overrides can be configured in `[ios]` and `[android]` sections.\n///\n/// Example:\n/// ```toml\n/// [background]\n/// location = true\n/// audio = true\n/// fetch = true\n/// ```\n#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]\npub struct BackgroundConfig {\n    /// Background location updates.\n    /// iOS: UIBackgroundModes \"location\"\n    /// Android: ACCESS_BACKGROUND_LOCATION permission\n    #[serde(default)]\n    pub location: bool,\n\n    /// Background audio playback.\n    /// iOS: UIBackgroundModes \"audio\"\n    /// Android: FOREGROUND_SERVICE_MEDIA_PLAYBACK\n    #[serde(default)]\n    pub audio: bool,\n\n    /// Background data fetch.\n    /// iOS: UIBackgroundModes \"fetch\"\n    /// Android: WorkManager or foreground service\n    #[serde(default)]\n    pub fetch: bool,\n\n    /// Remote push notifications.\n    /// iOS: UIBackgroundModes \"remote-notification\"\n    /// Android: Firebase Cloud Messaging\n    #[serde(default, rename = \"remote-notifications\")]\n    pub remote_notifications: bool,\n\n    /// VoIP calls.\n    /// iOS: UIBackgroundModes \"voip\"\n    /// Android: FOREGROUND_SERVICE_PHONE_CALL\n    #[serde(default)]\n    pub voip: bool,\n\n    /// Bluetooth LE accessories.\n    /// iOS: UIBackgroundModes \"bluetooth-central\" and \"bluetooth-peripheral\"\n    /// Android: FOREGROUND_SERVICE_CONNECTED_DEVICE\n    #[serde(default)]\n    pub bluetooth: bool,\n\n    /// External accessory communication.\n    /// iOS: UIBackgroundModes \"external-accessory\"\n    #[serde(default, rename = \"external-accessory\")]\n    pub external_accessory: bool,\n\n    /// Background processing tasks.\n    /// iOS: UIBackgroundModes \"processing\"\n    /// Android: WorkManager\n    #[serde(default)]\n    pub processing: bool,\n}\n\n/// Location permission with precision control.\n#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]\npub struct LocationPermission {\n    /// Precision level: \"fine\" (GPS) or \"coarse\" (network-based).\n    #[serde(default)]\n    pub precision: LocationPrecision,\n\n    /// User-facing description shown in permission dialogs.\n    pub description: String,\n}\n\n#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, JsonSchema)]\npub enum LocationPrecision {\n    #[default]\n    #[serde(rename = \"fine\")]\n    Fine,\n    #[serde(rename = \"coarse\")]\n    Coarse,\n}\n\n/// Storage permission with access level control.\n#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]\npub struct StoragePermission {\n    /// Access level: \"read\", \"write\", or \"read-write\".\n    #[serde(default)]\n    pub access: StorageAccess,\n\n    /// User-facing description shown in permission dialogs.\n    pub description: String,\n}\n\n#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, JsonSchema)]\npub enum StorageAccess {\n    #[serde(rename = \"read\")]\n    Read,\n    #[serde(rename = \"write\")]\n    Write,\n    #[default]\n    #[serde(rename = \"read-write\")]\n    ReadWrite,\n}\n\n/// Raw platform permission entry.\n#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]\npub struct RawPermission {\n    pub description: String,\n}\n\n// ============================================================================\n// iOS Configuration\n// ============================================================================\n\n/// iOS-specific configuration.\n///\n/// Example:\n/// ```toml\n/// [ios]\n/// deployment_target = \"15.0\"\n/// identifier = \"com.example.myapp.ios\"  # Override bundle.identifier for iOS\n///\n/// [ios.entitlements]\n/// app-groups = [\"group.com.example.app\"]\n///\n/// [ios.plist]\n/// UIBackgroundModes = [\"location\", \"fetch\"]\n/// ```\n#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]\npub struct IosConfig {\n    // === Bundle settings (override [bundle] section) ===\n    /// The app's identifier (e.g., \"com.example.myapp\").\n    /// Overrides `bundle.identifier` for iOS builds.\n    #[serde(default)]\n    pub identifier: Option<String>,\n\n    /// The app's publisher.\n    /// Overrides `bundle.publisher` for iOS builds.\n    #[serde(default)]\n    pub publisher: Option<String>,\n\n    /// Icons for the app.\n    /// Overrides `bundle.icon` for iOS builds.\n    #[serde(default)]\n    pub icon: Option<Vec<String>>,\n\n    /// Additional resources to bundle.\n    /// Overrides `bundle.resources` for iOS builds.\n    #[serde(default)]\n    pub resources: Option<Vec<String>>,\n\n    /// Copyright notice.\n    /// Overrides `bundle.copyright` for iOS builds.\n    #[serde(default)]\n    pub copyright: Option<String>,\n\n    /// App category.\n    /// Overrides `bundle.category` for iOS builds.\n    #[serde(default)]\n    pub category: Option<String>,\n\n    /// Short description.\n    /// Overrides `bundle.short_description` for iOS builds.\n    #[serde(default)]\n    pub short_description: Option<String>,\n\n    /// Long description.\n    /// Overrides `bundle.long_description` for iOS builds.\n    #[serde(default)]\n    pub long_description: Option<String>,\n\n    // === iOS-specific settings ===\n    /// Minimum iOS deployment target (e.g., \"15.0\").\n    #[serde(default)]\n    pub deployment_target: Option<String>,\n\n    /// Path to custom Info.plist to merge with generated.\n    #[serde(default)]\n    pub info_plist: Option<PathBuf>,\n\n    /// iOS entitlements configuration.\n    #[serde(default)]\n    pub entitlements: IosEntitlements,\n\n    /// Additional Info.plist keys to merge.\n    #[serde(default)]\n    pub plist: HashMap<String, serde_json::Value>,\n\n    /// Raw XML injection points.\n    #[serde(default)]\n    pub raw: IosRawConfig,\n\n    // === Platform-specific overrides (extend unified config) ===\n    /// Additional URL schemes beyond unified `[deep_links]`.schemes.\n    /// These are merged with the unified schemes.\n    #[serde(default)]\n    pub url_schemes: Vec<String>,\n\n    /// Additional background modes beyond unified `[background]`.\n    /// Valid values: \"audio\", \"location\", \"voip\", \"fetch\", \"remote-notification\",\n    /// \"newsstand-content\", \"external-accessory\", \"bluetooth-central\",\n    /// \"bluetooth-peripheral\", \"processing\"\n    #[serde(default)]\n    pub background_modes: Vec<String>,\n\n    /// Document types the app can open.\n    #[serde(default)]\n    pub document_types: Vec<IosDocumentType>,\n\n    /// Exported type identifiers (custom UTIs).\n    #[serde(default)]\n    pub exported_type_identifiers: Vec<IosTypeIdentifier>,\n\n    /// Imported type identifiers.\n    #[serde(default)]\n    pub imported_type_identifiers: Vec<IosTypeIdentifier>,\n\n    /// Widget extensions to compile and bundle.\n    /// Each entry defines a Swift-based widget extension (.appex) that will be\n    /// compiled and installed into the app's PlugIns folder.\n    #[serde(default)]\n    pub widget_extensions: Vec<WidgetExtensionConfig>,\n}\n\n/// Configuration for an iOS Widget Extension.\n///\n/// Widget extensions are compiled as Swift executables and bundled as .appex\n/// bundles in the app's PlugIns folder.\n///\n/// Example in Dioxus.toml:\n/// ```toml\n/// [[ios.widget_extensions]]\n/// source = \"src/ios/widget\"\n/// display_name = \"Location Widget\"\n/// bundle_id_suffix = \"location-widget\"\n/// deployment_target = \"16.2\"\n/// module_name = \"GeolocationPlugin\"\n/// ```\n#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]\npub struct WidgetExtensionConfig {\n    /// Path to the Swift package source directory (relative to project root).\n    pub source: String,\n\n    /// Display name for the widget (shown in system UI).\n    pub display_name: String,\n\n    /// Bundle ID suffix appended to the app's bundle identifier.\n    /// For example, if the app is \"com.example.app\" and suffix is \"location-widget\",\n    /// the widget bundle ID will be \"com.example.app.location-widget\".\n    pub bundle_id_suffix: String,\n\n    /// Minimum deployment target (e.g., \"16.2\").\n    /// Defaults to the app's iOS deployment target if not specified.\n    #[serde(default)]\n    pub deployment_target: Option<String>,\n\n    /// Swift module name for the widget.\n    /// This MUST match the module name used by the main app's Swift plugin\n    /// for ActivityKit type matching to work.\n    pub module_name: String,\n}\n\n/// iOS document type declaration.\n#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]\npub struct IosDocumentType {\n    /// Document type name.\n    pub name: String,\n\n    /// File extensions (e.g., [\"txt\", \"md\"]).\n    #[serde(default)]\n    pub extensions: Vec<String>,\n\n    /// MIME types.\n    #[serde(default)]\n    pub mime_types: Vec<String>,\n\n    /// UTI types.\n    #[serde(default)]\n    pub types: Vec<String>,\n\n    /// Icon file name.\n    #[serde(default)]\n    pub icon: Option<String>,\n\n    /// Role: \"Editor\", \"Viewer\", \"Shell\", or \"None\".\n    #[serde(default)]\n    pub role: Option<String>,\n}\n\n/// iOS Uniform Type Identifier declaration.\n#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]\npub struct IosTypeIdentifier {\n    /// UTI identifier (e.g., \"com.example.myformat\").\n    pub identifier: String,\n\n    /// Human-readable description.\n    #[serde(default)]\n    pub description: Option<String>,\n\n    /// Conforms to these UTIs.\n    #[serde(default)]\n    pub conforms_to: Vec<String>,\n\n    /// File extensions.\n    #[serde(default)]\n    pub extensions: Vec<String>,\n\n    /// MIME types.\n    #[serde(default)]\n    pub mime_types: Vec<String>,\n}\n\n#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]\npub struct IosEntitlements {\n    /// App groups for shared data.\n    #[serde(default, rename = \"app-groups\")]\n    pub app_groups: Vec<String>,\n\n    /// Push notification environment: \"development\" or \"production\".\n    #[serde(default, rename = \"aps-environment\")]\n    pub aps_environment: Option<String>,\n\n    /// Associated domains for universal links.\n    #[serde(default, rename = \"associated-domains\")]\n    pub associated_domains: Vec<String>,\n\n    /// Enable iCloud container support.\n    #[serde(default)]\n    pub icloud: bool,\n\n    /// Keychain access groups.\n    #[serde(default, rename = \"keychain-access-groups\")]\n    pub keychain_access_groups: Vec<String>,\n\n    /// Enable Apple Pay.\n    #[serde(default, rename = \"apple-pay\")]\n    pub apple_pay: bool,\n\n    /// Enable HealthKit.\n    #[serde(default)]\n    pub healthkit: bool,\n\n    /// Enable HomeKit.\n    #[serde(default)]\n    pub homekit: bool,\n\n    /// Additional entitlements.\n    #[serde(flatten)]\n    pub additional: HashMap<String, serde_json::Value>,\n}\n\n#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]\npub struct IosRawConfig {\n    /// Raw XML to inject into Info.plist.\n    #[serde(default)]\n    pub info_plist: Option<String>,\n\n    /// Raw XML to inject into entitlements.plist.\n    #[serde(default)]\n    pub entitlements: Option<String>,\n}\n\n// ============================================================================\n// Android Configuration\n// ============================================================================\n\n/// Android-specific configuration.\n///\n/// Example:\n/// ```toml\n/// [android]\n/// min_sdk = 24\n/// target_sdk = 34\n/// identifier = \"com.example.myapp.android\"  # Override bundle.identifier for Android\n/// features = [\"android.hardware.location.gps\"]\n///\n/// # Android signing configuration (previously in [bundle.android])\n/// [android.signing]\n/// jks_file = \"keystore.jks\"\n/// jks_password = \"password\"\n/// key_alias = \"mykey\"\n/// key_password = \"keypassword\"\n///\n/// [android.permissions]\n/// \"android.permission.FOREGROUND_SERVICE\" = { description = \"Background service\" }\n/// ```\n#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]\npub struct AndroidConfig {\n    // === Bundle settings (override [bundle] section) ===\n    /// The app's identifier (e.g., \"com.example.myapp\").\n    /// Overrides `bundle.identifier` for Android builds.\n    #[serde(default)]\n    pub identifier: Option<String>,\n\n    /// The app's publisher.\n    /// Overrides `bundle.publisher` for Android builds.\n    #[serde(default)]\n    pub publisher: Option<String>,\n\n    /// Icons for the app.\n    /// Overrides `bundle.icon` for Android builds.\n    #[serde(default)]\n    pub icon: Option<Vec<String>>,\n\n    /// Additional resources to bundle.\n    /// Overrides `bundle.resources` for Android builds.\n    #[serde(default)]\n    pub resources: Option<Vec<String>>,\n\n    /// Copyright notice.\n    /// Overrides `bundle.copyright` for Android builds.\n    #[serde(default)]\n    pub copyright: Option<String>,\n\n    /// App category.\n    /// Overrides `bundle.category` for Android builds.\n    #[serde(default)]\n    pub category: Option<String>,\n\n    /// Short description.\n    /// Overrides `bundle.short_description` for Android builds.\n    #[serde(default)]\n    pub short_description: Option<String>,\n\n    /// Long description.\n    /// Overrides `bundle.long_description` for Android builds.\n    #[serde(default)]\n    pub long_description: Option<String>,\n\n    // === Android signing settings (previously in bundle.android) ===\n    /// Android signing configuration for release builds.\n    /// This replaces the deprecated `[bundle.android]` section.\n    #[serde(default)]\n    pub signing: Option<AndroidSigningConfig>,\n\n    // === Android-specific settings ===\n    /// Minimum SDK version.\n    #[serde(default)]\n    pub min_sdk: Option<u32>,\n\n    /// Target SDK version.\n    #[serde(default)]\n    pub target_sdk: Option<u32>,\n\n    /// Compile SDK version.\n    #[serde(default)]\n    pub compile_sdk: Option<u32>,\n\n    /// Hardware/software features required.\n    #[serde(default)]\n    pub features: Vec<String>,\n\n    /// Path to custom AndroidManifest.xml to merge.\n    #[serde(default)]\n    pub manifest: Option<PathBuf>,\n\n    /// Gradle dependencies to add.\n    #[serde(default)]\n    pub gradle_dependencies: Vec<String>,\n\n    /// Gradle plugins to apply.\n    #[serde(default)]\n    pub gradle_plugins: Vec<String>,\n\n    /// ProGuard rule files.\n    #[serde(default)]\n    pub proguard_rules: Vec<PathBuf>,\n\n    /// Additional Android permissions not in unified config.\n    #[serde(default)]\n    pub permissions: HashMap<String, RawPermission>,\n\n    /// Raw XML injection points.\n    #[serde(default)]\n    pub raw: AndroidRawConfig,\n\n    /// Application-level config.\n    #[serde(default)]\n    pub application: AndroidApplicationConfig,\n\n    // === Platform-specific overrides (extend unified config) ===\n    /// Additional URL schemes beyond unified `[deep_links]`.schemes.\n    /// These are merged with the unified schemes.\n    #[serde(default)]\n    pub url_schemes: Vec<String>,\n\n    /// Intent filters for deep linking.\n    /// These extend the unified `[deep_links]` configuration with Android-specific options.\n    #[serde(default)]\n    pub intent_filters: Vec<AndroidIntentFilter>,\n\n    /// Foreground service types for background operations.\n    /// Valid values: \"camera\", \"connectedDevice\", \"dataSync\", \"health\", \"location\",\n    /// \"mediaPlayback\", \"mediaProjection\", \"microphone\", \"phoneCall\", \"remoteMessaging\",\n    /// \"shortService\", \"specialUse\", \"systemExempted\"\n    #[serde(default)]\n    pub foreground_service_types: Vec<String>,\n\n    /// Queries for package visibility (required for Android 11+).\n    /// Specify packages or intents your app needs to query.\n    #[serde(default)]\n    pub queries: AndroidQueries,\n}\n\n/// Android signing configuration for release builds.\n#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]\npub struct AndroidSigningConfig {\n    /// Path to the Java keystore file.\n    pub jks_file: PathBuf,\n\n    /// Password for the keystore.\n    pub jks_password: String,\n\n    /// Alias of the key in the keystore.\n    pub key_alias: String,\n\n    /// Password for the key.\n    pub key_password: String,\n}\n\n/// Android intent filter for deep linking.\n#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]\npub struct AndroidIntentFilter {\n    /// Actions (e.g., \"android.intent.action.VIEW\").\n    #[serde(default)]\n    pub actions: Vec<String>,\n\n    /// Categories (e.g., \"android.intent.category.DEFAULT\", \"android.intent.category.BROWSABLE\").\n    #[serde(default)]\n    pub categories: Vec<String>,\n\n    /// Data specifications.\n    #[serde(default)]\n    pub data: Vec<AndroidIntentData>,\n\n    /// Auto-verify for App Links (requires HTTPS and assetlinks.json).\n    #[serde(default)]\n    pub auto_verify: bool,\n}\n\n/// Android intent data specification.\n#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]\npub struct AndroidIntentData {\n    /// URL scheme (e.g., \"https\", \"myapp\").\n    #[serde(default)]\n    pub scheme: Option<String>,\n\n    /// Host (e.g., \"example.com\").\n    #[serde(default)]\n    pub host: Option<String>,\n\n    /// Port number.\n    #[serde(default)]\n    pub port: Option<String>,\n\n    /// Path (exact match).\n    #[serde(default)]\n    pub path: Option<String>,\n\n    /// Path prefix.\n    #[serde(default)]\n    pub path_prefix: Option<String>,\n\n    /// Path pattern (with wildcards).\n    #[serde(default)]\n    pub path_pattern: Option<String>,\n\n    /// MIME type.\n    #[serde(default)]\n    pub mime_type: Option<String>,\n}\n\n/// Android package visibility queries.\n#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]\npub struct AndroidQueries {\n    /// Package names to query.\n    #[serde(default)]\n    pub packages: Vec<String>,\n\n    /// Intent actions to query.\n    #[serde(default)]\n    pub intents: Vec<AndroidQueryIntent>,\n}\n\n/// Android query intent specification.\n#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]\npub struct AndroidQueryIntent {\n    /// Action (e.g., \"android.intent.action.SEND\").\n    pub action: String,\n\n    /// Data scheme (e.g., \"mailto\").\n    #[serde(default)]\n    pub scheme: Option<String>,\n\n    /// MIME type (e.g., \"text/plain\").\n    #[serde(default)]\n    pub mime_type: Option<String>,\n}\n\n#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]\npub struct AndroidRawConfig {\n    /// Raw XML to inject into manifest (after permissions).\n    #[serde(default)]\n    pub manifest: Option<String>,\n\n    /// Raw attributes for `<application>` element.\n    #[serde(default)]\n    pub application_attrs: Option<String>,\n\n    /// Raw XML inside `<application>` element.\n    #[serde(default)]\n    pub application: Option<String>,\n}\n\n#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]\npub struct AndroidApplicationConfig {\n    /// Enable cleartext (HTTP) traffic.\n    #[serde(default)]\n    pub uses_cleartext_traffic: Option<bool>,\n\n    /// Application theme.\n    #[serde(default)]\n    pub theme: Option<String>,\n\n    /// RTL layout support.\n    #[serde(default)]\n    pub supports_rtl: Option<bool>,\n\n    /// Enable large heap.\n    #[serde(default)]\n    pub large_heap: Option<bool>,\n}\n\n// ============================================================================\n// macOS Configuration\n// ============================================================================\n\n/// macOS-specific configuration.\n///\n/// Example:\n/// ```toml\n/// [macos]\n/// minimum_system_version = \"11.0\"\n/// identifier = \"com.example.myapp.macos\"  # Override bundle.identifier for macOS\n///\n/// # macOS signing (previously in [bundle.macos])\n/// signing_identity = \"Developer ID Application: My Company\"\n/// provider_short_name = \"MYCOMPANY\"\n/// ```\n#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]\npub struct MacosConfig {\n    // === Bundle settings (override [bundle] section) ===\n    /// The app's identifier (e.g., \"com.example.myapp\").\n    /// Overrides `bundle.identifier` for macOS builds.\n    #[serde(default)]\n    pub identifier: Option<String>,\n\n    /// The app's publisher.\n    /// Overrides `bundle.publisher` for macOS builds.\n    #[serde(default)]\n    pub publisher: Option<String>,\n\n    /// Icons for the app.\n    /// Overrides `bundle.icon` for macOS builds.\n    #[serde(default)]\n    pub icon: Option<Vec<String>>,\n\n    /// Additional resources to bundle.\n    /// Overrides `bundle.resources` for macOS builds.\n    #[serde(default)]\n    pub resources: Option<Vec<String>>,\n\n    /// Copyright notice.\n    /// Overrides `bundle.copyright` for macOS builds.\n    #[serde(default)]\n    pub copyright: Option<String>,\n\n    /// Short description.\n    /// Overrides `bundle.short_description` for macOS builds.\n    #[serde(default)]\n    pub short_description: Option<String>,\n\n    /// Long description.\n    /// Overrides `bundle.long_description` for macOS builds.\n    #[serde(default)]\n    pub long_description: Option<String>,\n\n    // === macOS bundle settings (previously in bundle.macos) ===\n    /// The bundle version string (CFBundleVersion).\n    #[serde(default)]\n    pub bundle_version: Option<String>,\n\n    /// The bundle short version string (CFBundleShortVersionString).\n    #[serde(default)]\n    pub bundle_name: Option<String>,\n\n    /// The signing identity to use for code signing.\n    /// E.g., \"Developer ID Application: My Company (TEAMID)\"\n    #[serde(default)]\n    pub signing_identity: Option<String>,\n\n    /// The provider short name for notarization.\n    #[serde(default)]\n    pub provider_short_name: Option<String>,\n\n    /// Path to custom entitlements file for code signing.\n    /// This overrides the generated entitlements.\n    #[serde(default)]\n    pub entitlements_file: Option<String>,\n\n    /// Exception domain for App Transport Security.\n    #[serde(default)]\n    pub exception_domain: Option<String>,\n\n    /// License file to include in DMG.\n    #[serde(default)]\n    pub license: Option<String>,\n\n    /// Preserve the hardened runtime version flag.\n    /// Setting this to false is useful when using an ad-hoc signature.\n    #[serde(default)]\n    pub hardened_runtime: Option<bool>,\n\n    /// Additional files to include in the app bundle.\n    /// Maps the path in the Contents directory to the source file path.\n    #[serde(default)]\n    pub files: HashMap<PathBuf, PathBuf>,\n\n    // === macOS-specific settings ===\n    /// Minimum macOS version (e.g., \"11.0\").\n    #[serde(default)]\n    pub minimum_system_version: Option<String>,\n\n    /// Path to custom Info.plist.\n    #[serde(default)]\n    pub info_plist: Option<PathBuf>,\n\n    /// Frameworks to embed.\n    #[serde(default)]\n    pub frameworks: Vec<String>,\n\n    /// macOS entitlements.\n    #[serde(default)]\n    pub entitlements: MacosEntitlements,\n\n    /// Additional Info.plist keys.\n    #[serde(default)]\n    pub plist: HashMap<String, serde_json::Value>,\n\n    /// Raw injection points.\n    #[serde(default)]\n    pub raw: MacosRawConfig,\n\n    // === Platform-specific overrides (extend unified config) ===\n    /// Additional URL schemes beyond unified `[deep_links]`.schemes.\n    /// These are merged with the unified schemes.\n    #[serde(default)]\n    pub url_schemes: Vec<String>,\n\n    /// Document types the app can open (uses same format as iOS).\n    #[serde(default)]\n    pub document_types: Vec<IosDocumentType>,\n\n    /// Exported type identifiers (custom UTIs).\n    #[serde(default)]\n    pub exported_type_identifiers: Vec<IosTypeIdentifier>,\n\n    /// Imported type identifiers.\n    #[serde(default)]\n    pub imported_type_identifiers: Vec<IosTypeIdentifier>,\n\n    /// App category for the Mac App Store.\n    /// E.g., \"public.app-category.productivity\"\n    #[serde(default)]\n    pub category: Option<String>,\n}\n\n#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]\npub struct MacosEntitlements {\n    /// Enable App Sandbox.\n    #[serde(default, rename = \"app-sandbox\")]\n    pub app_sandbox: Option<bool>,\n\n    /// User-selected file access (read-write).\n    #[serde(default, rename = \"files-user-selected\")]\n    pub files_user_selected: Option<bool>,\n\n    /// User-selected file access (read-only).\n    #[serde(default, rename = \"files-user-selected-readonly\")]\n    pub files_user_selected_readonly: Option<bool>,\n\n    /// Outgoing network connections.\n    #[serde(default, rename = \"network-client\")]\n    pub network_client: Option<bool>,\n\n    /// Incoming network connections.\n    #[serde(default, rename = \"network-server\")]\n    pub network_server: Option<bool>,\n\n    /// Camera access.\n    #[serde(default)]\n    pub camera: Option<bool>,\n\n    /// Microphone access.\n    #[serde(default)]\n    pub microphone: Option<bool>,\n\n    /// USB access.\n    #[serde(default)]\n    pub usb: Option<bool>,\n\n    /// Bluetooth access.\n    #[serde(default)]\n    pub bluetooth: Option<bool>,\n\n    /// Printing.\n    #[serde(default)]\n    pub print: Option<bool>,\n\n    /// Location services.\n    #[serde(default)]\n    pub location: Option<bool>,\n\n    /// Address book access.\n    #[serde(default)]\n    pub addressbook: Option<bool>,\n\n    /// Calendars access.\n    #[serde(default)]\n    pub calendars: Option<bool>,\n\n    /// Disable library validation.\n    #[serde(default, rename = \"disable-library-validation\")]\n    pub disable_library_validation: Option<bool>,\n\n    /// Allow JIT.\n    #[serde(default, rename = \"allow-jit\")]\n    pub allow_jit: Option<bool>,\n\n    /// Allow unsigned executable memory.\n    #[serde(default, rename = \"allow-unsigned-executable-memory\")]\n    pub allow_unsigned_executable_memory: Option<bool>,\n\n    /// Additional entitlements.\n    #[serde(flatten)]\n    pub additional: HashMap<String, serde_json::Value>,\n}\n\n#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]\npub struct MacosRawConfig {\n    /// Raw XML to inject into Info.plist.\n    #[serde(default)]\n    pub info_plist: Option<String>,\n\n    /// Raw XML to inject into entitlements.plist.\n    #[serde(default)]\n    pub entitlements: Option<String>,\n}\n\n// ============================================================================\n// Windows Configuration\n// ============================================================================\n\n/// Windows-specific configuration.\n///\n/// Example:\n/// ```toml\n/// [windows]\n/// identifier = \"com.example.myapp.windows\"  # Override bundle.identifier for Windows\n///\n/// # Windows installer settings (previously in [bundle.windows])\n/// [windows.nsis]\n/// install_mode = \"PerMachine\"\n///\n/// [windows.wix]\n/// language = [[\"en-US\", null]]\n/// ```\n#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]\npub struct WindowsConfig {\n    // === Bundle settings (override [bundle] section) ===\n    /// The app's identifier (e.g., \"com.example.myapp\").\n    /// Overrides `bundle.identifier` for Windows builds.\n    #[serde(default)]\n    pub identifier: Option<String>,\n\n    /// The app's publisher.\n    /// Overrides `bundle.publisher` for Windows builds.\n    #[serde(default)]\n    pub publisher: Option<String>,\n\n    /// Icons for the app.\n    /// Overrides `bundle.icon` for Windows builds.\n    #[serde(default)]\n    pub icon: Option<Vec<String>>,\n\n    /// Additional resources to bundle.\n    /// Overrides `bundle.resources` for Windows builds.\n    #[serde(default)]\n    pub resources: Option<Vec<String>>,\n\n    /// Copyright notice.\n    /// Overrides `bundle.copyright` for Windows builds.\n    #[serde(default)]\n    pub copyright: Option<String>,\n\n    /// App category.\n    /// Overrides `bundle.category` for Windows builds.\n    #[serde(default)]\n    pub category: Option<String>,\n\n    /// Short description.\n    /// Overrides `bundle.short_description` for Windows builds.\n    #[serde(default)]\n    pub short_description: Option<String>,\n\n    /// Long description.\n    /// Overrides `bundle.long_description` for Windows builds.\n    #[serde(default)]\n    pub long_description: Option<String>,\n\n    // === Windows bundle settings (previously in bundle.windows) ===\n    /// Digest algorithm for code signing.\n    #[serde(default)]\n    pub digest_algorithm: Option<String>,\n\n    /// Certificate thumbprint for code signing.\n    #[serde(default)]\n    pub certificate_thumbprint: Option<String>,\n\n    /// Timestamp server URL for code signing.\n    #[serde(default)]\n    pub timestamp_url: Option<String>,\n\n    /// Use TSP (RFC 3161) timestamp.\n    #[serde(default)]\n    pub tsp: Option<bool>,\n\n    /// WiX installer settings.\n    #[serde(default)]\n    pub wix: Option<WindowsWixSettings>,\n\n    /// NSIS installer settings.\n    #[serde(default)]\n    pub nsis: Option<WindowsNsisSettings>,\n\n    /// Path to custom Windows icon.\n    #[serde(default)]\n    pub icon_path: Option<PathBuf>,\n\n    /// WebView2 installation mode.\n    #[serde(default)]\n    pub webview_install_mode: Option<WindowsWebviewInstallMode>,\n\n    /// Allow downgrades when installing.\n    #[serde(default)]\n    pub allow_downgrades: Option<bool>,\n\n    /// Custom sign command.\n    #[serde(default)]\n    pub sign_command: Option<WindowsSignCommand>,\n\n    // === Windows-specific settings ===\n    /// UWP/MSIX capabilities.\n    #[serde(default)]\n    pub capabilities: Vec<String>,\n\n    /// Restricted capabilities.\n    #[serde(default)]\n    pub restricted_capabilities: Vec<String>,\n\n    /// Device capabilities.\n    #[serde(default)]\n    pub device_capabilities: Vec<String>,\n}\n\n/// WiX installer settings.\n#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]\npub struct WindowsWixSettings {\n    /// Languages and their locale paths.\n    #[serde(default)]\n    pub language: Vec<(String, Option<PathBuf>)>,\n\n    /// Path to custom WiX template.\n    #[serde(default)]\n    pub template: Option<PathBuf>,\n\n    /// WiX fragment files to include.\n    #[serde(default)]\n    pub fragment_paths: Vec<PathBuf>,\n\n    /// Component group references.\n    #[serde(default)]\n    pub component_group_refs: Vec<String>,\n\n    /// Component references.\n    #[serde(default)]\n    pub component_refs: Vec<String>,\n\n    /// Feature group references.\n    #[serde(default)]\n    pub feature_group_refs: Vec<String>,\n\n    /// Feature references.\n    #[serde(default)]\n    pub feature_refs: Vec<String>,\n\n    /// Merge module references.\n    #[serde(default)]\n    pub merge_refs: Vec<String>,\n\n    /// Skip WebView2 installation.\n    #[serde(default)]\n    pub skip_webview_install: Option<bool>,\n\n    /// License file path.\n    #[serde(default)]\n    pub license: Option<PathBuf>,\n\n    /// Enable elevated update task.\n    #[serde(default)]\n    pub enable_elevated_update_task: Option<bool>,\n\n    /// Banner image path.\n    #[serde(default)]\n    pub banner_path: Option<PathBuf>,\n\n    /// Dialog image path.\n    #[serde(default)]\n    pub dialog_image_path: Option<PathBuf>,\n\n    /// FIPS compliant mode.\n    #[serde(default)]\n    pub fips_compliant: Option<bool>,\n\n    /// MSI version string.\n    #[serde(default)]\n    pub version: Option<String>,\n\n    /// MSI upgrade code (GUID).\n    #[serde(default)]\n    #[schemars(with = \"Option<String>\")]\n    pub upgrade_code: Option<uuid::Uuid>,\n}\n\n/// NSIS installer settings.\n#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]\npub struct WindowsNsisSettings {\n    /// Path to custom NSIS template.\n    #[serde(default)]\n    pub template: Option<PathBuf>,\n\n    /// License file path.\n    #[serde(default)]\n    pub license: Option<PathBuf>,\n\n    /// Header image path.\n    #[serde(default)]\n    pub header_image: Option<PathBuf>,\n\n    /// Sidebar image path.\n    #[serde(default)]\n    pub sidebar_image: Option<PathBuf>,\n\n    /// Installer icon path.\n    #[serde(default)]\n    pub installer_icon: Option<PathBuf>,\n\n    /// Installation mode: \"CurrentUser\", \"PerMachine\", or \"Both\".\n    #[serde(default)]\n    pub install_mode: Option<String>,\n\n    /// Languages to include.\n    #[serde(default)]\n    pub languages: Option<Vec<String>>,\n\n    /// Custom language files.\n    #[serde(default)]\n    pub custom_language_files: Option<HashMap<String, PathBuf>>,\n\n    /// Display language selector.\n    #[serde(default)]\n    pub display_language_selector: Option<bool>,\n\n    /// Start menu folder name.\n    #[serde(default)]\n    pub start_menu_folder: Option<String>,\n\n    /// Installer hooks script path.\n    #[serde(default)]\n    pub installer_hooks: Option<PathBuf>,\n\n    /// Minimum WebView2 version required.\n    #[serde(default)]\n    pub minimum_webview2_version: Option<String>,\n}\n\n/// WebView2 installation mode.\n#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]\n#[serde(tag = \"type\")]\npub enum WindowsWebviewInstallMode {\n    /// Skip WebView2 installation.\n    Skip,\n    /// Download bootstrapper.\n    DownloadBootstrapper {\n        #[serde(default)]\n        silent: bool,\n    },\n    /// Embed bootstrapper.\n    EmbedBootstrapper {\n        #[serde(default)]\n        silent: bool,\n    },\n    /// Use offline installer.\n    OfflineInstaller {\n        #[serde(default)]\n        silent: bool,\n    },\n    /// Use fixed runtime from path.\n    FixedRuntime { path: PathBuf },\n}\n\n/// Custom sign command for Windows code signing.\n#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]\npub struct WindowsSignCommand {\n    /// The command to run.\n    pub cmd: String,\n    /// Command arguments. Use \"%1\" as placeholder for binary path.\n    pub args: Vec<String>,\n}\n\n// ============================================================================\n// Linux Configuration\n// ============================================================================\n\n/// Linux-specific configuration.\n///\n/// Example:\n/// ```toml\n/// [linux]\n/// identifier = \"com.example.myapp.linux\"  # Override bundle.identifier for Linux\n/// categories = [\"Utility\"]\n///\n/// # Debian package settings (previously in [bundle.deb])\n/// [linux.deb]\n/// depends = [\"libwebkit2gtk-4.0-37\"]\n/// section = \"utils\"\n/// ```\n#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]\npub struct LinuxConfig {\n    // === Bundle settings (override [bundle] section) ===\n    /// The app's identifier (e.g., \"com.example.myapp\").\n    /// Overrides `bundle.identifier` for Linux builds.\n    #[serde(default)]\n    pub identifier: Option<String>,\n\n    /// The app's publisher.\n    /// Overrides `bundle.publisher` for Linux builds.\n    #[serde(default)]\n    pub publisher: Option<String>,\n\n    /// Icons for the app.\n    /// Overrides `bundle.icon` for Linux builds.\n    #[serde(default)]\n    pub icon: Option<Vec<String>>,\n\n    /// Additional resources to bundle.\n    /// Overrides `bundle.resources` for Linux builds.\n    #[serde(default)]\n    pub resources: Option<Vec<String>>,\n\n    /// Copyright notice.\n    /// Overrides `bundle.copyright` for Linux builds.\n    #[serde(default)]\n    pub copyright: Option<String>,\n\n    /// App category.\n    /// Overrides `bundle.category` for Linux builds.\n    #[serde(default)]\n    pub category: Option<String>,\n\n    /// Short description.\n    /// Overrides `bundle.short_description` for Linux builds.\n    #[serde(default)]\n    pub short_description: Option<String>,\n\n    /// Long description.\n    /// Overrides `bundle.long_description` for Linux builds.\n    #[serde(default)]\n    pub long_description: Option<String>,\n\n    // === Debian package settings (previously in bundle.deb) ===\n    /// Debian-specific package settings.\n    #[serde(default)]\n    pub deb: Option<LinuxDebSettings>,\n\n    // === Linux-specific settings ===\n    /// Flatpak sandbox permissions.\n    #[serde(default)]\n    pub flatpak_permissions: Vec<String>,\n\n    /// D-Bus interfaces to access.\n    #[serde(default)]\n    pub dbus_access: Vec<String>,\n\n    /// Desktop entry categories.\n    #[serde(default)]\n    pub categories: Vec<String>,\n\n    /// Desktop entry keywords.\n    #[serde(default)]\n    pub keywords: Vec<String>,\n\n    /// MIME types the app can handle.\n    #[serde(default)]\n    pub mime_types: Vec<String>,\n}\n\n/// Debian package settings.\n#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]\npub struct LinuxDebSettings {\n    /// Package dependencies.\n    #[serde(default)]\n    pub depends: Option<Vec<String>>,\n\n    /// Recommended packages.\n    #[serde(default)]\n    pub recommends: Option<Vec<String>>,\n\n    /// Packages this provides.\n    #[serde(default)]\n    pub provides: Option<Vec<String>>,\n\n    /// Package conflicts.\n    #[serde(default)]\n    pub conflicts: Option<Vec<String>>,\n\n    /// Packages this replaces.\n    #[serde(default)]\n    pub replaces: Option<Vec<String>>,\n\n    /// Additional files to include. Maps package path to source path.\n    #[serde(default)]\n    pub files: HashMap<PathBuf, PathBuf>,\n\n    /// Path to custom desktop template.\n    #[serde(default)]\n    pub desktop_template: Option<PathBuf>,\n\n    /// Debian section (e.g., \"utils\", \"web\").\n    #[serde(default)]\n    pub section: Option<String>,\n\n    /// Package priority (\"required\", \"important\", \"standard\", \"optional\", \"extra\").\n    #[serde(default)]\n    pub priority: Option<String>,\n\n    /// Path to changelog file.\n    #[serde(default)]\n    pub changelog: Option<PathBuf>,\n\n    /// Pre-install script path.\n    #[serde(default)]\n    pub pre_install_script: Option<PathBuf>,\n\n    /// Post-install script path.\n    #[serde(default)]\n    pub post_install_script: Option<PathBuf>,\n\n    /// Pre-remove script path.\n    #[serde(default)]\n    pub pre_remove_script: Option<PathBuf>,\n\n    /// Post-remove script path.\n    #[serde(default)]\n    pub post_remove_script: Option<PathBuf>,\n}\n\n// ============================================================================\n// Schema Generation\n// ============================================================================\n\n/// Generate a JSON schema for the complete Dioxus.toml configuration.\n///\n/// This can be used for IDE autocomplete when editing Dioxus.toml files.\n/// The schema includes all configuration: application, web, bundle, permissions,\n/// platform-specific settings, and more.\n///\n/// Note: Default values are stripped and allOf wrappers simplified to prevent\n/// stack overflow in some TOML LSP implementations (e.g., Taplo's WASM build).\npub fn generate_manifest_schema() -> schemars::schema::RootSchema {\n    let mut schema = schemars::schema_for!(super::DioxusConfig);\n\n    // Simplify schema to prevent Taplo WASM LSP stack overflow.\n    // 1. Strip default values (large nested objects cause issues)\n    // 2. Simplify allOf wrappers around single $refs\n    simplify_schema(&mut schema.schema);\n    for def in schema.definitions.values_mut() {\n        if let schemars::schema::Schema::Object(obj) = def {\n            simplify_schema(obj);\n        }\n    }\n\n    schema\n}\n\n/// Recursively simplify a schema object for LSP compatibility.\n/// - Removes default values (large nested objects cause stack overflow)\n/// - Simplifies `allOf: [$ref]` to just `$ref` (reduces recursion depth)\nfn simplify_schema(schema: &mut schemars::schema::SchemaObject) {\n    // Remove the default value from this schema\n    schema.metadata().default = None;\n\n    // Simplify allOf with single $ref: { allOf: [{ $ref: \"...\" }] } -> { $ref: \"...\" }\n    let mut ref_to_promote = None;\n    if let Some(subschemas) = &schema.subschemas {\n        if let Some(all_of) = &subschemas.all_of {\n            if all_of.len() == 1 {\n                if let schemars::schema::Schema::Object(inner) = &all_of[0] {\n                    if inner.reference.is_some()\n                        && inner.instance_type.is_none()\n                        && inner.object.is_none()\n                        && inner.array.is_none()\n                        && inner.subschemas.is_none()\n                    {\n                        ref_to_promote = inner.reference.clone();\n                    }\n                }\n            }\n        }\n    }\n    if let Some(r) = ref_to_promote {\n        schema.subschemas = None;\n        schema.reference = Some(r);\n    }\n\n    // Process remaining subschemas\n    if let Some(subschemas) = &mut schema.subschemas {\n        if let Some(all_of) = &mut subschemas.all_of {\n            for s in all_of {\n                if let schemars::schema::Schema::Object(obj) = s {\n                    simplify_schema(obj);\n                }\n            }\n        }\n        if let Some(any_of) = &mut subschemas.any_of {\n            for s in any_of {\n                if let schemars::schema::Schema::Object(obj) = s {\n                    simplify_schema(obj);\n                }\n            }\n        }\n        if let Some(one_of) = &mut subschemas.one_of {\n            for s in one_of {\n                if let schemars::schema::Schema::Object(obj) = s {\n                    simplify_schema(obj);\n                }\n            }\n        }\n    }\n\n    // Process object properties\n    if let Some(object) = &mut schema.object {\n        for prop in object.properties.values_mut() {\n            if let schemars::schema::Schema::Object(obj) = prop {\n                simplify_schema(obj);\n            }\n        }\n        if let Some(additional) = &mut object.additional_properties {\n            if let schemars::schema::Schema::Object(obj) = additional.as_mut() {\n                simplify_schema(obj);\n            }\n        }\n    }\n\n    // Process array items\n    if let Some(array) = &mut schema.array {\n        if let Some(items) = &mut array.items {\n            match items {\n                schemars::schema::SingleOrVec::Single(s) => {\n                    if let schemars::schema::Schema::Object(obj) = s.as_mut() {\n                        simplify_schema(obj);\n                    }\n                }\n                schemars::schema::SingleOrVec::Vec(v) => {\n                    for s in v {\n                        if let schemars::schema::Schema::Object(obj) = s {\n                            simplify_schema(obj);\n                        }\n                    }\n                }\n            }\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn test_parse_permissions() {\n        let toml = r#\"\n            [permissions]\n            location = { precision = \"fine\", description = \"Track your runs\" }\n            camera = { description = \"Take photos\" }\n        \"#;\n\n        #[derive(Deserialize)]\n        struct Config {\n            permissions: PermissionsConfig,\n        }\n\n        let config: Config = toml::from_str(toml).unwrap();\n        let loc = config.permissions.location.unwrap();\n        assert_eq!(loc.precision, LocationPrecision::Fine);\n        assert_eq!(loc.description, \"Track your runs\");\n        assert!(config.permissions.camera.is_some());\n    }\n\n    #[test]\n    fn test_parse_ios_config() {\n        let toml = r#\"\n            [ios]\n            deployment_target = \"15.0\"\n\n            [ios.entitlements]\n            app-groups = [\"group.com.example.app\"]\n        \"#;\n\n        #[derive(Deserialize)]\n        struct Config {\n            ios: IosConfig,\n        }\n\n        let config: Config = toml::from_str(toml).unwrap();\n        assert_eq!(config.ios.deployment_target, Some(\"15.0\".to_string()));\n        assert_eq!(\n            config.ios.entitlements.app_groups,\n            vec![\"group.com.example.app\"]\n        );\n    }\n\n    #[test]\n    fn test_parse_android_config() {\n        let toml = r#\"\n            [android]\n            min_sdk = 24\n            target_sdk = 34\n\n            [android.permissions]\n            \"android.permission.FOREGROUND_SERVICE\" = { description = \"Background\" }\n        \"#;\n\n        #[derive(Deserialize)]\n        struct Config {\n            android: AndroidConfig,\n        }\n\n        let config: Config = toml::from_str(toml).unwrap();\n        assert_eq!(config.android.min_sdk, Some(24));\n        assert!(config\n            .android\n            .permissions\n            .contains_key(\"android.permission.FOREGROUND_SERVICE\"));\n    }\n\n    #[test]\n    fn test_parse_deep_links() {\n        let toml = r#\"\n            [deep_links]\n            schemes = [\"myapp\", \"com.example.app\"]\n            hosts = [\"example.com\", \"*.example.com\"]\n            paths = [\"/app/*\", \"/share/*\"]\n        \"#;\n\n        #[derive(Deserialize)]\n        struct Config {\n            deep_links: DeepLinkConfig,\n        }\n\n        let config: Config = toml::from_str(toml).unwrap();\n        assert_eq!(config.deep_links.schemes, vec![\"myapp\", \"com.example.app\"]);\n        assert_eq!(\n            config.deep_links.hosts,\n            vec![\"example.com\", \"*.example.com\"]\n        );\n        assert_eq!(config.deep_links.paths, vec![\"/app/*\", \"/share/*\"]);\n    }\n\n    #[test]\n    fn test_parse_background_modes() {\n        let toml = r#\"\n            [background]\n            location = true\n            audio = true\n            fetch = true\n            remote-notifications = true\n        \"#;\n\n        #[derive(Deserialize)]\n        struct Config {\n            background: BackgroundConfig,\n        }\n\n        let config: Config = toml::from_str(toml).unwrap();\n        assert!(config.background.location);\n        assert!(config.background.audio);\n        assert!(config.background.fetch);\n        assert!(config.background.remote_notifications);\n        assert!(!config.background.voip);\n    }\n\n    #[test]\n    fn test_parse_ios_url_schemes_and_background() {\n        let toml = r#\"\n            [ios]\n            deployment_target = \"15.0\"\n            url_schemes = [\"myapp-ios\"]\n            background_modes = [\"location\", \"fetch\", \"remote-notification\"]\n\n            [[ios.document_types]]\n            name = \"My Document\"\n            extensions = [\"mydoc\"]\n            role = \"Editor\"\n        \"#;\n\n        #[derive(Deserialize)]\n        struct Config {\n            ios: IosConfig,\n        }\n\n        let config: Config = toml::from_str(toml).unwrap();\n        assert_eq!(config.ios.url_schemes, vec![\"myapp-ios\"]);\n        assert_eq!(\n            config.ios.background_modes,\n            vec![\"location\", \"fetch\", \"remote-notification\"]\n        );\n        assert_eq!(config.ios.document_types.len(), 1);\n        assert_eq!(config.ios.document_types[0].name, \"My Document\");\n    }\n\n    #[test]\n    fn test_parse_android_intent_filters() {\n        let toml = r#\"\n            [android]\n            min_sdk = 24\n            url_schemes = [\"myapp-android\"]\n            foreground_service_types = [\"location\", \"mediaPlayback\"]\n\n            [[android.intent_filters]]\n            actions = [\"android.intent.action.VIEW\"]\n            categories = [\"android.intent.category.DEFAULT\", \"android.intent.category.BROWSABLE\"]\n            auto_verify = true\n\n            [[android.intent_filters.data]]\n            scheme = \"https\"\n            host = \"example.com\"\n            path_prefix = \"/app\"\n        \"#;\n\n        #[derive(Deserialize)]\n        struct Config {\n            android: AndroidConfig,\n        }\n\n        let config: Config = toml::from_str(toml).unwrap();\n        assert_eq!(config.android.url_schemes, vec![\"myapp-android\"]);\n        assert_eq!(\n            config.android.foreground_service_types,\n            vec![\"location\", \"mediaPlayback\"]\n        );\n        assert_eq!(config.android.intent_filters.len(), 1);\n        assert!(config.android.intent_filters[0].auto_verify);\n    }\n\n    #[test]\n    fn test_parse_macos_url_schemes() {\n        let toml = r#\"\n            [macos]\n            minimum_system_version = \"11.0\"\n            url_schemes = [\"myapp-macos\"]\n            category = \"public.app-category.productivity\"\n\n            [[macos.document_types]]\n            name = \"My Format\"\n            extensions = [\"myfmt\"]\n        \"#;\n\n        #[derive(Deserialize)]\n        struct Config {\n            macos: MacosConfig,\n        }\n\n        let config: Config = toml::from_str(toml).unwrap();\n        assert_eq!(config.macos.url_schemes, vec![\"myapp-macos\"]);\n        assert_eq!(\n            config.macos.category,\n            Some(\"public.app-category.productivity\".to_string())\n        );\n        assert_eq!(config.macos.document_types.len(), 1);\n    }\n\n    #[test]\n    fn test_generate_schema() {\n        let schema = generate_manifest_schema();\n        let json = serde_json::to_string_pretty(&schema).unwrap();\n\n        // Verify the schema contains all top-level DioxusConfig types\n        assert!(json.contains(\"ApplicationConfig\"));\n        assert!(json.contains(\"WebConfig\"));\n        assert!(json.contains(\"BundleConfig\"));\n        assert!(json.contains(\"ComponentConfig\"));\n        assert!(json.contains(\"PermissionsConfig\"));\n        assert!(json.contains(\"DeepLinkConfig\"));\n        assert!(json.contains(\"BackgroundConfig\"));\n        assert!(json.contains(\"IosConfig\"));\n        assert!(json.contains(\"AndroidConfig\"));\n        assert!(json.contains(\"MacosConfig\"));\n        assert!(json.contains(\"WindowsConfig\"));\n        assert!(json.contains(\"LinuxConfig\"));\n\n        // Verify some specific properties exist\n        assert!(json.contains(\"location\"));\n        assert!(json.contains(\"camera\"));\n        assert!(json.contains(\"deployment_target\"));\n        assert!(json.contains(\"min_sdk\"));\n\n        // Verify application config properties\n        assert!(json.contains(\"asset_dir\"));\n        assert!(json.contains(\"public_dir\"));\n\n        // Verify web config properties\n        assert!(json.contains(\"pre_compress\"));\n        assert!(json.contains(\"wasm_opt\"));\n\n        // Verify bundle config properties\n        assert!(json.contains(\"identifier\"));\n        assert!(json.contains(\"publisher\"));\n    }\n}\n"
  },
  {
    "path": "packages/cli/src/config/mod.rs",
    "content": "mod app;\nmod bundle;\nmod component;\nmod dioxus_config;\nmod inline_config;\nmod manifest;\nmod serve;\nmod web;\n\npub(crate) use app::*;\npub(crate) use bundle::*;\npub(crate) use dioxus_config::*;\npub(crate) use inline_config::*;\npub(crate) use manifest::*;\npub(crate) use serve::*;\npub(crate) use web::*;\n"
  },
  {
    "path": "packages/cli/src/config/serve.rs",
    "content": "use clap::Parser;\n\n/// The arguments for the address the server will run on\n#[derive(Clone, Debug, Default, Parser)]\npub(crate) struct AddressArguments {\n    /// The port the server will run on\n    #[clap(long)]\n    pub(crate) port: Option<u16>,\n\n    /// The address the server will run on\n    #[clap(long)]\n    pub(crate) addr: Option<std::net::IpAddr>,\n}\n\nimpl crate::Anonymized for AddressArguments {\n    fn anonymized(&self) -> serde_json::Value {\n        serde_json::json!({\n            \"port\": self.port,\n            \"addr\": self.addr.map(|addr| if addr.is_loopback() { \"loopback\" } else if addr.is_unspecified() { \"unspecified\" } else { \"other\" }),\n        })\n    }\n}\n"
  },
  {
    "path": "packages/cli/src/config/web.rs",
    "content": "use schemars::JsonSchema;\nuse serde::{Deserialize, Serialize};\nuse std::path::PathBuf;\n\n#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]\npub(crate) struct WebConfig {\n    #[serde(default)]\n    pub(crate) app: WebAppConfig,\n\n    #[serde(default)]\n    pub(crate) proxy: Vec<WebProxyConfig>,\n\n    #[serde(default)]\n    pub(crate) watcher: WebWatcherConfig,\n\n    #[serde(default)]\n    pub(crate) resource: WebResourceConfig,\n\n    #[serde(default)]\n    pub(crate) https: WebHttpsConfig,\n\n    /// Whether to enable pre-compression of assets and wasm during a web build in release mode\n    #[serde(default = \"false_bool\")]\n    pub(crate) pre_compress: bool,\n\n    /// The wasm-opt configuration\n    #[serde(default)]\n    pub(crate) wasm_opt: WasmOptConfig,\n}\n\nimpl Default for WebConfig {\n    fn default() -> Self {\n        Self {\n            pre_compress: false_bool(),\n            app: Default::default(),\n            https: Default::default(),\n            wasm_opt: Default::default(),\n            proxy: Default::default(),\n            watcher: Default::default(),\n            resource: Default::default(),\n        }\n    }\n}\n\n/// The wasm-opt configuration\n#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]\npub(crate) struct WasmOptConfig {\n    /// The wasm-opt level to use for release builds [default: s]\n    /// Options:\n    /// - z: optimize aggressively for size\n    /// - s: optimize for size\n    /// - 1: optimize for speed\n    /// - 2: optimize for more for speed\n    /// - 3: optimize for even more for speed\n    /// - 4: optimize aggressively for speed\n    #[serde(default)]\n    pub(crate) level: WasmOptLevel,\n\n    /// Keep debug symbols in the wasm file\n    #[serde(default = \"false_bool\")]\n    pub(crate) debug: bool,\n\n    /// Keep the wasm name section, useful for profiling and debugging\n    ///\n    /// Unlike `debug` which preserves DWARF debug symbols (requiring a browser extension to read),\n    /// the name section allows tools like `console_error_panic_hook` to print backtraces with\n    /// human-readable function names without any browser extension.\n    #[serde(default = \"false_bool\")]\n    pub(crate) keep_names: bool,\n\n    /// Enable memory packing\n    #[serde(default = \"false_bool\")]\n    pub(crate) memory_packing: bool,\n\n    /// Extra arguments to pass to wasm-opt\n    ///\n    /// For example, to enable simd, you can set this to `[\"--enable-simd\"]`.\n    ///\n    /// You can also disable features by prefixing them with `--disable-`, e.g. `[\"--disable-bulk-memory\"]`.\n    ///\n    /// Currently only --enable and --disable flags are supported.\n    #[serde(default)]\n    pub(crate) extra_features: Vec<String>,\n}\n\n/// The wasm-opt level to use for release web builds [default: Z]\n#[derive(Default, Debug, Copy, Clone, Serialize, Deserialize, JsonSchema)]\npub(crate) enum WasmOptLevel {\n    /// Optimize aggressively for size\n    #[serde(rename = \"z\")]\n    #[default]\n    Z,\n    /// Optimize for size\n    #[serde(rename = \"s\")]\n    S,\n    /// Don't optimize\n    #[serde(rename = \"0\")]\n    Zero,\n    /// Optimize for speed\n    #[serde(rename = \"1\")]\n    One,\n    /// Optimize for more for speed\n    #[serde(rename = \"2\")]\n    Two,\n    /// Optimize for even more for speed\n    #[serde(rename = \"3\")]\n    Three,\n    /// Optimize aggressively for speed\n    #[serde(rename = \"4\")]\n    Four,\n}\n\n#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]\npub(crate) struct WebAppConfig {\n    #[serde(default = \"default_title\")]\n    pub(crate) title: String,\n    pub(crate) base_path: Option<String>,\n}\n\nimpl Default for WebAppConfig {\n    fn default() -> Self {\n        Self {\n            title: default_title(),\n            base_path: None,\n        }\n    }\n}\n\n#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]\npub(crate) struct WebProxyConfig {\n    pub(crate) backend: String,\n}\n\n#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]\npub(crate) struct WebWatcherConfig {\n    #[serde(default = \"watch_path_default\")]\n    pub(crate) watch_path: Vec<PathBuf>,\n\n    #[serde(default)]\n    pub(crate) reload_html: bool,\n\n    #[serde(default = \"true_bool\")]\n    pub(crate) index_on_404: bool,\n}\n\nimpl Default for WebWatcherConfig {\n    fn default() -> Self {\n        Self {\n            watch_path: watch_path_default(),\n            reload_html: false,\n            index_on_404: true,\n        }\n    }\n}\n\nfn watch_path_default() -> Vec<PathBuf> {\n    vec![PathBuf::from(\"src\"), PathBuf::from(\"examples\")]\n}\n\n#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema)]\npub(crate) struct WebResourceConfig {\n    pub(crate) dev: WebDevResourceConfig,\n    pub(crate) style: Option<Vec<PathBuf>>,\n    pub(crate) script: Option<Vec<PathBuf>>,\n}\n\n#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema)]\npub(crate) struct WebDevResourceConfig {\n    #[serde(default)]\n    pub(crate) style: Vec<PathBuf>,\n    #[serde(default)]\n    pub(crate) script: Vec<PathBuf>,\n}\n\n#[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema)]\npub(crate) struct WebHttpsConfig {\n    pub(crate) enabled: Option<bool>,\n    pub(crate) mkcert: Option<bool>,\n    pub(crate) key_path: Option<String>,\n    pub(crate) cert_path: Option<String>,\n}\n\nfn true_bool() -> bool {\n    true\n}\n\nfn false_bool() -> bool {\n    false\n}\n\npub(crate) fn default_title() -> String {\n    \"dioxus | ⛺\".into()\n}\n"
  },
  {
    "path": "packages/cli/src/devcfg.rs",
    "content": "//! Configuration of the CLI at runtime to enable certain experimental features.\n\n/// Should we force the entropy to be used on the main exe?\n///\n/// This is used to verify that binaries are copied with different names such that they don't collide\n/// and should generally be only enabled on certain platforms that require it.\npub(crate) fn should_force_entropy() -> bool {\n    std::env::var(\"DIOXUS_FORCE_ENTRY\").is_ok()\n}\n\n/// Should we test the installs?\n#[allow(dead_code)] // -> used in tests only\npub(crate) fn test_installs() -> bool {\n    std::env::var(\"TEST_INSTALLS\").is_ok()\n}\n"
  },
  {
    "path": "packages/cli/src/dx_build_info.rs",
    "content": "// The file has been placed there by the build script.\ninclude!(concat!(env!(\"OUT_DIR\"), \"/built.rs\"));\n"
  },
  {
    "path": "packages/cli/src/error.rs",
    "content": "pub(crate) type Result<T, E = Error> = std::result::Result<T, E>;\npub use anyhow::Error;\nuse itertools::Itertools;\n\npub fn log_stacktrace(err: &anyhow::Error, padding: usize) -> String {\n    let mut trace = format!(\"{err}\",);\n\n    for (idx, cause) in err.chain().enumerate().skip(1) {\n        trace.push_str(&format!(\n            \"\\n{}{IDX_STYLE}{idx}{IDX_STYLE:#}: {}\",\n            \" \".repeat(padding),\n            cause\n                .to_string()\n                .lines()\n                .enumerate()\n                .map(|(idx, line)| {\n                    if idx == 0 {\n                        line.to_string()\n                    } else {\n                        format!(\"{}{}\", \" \".repeat(padding + 3), line)\n                    }\n                })\n                .join(\"\\n\"),\n            IDX_STYLE = crate::styles::GLOW_STYLE,\n        ));\n    }\n\n    if crate::verbosity_or_default().trace {\n        trace.push_str(&format!(\"\\nBacktrace:\\n{}\", err.backtrace()));\n    }\n\n    trace\n}\n"
  },
  {
    "path": "packages/cli/src/fastfs.rs",
    "content": "//! Methods for working with the filesystem that are faster than the std fs methods\n//! Uses stuff like rayon, caching, and other optimizations\n//!\n//! Allows configuration in case you want to do some work while copying and allows you to track progress\n\nuse std::{\n    ffi::OsString,\n    path::{Path, PathBuf},\n};\n\nuse brotli::enc::BrotliEncoderParams;\nuse walkdir::WalkDir;\n\n/// Get the path to the compressed version of a file\nfn compressed_path(path: &Path) -> Option<PathBuf> {\n    let new_extension = match path.extension() {\n        Some(ext) => {\n            if ext.to_string_lossy().to_lowercase().ends_with(\"br\") {\n                return None;\n            }\n            let mut ext = ext.to_os_string();\n            ext.push(\".br\");\n            ext\n        }\n        None => OsString::from(\"br\"),\n    };\n\n    Some(path.with_extension(new_extension))\n}\n\n/// pre-compress a file with brotli\npub(crate) fn pre_compress_file(path: &Path) -> std::io::Result<()> {\n    let Some(compressed_path) = compressed_path(path) else {\n        return Ok(());\n    };\n\n    let file = std::fs::File::open(path)?;\n    let mut stream = std::io::BufReader::new(file);\n    let mut buffer = std::fs::File::create(compressed_path)?;\n    let params = BrotliEncoderParams::default();\n    brotli::BrotliCompress(&mut stream, &mut buffer, &params)?;\n\n    Ok(())\n}\n\n/// pre-compress all files in a folder\npub(crate) fn pre_compress_folder(path: &Path, pre_compress: bool) -> std::io::Result<()> {\n    let walk_dir = WalkDir::new(path);\n    for entry in walk_dir.into_iter().filter_map(|e| e.ok()) {\n        let entry_path = entry.path();\n        if entry_path.is_file() {\n            if pre_compress {\n                tracing::info!(\"Pre-compressing file {}\", entry_path.display());\n                if let Err(err) = pre_compress_file(entry_path) {\n                    tracing::error!(\"Failed to pre-compress file {entry_path:?}: {err}\");\n                }\n            }\n            // If pre-compression isn't enabled, we should remove the old compressed file if it exists\n            else if let Some(compressed_path) = compressed_path(entry_path) {\n                _ = std::fs::remove_file(compressed_path);\n            }\n        }\n    }\n    Ok(())\n}\n"
  },
  {
    "path": "packages/cli/src/logging.rs",
    "content": "//! CLI Tracing\n//!\n//! The CLI's tracing has internal and user-facing logs. User-facing logs are directly routed to the user in some form.\n//! Internal logs are stored in a log file for consumption in bug reports and debugging.\n//! We use tracing fields to determine whether a log is internal or external and additionally if the log should be\n//! formatted or not.\n//!\n//! These two fields are\n//! `dx_src` which tells the logger that this is a user-facing message and should be routed as so.\n//! `dx_no_fmt`which tells the logger to avoid formatting the log and to print it as-is.\n//!\n//! 1. Build general filter\n//! 2. Build file append layer for logging to a file. This file is reset on every CLI-run.\n//! 3. Build CLI layer for routing tracing logs to the TUI.\n//! 4. Build fmt layer for non-interactive logging with a custom writer that prevents output during interactive mode.\n//!\n//! ## Telemetry\n//!\n//! The CLI collects anonymized telemetry data to help us understand how the CLI is used. We primarily\n//! care about catching panics and fatal errors. Data is uploaded to PostHog through a custom proxy endpoint.\n//!\n//! Telemetry events are collected while the CLI is running and then flushed to disk at the end of the session.\n//! When the CLI starts again, it tries its best to upload the telemetry data from the FS. In CI,\n//! the telemetry data is uploaded immediately after the CLI completes with a 5 second timeout.\n//!\n//! You can opt out in a number of ways:\n//! - set TELEMETRY=false in your environment\n//! - set DX_TELEMETRY_ENABLED=false in your environment\n//! - set `dx config set disable-telemetry true`\n//!\n\nuse crate::component::ComponentCommand;\nuse crate::{dx_build_info::GIT_COMMIT_HASH_SHORT, serve::ServeUpdate, Cli, Commands, Verbosity};\nuse crate::{BundleFormat, CliSettings, Workspace};\nuse anyhow::{bail, Context, Error, Result};\nuse cargo_metadata::diagnostic::{Diagnostic, DiagnosticLevel};\nuse clap::Parser;\nuse dioxus_cli_telemetry::TelemetryEventData;\nuse dioxus_dx_wire_format::StructuredOutput;\nuse futures_channel::mpsc::{UnboundedReceiver, UnboundedSender};\nuse futures_util::FutureExt;\nuse itertools::Itertools;\nuse serde::{Deserialize, Serialize};\nuse serde_json::json;\nuse std::{any::Any, io::Read, str::FromStr, sync::Arc, time::SystemTime};\nuse std::{borrow::Cow, sync::OnceLock};\nuse std::{\n    collections::HashMap,\n    env,\n    fmt::{Debug, Display, Write as _},\n    sync::atomic::{AtomicBool, Ordering},\n    time::Instant,\n};\nuse std::{future::Future, panic::AssertUnwindSafe};\nuse tracing::{field::Visit, Level, Subscriber};\nuse tracing_subscriber::{\n    fmt::{\n        format::{self, Writer},\n        time::FormatTime,\n    },\n    prelude::*,\n    registry::LookupSpan,\n    EnvFilter, Layer,\n};\nuse uuid::Uuid;\n\nconst LOG_ENV: &str = \"DIOXUS_LOG\";\nconst DX_SRC_FLAG: &str = \"dx_src\";\n\npub static VERBOSITY: OnceLock<Verbosity> = OnceLock::new();\n\npub fn verbosity_or_default() -> Verbosity {\n    crate::VERBOSITY.get().cloned().unwrap_or_default()\n}\n\nfn reset_cursor() {\n    use std::io::IsTerminal;\n\n    // if we are running in a terminal, reset the cursor. The tui_active flag is not set for\n    // the cargo generate TUI, but we still want to reset the cursor.\n    if std::io::stdout().is_terminal() {\n        _ = console::Term::stdout().show_cursor();\n    }\n}\n\n/// A trait that emits an anonymous JSON representation of the object, suitable for telemetry.\npub(crate) trait Anonymized {\n    fn anonymized(&self) -> serde_json::Value;\n}\n\n/// A custom layer that wraps our special interception logic based on the mode of the CLI and its verbosity.\n///\n/// Redirects TUI logs, writes to files, and queues telemetry events.\n///\n/// It is cloned and passed directly as a layer to the tracing subscriber.\n#[derive(Clone)]\npub struct TraceController {\n    http_client: Option<reqwest::Client>,\n    reporter: Option<Reporter>,\n    telemetry_tx: UnboundedSender<TelemetryEventData>,\n    telemetry_rx: Arc<tokio::sync::Mutex<UnboundedReceiver<TelemetryEventData>>>,\n    log_to_file: Option<Arc<tokio::sync::Mutex<std::fs::File>>>,\n    tui_active: Arc<AtomicBool>,\n    tui_tx: UnboundedSender<TraceMsg>,\n    tui_rx: Arc<tokio::sync::Mutex<UnboundedReceiver<TraceMsg>>>,\n}\n\n/// An error that contains information about a captured panic, including the error, thread name, and location.\n/// This is passed through to the tracing subscriber which is downcasted into a `CapturedPanicError`\n#[derive(Debug, Clone)]\nstruct CapturedPanicError {\n    error: Arc<Error>,\n    error_type: String,\n    thread_name: Option<String>,\n    location: Option<SavedLocation>,\n}\nimpl std::fmt::Display for CapturedPanicError {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(\n            f,\n            \"CapturedBacktrace {{ error: {}, error_type: {}, thread_name: {:?}, location: {:?} }}\",\n            self.error, self.error_type, self.thread_name, self.location\n        )\n    }\n}\nimpl std::error::Error for CapturedPanicError {}\n\n#[derive(Debug, Clone)]\nstruct SavedLocation {\n    file: String,\n    line: u32,\n    column: u32,\n}\n\nimpl TraceController {\n    /// Initialize the CLI and set up the tracing infrastructure\n    ///\n    /// This captures panics and flushes telemetry to a file after the CLI has run.\n    ///\n    /// We pass the TraceController around the CLI in a few places, namely the serve command so the TUI\n    /// can access things like the logs.\n    pub async fn main<F>(run_app: impl FnOnce(Commands, Self) -> F) -> StructuredOutput\n    where\n        F: Future<Output = Result<StructuredOutput>>,\n    {\n        let args = Cli::parse();\n        let tui_active = Arc::new(AtomicBool::new(false));\n        let is_serve_cmd = matches!(args.action, Commands::Serve(_));\n\n        VERBOSITY\n            .set(args.verbosity.clone())\n            .expect(\"verbosity should only be set once\");\n\n        // Set up a basic env-based filter for the logs\n        let env_filter = match env::var(LOG_ENV) {\n            Ok(_) => EnvFilter::from_env(LOG_ENV),\n            _ if is_serve_cmd => EnvFilter::new(\"error,dx=trace,dioxus_cli=trace,manganis_cli_support=trace,wasm_split_cli=trace,subsecond_cli_support=trace\"),\n            _ => EnvFilter::new(format!(\n                \"error,dx={our_level},dioxus_cli={our_level},manganis_cli_support={our_level},wasm_split_cli={our_level},subsecond_cli_support={our_level}\",\n                our_level = if args.verbosity.verbose { \"debug\" } else { \"info\" }\n            ))\n        };\n\n        // Listen to a few more tokio events if the tokio-console feature is enabled\n        #[cfg(feature = \"tokio-console\")]\n        let env_filter = env_filter\n            .add_directive(\"tokio=trace\".parse().unwrap())\n            .add_directive(\"runtime=trace\".parse().unwrap());\n\n        // Set up the json filter which lets through JSON traces only if the `json` field is present in the trace metadata\n        // If the json is disabled, we filter it completely so it doesn't show up in the logs\n        let json_filter = tracing_subscriber::filter::filter_fn(move |meta| {\n            if meta.fields().len() == 1 && meta.fields().iter().next().unwrap().name() == \"json\" {\n                return args.verbosity.json_output;\n            }\n            true\n        });\n\n        // We complete filter out a few fields that are not relevant to the user, like `dx_src` and `json`\n        let fmt_layer = tracing_subscriber::fmt::layer()\n            .with_target(args.verbosity.verbose)\n            .fmt_fields(\n                format::debug_fn(move |writer, field, value| {\n                    if field.name() == \"json\" && !args.verbosity.json_output {\n                        return Ok(());\n                    }\n\n                    if field.name() == \"dx_src\" && !args.verbosity.verbose {\n                        return Ok(());\n                    }\n\n                    if field.name() == \"telemetry\" {\n                        return Ok(());\n                    }\n\n                    write!(writer, \"{}\", format_field(field.name(), value))\n                })\n                .delimited(\" \"),\n            )\n            .with_timer(PrettyUptime::default());\n\n        // If json output is enabled, we want to format the output as JSON\n        // When running in interactive mode (of which serve is the only one), we don't want to log to console directly\n        let fmt_layer = if args.verbosity.json_output {\n            fmt_layer.json().flatten_event(true).boxed()\n        } else {\n            fmt_layer.boxed()\n        }\n        .with_filter(tracing_subscriber::filter::filter_fn({\n            let tui_active = tui_active.clone();\n            move |re| {\n                // If the TUI is active, we don't want to log to the console directly\n                if tui_active.load(Ordering::Relaxed) {\n                    return false;\n                }\n\n                // If the TUI is disabled, then we might be draining the logs during an error report.\n                // In this case, we only show debug logs if the verbosity is set to verbose.\n                //\n                // If we're not running the serve command at all, this isn't relevant, so let the log through.\n                if !is_serve_cmd {\n                    return true;\n                }\n\n                let verbosity = VERBOSITY.get().unwrap();\n\n                // If the verbosity is trace, let through trace logs (most verbose)\n                if verbosity.trace {\n                    return re.level() <= &Level::TRACE;\n                }\n\n                // if the verbosity is verbose, let through debug logs\n                if verbosity.verbose {\n                    return re.level() <= &Level::DEBUG;\n                }\n\n                // Otherwise, only let through info and higher level logs\n                re.level() <= &Level::INFO\n            }\n        }));\n\n        // Set up the tokio console subscriber if enabled\n        #[cfg(feature = \"tokio-console\")]\n        let console_layer = console_subscriber::spawn();\n        #[cfg(not(feature = \"tokio-console\"))]\n        let console_layer = tracing_subscriber::layer::Identity::new();\n\n        // Construct our custom layer that handles the TUI and file logging\n        let log_to_file = args\n            .verbosity\n            .log_to_file\n            .as_deref()\n            .map(|file_path| {\n                std::fs::OpenOptions::new()\n                    .append(true)\n                    .create(true)\n                    .open(file_path)\n                    .map(|file| Arc::new(tokio::sync::Mutex::new(file)))\n            })\n            .transpose()\n            .context(\"Failed to open specified log_file for writing\")\n            .unwrap();\n\n        // Create a new session ID for this invocation of the CLI\n        let reporter = Self::enroll_reporter().ok();\n\n        // Create a new telemetry uploader\n        let http_client = reqwest::Client::builder()\n            .timeout(std::time::Duration::from_secs(5))\n            .build()\n            .ok();\n\n        // Create a new telemetry channel\n        // Note that we only drain the channel at the end of the CLI run, so it's not really being used as a channel - more of a vecdeque\n        let (telemetry_tx, telemetry_rx) = futures_channel::mpsc::unbounded();\n        let (tui_tx, tui_rx) = futures_channel::mpsc::unbounded();\n        let tracer = TraceController {\n            reporter,\n            telemetry_tx,\n            log_to_file,\n            tui_tx,\n            tui_rx: Arc::new(tokio::sync::Mutex::new(tui_rx)),\n            telemetry_rx: Arc::new(tokio::sync::Mutex::new(telemetry_rx)),\n            http_client,\n            tui_active,\n        };\n\n        // Spawn the telemetry uploader in the background\n        tokio::spawn(\n            tracer\n                .clone()\n                .upload_telemetry_files(Self::to_invoked_event(&args.action)),\n        );\n\n        // Construct the tracing subscriber\n        tracing_subscriber::registry()\n            .with(env_filter)\n            .with(json_filter)\n            .with(tracer.clone())\n            .with(console_layer)\n            .with(fmt_layer)\n            .init();\n\n        // Set the panic handler to capture backtraces in case of a panic\n        // let initial_thread = std::thread::current();\n        std::panic::set_hook(Box::new(move |panic_info| {\n            let payload = if let Some(s) = panic_info.payload().downcast_ref::<String>() {\n                s.to_string()\n            } else if let Some(s) = panic_info.payload().downcast_ref::<&str>() {\n                s.to_string()\n            } else {\n                \"<unknown panic>\".to_string()\n            };\n\n            let current_thread = std::thread::current();\n            let thread_name = current_thread.name().map(|s| s.to_string());\n            let location = panic_info.location().unwrap();\n            let err = anyhow::anyhow!(payload);\n            let err_display = format!(\"{:?}\", err)\n                .lines()\n                .take_while(|line| !line.ends_with(\"___rust_try\"))\n                .join(\"\\n\");\n\n            let boxed_panic: Box<dyn std::error::Error + Send + 'static> =\n                Box::new(CapturedPanicError {\n                    error: Arc::new(err),\n                    thread_name: thread_name.clone(),\n                    error_type: \"Rust Panic\".to_string(),\n                    location: panic_info.location().map(|l| SavedLocation {\n                        file: l.file().to_string(),\n                        line: l.line(),\n                        column: l.column(),\n                    }),\n                });\n\n            tracing::error!(\n                telemetry = %json!({ \"event\": \"Captured Panic\" }),\n                backtrace = boxed_panic,\n                \"Thread {} panicked at {location}:\\n\\n               {err_display}\",\n                thread_name.as_deref().unwrap_or(\"<unknown>\"),\n            );\n        }));\n\n        // Run the app, catching panics and errors, early flushing if `ctrl_c` is pressed.\n        let app_res = AssertUnwindSafe(run_with_ctrl_c(run_app(args.action, tracer.clone())))\n            .catch_unwind()\n            .await;\n\n        // Do any final logging cleanup\n        tracer.finish(app_res).await\n    }\n\n    // Redirects the tracing logs to the TUI if it's active, otherwise it just collects them.\n    pub fn redirect_to_tui(&self) {\n        self.tui_active.store(true, Ordering::Relaxed);\n    }\n\n    /// Wait for the internal logger to send a message\n    pub(crate) async fn wait(&self) -> ServeUpdate {\n        use futures_util::StreamExt;\n\n        let Some(log) = self.tui_rx.lock().await.next().await else {\n            return std::future::pending().await;\n        };\n\n        ServeUpdate::TracingLog { log }\n    }\n\n    /// Uploads telemetry logs from the filesystem to the telemetry endpoint.\n    ///\n    /// As the app runs, we simply fire off messages into the TelemetryTx handle.\n    ///\n    /// Once the session is over, or the tx is flushed manually, we then log to a file.\n    /// This prevents any performance issues from building up during long session.\n    /// For `dx serve`, we asynchronously flush after full rebuilds are *completed*.\n    /// Initialize a user session with a stable ID.\n    ///\n    /// This also sends a heartbeat event to the telemetry endpoint to indicate that the CLI is alive.\n    ///\n    /// Docs on how to send posthog:\n    ///    <https://posthog.com/docs/api/capture>\n    ///\n    /// We try to send batched requests *without* the api key in the header. It's usually fine to send\n    /// the API key along with the request, but we want to control revoking key on the backend.\n    ///\n    /// Todo: we should accept some sort of configuration from posthog to allow us to downsample telemetry events.\n    ///       otherwise we might end up being flooded by telemetry events.\n    ///\n    /// We loop receive messages, pushing them into a batch.\n    async fn upload_telemetry_files(self, invoked_event: TelemetryEventData) -> Result<()> {\n        use fs2::FileExt;\n\n        // Wait a little bit to prevent abuse (spam loops) and not do extra work if it's a simple `--help` call\n        tokio::time::sleep(std::time::Duration::from_millis(100)).await;\n\n        // Send off a heartbeat request. If this fails, we skip anything else.\n        self.send_invoked_event(invoked_event).await?;\n\n        // Wait a few seconds to see if we can end up in `dx serve` or a long-running task\n        // If we're in CI though, we do want to flush telemetry immediately\n        if !CliSettings::is_ci() {\n            tokio::time::sleep(std::time::Duration::from_millis(3000)).await;\n        }\n\n        // Now start loading telemetry files, locking them, and then uploading them.\n        let stats_dir = Workspace::dioxus_data_dir().join(\"stats\").join(\"sessions\");\n        for entry in stats_dir.read_dir()?.flatten() {\n            // Try to open the file...\n            let Ok(mut file) = std::fs::File::open(entry.path()) else {\n                continue;\n            };\n\n            // And then we hold an exclusive lock on the file while we upload it\n            // This prevents multiple processes from trying to upload the same file at the same time which would cause duplicate uploads\n            if file.try_lock_exclusive().is_err() {\n                continue;\n            };\n\n            // Now that we have the lock, we can read the file and upload it\n            // todo: validate that _bytes_read is not greater than 20mb - this will fail to upload\n            let mut jsonl_file = String::new();\n            let Ok(_bytes_read) = file.read_to_string(&mut jsonl_file) else {\n                continue;\n            };\n\n            // If the file is empty, we delete it and continue\n            if jsonl_file.trim().is_empty() {\n                _ = std::fs::remove_file(entry.path());\n                continue;\n            }\n\n            // We assume since this is a jsonl file that every line is valid json. We just concat the lines together\n            // and then send them using the batched client.\n            // The session ID is the file stem of the file, which is a UUID. If not, we just make up a\n            // new session ID on the fly.\n            let reporter = self.reporter.as_ref().context(\"no reporter\")?;\n            let session_id = entry\n                .path()\n                .file_stem()\n                .and_then(|s| Uuid::from_str(s.to_str()?).ok())\n                .unwrap_or_else(Uuid::new_v4);\n            let request_body = jsonl_file\n                .lines()\n                .map(serde_json::from_str::<TelemetryEventData>)\n                .filter_map(Result::ok)\n                .map(|event| Self::telemetry_to_posthog(session_id, reporter.distinct_id, event))\n                .collect::<Vec<_>>();\n\n            // Send the request\n            // - If the request fails, we just log the error and continue\n            // - If the request succeeds, we remove the file\n            match self.upload_to_posthog(&request_body).await {\n                Ok(()) => _ = std::fs::remove_file(entry.path()),\n                Err(err) => {\n                    tracing::trace!(\n                        \"Failed to upload telemetry file: {} because {}\",\n                        entry.path().display(),\n                        err\n                    );\n                }\n            }\n        }\n\n        Ok(())\n    }\n\n    /// Uploads a set of telemetry events to the PostHog endpoint.\n    async fn upload_to_posthog(&self, body: &Vec<serde_json::Value>) -> Result<()> {\n        use hyper::header::CONTENT_TYPE;\n\n        let reporter = self.reporter.as_ref().context(\"No reporter initialized\")?;\n        let res = self\n            .http_client\n            .as_ref()\n            .context(\"HTTP client not initialized\")?\n            .post(Self::posthog_capture_endpoint())\n            .header(CONTENT_TYPE, \"application/json\")\n            .header(\"X-Reporter-ID\", reporter.distinct_id.to_string())\n            .json(&body)\n            .send()\n            .await\n            .context(\"Failed to send telemetry data\")?;\n\n        if !res.status().is_success() {\n            bail!(\n                \"Failed to upload telemetry event: {:?}. Response: {:?}\",\n                res.status(),\n                res.text().await\n            );\n        }\n\n        Ok(())\n    }\n\n    async fn send_invoked_event(&self, heartbeat: TelemetryEventData) -> Result<()> {\n        let reporter = self.reporter.as_ref().context(\"No reporter initialized\")?;\n        let body = Self::telemetry_to_posthog(reporter.session_id, reporter.distinct_id, heartbeat);\n        self.upload_to_posthog(&vec![body]).await\n    }\n\n    /// Convert the dioxus-cli-telemetry event into a posthog event.\n    ///\n    /// We try to maintain the same structure for each telemetry event to do advanced filtering on the backend.\n    fn telemetry_to_posthog(\n        session_id: Uuid,\n        distinct_id: Uuid,\n        event: TelemetryEventData,\n    ) -> serde_json::Value {\n        let TelemetryEventData {\n            action,\n            message,\n            time,\n            values,\n            command,\n            stack_frames,\n            file,\n            line,\n            column,\n            module,\n            error_type,\n            error_handled,\n        } = event;\n\n        let mut ph_event = posthog_rs::Event::new(action, distinct_id.to_string());\n\n        // The reporter's fields\n        _ = ph_event.insert_prop(\"is_ci\", CliSettings::is_ci());\n        _ = ph_event.insert_prop(\"session_id\", session_id.to_string());\n        _ = ph_event.insert_prop(\"distinct_id\", distinct_id.to_string());\n        _ = ph_event.insert_prop(\"host_os\", target_lexicon::HOST.operating_system.to_string());\n        _ = ph_event.insert_prop(\"host_arch\", target_lexicon::HOST.architecture.to_string());\n        _ = ph_event.insert_prop(\"host_triple\", target_lexicon::Triple::host().to_string());\n        _ = ph_event.insert_prop(\"cli_version_major\", crate::dx_build_info::PKG_VERSION_MAJOR);\n        _ = ph_event.insert_prop(\"cli_version_minor\", crate::dx_build_info::PKG_VERSION_MINOR);\n        _ = ph_event.insert_prop(\"cli_version_patch\", crate::dx_build_info::PKG_VERSION_PATCH);\n        _ = ph_event.insert_prop(\"cli_version_pre\", crate::dx_build_info::PKG_VERSION_PRE);\n        _ = ph_event.insert_prop(\"cli_commit_hash\", GIT_COMMIT_HASH_SHORT.unwrap_or_default());\n        _ = ph_event.insert_prop(\"cli_source_file\", line);\n        _ = ph_event.insert_prop(\"cli_source_line\", file);\n        _ = ph_event.insert_prop(\"cli_source_column\", column);\n        _ = ph_event.insert_prop(\"cli_source_module\", module);\n\n        // And the TelemetryEventData fields\n        _ = ph_event.insert_prop(\"command\", &command);\n        _ = ph_event.insert_prop(\"message\", &message);\n\n        // And the rest of the event values\n        for (key, value) in values {\n            _ = ph_event.insert_prop(key, value);\n        }\n\n        // We need to go add the api key to the event, since posthog_rs doesn't expose it for us...\n        let mut value = serde_json::to_value(ph_event).unwrap();\n        value[\"timestamp\"] = serde_json::Value::String(time.to_rfc3339());\n        value[\"api_key\"] = serde_json::Value::String(\n            \"phc_d2jQTZMqAWxSkzv3NQ8TlxCP49vtBZ5ZmlYMIZLFNNU\".to_string(),\n        );\n\n        // If the event is an error, we need to add the error properties so posthog picks it up.\n        // The stackframes should be transformed already.\n        if let Some(error_type) = error_type {\n            value[\"event\"] = \"$exception\".into();\n            value[\"properties\"][\"$session_id\"] = session_id.to_string().into();\n            value[\"properties\"][\"$exception_list\"] = json!([{\n                \"type\": error_type,\n                \"value\": &message,\n                \"mechanism\": {\n                    \"handled\": error_handled, // panics are not handled\n                    \"synthetic\": false, // all errors/panics and \"real\", not emitted by sentry or the sdk\n                },\n                \"stacktrace\": {\n                    \"type\": \"raw\", // debug symbols are generally used. this might be wrong?\n                    \"frames\": stack_frames\n                }\n            }]);\n        }\n\n        value\n    }\n\n    fn enroll_reporter() -> Result<Reporter> {\n        #[derive(Debug, Deserialize, Serialize)]\n        struct ReporterSession {\n            reporter_id: Uuid,\n            session_id: Uuid,\n            created_at: SystemTime,\n            last_used: SystemTime,\n        }\n\n        // If the user requests telemetry disabled, we don't enroll them\n        if CliSettings::telemetry_disabled() {\n            bail!(\"Telemetry is disabled\");\n        }\n\n        // Create the sessions folder if it doesn't exist\n        let stats_folder = Workspace::dioxus_data_dir().join(\"stats\");\n        let sessions_folder = stats_folder.join(\"sessions\");\n        if !sessions_folder.exists() {\n            std::fs::create_dir_all(&sessions_folder)?;\n        }\n\n        // Create a reporter_id. If we find an invalid reporter_id, we use `nil` as the reporter ID.\n        let distinct_id_file = stats_folder.join(\"reporter.json\");\n        let reporter_session = std::fs::read_to_string(&distinct_id_file)\n            .map(|e| serde_json5::from_str::<ReporterSession>(&e));\n\n        // If we have a valid reporter session, we use it, otherwise we create a new one.\n        let mut reporter = match reporter_session {\n            Ok(Ok(session)) => session,\n            _ => ReporterSession {\n                reporter_id: Uuid::new_v4(),\n                session_id: Uuid::new_v7(uuid::Timestamp::now(\n                    uuid::timestamp::context::ContextV7::new(),\n                )),\n                created_at: SystemTime::now(),\n                last_used: SystemTime::now(),\n            },\n        };\n\n        // Update the last used time to now, updating the session ID if it's older than 30 minutes.\n        if reporter\n            .last_used\n            .duration_since(SystemTime::now())\n            .map(|d| d.as_secs() > (30 * 60))\n            .unwrap_or(true)\n        {\n            reporter.created_at = SystemTime::now();\n            reporter.session_id = Uuid::new_v7(uuid::Timestamp::now(\n                uuid::timestamp::context::ContextV7::new(),\n            ));\n        }\n        reporter.last_used = SystemTime::now();\n\n        // Write the reporter session back to the file\n        std::fs::write(&distinct_id_file, serde_json5::to_string(&reporter)?)?;\n\n        Ok(Reporter {\n            distinct_id: reporter.reporter_id,\n            session_id: reporter.session_id,\n        })\n    }\n\n    async fn finish(\n        &self,\n        res: Result<Result<StructuredOutput>, Box<dyn Any + Send>>,\n    ) -> StructuredOutput {\n        // Drain the tracer as regular messages\n        self.tui_active.store(false, Ordering::Relaxed);\n        reset_cursor();\n\n        // re-emit any remaining messages in case they're useful.\n        while let Ok(Some(msg)) = self.tui_rx.lock().await.try_next() {\n            let content = match msg.content {\n                TraceContent::Text(text) => text,\n                TraceContent::Cargo(msg) => msg.message.to_string(),\n            };\n            match msg.level {\n                Level::ERROR => tracing::error!(\"{content}\"),\n                Level::WARN => tracing::warn!(\"{content}\"),\n                Level::INFO => tracing::info!(\"{content}\"),\n                Level::DEBUG => tracing::debug!(\"{content}\"),\n                Level::TRACE => tracing::trace!(\"{content}\"),\n            }\n        }\n\n        // If we have \"safe\" error, we need to log it\n        if let Ok(Err(err)) = &res {\n            self.record_backtrace(\n                err,\n                \"Fatal error\",\n                std::thread::current().name(),\n                None,\n                Default::default(),\n            );\n        }\n\n        // And then we can finally flush telemetry to disk\n        _ = self.flush_telemetry_to_disk().await;\n\n        // Create the final output based on the result of the CLI run.\n        match res {\n            // No printing needed for successful runs, we just return the output for JSON output.\n            Ok(Ok(output)) => output,\n\n            // If the CLI run failed, we print the error and return it.\n            // Anyhow gives us a nice stack trace, so we can just print it.\n            // Eventually we might want to format this better since a full stack trace is kinda ugly.\n            Ok(Err(err)) => {\n                use crate::styles::{ERROR_STYLE, GLOW_STYLE};\n                let arg = std::env::args().nth(1).unwrap_or_else(|| \"dx\".to_string());\n                let err_display = format!(\"{err:?}\")\n                    .lines()\n                    .take_while(|line| !line.ends_with(\"___rust_try\"))\n                    .join(\"\\n\");\n                let message = format!(\n                    \"{ERROR_STYLE}ERROR{ERROR_STYLE:#} {GLOW_STYLE}dx {}{GLOW_STYLE:#}: {}\",\n                    arg, err_display\n                );\n                eprintln!(\"\\n{message}\");\n                StructuredOutput::Error { message }\n            }\n\n            // The top-level thread panicked entirely. The panic handler should print the error for us.\n            // Just return the error for the structured output.\n            Err(e) => StructuredOutput::Error {\n                message: if let Some(s) = e.downcast_ref::<String>() {\n                    s.to_string()\n                } else if let Some(s) = e.downcast_ref::<&str>() {\n                    s.to_string()\n                } else {\n                    \"<unknown error>\".to_string()\n                },\n            },\n        }\n    }\n\n    /// Flush telemetry to disk.\n    ///\n    /// Maybe flush telemetry immediately if the command is a long-running command like `dx serve` or `dx run` and there's an error.\n    /// Currently just flushes telemetry immediately if we're in CI.\n    async fn flush_telemetry_to_disk(&self) -> Result<()> {\n        use std::io::Write;\n\n        let reporter = self.reporter.as_ref().context(\"No reporter initialized\")?;\n\n        // If we're in CI, we try to upload the telemetry immediately, with a short timeout (5 seconds or so)\n        // Hopefully it doesn't fail! Not much we can do in CI.\n        match CliSettings::is_ci() {\n            true => {\n                let mut msgs = self.telemetry_rx.lock().await;\n\n                let request_body = std::iter::from_fn(|| msgs.try_next().ok().flatten())\n                    .filter_map(|msg| serde_json::to_value(msg).ok())\n                    .collect::<Vec<_>>();\n\n                _ = self.upload_to_posthog(&request_body).await;\n            }\n\n            // Dump the logs to a the session file as jsonl\n            false => {\n                let mut msgs = self.telemetry_rx.lock().await;\n\n                let msg_list =\n                    std::iter::from_fn(|| msgs.try_next().ok().flatten()).collect::<Vec<_>>();\n\n                if !msg_list.is_empty() {\n                    let dest = Workspace::dioxus_data_dir()\n                        .join(\"stats\")\n                        .join(\"sessions\")\n                        .join(format!(\"{}.jsonl\", reporter.session_id));\n\n                    let mut logfile = std::fs::OpenOptions::new()\n                        .append(true)\n                        .create(true)\n                        .open(dest)?;\n\n                    for msg in msg_list {\n                        serde_json::to_writer(&mut logfile, &msg)?;\n                        writeln!(logfile)?;\n                    }\n                }\n            }\n        }\n\n        Ok(())\n    }\n\n    fn posthog_capture_endpoint() -> String {\n        format!(\n            \"{}/capture/\",\n            Self::posthog_endpoint().trim_end_matches('/')\n        )\n    }\n\n    fn posthog_endpoint() -> String {\n        // In dev mode we can override the endpoint with an environment variable\n        if cfg!(debug_assertions) {\n            if let Ok(endpoint) = env::var(\"DX_REPORTER_ENDPOINT\") {\n                return endpoint;\n            }\n        }\n\n        \"https://dx-cli-stats.dioxus.dev/\".to_string()\n    }\n\n    /// Collect the raw arguments passed to the CLI and convert them into a telemetry event.\n    ///\n    /// This gives some usage information about which commands are being used and how. Especially useful\n    /// if a particular session is failing in some way.\n    pub(crate) fn to_invoked_event(arg: &crate::Commands) -> TelemetryEventData {\n        let (message, anonymized) = Self::command_anonymized(arg);\n        let raw_arguments = std::env::args()\n            .map(|s| dioxus_cli_telemetry::strip_paths(&s))\n            .collect::<Vec<_>>();\n        TelemetryEventData::new(\"cli_invoked\", message)\n            .with_value(\"raw_arguments\", raw_arguments)\n            .with_value(\"arguments\", anonymized)\n    }\n\n    /// Returns arguments that we're curious about\n    ///\n    /// The first return is the message, basically the command name in sequence\n    /// The second return is a JSON object with the anonymized arguments as a structured value.\n    pub(crate) fn command_anonymized(arg: &crate::Commands) -> (String, serde_json::Value) {\n        use crate::cli::config::{Config, Setting};\n        use crate::{print::Print, BuildTools};\n        use cargo_generate::Vcs;\n\n        match arg {\n            Commands::New(new) => (\n                \"new\".to_string(),\n                json!({\n                    \"subtemplate\": new.subtemplate,\n                    \"option\": new.option,\n                    \"yes\": new.yes,\n                    \"vcs\": new.vcs.map(|v| match v {\n                        Vcs::Git => \"git\",\n                        Vcs::None => \"none\",\n                    }),\n                }),\n            ),\n            Commands::Serve(serve) => (\"serve\".to_string(), serve.anonymized()),\n            Commands::Bundle(bundle) => (\n                \"bundle\".to_string(),\n                json!({\n                    \"package_types\": bundle.package_types,\n                    \"out_dir\": bundle.out_dir.is_some(),\n                }),\n            ),\n            Commands::Build(build) => (\"build\".to_string(), build.anonymized()),\n            Commands::Run(run) => (\"run\".to_string(), run.args.anonymized()),\n            Commands::Init(cmd) => (\n                \"new\".to_string(),\n                json!({\n                    \"subtemplate\": cmd.subtemplate,\n                    \"option\": cmd.option,\n                    \"yes\": cmd.yes,\n                    \"vcs\": cmd.vcs.map(|v| match v {\n                        Vcs::Git => \"git\",\n                        Vcs::None => \"none\",\n                    }),\n                }),\n            ),\n            Commands::Doctor(_cmd) => (\"doctor\".to_string(), json!({})),\n            Commands::Translate(cmd) => (\n                \"translate\".to_string(),\n                json!({\n                    \"file\": cmd.file.is_some(),\n                    \"raw\": cmd.raw.is_some(),\n                    \"output\": cmd.output.is_some(),\n                    \"component\": cmd.component,\n                }),\n            ),\n            Commands::Autoformat(cmd) => (\n                \"fmt\".to_string(),\n                json!({\n                    \"all_code\": cmd.all_code,\n                    \"check\": cmd.check,\n                    \"raw\": cmd.raw.is_some(),\n                    \"file\": cmd.file.is_some(),\n                    \"split_line_attributes\": cmd.split_line_attributes,\n                    \"package\": cmd.package.is_some(),\n                }),\n            ),\n            Commands::Check(cmd) => (\n                \"check\".to_string(),\n                json!({\n                    \"file\": cmd.file.is_some(),\n                    \"build_args\": cmd.build_args.anonymized(),\n                }),\n            ),\n            Commands::Config(config) => match config {\n                Config::Init { force, .. } => (\n                    \"config init\".to_string(),\n                    json!({\n                        \"force\": force,\n                    }),\n                ),\n                Config::FormatPrint {} => (\"config format-print\".to_string(), json!({})),\n                Config::CustomHtml {} => (\"config custom-html\".to_string(), json!({})),\n                Config::Schema { out } => (\"config schema\".to_string(), json!({ \"out\": out })),\n                Config::Set(setting) => (\n                    format!(\"config set {}\", setting),\n                    match setting {\n                        Setting::AlwaysHotReload { value } => json!({ \"value\": value }),\n                        Setting::AlwaysOpenBrowser { value } => json!({ \"value\": value }),\n                        Setting::AlwaysOnTop { value } => json!({ \"value\": value }),\n                        Setting::WSLFilePollInterval { value } => json!({ \"value\": value }),\n                        Setting::DisableTelemetry { value } => json!({ \"value\": value }),\n                    },\n                ),\n            },\n            Commands::SelfUpdate(cmd) => (\n                \"update\".to_string(),\n                json!({\n                    \"nightly\": cmd.nightly,\n                    \"version\": cmd.version,\n                    \"install\": cmd.install,\n                    \"list\": cmd.list,\n                    \"force\": cmd.force,\n                }),\n            ),\n            Commands::Tools(tool) => match tool {\n                BuildTools::BuildAssets(_build_assets) => (\"tools assets\".to_string(), json!({})),\n                BuildTools::HotpatchTip(_hotpatch_tip) => (\"tools hotpatch\".to_string(), json!({})),\n            },\n            Commands::Print(print) => match print {\n                Print::ClientArgs(_args) => (\"print client-args\".to_string(), json!({})),\n                Print::ServerArgs(_args) => (\"print server-args\".to_string(), json!({})),\n            },\n            Commands::Components(cmd) => match cmd {\n                ComponentCommand::Add {\n                    component,\n                    registry,\n                    force,\n                } => (\n                    \"components add\".to_string(),\n                    json!({\n                        \"component\": component,\n                        \"registry\": registry,\n                        \"force\": force,\n                    }),\n                ),\n                ComponentCommand::Remove {\n                    component,\n                    registry,\n                } => (\n                    \"components remove\".to_string(),\n                    json!({\n                        \"component\": component,\n                        \"registry\": registry,\n                    }),\n                ),\n                ComponentCommand::Update { registry } => (\n                    \"components update\".to_string(),\n                    json!({\n                        \"registry\": registry,\n                    }),\n                ),\n                ComponentCommand::List { registry } => (\n                    \"components list\".to_string(),\n                    json!({\n                        \"registry\": registry,\n                    }),\n                ),\n                ComponentCommand::Clean => (\"components clean\".to_string(), json!({})),\n                ComponentCommand::Schema => (\"components schema\".to_string(), json!({})),\n            },\n        }\n    }\n\n    fn record_backtrace(\n        &self,\n        error: &Error,\n        error_type: &str,\n        thread_name: Option<&str>,\n        location: Option<&SavedLocation>,\n        extra: serde_json::Map<String, serde_json::Value>,\n    ) {\n        let stacktrace = sentry_backtrace::parse_stacktrace(&format!(\"{:#}\", error.backtrace()));\n        let stack_frames: Vec<_> = stacktrace\n            .unwrap_or_default()\n            .frames\n            .into_iter()\n            .rev()\n            .map(|frame| {\n                // Convert the sentry type to the posthog type\n                dioxus_cli_telemetry::StackFrame {\n                    platform: \"custom\".to_string(),\n                    raw_id: frame\n                        .image_addr\n                        .map(|addr| addr.to_string())\n                        .unwrap_or_else(|| Uuid::new_v4().to_string()),\n                    mangled_name: frame\n                        .function\n                        .as_ref()\n                        .map(|f| f.to_string())\n                        .unwrap_or_else(|| \"<unknown>\".to_string()),\n                    resolved_name: frame\n                        .function\n                        .as_ref()\n                        .map(|f| f.to_string())\n                        .unwrap_or_else(|| \"<unknown>\".to_string()),\n                    lang: \"rust\".to_string(),\n                    resolved: true,\n                    filename: frame.filename,\n                    function: frame.function,\n                    module: frame.module,\n                    lineno: frame.lineno,\n                    colno: frame.colno,\n                    abs_path: frame.abs_path,\n                    context_line: frame.context_line,\n                    pre_context: frame.pre_context,\n                    post_context: frame.post_context,\n                    in_app: frame.in_app,\n                    instruction_addr: frame.instruction_addr.map(|addr| addr.to_string()),\n                    addr_mode: frame.addr_mode,\n                    vars: frame.vars,\n                    chunk_id: None,\n                }\n            })\n            .collect();\n\n        let (mut file, mut line, mut column) = location\n            .as_ref()\n            .map(|l| (l.file.to_string(), l.line, l.column))\n            .unwrap_or_else(|| (\"<unknown>\".to_string(), 0, 0));\n\n        // If the location is not provided, we try to extract it from the backtrace.\n        // I eyeballed that 5 is enough to get to the actual panic location in most cases.\n        if location.is_none() {\n            if let Some(frame) = stack_frames.get(5) {\n                if let Some(abs_path) = &frame.abs_path {\n                    file = abs_path.to_string();\n                }\n                if let Some(lineno) = frame.lineno {\n                    line = lineno as _;\n                }\n                if let Some(colno) = frame.colno {\n                    column = colno as _;\n                }\n            }\n        }\n\n        let event = TelemetryEventData::new(error_type.to_string(), error.root_cause())\n            .with_value(\"thread_name\", thread_name)\n            .with_error_type(error_type.to_string())\n            .with_error_handled(false)\n            .with_file(file)\n            .with_line_column(line, column)\n            .with_stack_frames(stack_frames)\n            .with_values(extra);\n\n        _ = self.telemetry_tx.unbounded_send(event);\n    }\n}\n\nimpl<S> Layer<S> for TraceController\nwhere\n    S: Subscriber + for<'a> LookupSpan<'a>,\n{\n    fn on_event(\n        &self,\n        event: &tracing::Event<'_>,\n        _ctx: tracing_subscriber::layer::Context<'_, S>,\n    ) {\n        let mut visitor = CollectVisitor::default();\n        event.record(&mut visitor);\n        let meta = event.metadata();\n        let level = meta.level();\n\n        // Redirect to the TUI if it's active\n        if self.tui_active.load(Ordering::Relaxed) {\n            let final_msg = visitor.pretty();\n\n            if visitor.source == TraceSrc::Unknown {\n                visitor.source = TraceSrc::Dev;\n            }\n\n            _ = self\n                .tui_tx\n                .unbounded_send(TraceMsg::text(visitor.source, *level, final_msg));\n        }\n\n        // Handle telemetry events.\n        // Only tracing events annotated with `telemetry = %json! { ... }` will be captured.\n        // This applies to errors too - if you want an error to be captured, it must have a telemetry field!\n        //\n        // If the event is `error!()` it counts as a an \"$exception\" event in posthog\n        // If it has a backtrace, then we upload the backtrace as well.\n        if let Some(telemetry) = visitor.fields.get(\"telemetry\") {\n            let extra =\n                serde_json::from_str::<serde_json::Map<String, serde_json::Value>>(telemetry)\n                    .unwrap_or_default();\n\n            let stage = match visitor.source {\n                TraceSrc::Cargo | TraceSrc::App(_) => \"\",\n                TraceSrc::Dev => \"dev\",\n                TraceSrc::Build => \"build\",\n                TraceSrc::Bundle => \"bundle\",\n                TraceSrc::Unknown => \"unknown\",\n            };\n\n            // If the event has the captured panic attached, we record it through capture_backtrace directly.\n            // Otherwise, we won't have a backtrace to work with, so we just log the event as a telemetry event.\n            if let Some(error) = visitor.captured_panic {\n                self.record_backtrace(\n                    error.error.as_ref(),\n                    &error.error_type,\n                    error.thread_name.as_deref(),\n                    error.location.as_ref(),\n                    extra,\n                );\n            } else {\n                let is_err = level == &Level::ERROR;\n                let event_name = match visitor.fields.get(\"event\") {\n                    Some(event) => event.clone(),\n                    None if is_err => \"Runtime Error\".into(),\n                    None => \"Telemetry Event\".into(),\n                };\n\n                let mut event = TelemetryEventData::new(event_name, visitor.message.as_str())\n                    .with_file(meta.file().unwrap_or(\"<unknown>\").to_string())\n                    .with_line_column(meta.line().unwrap_or_default(), 0)\n                    .with_value(\"trace_stage\", stage)\n                    .with_value(\"trace_level\", meta.level().to_string())\n                    .with_value(\"trace_name\", meta.name())\n                    .with_value(\"trace_target\", meta.target())\n                    .with_value(\"trace_module_path\", meta.module_path())\n                    .with_values(extra)\n                    .with_values(visitor.fields_to_json());\n\n                if is_err {\n                    event = event\n                        .with_error_type(\"Runtime Error\".to_string())\n                        .with_error_handled(false);\n                }\n\n                _ = self.telemetry_tx.unbounded_send(event);\n            }\n        }\n\n        // Write to a file if we need to.\n        if let Some(open_file) = self.log_to_file.as_ref() {\n            let new_line = if visitor.source == TraceSrc::Cargo {\n                Cow::Borrowed(visitor.message.as_str())\n            } else {\n                let meta = event.metadata();\n                let level = meta.level();\n\n                let mut final_msg = String::new();\n                _ = write!(\n                    final_msg,\n                    \"[{level}] {}: {} \",\n                    meta.module_path().unwrap_or(\"dx\"),\n                    visitor.message.as_str()\n                );\n\n                for (field, value) in visitor.fields.iter() {\n                    _ = write!(final_msg, \"{} \", format_field(field, value));\n                }\n                _ = writeln!(final_msg);\n                Cow::Owned(final_msg)\n            };\n\n            let new_data = console::strip_ansi_codes(&new_line).to_string();\n            if let Ok(mut file) = open_file.try_lock() {\n                use std::io::Write;\n                _ = writeln!(file, \"{}\", new_data);\n            }\n        }\n    }\n}\n\n/// A record visitor that collects dx-specific info and user-provided fields for logging consumption.\n#[derive(Default)]\nstruct CollectVisitor {\n    message: String,\n    source: TraceSrc,\n    fields: HashMap<String, String>,\n    captured_panic: Option<CapturedPanicError>,\n}\n\nimpl Visit for CollectVisitor {\n    fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) {\n        let name = field.name();\n\n        let mut value_string = String::new();\n        write!(value_string, \"{value:?}\").unwrap();\n\n        if name == \"message\" {\n            self.message = value_string;\n            return;\n        }\n\n        if name == DX_SRC_FLAG {\n            self.source = TraceSrc::from(value_string);\n            return;\n        }\n\n        if name == \"json\" || name == \"backtrace\" {\n            return;\n        }\n\n        self.fields.insert(name.to_string(), value_string);\n    }\n\n    fn record_error(\n        &mut self,\n        field: &tracing::field::Field,\n        value: &(dyn std::error::Error + 'static),\n    ) {\n        if let Some(captured) = value.downcast_ref::<CapturedPanicError>() {\n            self.captured_panic = Some(captured.clone());\n        } else {\n            self.record_debug(field, &tracing::field::display(value));\n        }\n    }\n}\nimpl CollectVisitor {\n    fn pretty(&self) -> String {\n        let mut final_msg = String::new();\n        write!(final_msg, \"{} \", self.message).unwrap();\n\n        for (field, value) in self.fields.iter() {\n            if field == \"json\" || field == \"telemetry\" || field == \"backtrace\" {\n                continue;\n            }\n\n            write!(final_msg, \"{} \", format_field(field, value)).unwrap();\n        }\n        final_msg\n    }\n\n    fn fields_to_json(&self) -> serde_json::Map<String, serde_json::Value> {\n        self.fields\n            .iter()\n            .map(|(k, v)| (k.clone(), serde_json::Value::String(v.clone())))\n            .collect()\n    }\n}\n\n/// Formats a tracing field and value, removing any internal fields from the final output.\nfn format_field(field_name: &str, value: &dyn Debug) -> String {\n    match field_name {\n        \"message\" => format!(\"{value:?}\"),\n        _ => format!(\"{field_name}={value:?}\"),\n    }\n}\n\n#[derive(Clone, PartialEq)]\npub struct TraceMsg {\n    pub source: TraceSrc,\n    pub level: Level,\n    pub content: TraceContent,\n    pub timestamp: chrono::DateTime<chrono::Local>,\n}\n\n#[derive(Clone, PartialEq)]\n#[allow(clippy::large_enum_variant)]\npub enum TraceContent {\n    Cargo(Diagnostic),\n    Text(String),\n}\n\nimpl TraceMsg {\n    pub fn text(source: TraceSrc, level: Level, content: String) -> Self {\n        Self {\n            source,\n            level,\n            content: TraceContent::Text(content),\n            timestamp: chrono::Local::now(),\n        }\n    }\n\n    /// Create a new trace message from a cargo compiler message\n    ///\n    /// All `cargo` messages are logged at the `TRACE` level since they get *very* noisy during development\n    pub fn cargo(content: Diagnostic) -> Self {\n        Self {\n            level: match content.level {\n                DiagnosticLevel::Ice => Level::ERROR,\n                DiagnosticLevel::Error => Level::ERROR,\n                DiagnosticLevel::FailureNote => Level::ERROR,\n                DiagnosticLevel::Warning => Level::TRACE,\n                DiagnosticLevel::Note => Level::TRACE,\n                DiagnosticLevel::Help => Level::TRACE,\n                _ => Level::TRACE,\n            },\n            timestamp: chrono::Local::now(),\n            source: TraceSrc::Cargo,\n            content: TraceContent::Cargo(content),\n        }\n    }\n}\n\n#[derive(Clone, Copy, PartialEq, Default)]\npub enum TraceSrc {\n    App(BundleFormat),\n    Dev,\n    Build,\n    Bundle,\n    Cargo,\n\n    #[default]\n    Unknown,\n}\n\nimpl std::fmt::Debug for TraceSrc {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        <Self as Display>::fmt(self, f)\n    }\n}\n\nimpl From<String> for TraceSrc {\n    fn from(value: String) -> Self {\n        match value.as_str() {\n            \"dev\" => Self::Dev,\n            \"bld\" => Self::Build,\n            \"cargo\" => Self::Cargo,\n            other => BundleFormat::from_str(other)\n                .map(Self::App)\n                .unwrap_or_else(|_| Self::Unknown),\n        }\n    }\n}\n\nimpl Display for TraceSrc {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        match self {\n            Self::App(bundle) => write!(f, \"{bundle}\"),\n            Self::Dev => write!(f, \"dev\"),\n            Self::Build => write!(f, \"build\"),\n            Self::Cargo => write!(f, \"cargo\"),\n            Self::Unknown => write!(f, \"n/a\"),\n            Self::Bundle => write!(f, \"bundle\"),\n        }\n    }\n}\n\n/// Retrieve and print the relative elapsed wall-clock time since an epoch.\n///\n/// The `Default` implementation for `Uptime` makes the epoch the current time.\n#[derive(Debug, Clone, Copy, Eq, PartialEq)]\npub struct PrettyUptime {\n    epoch: Instant,\n}\n\nimpl Default for PrettyUptime {\n    fn default() -> Self {\n        Self {\n            epoch: Instant::now(),\n        }\n    }\n}\n\nimpl FormatTime for PrettyUptime {\n    fn format_time(&self, w: &mut Writer<'_>) -> std::fmt::Result {\n        let e = self.epoch.elapsed();\n        write!(w, \"{:4}.{:2}s\", e.as_secs(), e.subsec_millis())\n    }\n}\n/// Run the provided future and wait for it to complete, handling Ctrl-C gracefully.\n///\n/// If ctrl-c is pressed twice, it exits immediately, skipping our telemetry flush.\n/// Todo: maybe we want a side thread continuously flushing telemetry, esp if the user double ctrl-cs\nasync fn run_with_ctrl_c(\n    f: impl Future<Output = Result<StructuredOutput>>,\n) -> Result<StructuredOutput> {\n    let (tx, rx) = tokio::sync::oneshot::channel();\n    let mut tx = Some(tx);\n\n    _ = ctrlc::set_handler(move || {\n        match tx.take() {\n            // If we have a sender, we unset the following `select!` and continue from there\n            Some(tx) => _ = tx.send(()),\n\n            // If we get a second ctrl-c, we just exit immediately\n            None => {\n                reset_cursor();\n                std::process::exit(1);\n            }\n        }\n    });\n\n    tokio::select! {\n        _ = rx => Ok(StructuredOutput::Success),\n        res = f => res,\n    }\n}\n\n/// We only store non-pii information in telemetry to track issues and performance\n/// across the CLI.\n#[derive(Serialize, Deserialize, Debug, Clone)]\npub struct Reporter {\n    pub session_id: Uuid,\n    pub distinct_id: Uuid,\n}\n"
  },
  {
    "path": "packages/cli/src/main.rs",
    "content": "#![doc = include_str!(\"../README.md\")]\n#![doc(html_logo_url = \"https://avatars.githubusercontent.com/u/79236386\")]\n#![doc(html_favicon_url = \"https://avatars.githubusercontent.com/u/79236386\")]\n#![cfg_attr(docsrs, feature(doc_cfg))]\n#![allow(clippy::doc_overindented_list_items)]\n\nmod build;\nmod bundle_utils;\nmod cargo_toml;\nmod cli;\nmod config;\nmod devcfg;\nmod dx_build_info;\nmod error;\nmod fastfs;\nmod logging;\nmod platform;\nmod rustcwrapper;\nmod serve;\nmod settings;\nmod tailwind;\nmod test_harnesses;\nmod wasm_bindgen;\nmod wasm_opt;\nmod workspace;\n\nuse std::process::ExitCode;\n\npub(crate) use build::*;\npub(crate) use cli::*;\npub(crate) use config::*;\npub(crate) use dioxus_dx_wire_format::*;\npub(crate) use error::*;\npub(crate) use link::*;\npub(crate) use logging::*;\npub(crate) use platform::*;\npub(crate) use rustcwrapper::*;\npub(crate) use settings::*;\npub(crate) use tailwind::*;\npub(crate) use wasm_bindgen::*;\npub(crate) use workspace::*;\n\n#[tokio::main]\nasync fn main() -> ExitCode {\n    // The CLI uses dx as a rustcwrapper in some instances (like binary patching)\n    if rustcwrapper::is_wrapping_rustc() {\n        return rustcwrapper::run_rustc();\n    }\n\n    // If we're being ran as a linker (likely from ourselves), we want to act as a linker instead.\n    if let Some(link_args) = link::LinkAction::from_env() {\n        return link_args.run_link();\n    }\n\n    // Run under the tracing collector so we can capture errors/panics.\n    let result = TraceController::main(|args, tracer| async move {\n        match args {\n            Commands::Serve(opts) => opts.serve(&tracer).await,\n            Commands::Translate(opts) => opts.translate(),\n            Commands::New(opts) => opts.create().await,\n            Commands::Init(opts) => opts.init().await,\n            Commands::Config(opts) => opts.config().await,\n            Commands::Autoformat(opts) => opts.autoformat().await,\n            Commands::Check(opts) => opts.check().await,\n            Commands::Build(opts) => opts.build().await,\n            Commands::Bundle(opts) => opts.bundle().await,\n            Commands::Run(opts) => opts.run().await,\n            Commands::SelfUpdate(opts) => opts.self_update().await,\n            Commands::Tools(BuildTools::BuildAssets(opts)) => opts.run().await,\n            Commands::Tools(BuildTools::HotpatchTip(opts)) => opts.run().await,\n            Commands::Doctor(opts) => opts.doctor().await,\n            Commands::Print(opts) => opts.print().await,\n            Commands::Components(opts) => opts.run().await,\n        }\n    });\n\n    // Print the structured output in JSON format for third-party tools to consume.\n    // Make sure we do this as the last step so you can always `tail -1` it\n    match result.await {\n        StructuredOutput::Error { message } => {\n            tracing::error!(json = %StructuredOutput::Error { message });\n            std::process::exit(1);\n        }\n\n        output => tracing::info!(json = %output),\n    }\n\n    ExitCode::SUCCESS\n}\n"
  },
  {
    "path": "packages/cli/src/platform.rs",
    "content": "use anyhow::Result;\nuse clap::{arg, Arg, ArgMatches, Args, FromArgMatches};\nuse serde::{Deserialize, Serialize};\nuse std::fmt::Display;\nuse std::str::FromStr;\nuse target_lexicon::{Environment, OperatingSystem, Triple};\n\n#[derive(\n    Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Debug, Default,\n)]\n#[non_exhaustive]\npub(crate) enum Platform {\n    /// Alias for `--target wasm32-unknown-unknown --renderer websys --bundle-format web`\n    #[serde(rename = \"web\")]\n    Web,\n\n    /// Alias for `--target <host> --renderer webview --bundle-format macos`\n    #[serde(rename = \"macos\")]\n    MacOS,\n\n    /// Alias for `--target <host> --renderer webview --bundle-format windows`\n    #[serde(rename = \"windows\")]\n    Windows,\n\n    /// Alias for `--target <host> --renderer webview --bundle-format linux`\n    #[serde(rename = \"linux\")]\n    Linux,\n\n    /// Alias for `--target <aarch64-apple-ios/sim> --renderer webview --bundle-format ios`\n    #[serde(rename = \"ios\")]\n    Ios,\n\n    /// Alias for `--target <device-triple> --renderer webview --bundle-format android`\n    #[serde(rename = \"android\")]\n    Android,\n\n    /// Alias for `--target <host> --renderer ssr --bundle-format server`\n    #[serde(rename = \"server\")]\n    Server,\n\n    /// Alias for `--target <host> --renderer liveview --bundle-format server`\n    #[serde(rename = \"liveview\")]\n    Liveview,\n\n    /// No platform was specified, so the CLI is free to choose the best one.\n    #[default]\n    Unknown,\n}\n\nimpl Platform {\n    fn from_identifier(identifier: &str) -> std::result::Result<Self, clap::Error> {\n        match identifier {\n            \"web\" => Ok(Self::Web),\n            \"macos\" => Ok(Self::MacOS),\n            \"windows\" => Ok(Self::Windows),\n            \"linux\" => Ok(Self::Linux),\n            \"ios\" => Ok(Self::Ios),\n            \"android\" => Ok(Self::Android),\n            \"server\" => Ok(Self::Server),\n            \"liveview\" => Ok(Self::Liveview),\n            \"desktop\" => {\n                if cfg!(target_os = \"macos\") {\n                    Ok(Self::MacOS)\n                } else if cfg!(target_os = \"windows\") {\n                    Ok(Self::Windows)\n                } else if cfg!(unix) {\n                    Ok(Self::Linux)\n                } else {\n                    Err(clap::Error::raw(\n                        clap::error::ErrorKind::InvalidValue,\n                        \"Desktop alias is not supported on this platform\",\n                    ))\n                }\n            }\n            _ => Err(clap::Error::raw(\n                clap::error::ErrorKind::InvalidValue,\n                format!(\"Unknown platform: {identifier}\"),\n            )),\n        }\n    }\n}\n\nimpl Args for Platform {\n    fn augment_args_for_update(cmd: clap::Command) -> clap::Command {\n        Self::augment_args(cmd)\n    }\n\n    fn augment_args(cmd: clap::Command) -> clap::Command {\n        const HELP_HEADING: &str = \"Platform\";\n        cmd.arg(arg!(--web \"Target a web app\").help_heading(HELP_HEADING))\n            .arg(arg!(--desktop \"Target a desktop app\").help_heading(HELP_HEADING))\n            .arg(arg!(--macos \"Target a macos desktop app\").help_heading(HELP_HEADING))\n            .arg(arg!(--windows \"Target a windows desktop app\").help_heading(HELP_HEADING))\n            .arg(arg!(--linux \"Target a linux desktop app\").help_heading(HELP_HEADING))\n            .arg(arg!(--ios \"Target an ios app\").help_heading(HELP_HEADING))\n            .arg(arg!(--android \"Target an android app\").help_heading(HELP_HEADING))\n            .arg(arg!(--server \"Target a server build\").help_heading(HELP_HEADING))\n            .arg(arg!(--liveview \"Target a liveview build\").help_heading(HELP_HEADING))\n            .arg(\n                Arg::new(\"platform\")\n                    .long(\"platform\")\n                    .value_name(\"PLATFORM\")\n                    .help(\"Manually set the platform (web, macos, windows, linux, ios, android, server, liveview)\")\n                    .help_heading(HELP_HEADING)\n                    .value_parser([\n                        \"web\", \"macos\", \"windows\", \"linux\", \"ios\", \"android\", \"server\", \"liveview\", \"desktop\",\n                    ])\n                    .conflicts_with(\"target_alias\"),\n            )\n            .group(\n                clap::ArgGroup::new(\"target_alias\")\n                    .args([\n                        \"web\", \"desktop\", \"macos\", \"windows\", \"linux\", \"ios\", \"android\", \"server\",\n                        \"liveview\",\n                    ])\n                    .multiple(false)\n                    .required(false),\n            )\n    }\n}\n\nimpl FromArgMatches for Platform {\n    fn from_arg_matches(matches: &ArgMatches) -> Result<Self, clap::Error> {\n        if let Some(identifier) = matches.get_one::<String>(\"platform\") {\n            Self::from_identifier(identifier)\n        } else if let Some(platform) = matches.get_one::<clap::Id>(\"target_alias\") {\n            Self::from_identifier(platform.as_str())\n        } else {\n            Ok(Self::Unknown)\n        }\n    }\n\n    fn update_from_arg_matches(&mut self, matches: &ArgMatches) -> Result<(), clap::Error> {\n        *self = Self::from_arg_matches(matches)?;\n        Ok(())\n    }\n}\n\n#[derive(\n    Copy,\n    Clone,\n    Hash,\n    PartialEq,\n    Eq,\n    PartialOrd,\n    Ord,\n    Serialize,\n    Deserialize,\n    Debug,\n    clap::ValueEnum,\n)]\n#[non_exhaustive]\npub(crate) enum Renderer {\n    /// Targeting webview renderer\n    Webview,\n\n    /// Targeting native renderer\n    Native,\n\n    /// Targeting the server platform using Axum and Dioxus-Fullstack\n    ///\n    /// This is implicitly passed if `fullstack` is enabled as a feature. Using this variant simply\n    /// means you're only building the server variant without the `.wasm` to serve.\n    Server,\n\n    /// Targeting the static generation platform using SSR and Dioxus-Fullstack\n    Liveview,\n\n    /// Targeting the web-sys renderer\n    Web,\n}\n\nimpl Renderer {\n    /// Get the feature name for the platform in the dioxus crate\n    pub(crate) fn feature_name(&self, target: &Triple) -> &str {\n        match self {\n            Renderer::Webview => match (target.environment, target.operating_system) {\n                (Environment::Android, _) | (_, OperatingSystem::IOS(_)) => \"mobile\",\n                _ => \"desktop\",\n            },\n            Renderer::Native => \"native\",\n            Renderer::Server => \"server\",\n            Renderer::Liveview => \"liveview\",\n            Renderer::Web => \"web\",\n        }\n    }\n\n    pub(crate) fn autodetect_from_cargo_feature(feature: &str) -> Option<Self> {\n        match feature {\n            \"web\" => Some(Self::Web),\n            \"desktop\" | \"mobile\" => Some(Self::Webview),\n            \"native\" => Some(Self::Native),\n            \"liveview\" => Some(Self::Liveview),\n            \"server\" => Some(Self::Server),\n            _ => None,\n        }\n    }\n}\n\n#[derive(Debug)]\npub(crate) struct UnknownRendererError;\n\nimpl std::error::Error for UnknownRendererError {}\n\nimpl std::fmt::Display for UnknownRendererError {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(f, \"Unknown renderer\")\n    }\n}\n\nimpl FromStr for Renderer {\n    type Err = UnknownRendererError;\n\n    fn from_str(s: &str) -> Result<Self, Self::Err> {\n        match s {\n            \"webview\" => Ok(Self::Webview),\n            \"native\" => Ok(Self::Native),\n            \"server\" => Ok(Self::Server),\n            \"liveview\" => Ok(Self::Liveview),\n            \"web\" => Ok(Self::Web),\n            _ => Err(UnknownRendererError),\n        }\n    }\n}\n\nimpl Display for Renderer {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.write_str(match self {\n            Renderer::Webview => \"webview\",\n            Renderer::Native => \"native\",\n            Renderer::Server => \"server\",\n            Renderer::Liveview => \"liveview\",\n            Renderer::Web => \"web\",\n        })\n    }\n}\n#[derive(\n    Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Debug, Default,\n)]\n#[non_exhaustive]\npub(crate) enum BundleFormat {\n    /// Targeting the web bundle structure\n    #[serde(rename = \"web\")]\n    #[default]\n    Web,\n\n    /// Targeting the macos desktop bundle structure\n    #[cfg_attr(target_os = \"macos\", serde(alias = \"desktop\"))]\n    #[serde(rename = \"macos\")]\n    MacOS,\n\n    /// Targeting the windows desktop bundle structure\n    #[cfg_attr(target_os = \"windows\", serde(alias = \"desktop\"))]\n    #[serde(rename = \"windows\")]\n    Windows,\n\n    /// Targeting the linux desktop bundle structure\n    #[cfg_attr(target_os = \"linux\", serde(alias = \"desktop\"))]\n    #[serde(rename = \"linux\")]\n    Linux,\n\n    /// Targeting the server bundle structure (a single binary placed next to the web build)\n    #[serde(rename = \"server\")]\n    Server,\n\n    /// Targeting the ios bundle structure\n    ///\n    /// Can't work properly if you're not building from an Apple device.\n    #[serde(rename = \"ios\")]\n    Ios,\n\n    /// Targeting the android bundle structure\n    #[serde(rename = \"android\")]\n    Android,\n}\n\nimpl BundleFormat {\n    /// The native \"desktop\" host app format.\n    pub(crate) fn host() -> Self {\n        if cfg!(target_os = \"macos\") {\n            Self::MacOS\n        } else if cfg!(target_os = \"windows\") {\n            Self::Windows\n        } else if cfg!(target_os = \"linux\") {\n            Self::Linux\n        } else {\n            Self::Web\n        }\n    }\n\n    /// Get the name of the folder we need to generate for this platform\n    ///\n    /// Note that web and server share the same platform folder since we'll export the web folder as a bundle on its own\n    pub(crate) fn build_folder_name(&self) -> &'static str {\n        match self {\n            Self::Web => \"web\",\n            Self::Server => \"web\",\n            Self::Ios => \"ios\",\n            Self::Android => \"android\",\n            Self::Windows => \"windows\",\n            Self::Linux => \"linux\",\n            Self::MacOS => \"macos\",\n        }\n    }\n\n    pub(crate) fn profile_name(&self, release: bool) -> String {\n        let base_profile = match self {\n            Self::MacOS | Self::Windows | Self::Linux => \"desktop\",\n            Self::Web => \"wasm\",\n            Self::Ios => \"ios\",\n            Self::Android => \"android\",\n            Self::Server => \"server\",\n        };\n\n        let opt_level = if release { \"release\" } else { \"dev\" };\n\n        format!(\"{base_profile}-{opt_level}\")\n    }\n\n    pub(crate) fn expected_name(&self) -> &'static str {\n        match self {\n            Self::Web => \"Web\",\n            Self::MacOS => \"MacOS\",\n            Self::Windows => \"Windows\",\n            Self::Linux => \"Linux\",\n            Self::Ios => \"iOS\",\n            Self::Android => \"Android\",\n            Self::Server => \"Server\",\n        }\n    }\n}\n\n#[derive(Debug)]\npub(crate) struct UnknownBundleFormatError;\n\nimpl std::error::Error for UnknownBundleFormatError {}\n\nimpl std::fmt::Display for UnknownBundleFormatError {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(f, \"Unknown bundle format\")\n    }\n}\n\nimpl FromStr for BundleFormat {\n    type Err = UnknownBundleFormatError;\n\n    fn from_str(s: &str) -> Result<Self, Self::Err> {\n        match s {\n            \"web\" => Ok(Self::Web),\n            \"macos\" => Ok(Self::MacOS),\n            \"windows\" => Ok(Self::Windows),\n            \"linux\" => Ok(Self::Linux),\n            \"server\" => Ok(Self::Server),\n            \"ios\" => Ok(Self::Ios),\n            \"android\" => Ok(Self::Android),\n            _ => Err(UnknownBundleFormatError),\n        }\n    }\n}\n\nimpl Display for BundleFormat {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.write_str(match self {\n            BundleFormat::Web => \"web\",\n            BundleFormat::MacOS => \"macos\",\n            BundleFormat::Windows => \"windows\",\n            BundleFormat::Linux => \"linux\",\n            BundleFormat::Server => \"server\",\n            BundleFormat::Ios => \"ios\",\n            BundleFormat::Android => \"android\",\n        })\n    }\n}\n"
  },
  {
    "path": "packages/cli/src/rustcwrapper.rs",
    "content": "use serde::{Deserialize, Serialize};\nuse std::{\n    env::{args, vars},\n    path::PathBuf,\n    process::ExitCode,\n};\n\n/// The environment variable indicating where the args directory is located.\n///\n/// When `dx-rustc` runs, it writes each workspace crate's arguments to a\n/// separate file in this directory: `{dir}/{crate_name}.json`.\npub const DX_RUSTC_WRAPPER_ENV_VAR: &str = \"DX_RUSTC\";\n\n/// Is `dx` being used as a rustc wrapper?\n///\n/// This is primarily used to intercept cargo, enabling fast hot-patching by caching the environment\n/// cargo setups up for the user's current project.\n///\n/// In a different world we could simply rely on cargo printing link args and the rustc command, but\n/// it doesn't seem to output that in a reliable, parseable, cross-platform format (ie using command\n/// files on windows...), so we're forced to do this interception nonsense.\npub fn is_wrapping_rustc() -> bool {\n    std::env::var(DX_RUSTC_WRAPPER_ENV_VAR).is_ok()\n}\n\n#[derive(Default, Clone, Debug, PartialEq, Serialize, Deserialize)]\npub struct RustcArgs {\n    pub args: Vec<String>,\n    pub envs: Vec<(String, String)>,\n    /// it doesn't include first program name argument\n    pub link_args: Vec<String>,\n}\n\n/// Check if the arguments indicate a linking step, including those in command files.\nfn has_linking_args() -> bool {\n    for arg in std::env::args() {\n        // Direct check for linker-like arguments\n        if arg.ends_with(\".o\") || arg == \"-flavor\" {\n            return true;\n        }\n\n        // Check inside command files\n        if let Some(path_str) = arg.strip_prefix('@') {\n            if let Ok(file_binary) = std::fs::read(path_str) {\n                // Handle both UTF-8 and UTF-16LE encodings for response files.\n                let content = String::from_utf8(file_binary.clone()).unwrap_or_else(|_| {\n                    let binary_u16le: Vec<u16> = file_binary\n                        .chunks_exact(2)\n                        .map(|a| u16::from_le_bytes([a[0], a[1]]))\n                        .collect();\n                    String::from_utf16_lossy(&binary_u16le)\n                });\n\n                // Check if any line in the command file contains linking indicators.\n                if content.lines().any(|line| {\n                    let trimmed_line = line.trim().trim_matches('\"');\n                    trimmed_line.ends_with(\".o\") || trimmed_line == \"-flavor\"\n                }) {\n                    return true;\n                }\n            }\n        }\n    }\n\n    false\n}\n\n/// Run rustc directly, but output the result to a per-crate file in the args directory.\n///\n/// <https://doc.rust-lang.org/cargo/reference/config.html#buildrustc>\npub fn run_rustc() -> ExitCode {\n    // If we are being asked to link, delegate to the linker action.\n    if has_linking_args() {\n        return crate::link::LinkAction::from_env()\n            .expect(\"Linker action not found\")\n            .run_link();\n    }\n\n    let args_dir: PathBuf = std::env::var(DX_RUSTC_WRAPPER_ENV_VAR)\n        .expect(\"DX_RUSTC env var must be set\")\n        .into();\n\n    // Cargo invokes a workspace wrapper like: `wrapper-name rustc [args...]`\n    // We skip our own executable name (`wrapper-name`) to get the args passed to us.\n    let captured_args = args().skip(1).collect::<Vec<_>>();\n\n    let rustc_args = RustcArgs {\n        args: captured_args.clone(),\n        envs: vars().collect::<_>(),\n        link_args: Default::default(),\n    };\n\n    // Extract the crate name from the args to use as the filename.\n    // Skip non-sensical args when a build is completely fresh (rustc is invoked with --crate-name ___)\n    let crate_name = rustc_args\n        .args\n        .iter()\n        .skip_while(|arg| *arg != \"--crate-name\")\n        .nth(1);\n\n    if let Some(crate_name) = crate_name {\n        if crate_name != \"___\" {\n            std::fs::create_dir_all(&args_dir)\n                .expect(\"Failed to create args directory for rustc wrapper\");\n\n            let crate_type = rustc_args\n                .args\n                .iter()\n                .skip_while(|arg| *arg != \"--crate-type\")\n                .nth(1)\n                .map(|s| s.as_str());\n\n            let serialized_args =\n                serde_json::to_string(&rustc_args).expect(\"Failed to serialize rustc args\");\n\n            // Write args with an explicit target suffix: {crate_name}.lib.json or\n            // {crate_name}.bin.json. This avoids the ambiguity of a bare {crate_name}.json\n            // and ensures lib+bin crates don't overwrite each other.\n            let suffix = match crate_type {\n                Some(\"lib\" | \"rlib\") => \"lib\",\n                Some(\"bin\") => \"bin\",\n                _ => \"bin\", // proc-macro, cdylib, etc. — treat as bin\n            };\n\n            std::fs::write(\n                args_dir.join(format!(\"{crate_name}.{suffix}.json\")),\n                &serialized_args,\n            )\n            .expect(\"Failed to write rustc args to file\");\n        }\n    }\n\n    // Run the actual rustc command.\n    // We want all stdout/stderr to be inherited, so the user sees the compiler output.\n    let mut cmd = std::process::Command::new(\"rustc\");\n\n    // The first argument in `captured_args` is the rustc path, which we need to skip\n    // when passing arguments to the `rustc` command we are spawning.\n    cmd.args(captured_args.iter().skip(1));\n    cmd.envs(rustc_args.envs);\n    cmd.stdout(std::process::Stdio::inherit());\n    cmd.stderr(std::process::Stdio::inherit());\n    cmd.current_dir(std::env::current_dir().expect(\"Failed to get current dir\"));\n\n    // Spawn the process and propagate its exit code.\n    let status = cmd.status().expect(\"Failed to execute rustc command\");\n    std::process::exit(status.code().unwrap_or(1)); // Exit with 1 if process was killed by signal\n}\n"
  },
  {
    "path": "packages/cli/src/serve/ansi_buffer.rs",
    "content": "use ratatui::{buffer::Cell, prelude::*};\nuse std::fmt::{self, Write};\n\n/// A buffer that can be rendered to and then dumped as raw ansi codes\n///\n/// This is taken from a PR on the ratatui repo (<https://github.com/ratatui/ratatui/pull/1065>) and\n/// modified to be more appropriate for our use case.\npub fn ansi_string_to_line(line: Line) -> String {\n    let graphemes = line.styled_graphemes(Style::default());\n    let mut out = String::with_capacity(line.spans.first().map_or(0, |s| s.content.len() * 2));\n    let mut last_style = None;\n    for grapheme in graphemes {\n        let mut cell = Cell::default();\n        cell.set_symbol(grapheme.symbol);\n        cell.set_style(grapheme.style);\n\n        let style = (cell.fg, cell.bg, cell.modifier);\n        if last_style.is_none() || last_style != Some(style) {\n            _ = write_cell_style(&mut out, cell.modifier, cell.fg, cell.bg);\n            last_style = Some(style);\n        }\n\n        _ = out.write_str(cell.symbol());\n    }\n\n    _ = out.write_str(\"\\u{1b}[0m\");\n\n    out\n}\n\nfn write_cell_style(f: &mut impl Write, modifier: Modifier, fg: Color, bg: Color) -> fmt::Result {\n    f.write_str(\"\\u{1b}[\")?;\n\n    // Write the modifier codes\n    if modifier.contains(Modifier::BOLD) {\n        f.write_str(\"1;\")?;\n    }\n    if modifier.contains(Modifier::DIM) {\n        f.write_str(\"2;\")?;\n    }\n    if modifier.contains(Modifier::ITALIC) {\n        f.write_str(\"3;\")?;\n    }\n    if modifier.contains(Modifier::UNDERLINED) {\n        f.write_str(\"4;\")?;\n    }\n    if modifier.contains(Modifier::SLOW_BLINK) {\n        f.write_str(\"5;\")?;\n    }\n    if modifier.contains(Modifier::RAPID_BLINK) {\n        f.write_str(\"6;\")?;\n    }\n    if modifier.contains(Modifier::REVERSED) {\n        f.write_str(\"7;\")?;\n    }\n    if modifier.contains(Modifier::HIDDEN) {\n        f.write_str(\"8;\")?;\n    }\n    if modifier.contains(Modifier::CROSSED_OUT) {\n        f.write_str(\"9;\")?;\n    }\n\n    // Write the foreground\n    f.write_str(match fg {\n        Color::Reset => \"39\",\n        Color::Black => \"30\",\n        Color::Red => \"31\",\n        Color::Green => \"32\",\n        Color::Yellow => \"33\",\n        Color::Blue => \"34\",\n        Color::Magenta => \"35\",\n        Color::Cyan => \"36\",\n        Color::Gray => \"37\",\n        Color::DarkGray => \"90\",\n        Color::LightRed => \"91\",\n        Color::LightGreen => \"92\",\n        Color::LightYellow => \"93\",\n        Color::LightBlue => \"94\",\n        Color::LightMagenta => \"95\",\n        Color::LightCyan => \"96\",\n        Color::White => \"97\",\n        _ => \"\",\n    })?;\n    if let Color::Rgb(red, green, blue) = fg {\n        f.write_fmt(format_args!(\"38;2;{red};{green};{blue}\"))?;\n    }\n    if let Color::Indexed(i) = fg {\n        f.write_fmt(format_args!(\"38;5;{i}\"))?;\n    }\n    f.write_str(\";\")?;\n\n    // Write the background\n    f.write_str(match bg {\n        Color::Reset => \"49\",\n        Color::Black => \"40\",\n        Color::Red => \"41\",\n        Color::Green => \"42\",\n        Color::Yellow => \"43\",\n        Color::Blue => \"44\",\n        Color::Magenta => \"45\",\n        Color::Cyan => \"46\",\n        Color::Gray => \"47\",\n        Color::DarkGray => \"100\",\n        Color::LightRed => \"101\",\n        Color::LightGreen => \"102\",\n        Color::LightYellow => \"103\",\n        Color::LightBlue => \"104\",\n        Color::LightMagenta => \"105\",\n        Color::LightCyan => \"106\",\n        Color::White => \"107\",\n        _ => \"\",\n    })?;\n    if let Color::Rgb(red, green, blue) = bg {\n        f.write_fmt(format_args!(\"48;2;{red};{green};{blue}\"))?;\n    }\n    if let Color::Indexed(i) = bg {\n        f.write_fmt(format_args!(\"48;5;{i}\"))?;\n    }\n\n    f.write_str(\"m\")\n}\n"
  },
  {
    "path": "packages/cli/src/serve/mod.rs",
    "content": "use crate::{\n    styles::{GLOW_STYLE, LINK_STYLE},\n    AppBuilder, BuildId, BuildMode, BuilderUpdate, BundleFormat, Result, ServeArgs,\n    TraceController,\n};\n\nmod ansi_buffer;\nmod output;\nmod proxy;\nmod proxy_ws;\nmod runner;\nmod server;\nmod update;\n\nuse anyhow::bail;\nuse dioxus_dx_wire_format::BuildStage;\npub(crate) use output::*;\npub(crate) use runner::*;\npub(crate) use server::*;\npub(crate) use update::*;\n\n/// For *all* builds, the CLI spins up a dedicated webserver, file watcher, and build infrastructure to serve the project.\n///\n/// This includes web, desktop, mobile, fullstack, etc.\n///\n/// Platform specifics:\n/// -------------------\n/// - Web:         We need to attach a filesystem server to our devtools webserver to serve the project. We\n///                want to emulate GithubPages here since most folks are deploying there and expect things like\n///                basepath to match.\n/// - Desktop:     We spin up the dev server but without a filesystem server.\n/// - Mobile:      Basically the same as desktop.\n///\n/// When fullstack is enabled, we'll also build for the `server` target and then hotreload the server.\n/// The \"server\" is special here since \"fullstack\" is functionally just an addition to the regular client\n/// setup.\n///\n/// Todos(Jon):\n/// - I'd love to be able to configure the CLI while it's running so we can change settings on the fly.\n/// - I want us to be able to detect a `server_fn` in the project and then upgrade from a static server\n///   to a dynamic one on the fly.\npub(crate) async fn serve_all(args: ServeArgs, tracer: &TraceController) -> Result<()> {\n    // Load the args into a plan, resolving all tooling, build dirs, arguments, decoding the multi-target, etc\n    let exit_on_error = args.exit_on_error;\n    let mut builder = AppServer::new(args).await?;\n    let mut devserver = WebServer::start(&builder)?;\n    let mut screen = Output::start(builder.interactive).await?;\n\n    // This is our default splash screen. We might want to make this a fancier splash screen in the future\n    // Also, these commands might not be the most important, but it's all we've got enabled right now\n    tracing::info!(\n        r#\"-----------------------------------------------------------------\n                Serving your app: {binname}! 🚀\n                • Press {GLOW_STYLE}`ctrl+c`{GLOW_STYLE:#} to exit the server\n                • Press {GLOW_STYLE}`r`{GLOW_STYLE:#} to rebuild the app\n                • Press {GLOW_STYLE}`p`{GLOW_STYLE:#} to toggle automatic rebuilds\n                • Press {GLOW_STYLE}`v`{GLOW_STYLE:#} to toggle verbose logging\n                • Press {GLOW_STYLE}`/`{GLOW_STYLE:#} for more commands and shortcuts{extra}\n               ----------------------------------------------------------------\"#,\n        binname = builder.client.build.executable_name(),\n        extra = if builder.client.build.using_dioxus_explicitly {\n            format!(\n                \"\\n                Learn more at {LINK_STYLE}https://dioxuslabs.com/learn/0.7/getting_started{LINK_STYLE:#}\"\n            )\n        } else {\n            String::new()\n        }\n    );\n\n    builder.initialize();\n\n    loop {\n        // Draw the state of the server to the screen\n        screen.render(&builder, &devserver);\n\n        // And then wait for any updates before redrawing\n        let msg = tokio::select! {\n            msg = builder.wait() => msg,\n            msg = devserver.wait() => msg,\n            msg = screen.wait() => msg,\n            msg = tracer.wait() => msg,\n        };\n\n        match msg {\n            ServeUpdate::FilesChanged { files } => {\n                if files.is_empty() || !builder.hot_reload {\n                    continue;\n                }\n\n                builder.handle_file_change(&files, &mut devserver).await;\n            }\n\n            ServeUpdate::RequestRebuild => {\n                // The spacing here is important-ish: we want\n                // `Full rebuild:` to line up with\n                // `Hotreloading:` to keep the alignment during long edit sessions\n                // `Hot-patching:` to keep the alignment during long edit sessions\n                tracing::info!(\"Full rebuild: triggered manually\");\n                builder.full_rebuild().await;\n                devserver.send_reload_start().await;\n                devserver.start_build().await;\n            }\n\n            // Run the server in the background\n            // Waiting for updates here lets us tap into when clients are added/removed\n            ServeUpdate::NewConnection {\n                id,\n                aslr_reference,\n                pid,\n            } => {\n                devserver\n                    .send_hotreload(builder.applied_hot_reload_changes(BuildId::PRIMARY))\n                    .await;\n\n                if builder.server.is_some() {\n                    devserver\n                        .send_hotreload(builder.applied_hot_reload_changes(BuildId::SECONDARY))\n                        .await;\n                }\n\n                builder.client_connected(id, aslr_reference, pid).await;\n            }\n\n            // Received a message from the devtools server - currently we only use this for\n            // logging, so we just forward it the tui\n            ServeUpdate::WsMessage { msg, bundle } => {\n                screen.push_ws_message(bundle, &msg);\n            }\n\n            // Wait for logs from the build engine\n            // These will cause us to update the screen\n            // We also can check the status of the builds here in case we have multiple ongoing builds\n            ServeUpdate::BuilderUpdate { id, update } => {\n                let bundle_format = builder.get_build(id).unwrap().build.bundle;\n\n                // Queue any logs to be printed if need be\n                screen.new_build_update(&update);\n\n                // And then update the websocketed clients with the new build status in case they want it\n                devserver.new_build_update(&update).await;\n\n                // Start the SSG build if we need to\n                builder.new_build_update(&update, &devserver).await;\n\n                // And then open the app if it's ready\n                match update {\n                    BuilderUpdate::Progress {\n                        stage: BuildStage::Failed,\n                    } => {\n                        if exit_on_error {\n                            bail!(\"Build failed for platform: {bundle_format}\");\n                        }\n                    }\n                    BuilderUpdate::Progress {\n                        stage: BuildStage::Aborted,\n                    } => {\n                        if exit_on_error {\n                            bail!(\"Build aborted for platform: {bundle_format}\");\n                        }\n                    }\n                    BuilderUpdate::Progress { .. } => {}\n                    BuilderUpdate::CompilerMessage { message } => {\n                        screen.push_cargo_log(message);\n                    }\n                    BuilderUpdate::BuildFailed { err } => {\n                        tracing::error!(\n                            \"{ERROR_STYLE}Build failed{ERROR_STYLE:#}: {}\",\n                            crate::error::log_stacktrace(&err, 15),\n                            ERROR_STYLE = crate::styles::ERROR_STYLE,\n                        );\n\n                        if exit_on_error {\n                            return Err(err);\n                        }\n                    }\n                    BuilderUpdate::BuildReady { bundle } => {\n                        match bundle.mode {\n                            BuildMode::Thin { ref cache, .. } => {\n                                if let Err(err) =\n                                    builder.hotpatch(&bundle, id, cache, &mut devserver).await\n                                {\n                                    tracing::error!(\"Failed to hot-patch app: {err}\");\n\n                                    if let Some(_patching) =\n                                        err.downcast_ref::<crate::build::PatchError>()\n                                    {\n                                        tracing::info!(\"Starting full rebuild: {err}\");\n                                        builder.full_rebuild().await;\n                                        devserver.send_reload_start().await;\n                                        devserver.start_build().await;\n                                    }\n                                }\n                            }\n                            BuildMode::Base { .. } | BuildMode::Fat => {\n                                _ = builder\n                                    .open(&bundle, &mut devserver)\n                                    .await\n                                    .inspect_err(|e| tracing::error!(\"Failed to open app: {}\", e));\n                            }\n                        }\n\n                        // Process any file changes that were queued while the build was in progress.\n                        // This handles tools like stylance, tailwind, or sass that generate files\n                        // in response to source changes - those changes would otherwise be lost.\n                        let pending = builder.take_pending_file_changes();\n                        if !pending.is_empty() {\n                            tracing::debug!(\n                                \"Processing {} pending file changes after build\",\n                                pending.len()\n                            );\n                            builder.handle_file_change(&pending, &mut devserver).await;\n                        }\n                    }\n                    BuilderUpdate::StdoutReceived { msg } => {\n                        screen.push_stdio(bundle_format, msg, tracing::Level::INFO);\n                    }\n                    BuilderUpdate::StderrReceived { msg } => {\n                        screen.push_stdio(bundle_format, msg, tracing::Level::ERROR);\n                    }\n                    BuilderUpdate::ProcessExited { status } => {\n                        if status.success() {\n                            tracing::info!(\n                                r#\"Application [{bundle_format}] exited gracefully.\n               • To restart the app, press `r` to rebuild or `o` to open\n               • To exit the server, press `ctrl+c`\"#\n                            );\n                        } else {\n                            tracing::error!(\n                                \"Application [{bundle_format}] exited with error: {status}\"\n                            );\n                            if exit_on_error {\n                                bail!(\"Application [{bundle_format}] exited with error: {status}\");\n                            }\n                        }\n                    }\n                    BuilderUpdate::ProcessWaitFailed { err } => {\n                        tracing::warn!(\n                            \"Failed to wait for process - maybe it's hung or being debugged?: {err}\"\n                        );\n                        if exit_on_error {\n                            return Err(err.into());\n                        }\n                    }\n                }\n            }\n\n            ServeUpdate::TracingLog { log } => {\n                screen.push_log(log);\n            }\n\n            ServeUpdate::OpenApp => match builder.use_hotpatch_engine {\n                true if !matches!(builder.client.build.bundle, BundleFormat::Web) => {\n                    tracing::warn!(\n                        \"Opening a native app with hotpatching enabled requires a full rebuild...\"\n                    );\n                    builder.full_rebuild().await;\n                    devserver.send_reload_start().await;\n                    devserver.start_build().await;\n                }\n                _ => {\n                    if let Err(err) = builder.open_all(&devserver, true).await {\n                        tracing::error!(\n                            \"Failed to open app: {}\",\n                            crate::error::log_stacktrace(&err, 15)\n                        )\n                    }\n                }\n            },\n\n            ServeUpdate::Redraw => {\n                // simply returning will cause a redraw\n            }\n\n            ServeUpdate::ToggleShouldRebuild => {\n                use crate::styles::{ERROR, NOTE_STYLE};\n                builder.automatic_rebuilds = !builder.automatic_rebuilds;\n                tracing::info!(\n                    \"Automatic rebuilds are currently: {}\",\n                    if builder.automatic_rebuilds {\n                        format!(\"{NOTE_STYLE}enabled{NOTE_STYLE:#}\")\n                    } else {\n                        format!(\"{ERROR}disabled{ERROR:#}\")\n                    }\n                )\n            }\n\n            ServeUpdate::OpenDebugger { id } => {\n                builder.open_debugger(&devserver, id).await;\n            }\n\n            ServeUpdate::Exit { error } => {\n                _ = builder.shutdown().await;\n                _ = devserver.shutdown().await;\n\n                match error {\n                    Some(err) => return Err(err),\n                    None => return Ok(()),\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "packages/cli/src/serve/output.rs",
    "content": "use crate::Result;\nuse crate::{\n    serve::{ansi_buffer::ansi_string_to_line, ServeUpdate, WebServer},\n    BuildId, BuildStage, BuilderUpdate, BundleFormat, TraceContent, TraceMsg, TraceSrc,\n};\nuse anyhow::{anyhow, bail, Context};\nuse cargo_metadata::diagnostic::Diagnostic;\nuse crossterm::{\n    cursor::{Hide, Show},\n    event::{\n        DisableBracketedPaste, DisableFocusChange, EnableBracketedPaste, EnableFocusChange, Event,\n        EventStream, KeyCode, KeyEvent, KeyEventKind, KeyModifiers,\n    },\n    terminal::{disable_raw_mode, enable_raw_mode, Clear, ClearType},\n    ExecutableCommand,\n};\nuse ratatui::{\n    prelude::*,\n    widgets::{Block, BorderType, Borders, LineGauge, Paragraph},\n    TerminalOptions, Viewport,\n};\nuse std::{\n    cell::RefCell,\n    collections::VecDeque,\n    io::{self, stdout},\n    rc::Rc,\n    time::Duration,\n};\nuse tracing::Level;\n\nuse super::AppServer;\n\nconst TICK_RATE_MS: u64 = 100;\nconst VIEWPORT_MAX_WIDTH: u16 = 90;\nconst VIEWPORT_HEIGHT_SMALL: u16 = 5;\nconst VIEWPORT_HEIGHT_BIG: u16 = 14;\n\n/// The TUI that drives the console output.\n///\n/// We try not to store too much state about the world here, just the state about the tui itself.\n/// This is to prevent out-of-sync issues with the rest of the build engine and to use the components\n/// of the serve engine as the source of truth.\n///\n/// Please please, do not add state here that does not belong here. We should only be storing state\n/// here that is used to change how we display *other* state. Things like throbbers, modals, etc.\npub struct Output {\n    term: Rc<RefCell<Option<Terminal<CrosstermBackend<io::Stdout>>>>>,\n    events: Option<EventStream>,\n\n    // A list of all messages from build, dev, app, and more.\n    more_modal_open: bool,\n    interactive: bool,\n\n    // Whether to show verbose logs or not\n    // We automatically hide \"debug\" logs if verbose is false (only showing \"info\" / \"warn\" / \"error\")\n    verbose: bool,\n    trace: bool,\n\n    // Pending logs\n    pending_logs: VecDeque<TraceMsg>,\n\n    dx_version: String,\n    tick_animation: bool,\n\n    tick_interval: tokio::time::Interval,\n\n    // ! needs to be wrapped in an &mut since `render stateful widget` requires &mut... but our\n    // \"render\" method only borrows &self (for no particular reason at all...)\n    throbber: RefCell<throbber_widgets_tui::ThrobberState>,\n}\n\n#[derive(Clone, Copy)]\nstruct RenderState<'a> {\n    runner: &'a AppServer,\n    server: &'a WebServer,\n}\n\nimpl Output {\n    pub(crate) async fn start(interactive: bool) -> crate::Result<Self> {\n        let mut output = Self {\n            interactive,\n            term: Rc::new(RefCell::new(None)),\n            dx_version: format!(\n                \"{}-{}\",\n                env!(\"CARGO_PKG_VERSION\"),\n                crate::dx_build_info::GIT_COMMIT_HASH_SHORT.unwrap_or(\"main\")\n            ),\n            events: None,\n            more_modal_open: false,\n            pending_logs: VecDeque::new(),\n            throbber: RefCell::new(throbber_widgets_tui::ThrobberState::default()),\n            trace: crate::logging::VERBOSITY.get().unwrap().trace,\n            verbose: crate::logging::VERBOSITY.get().unwrap().verbose,\n            tick_animation: false,\n            tick_interval: {\n                let mut interval = tokio::time::interval(Duration::from_millis(TICK_RATE_MS));\n                interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay);\n                interval\n            },\n        };\n\n        output.startup()?;\n\n        Ok(output)\n    }\n\n    /// Call the startup functions that might mess with the terminal settings.\n    /// This is meant to be paired with \"shutdown\" to restore the terminal to its original state.\n    fn startup(&mut self) -> Result<()> {\n        if self.interactive {\n            // Check if writing the terminal is going to block infinitely.\n            // If it does, we should disable interactive mode. This ensures we work with programs like `bg`\n            // which suspend the process and cause us to block when writing output.\n            if Self::enable_raw_mode().is_err() {\n                self.term.take();\n                self.interactive = false;\n                return Ok(());\n            }\n\n            self.term.replace(\n                Terminal::with_options(\n                    CrosstermBackend::new(stdout()),\n                    TerminalOptions {\n                        viewport: Viewport::Inline(VIEWPORT_HEIGHT_SMALL),\n                    },\n                )\n                .ok(),\n            );\n\n            // Initialize the event stream here - this is optional because an EvenStream in a non-interactive\n            // terminal will cause a panic instead of simply doing nothing.\n            // https://github.com/crossterm-rs/crossterm/issues/659\n            self.events = Some(EventStream::new());\n        }\n\n        Ok(())\n    }\n\n    /// Enable raw mode, but don't let it block forever.\n    ///\n    /// This lets us check if writing to tty is going to block forever and then recover, allowing\n    /// interopability with programs like `bg`.\n    fn enable_raw_mode() -> Result<()> {\n        #[cfg(unix)]\n        {\n            use tokio::signal::unix::{signal, SignalKind};\n\n            // Ignore SIGTSTP, SIGTTIN, and SIGTTOU\n            _ = signal(SignalKind::from_raw(20))?; // SIGTSTP\n            _ = signal(SignalKind::from_raw(21))?; // SIGTTIN\n            _ = signal(SignalKind::from_raw(22))?; // SIGTTOU\n        }\n\n        use std::io::IsTerminal;\n\n        if !stdout().is_terminal() {\n            bail!(\"Cannot enable raw mode on a non-terminal output\");\n        }\n\n        enable_raw_mode()?;\n        stdout()\n            .execute(Hide)?\n            .execute(EnableFocusChange)?\n            .execute(EnableBracketedPaste)?;\n\n        Ok(())\n    }\n\n    pub(crate) fn remote_shutdown(interactive: bool) -> Result<()> {\n        if interactive && crossterm::terminal::is_raw_mode_enabled().unwrap_or(true) {\n            stdout()\n                .execute(Show)?\n                .execute(DisableFocusChange)?\n                .execute(DisableBracketedPaste)?;\n            disable_raw_mode()?;\n\n            // print a line to force the cursor down (no tearing)\n            println!();\n        }\n\n        Ok(())\n    }\n\n    pub(crate) async fn wait(&mut self) -> ServeUpdate {\n        use futures_util::future::OptionFuture;\n        use futures_util::StreamExt;\n\n        if !self.interactive {\n            return std::future::pending().await;\n        }\n\n        // Wait for the next user event or animation tick\n        loop {\n            let next = OptionFuture::from(self.events.as_mut().map(|f| f.next()));\n            let event = tokio::select! {\n                biased; // Always choose the event over the animation tick to not lose the event\n                Some(Some(Ok(event))) = next => event,\n                _ = self.tick_interval.tick(), if self.tick_animation => {\n                    self.throbber.borrow_mut().calc_next();\n                    return ServeUpdate::Redraw\n                },\n                else => futures_util::future::pending().await\n            };\n\n            match self.handle_input(event) {\n                Ok(Some(update)) => return update,\n                Err(ee) => {\n                    return ServeUpdate::Exit {\n                        error: Some(anyhow::anyhow!(ee)),\n                    }\n                }\n                Ok(None) => {}\n            }\n        }\n    }\n\n    /// Handle an input event, returning `true` if the event should cause the program to restart.\n    fn handle_input(&mut self, input: Event) -> Result<Option<ServeUpdate>> {\n        match input {\n            Event::Key(key) if key.kind == KeyEventKind::Press => self.handle_keypress(key),\n            _ => Ok(Some(ServeUpdate::Redraw)),\n        }\n    }\n\n    fn handle_keypress(&mut self, key: KeyEvent) -> Result<Option<ServeUpdate>> {\n        // Some dev helpers for testing panic propagation and error handling. Remove this eventually.\n        if cfg!(debug_assertions) && std::env::var(\"DEBUG_PANICS\").is_ok() {\n            match key.code {\n                KeyCode::Char('Z') => panic!(\"z pressed so we panic -> {}\", uuid::Uuid::new_v4()),\n                KeyCode::Char('X') => bail!(\"x pressed so we bail -> {}\", uuid::Uuid::new_v4()),\n                KeyCode::Char('E') => {\n                    Err(anyhow!(\n                        \"E pressed so we bail with context -> {}\",\n                        uuid::Uuid::new_v4()\n                    ))\n                    .context(\"With a message\")\n                    .context(\"With a context\")?;\n                }\n                KeyCode::Char('C') => {\n                    return Ok(Some(ServeUpdate::Exit {\n                        error: Some(anyhow!(\n                            \"C pressed, so exiting with safe error -> {}\",\n                            uuid::Uuid::new_v4()\n                        )),\n                    }));\n                }\n                _ => {}\n            }\n        }\n\n        match key.code {\n            KeyCode::Char('c') if key.modifiers.contains(KeyModifiers::CONTROL) => {\n                return Ok(Some(ServeUpdate::Exit { error: None }))\n            }\n            KeyCode::Char('r') => return Ok(Some(ServeUpdate::RequestRebuild)),\n            KeyCode::Char('o') => return Ok(Some(ServeUpdate::OpenApp)),\n            KeyCode::Char('p') => return Ok(Some(ServeUpdate::ToggleShouldRebuild)),\n            KeyCode::Char('v') => {\n                self.verbose = !self.verbose;\n                tracing::info!(\n                    \"Verbose logging is now {}\",\n                    if self.verbose { \"on\" } else { \"off\" }\n                );\n            }\n            KeyCode::Char('t') => {\n                self.trace = !self.trace;\n                tracing::info!(\"Tracing is now {}\", if self.trace { \"on\" } else { \"off\" });\n            }\n            KeyCode::Char('D') => {\n                return Ok(Some(ServeUpdate::OpenDebugger {\n                    id: BuildId::SECONDARY,\n                }));\n            }\n            KeyCode::Char('d') => {\n                return Ok(Some(ServeUpdate::OpenDebugger {\n                    id: BuildId::PRIMARY,\n                }));\n            }\n            KeyCode::Char('c') => {\n                stdout()\n                    .execute(Clear(ClearType::All))?\n                    .execute(Clear(ClearType::Purge))?;\n\n                // Clear the terminal and push the frame to the bottom\n                _ = self.term.borrow_mut().as_mut().map(|t| {\n                    let frame_rect = t.get_frame().area();\n                    let term_size = t.size().unwrap();\n                    let remaining_space = term_size\n                        .height\n                        .saturating_sub(frame_rect.y + frame_rect.height);\n                    t.insert_before(remaining_space, |_| {})\n                });\n            }\n\n            // Toggle the more modal by swapping the terminal with a new one\n            // This is a bit of a hack since crossterm doesn't technically support changing the\n            // size of an inline viewport.\n            KeyCode::Char('/') => {\n                if let Some(terminal) = self.term.borrow_mut().as_mut() {\n                    // Toggle the more modal, which will change our current viewport height\n                    self.more_modal_open = !self.more_modal_open;\n\n                    // Clear the terminal before resizing it, such that it doesn't tear\n                    terminal.clear()?;\n\n                    // And then set the new viewport, which essentially mimics a resize\n                    *terminal = Terminal::with_options(\n                        CrosstermBackend::new(stdout()),\n                        TerminalOptions {\n                            viewport: Viewport::Inline(self.viewport_current_height()),\n                        },\n                    )?;\n                }\n            }\n\n            _ => {}\n        }\n\n        // Out of safety, we always redraw, since it's relatively cheap operation\n        Ok(Some(ServeUpdate::Redraw))\n    }\n\n    /// Push a TraceMsg to be printed on the next render\n    pub fn push_log(&mut self, message: TraceMsg) {\n        self.pending_logs.push_front(message);\n    }\n\n    pub fn push_cargo_log(&mut self, message: Diagnostic) {\n        use cargo_metadata::diagnostic::DiagnosticLevel;\n\n        if self.trace || !matches!(message.level, DiagnosticLevel::Note) {\n            self.push_log(TraceMsg::cargo(message));\n        }\n    }\n\n    /// Add a message from stderr to the logs\n    /// This will queue the stderr message as a TraceMsg and print it on the next render\n    /// We'll use the `App` TraceSrc for the msg, and whatever level is provided\n    pub fn push_stdio(&mut self, bundle: BundleFormat, msg: String, level: Level) {\n        match bundle {\n            // If tracing is disabled, we need to filter out all the noise from Android logcat\n            BundleFormat::Android if !self.trace => {\n                // By default (trace off): only show RustStdoutStderr logs with proper log levels\n                // Filter out raw output like GL bindings, WebView internals, etc.\n                let is_rust_log = msg.contains(\"RustStdoutStderr\");\n                let is_fatal = msg.contains(\"AndroidRuntime\") || msg.starts_with('F');\n                let mut rendered_msg = None;\n\n                // If we're not in trace mode, then we need to filter out non-log messages and clean them up\n                // Only show logs with standard tracing level prefixes (check in the message after the colon)\n                if is_rust_log {\n                    if let Some(colon_pos) = msg.find(':') {\n                        let content = &msg[colon_pos + 1..];\n                        if content.contains(\" TRACE \")\n                            || content.contains(\" DEBUG \")\n                            || content.contains(\" INFO \")\n                            || content.contains(\" WARN \")\n                            || content.contains(\" ERROR \")\n                        {\n                            rendered_msg = Some(content.trim().to_string());\n                        }\n                    }\n                }\n\n                // Always show fatal errors, even if they don't come from Rust logging\n                if is_fatal {\n                    rendered_msg = Some(msg);\n                }\n\n                if let Some(msg) = rendered_msg {\n                    self.push_log(TraceMsg::text(TraceSrc::App(bundle), level, msg));\n                }\n            }\n\n            _ => self.push_log(TraceMsg::text(TraceSrc::App(bundle), level, msg)),\n        }\n    }\n\n    /// Push a message from the websocket to the logs\n    pub fn push_ws_message(&mut self, bundle: BundleFormat, message: &axum::extract::ws::Message) {\n        use dioxus_devtools_types::ClientMsg;\n\n        // We can only handle text messages from the websocket...\n        let axum::extract::ws::Message::Text(text) = message else {\n            return;\n        };\n\n        // ...and then decode them into a ClientMsg\n        let res = serde_json::from_str::<ClientMsg>(text.as_str());\n\n        // Client logs being errors aren't fatal, but we should still report them them\n        let msg = match res {\n            Ok(msg) => msg,\n            Err(err) => {\n                tracing::error!(dx_src = ?TraceSrc::Dev, \"Error parsing message from {}: {} -> {:?}\", bundle, err, text.as_str());\n                return;\n            }\n        };\n\n        let ClientMsg::Log { level, messages } = msg else {\n            return;\n        };\n\n        // FIXME(jon): why are we pulling only the first message here?\n        let content = messages.first().unwrap_or(&String::new()).clone();\n\n        let level = match level.as_str() {\n            \"trace\" => Level::TRACE,\n            \"debug\" => Level::DEBUG,\n            \"info\" => Level::INFO,\n            \"warn\" => Level::WARN,\n            \"error\" => Level::ERROR,\n            _ => Level::INFO,\n        };\n\n        // We don't care about logging the app's message so we directly push it instead of using tracing.\n        self.push_log(TraceMsg::text(TraceSrc::App(bundle), level, content));\n    }\n\n    /// Change internal state based on the build engine's update\n    ///\n    /// We want to keep internal state as limited as possible, so currently we're only setting our\n    /// animation tick. We could, in theory, just leave animation running and have no internal state,\n    /// but that seems a bit wasteful. We might eventually change this to be more of a \"requestAnimationFrame\"\n    /// approach, but then we'd need to do that *everywhere* instead of simply performing a react-like\n    /// re-render when external state changes. Ratatui will diff the intermediate buffer, so we at least\n    /// we won't be drawing it.\n    pub(crate) fn new_build_update(&mut self, update: &BuilderUpdate) {\n        match update {\n            BuilderUpdate::Progress {\n                stage: BuildStage::Starting { .. },\n            } => self.tick_animation = true,\n            BuilderUpdate::BuildReady { .. } => self.tick_animation = false,\n            BuilderUpdate::BuildFailed { .. } => self.tick_animation = false,\n            _ => {}\n        }\n    }\n\n    /// Render the current state of everything to the console screen\n    pub fn render(&mut self, runner: &AppServer, server: &WebServer) {\n        if !self.interactive {\n            return;\n        }\n\n        // Get a handle to the terminal with a different lifetime so we can continue to call &self methods\n        let owned_term = self.term.clone();\n        let mut term = owned_term.borrow_mut();\n        let Some(term) = term.as_mut() else {\n            return;\n        };\n\n        // First, dequeue any logs that have built up from event handling\n        while let Some(log) = self.pending_logs.pop_back() {\n            _ = self.render_log(term, log);\n        }\n\n        // Then, draw the frame, passing along all the state of the TUI so we can render it properly\n        _ = term.draw(|frame| {\n            self.render_frame(frame, RenderState { runner, server });\n        });\n    }\n\n    fn render_frame(&self, frame: &mut Frame, state: RenderState) {\n        // Use the max size of the viewport, but shrunk to a sensible max width\n        let mut area = frame.area();\n        area.width = area.width.clamp(0, VIEWPORT_MAX_WIDTH);\n\n        let [_top, body, _bottom] = Layout::vertical([\n            Constraint::Length(1),\n            Constraint::Fill(1),\n            Constraint::Length(1),\n        ])\n        .horizontal_margin(1)\n        .areas(area);\n\n        self.render_borders(frame, area);\n        self.render_body(frame, body, state);\n        self.render_body_title(frame, _top, state);\n    }\n\n    fn render_body_title(&self, frame: &mut Frame<'_>, area: Rect, _state: RenderState) {\n        frame.render_widget(\n            Line::from(vec![\n                \" \".dark_gray(),\n                match self.more_modal_open {\n                    true => \"/:more\".light_yellow(),\n                    false => \"/:more\".dark_gray(),\n                },\n                \" \".dark_gray(),\n            ])\n            .right_aligned(),\n            area,\n        );\n    }\n\n    fn render_body(&self, frame: &mut Frame<'_>, area: Rect, state: RenderState) {\n        let [_title, body, more, _foot] = Layout::vertical([\n            Constraint::Length(0),\n            Constraint::Length(VIEWPORT_HEIGHT_SMALL - 2),\n            Constraint::Fill(1),\n            Constraint::Length(0),\n        ])\n        .horizontal_margin(1)\n        .areas(area);\n\n        let [col1, col2] = Layout::horizontal([Constraint::Length(50), Constraint::Fill(1)])\n            .horizontal_margin(1)\n            .areas(body);\n\n        self.render_gauges(frame, col1, state);\n        self.render_stats(frame, col2, state);\n\n        if self.more_modal_open {\n            self.render_more_modal(frame, more, state);\n        }\n    }\n\n    fn render_gauges(&self, frame: &mut Frame<'_>, area: Rect, state: RenderState) {\n        let [gauge_area, _margin] =\n            Layout::horizontal([Constraint::Fill(1), Constraint::Length(3)]).areas(area);\n\n        let [app_progress, second_progress, status_line]: [_; 3] = Layout::vertical([\n            Constraint::Length(1),\n            Constraint::Length(1),\n            Constraint::Length(1),\n        ])\n        .areas(gauge_area);\n\n        let client = &state.runner.client();\n        self.render_single_gauge(\n            frame,\n            app_progress,\n            client.compile_progress(),\n            \"App:    \",\n            state,\n            client.compile_duration(),\n        );\n\n        if state.runner.is_fullstack() {\n            self.render_single_gauge(\n                frame,\n                second_progress,\n                state.runner.server_compile_progress(),\n                \"Server: \",\n                state,\n                client.compile_duration(),\n            );\n        } else {\n            self.render_single_gauge(\n                frame,\n                second_progress,\n                client.bundle_progress(),\n                \"Bundle: \",\n                state,\n                client.bundle_duration(),\n            );\n        }\n\n        let mut lines = vec![\"Status:  \".white()];\n        match &client.stage {\n            BuildStage::Initializing => lines.push(\"Initializing\".yellow()),\n            BuildStage::Starting { patch, .. } => {\n                if *patch {\n                    lines.push(\"Hot-patching...\".yellow())\n                } else {\n                    lines.push(\"Starting build\".yellow())\n                }\n            }\n            BuildStage::InstallingTooling => lines.push(\"Installing tooling\".yellow()),\n            BuildStage::Compiling {\n                current,\n                total,\n                krate,\n                ..\n            } => {\n                lines.push(\"Compiling \".yellow());\n                lines.push(format!(\"{current}/{total} \").gray());\n                lines.push(krate.as_str().dark_gray())\n            }\n            BuildStage::OptimizingWasm => lines.push(\"Optimizing wasm\".yellow()),\n            BuildStage::SplittingBundle => lines.push(\"Splitting bundle\".yellow()),\n            BuildStage::CompressingAssets => lines.push(\"Compressing assets\".yellow()),\n            BuildStage::RunningBindgen => lines.push(\"Running wasm-bindgen\".yellow()),\n            BuildStage::RunningGradle => lines.push(\"Running gradle assemble\".yellow()),\n            BuildStage::CompilingNativePlugins { detail } => {\n                // detail is \"Swift build: name\" — split into label (yellow) and name (white)\n                if let Some((label, name)) = detail.split_once(\": \") {\n                    lines.push(format!(\"{label}: \").yellow());\n                    lines.push(name.white());\n                } else {\n                    lines.push(detail.clone().yellow());\n                }\n            }\n            BuildStage::CodeSigning => lines.push(\"Code signing app\".yellow()),\n            BuildStage::Bundling => lines.push(\"Bundling app\".yellow()),\n            BuildStage::CopyingAssets {\n                current,\n                total,\n                path,\n            } => {\n                lines.push(\"Copying asset \".yellow());\n                lines.push(format!(\"{current}/{total} \").gray());\n                if let Some(name) = path.file_name().and_then(|f| f.to_str()) {\n                    lines.push(name.dark_gray())\n                }\n            }\n            BuildStage::Success => {\n                lines.push(\"Serving \".yellow());\n                lines.push(client.build.executable_name().white());\n                lines.push(\" 🚀 \".green());\n                if let Some(comp_time) = client.total_build_time() {\n                    lines.push(format!(\"{:.1}s\", comp_time.as_secs_f32()).dark_gray());\n                }\n            }\n            BuildStage::Failed => lines.push(\"Failed\".red()),\n            BuildStage::Aborted => lines.push(\"Aborted\".red()),\n            BuildStage::Restarting => lines.push(\"Restarting\".yellow()),\n            BuildStage::Linking => lines.push(\"Linking\".yellow()),\n            BuildStage::Hotpatching => lines.push(\"Hot-patching...\".yellow()),\n            BuildStage::ExtractingAssets => lines.push(\"Extracting assets\".yellow()),\n            BuildStage::Prerendering => lines.push(\"Pre-rendering...\".yellow()),\n            _ => {}\n        };\n\n        frame.render_widget(Line::from(lines), status_line);\n    }\n\n    fn render_single_gauge(\n        &self,\n        frame: &mut Frame<'_>,\n        area: Rect,\n        value: f64,\n        label: &str,\n        state: RenderState,\n        time_taken: Option<Duration>,\n    ) {\n        let failed = state.runner.client.stage == BuildStage::Failed;\n        let value = if failed { 1.0 } else { value.clamp(0.0, 1.0) };\n\n        let [gauge_row, _, icon] = Layout::horizontal([\n            Constraint::Fill(1),\n            Constraint::Length(2),\n            Constraint::Length(10),\n        ])\n        .areas(area);\n\n        frame.render_widget(\n            LineGauge::default()\n                .filled_style(Style::default().fg(match value {\n                    1.0 if failed => Color::Red,\n                    1.0 => Color::Green,\n                    _ => Color::Yellow,\n                }))\n                .unfilled_style(Style::default().fg(Color::DarkGray))\n                .label(label.gray())\n                .line_set(symbols::line::THICK)\n                .ratio(if !failed { value } else { 1.0 }),\n            gauge_row,\n        );\n\n        let [throbber_frame, time_frame] = Layout::default()\n            .direction(Direction::Horizontal)\n            .constraints([Constraint::Length(3), Constraint::Fill(1)])\n            .areas(icon);\n\n        if value != 1.0 {\n            let throb = throbber_widgets_tui::Throbber::default()\n                .style(ratatui::style::Style::default().fg(ratatui::style::Color::Cyan))\n                .throbber_style(\n                    ratatui::style::Style::default()\n                        .fg(ratatui::style::Color::White)\n                        .add_modifier(ratatui::style::Modifier::BOLD),\n                )\n                .throbber_set(throbber_widgets_tui::BLACK_CIRCLE)\n                .use_type(throbber_widgets_tui::WhichUse::Spin);\n            frame.render_stateful_widget(throb, throbber_frame, &mut self.throbber.borrow_mut());\n        } else {\n            frame.render_widget(\n                Line::from(vec![if failed {\n                    \"❌ \".white()\n                } else {\n                    \"🎉 \".white()\n                }])\n                .left_aligned(),\n                throbber_frame,\n            );\n        }\n\n        if let Some(time_taken) = time_taken {\n            if !failed {\n                frame.render_widget(\n                    Line::from(vec![format!(\"{:.1}s\", time_taken.as_secs_f32()).dark_gray()])\n                        .left_aligned(),\n                    time_frame,\n                );\n            }\n        }\n    }\n\n    fn render_stats(&self, frame: &mut Frame<'_>, area: Rect, state: RenderState) {\n        let [current_platform, app_features, serve_address]: [_; 3] = Layout::vertical([\n            Constraint::Length(1),\n            Constraint::Length(1),\n            Constraint::Length(1),\n        ])\n        .areas(area);\n\n        let client = &state.runner.client();\n        frame.render_widget(\n            Paragraph::new(Line::from(vec![\n                \"Platform: \".gray(),\n                client.build.bundle.expected_name().yellow(),\n                if state.runner.is_fullstack() {\n                    \" + fullstack\".yellow()\n                } else {\n                    \" \".dark_gray()\n                },\n            ])),\n            current_platform,\n        );\n\n        self.render_feature_list(frame, app_features, state);\n\n        // todo(jon) should we write https ?\n        let address = match state.server.displayed_address() {\n            Some(address) => format!(\n                \"http://{}{}\",\n                address,\n                state\n                    .runner\n                    .client\n                    .build\n                    .base_path()\n                    .map(|f| format!(\"/{f}/\"))\n                    .unwrap_or_default()\n            )\n            .blue(),\n            None => \"no server address\".dark_gray(),\n        };\n\n        frame.render_widget_ref(\n            Paragraph::new(Line::from(vec![\n                if client.build.bundle == BundleFormat::Web {\n                    \"Serving at: \".gray()\n                } else {\n                    \"Server at: \".gray()\n                },\n                address,\n            ])),\n            serve_address,\n        );\n    }\n\n    fn render_feature_list(&self, frame: &mut Frame<'_>, area: Rect, state: RenderState) {\n        frame.render_widget(\n            Paragraph::new(Line::from({\n                let mut lines = vec![\"Features: \".gray(), \"[\".yellow()];\n\n                let feature_list: Vec<String> = state.runner.client().build.all_target_features();\n                let num_features = feature_list.len();\n\n                for (idx, feature) in feature_list.into_iter().enumerate() {\n                    lines.push(\"\\\"\".yellow());\n                    lines.push(feature.yellow());\n                    lines.push(\"\\\"\".yellow());\n                    if idx != num_features - 1 {\n                        lines.push(\", \".dark_gray());\n                    }\n                }\n\n                lines.push(\"]\".yellow());\n\n                lines\n            })),\n            area,\n        );\n    }\n\n    fn render_more_modal(&self, frame: &mut Frame<'_>, area: Rect, state: RenderState) {\n        let [col1, col2] =\n            Layout::horizontal([Constraint::Length(50), Constraint::Fill(1)]).areas(area);\n\n        let [top, bottom] = Layout::vertical([Constraint::Fill(1), Constraint::Length(2)])\n            .horizontal_margin(1)\n            .areas(col1);\n\n        let meta_list: [_; 6] = Layout::vertical([\n            Constraint::Length(1), // spacing\n            Constraint::Length(1), // item 1\n            Constraint::Length(1), // item 2\n            Constraint::Length(1), // item 3\n            Constraint::Length(1), // item 4\n            Constraint::Length(1), // Spacing\n        ])\n        .areas(top);\n\n        frame.render_widget(\n            Paragraph::new(Line::from(vec![\n                \"dx version: \".gray(),\n                self.dx_version.as_str().yellow(),\n            ])),\n            meta_list[1],\n        );\n        frame.render_widget(\n            Paragraph::new(Line::from(vec![\n                \"rustc: \".gray(),\n                state.runner.workspace.rustc_version.as_str().yellow(),\n            ])),\n            meta_list[2],\n        );\n        frame.render_widget(\n            Paragraph::new(Line::from(vec![\n                \"Hotreload: \".gray(),\n                if !state.runner.automatic_rebuilds {\n                    \"disabled\".dark_gray()\n                } else if state.runner.use_hotpatch_engine {\n                    \"hot-patching\".yellow()\n                } else {\n                    \"rsx and assets\".yellow()\n                },\n            ])),\n            meta_list[3],\n        );\n\n        let server_address = match state.server.server_address() {\n            Some(address) => format!(\"http://{address}\").yellow(),\n            None => \"no address\".dark_gray(),\n        };\n        frame.render_widget(\n            Paragraph::new(Line::from(vec![\"Network: \".gray(), server_address])),\n            meta_list[4],\n        );\n\n        let links_list: [_; 2] =\n            Layout::vertical([Constraint::Length(1), Constraint::Length(1)]).areas(bottom);\n\n        if state.runner.client.build.using_dioxus_explicitly {\n            frame.render_widget(\n                Paragraph::new(Line::from(vec![\n                    \"Read the docs: \".gray(),\n                    \"https://dioxuslabs.com/learn/0.7/\".blue(),\n                ])),\n                links_list[0],\n            );\n\n            frame.render_widget(\n                Paragraph::new(Line::from(vec![\n                    \"Video tutorials: \".gray(),\n                    \"https://youtube.com/@DioxusLabs\".blue(),\n                ])),\n                links_list[1],\n            );\n        }\n\n        let cmds = [\n            \"\",\n            \"r: rebuild the app\",\n            \"o: open the app\",\n            \"p: pause rebuilds\",\n            \"v: toggle verbose logs\",\n            \"t: toggle tracing logs\",\n            \"c: clear the screen\",\n            \"d: attach debugger\",\n            \"/: toggle more commands\",\n        ];\n        let layout: [_; 9] = Layout::vertical(cmds.iter().map(|_| Constraint::Length(1)))\n            .horizontal_margin(1)\n            .areas(col2);\n        for (idx, cmd) in cmds.iter().enumerate() {\n            if cmd.is_empty() {\n                continue;\n            }\n\n            let (cmd, detail) = cmd.split_once(\": \").unwrap_or((cmd, \"\"));\n            frame.render_widget(\n                Paragraph::new(Line::from(vec![\n                    cmd.gray(),\n                    \": \".gray(),\n                    detail.dark_gray(),\n                ])),\n                layout[idx],\n            );\n        }\n    }\n\n    /// Render borders around the terminal, forcing an inner clear while we're at it\n    fn render_borders(&self, frame: &mut Frame, area: Rect) {\n        frame.render_widget(ratatui::widgets::Clear, area);\n        frame.render_widget(\n            Block::default()\n                .borders(Borders::ALL)\n                .border_type(BorderType::Rounded)\n                .border_style(Style::default().fg(Color::DarkGray)),\n            area,\n        );\n    }\n\n    /// Print logs to the terminal as close to a regular \"println!()\" as possible.\n    ///\n    /// We don't want alternate screens or other terminal tricks because we want these logs to be as\n    /// close to real as possible. Once the log is printed, it is lost, so we need to be very careful\n    /// here to not print it incorrectly.\n    ///\n    /// This method works by printing lines at the top of the viewport frame, and then scrolling up\n    /// the viewport accordingly, such that our final call to \"clear\"  will cause the terminal the viewport\n    /// to be comlpetely erased and rewritten. This is slower since we're going around ratatui's diff\n    /// logic, but it's the only way to do this that gives us \"true println!\" semantics.\n    ///\n    /// In the future, Ratatui's insert_before method will get scroll regions, which will make this logic\n    /// much simpler. In that future, we'll simply insert a line into the scrollregion which should automatically\n    /// force that portion of the terminal to scroll up.\n    ///\n    /// TODO(jon): we could look into implementing scroll regions ourselves, but I think insert_before will\n    /// land in a reasonable amount of time.\n    #[deny(clippy::manual_saturating_arithmetic)]\n    fn render_log(\n        &mut self,\n        terminal: &mut Terminal<CrosstermBackend<io::Stdout>>,\n        log: TraceMsg,\n    ) -> Result<()> {\n        use unicode_segmentation::UnicodeSegmentation;\n\n        // Only show debug logs if verbose is enabled\n        if log.level == Level::DEBUG && !self.verbose {\n            return Ok(());\n        }\n\n        if log.level == Level::TRACE && !self.trace {\n            return Ok(());\n        }\n\n        // Grab out the size and location of the terminal and its viewport before we start messing with it\n        let frame_rect = terminal.get_frame().area();\n        let term_size = terminal.size().unwrap();\n\n        // Render the log into an ansi string\n        // We're going to add some metadata to it like the timestamp and source and then dump it to the raw ansi sequences we need to send to crossterm\n        let lines = Self::tracemsg_to_ansi_string(log);\n\n        // Get the lines of the output sequence and their overflow\n        let lines_printed = lines\n            .iter()\n            .map(|line| {\n                // Very important to strip ansi codes before counting graphemes - the ansi codes count as multiple graphemes!\n                let grapheme_count = console::strip_ansi_codes(line).graphemes(true).count();\n                grapheme_count.max(1).div_ceil(term_size.width as usize) as u16\n            })\n            .sum::<u16>();\n\n        // The viewport might be clipped, but the math still needs to work out.\n        let actual_vh_height = self.viewport_current_height().min(term_size.height);\n\n        // Move the terminal's cursor down to the number of lines printed\n        let remaining_space = term_size\n            .height\n            .saturating_sub(frame_rect.y + frame_rect.height);\n\n        // Calculate how many lines we need to push back\n        // - padding equals lines_printed when the frame is at the bottom\n        // - padding is zero when the remaining space is greater/equal than the scrollback (the frame will get pushed naturally)\n        // Determine what extra padding is remaining after we've shifted the terminal down\n        // this will be the distance between the final line and the top of the frame, only if the\n        // final line has extended into the frame\n        let final_line = frame_rect.y + lines_printed;\n        let max_frame_top = term_size.height - actual_vh_height;\n        let padding = final_line\n            .saturating_sub(max_frame_top)\n            .clamp(0, actual_vh_height - 1);\n\n        // The only reliable way we can force the terminal downards is through \"insert_before\".\n        //\n        // If we need to push the terminal down, we'll use this method with the number of lines\n        // Ratatui will handle this rest.\n        //\n        // This also calls `.clear()` so we don't need to call clear at the end of this function.\n        //\n        // FIXME(jon): eventually insert_before will get scroll regions, breaking this, but making the logic here simpler\n        terminal.insert_before(remaining_space.min(lines_printed), |_| {})?;\n\n        // Wipe the viewport clean so it doesn't tear\n        crossterm::queue!(\n            std::io::stdout(),\n            crossterm::cursor::MoveTo(0, frame_rect.y),\n            crossterm::terminal::Clear(ClearType::FromCursorDown),\n        )?;\n\n        // Start printing the log by writing on top of the topmost line\n        for (idx, line) in lines.into_iter().enumerate() {\n            // Move the cursor to the correct line offset but don't go past the bottom of the terminal\n            let start = frame_rect.y + idx as u16;\n            let start = start.min(term_size.height - 1);\n            crossterm::queue!(\n                std::io::stdout(),\n                crossterm::cursor::MoveTo(0, start),\n                crossterm::style::Print(line),\n                crossterm::style::Print(\"\\n\"),\n            )?;\n        }\n\n        // Scroll the terminal if we need to\n        for _ in 0..padding {\n            crossterm::queue!(\n                std::io::stdout(),\n                crossterm::cursor::MoveTo(0, term_size.height - 1),\n                crossterm::style::Print(\"\\n\"),\n            )?;\n        }\n\n        Ok(())\n    }\n\n    fn viewport_current_height(&self) -> u16 {\n        match self.more_modal_open {\n            true => VIEWPORT_HEIGHT_BIG,\n            false => VIEWPORT_HEIGHT_SMALL,\n        }\n    }\n\n    fn tracemsg_to_ansi_string(log: TraceMsg) -> Vec<String> {\n        use ansi_to_tui::IntoText;\n        use chrono::Timelike;\n\n        let rendered = match log.content {\n            TraceContent::Cargo(msg) => msg.rendered.unwrap_or_default(),\n            TraceContent::Text(text) => text,\n        };\n\n        let mut lines = vec![];\n\n        for (idx, raw_line) in rendered.lines().enumerate() {\n            let line_as_text = raw_line.into_text().unwrap();\n            let is_pretending_to_be_frame = !raw_line.is_empty()\n                && raw_line\n                    .chars()\n                    .all(|c| c == '=' || c == '-' || c == ' ' || c == '─');\n\n            for (subline_idx, mut line) in line_as_text.lines.into_iter().enumerate() {\n                if idx == 0 && subline_idx == 0 {\n                    let mut formatted_line = Line::default();\n\n                    formatted_line.push_span(\n                        Span::raw(format!(\n                            \"{:02}:{:02}:{:02} \",\n                            log.timestamp.hour(),\n                            log.timestamp.minute(),\n                            log.timestamp.second()\n                        ))\n                        .dark_gray(),\n                    );\n\n                    formatted_line.push_span(\n                        Span::raw(format!(\n                            \"[{src}] {padding}\",\n                            src = log.source,\n                            padding =\n                                \" \".repeat(3usize.saturating_sub(log.source.to_string().len()))\n                        ))\n                        .style(match log.source {\n                            TraceSrc::App(_platform) => match log.level {\n                                Level::ERROR => Style::new().red(),\n                                Level::WARN => Style::new().yellow(),\n                                Level::INFO => Style::new().magenta(),\n                                Level::DEBUG => Style::new().magenta(),\n                                Level::TRACE => Style::new().magenta(),\n                            },\n                            TraceSrc::Dev => match log.level {\n                                Level::ERROR => Style::new().red(),\n                                Level::WARN => Style::new().yellow(),\n                                Level::INFO => Style::new().blue(),\n                                Level::DEBUG => Style::new().blue(),\n                                Level::TRACE => Style::new().blue(),\n                            },\n                            TraceSrc::Cargo => Style::new().yellow(),\n                            TraceSrc::Build => Style::new().blue(),\n                            TraceSrc::Bundle => Style::new().blue(),\n                            TraceSrc::Unknown => Style::new().gray(),\n                        }),\n                    );\n\n                    for span in line.spans {\n                        formatted_line.push_span(span);\n                    }\n\n                    line = formatted_line;\n                }\n\n                if is_pretending_to_be_frame {\n                    line = line.dark_gray();\n                }\n\n                lines.push(ansi_string_to_line(line));\n            }\n        }\n\n        lines\n    }\n}\n\nimpl std::ops::Drop for Output {\n    fn drop(&mut self) {\n        if !self.interactive {\n            return;\n        }\n\n        // Get a handle to the terminal with a different lifetime so we can continue to call &self methods\n        let owned_term = self.term.clone();\n        let mut term = owned_term.borrow_mut();\n        let Some(term) = term.as_mut() else {\n            return;\n        };\n\n        // First, dequeue any logs that have built up from event handling\n        while let Some(log) = self.pending_logs.pop_back() {\n            _ = self.render_log(term, log);\n        }\n\n        // And then lets move the cursor to the bottom of the terminal\n        let frame_rect = term.get_frame().area();\n        _ = term.set_cursor_position(Position {\n            x: 0,\n            y: frame_rect.y + frame_rect.height,\n        });\n\n        // Tear down the TUI if it's active at this point.\n        _ = crate::serve::Output::remote_shutdown(self.interactive);\n    }\n}\n"
  },
  {
    "path": "packages/cli/src/serve/proxy.rs",
    "content": "use crate::config::WebProxyConfig;\nuse crate::TraceSrc;\nuse crate::{Error, Result};\n\nuse anyhow::bail;\nuse axum::body::Body;\nuse axum::http::request::Parts;\nuse axum::{body::Body as MyBody, response::IntoResponse};\nuse axum::{\n    http::StatusCode,\n    routing::{any, MethodRouter},\n    Router,\n};\nuse hyper::client::conn::http1;\nuse hyper::header::*;\nuse hyper::{Request, Response, Uri};\nuse hyper_util::rt::TokioIo;\nuse tokio::net::TcpStream;\n\n/// Establish a TCP connection to the backend with retry, then send the HTTP request.\n/// This reuses the same TCP connection for both health check and request,\n/// and supports streaming request bodies (no buffering).\nasync fn send_with_retry(\n    url: &Uri,\n    req: Request<MyBody>,\n    handle_error: fn(Error) -> Response<Body>,\n) -> std::result::Result<Response<hyper::body::Incoming>, Response<Body>> {\n    let host = url.host().unwrap_or(\"127.0.0.1\");\n    let port = url.port_u16().unwrap_or(80);\n    let addr = format!(\"{host}:{port}\");\n\n    let mut backoff = std::time::Duration::from_millis(100);\n    let max_wait = std::time::Duration::from_secs(30);\n    let start = std::time::Instant::now();\n\n    // Retry TCP connect until backend is ready\n    let stream = loop {\n        match TcpStream::connect(&addr).await {\n            Ok(stream) => break stream,\n            Err(e) => {\n                if start.elapsed() >= max_wait {\n                    return Err(handle_error(anyhow::anyhow!(\n                        \"Backend not ready after {max_wait:?}: {e}\"\n                    )));\n                }\n                tracing::debug!(\"Backend not ready, retrying in {backoff:?}...\");\n                tokio::time::sleep(backoff).await;\n                backoff = (backoff * 2).min(std::time::Duration::from_secs(2));\n            }\n        }\n    };\n\n    // Wrap the TCP stream for hyper\n    let io = TokioIo::new(stream);\n\n    // Perform HTTP/1.1 handshake on the same connection\n    let (mut sender, conn) = http1::handshake(io)\n        .await\n        .map_err(|e| handle_error(anyhow::anyhow!(\"HTTP handshake failed: {e}\")))?;\n\n    // Spawn connection driver to keep it alive\n    tokio::spawn(async move {\n        if let Err(e) = conn.await {\n            tracing::debug!(\"Connection closed: {e}\");\n        }\n    });\n\n    // Send request through the established connection (streaming body)\n    sender\n        .send_request(req)\n        .await\n        .map_err(|e| handle_error(anyhow::anyhow!(\"Request failed: {e}\")))\n}\n\n/// Add routes to the router handling the specified proxy config.\n///\n/// We will proxy requests directed at either:\n///\n/// - the exact path of the proxy config's backend URL, e.g. /api\n/// - the exact path with a trailing slash, e.g. /api/\n/// - any subpath of the backend URL, e.g. /api/foo/bar\npub(crate) fn add_proxy(mut router: Router, proxy: &WebProxyConfig) -> Result<Router> {\n    let url: Uri = proxy.backend.parse()?;\n    let path = url.path().to_string();\n    let trimmed_path = path.trim_start_matches('/');\n\n    if trimmed_path.is_empty() {\n        bail!(\n            \"Proxy backend URL must have a non-empty path, e.g. {}/api instead of {}\",\n            proxy.backend.trim_end_matches('/'),\n            proxy.backend\n        );\n    }\n\n    let method_router = proxy_to(url, false, handle_proxy_error);\n\n    // api/*path\n    router = router.route(\n        &format!(\"/{}/{{*path}}\", trimmed_path.trim_end_matches('/')),\n        method_router.clone(),\n    );\n\n    // /api/\n    router = router.route(\n        &format!(\"/{}/\", trimmed_path.trim_end_matches('/')),\n        method_router.clone(),\n    );\n\n    // /api\n    router = router.route(\n        &format!(\"/{}\", trimmed_path.trim_end_matches('/')),\n        method_router,\n    );\n\n    Ok(router)\n}\n\npub(crate) fn proxy_to(\n    url: Uri,\n    nocache: bool,\n    handle_error: fn(Error) -> Response<Body>,\n) -> MethodRouter {\n    any(move |parts: Parts, mut req: Request<MyBody>| async move {\n        // Prevent request loops\n        if req.headers().get(\"x-proxied-by-dioxus\").is_some() {\n            return Err(Response::builder()\n                .status(StatusCode::NOT_FOUND)\n                .body(Body::from(\n                    \"API is sharing a loopback with the dev server. Try setting a different port on the API config.\",\n                ))\n                .unwrap());\n        }\n\n        req.headers_mut().insert(\n            \"x-proxied-by-dioxus\",\n            \"true\".parse().expect(\"header value is valid\"),\n        );\n\n        let upgrade = req.headers().get(UPGRADE);\n        if req.uri().scheme().map(|f| f.as_str()) == Some(\"ws\")\n            || req.uri().scheme().map(|f| f.as_str()) == Some(\"wss\")\n            || upgrade.is_some_and(|h| h.as_bytes().eq_ignore_ascii_case(b\"websocket\"))\n        {\n            return super::proxy_ws::proxy_websocket(parts, req, &url).await;\n        }\n\n        if nocache {\n            crate::serve::insert_no_cache_headers(req.headers_mut());\n        }\n\n        let uri = req.uri().clone();\n\n        // Set Host header for backend (send_with_retry handles TCP connection via url)\n        if let Some(authority) = url.authority() {\n            req.headers_mut().insert(\n                HOST,\n                authority\n                    .to_string()\n                    .parse()\n                    .expect(\"authority is valid header value\"),\n            );\n        }\n\n        // Send with retry - TCP connect retries, then reuses connection for HTTP\n        let res = send_with_retry(&url, req, handle_error).await;\n\n        match res {\n            Ok(res) => {\n                // log assets at a different log level\n                if uri.path().starts_with(\"/assets/\")\n                    || uri.path().starts_with(\"/_dioxus/\")\n                    || uri.path().starts_with(\"/public/\")\n                    || uri.path().starts_with(\"/wasm/\")\n                {\n                    tracing::trace!(dx_src = ?TraceSrc::App(crate::BundleFormat::Server), \"[{}] {}\", res.status().as_u16(), uri);\n                } else {\n                    tracing::info!(dx_src = ?TraceSrc::App(crate::BundleFormat::Server), \"[{}] {}\", res.status().as_u16(), uri);\n                }\n\n                Ok(res.into_response())\n            }\n            Err(err) => {\n                tracing::error!(dx_src = ?TraceSrc::App(crate::BundleFormat::Server), \"[{}] {}\", err.status().as_u16(), uri);\n                Err(err)\n            }\n        }\n    })\n}\n\npub(crate) fn handle_proxy_error(e: Error) -> axum::http::Response<axum::body::Body> {\n    tracing::error!(dx_src = ?TraceSrc::Dev, \"Proxy error: {}\", e);\n    axum::http::Response::builder()\n        .status(axum::http::StatusCode::INTERNAL_SERVER_ERROR)\n        .body(axum::body::Body::from(format!(\n            \"Proxy connection failed: {e:#?}\"\n        )))\n        .unwrap()\n}\n\n#[cfg(test)]\nmod test {\n\n    use super::*;\n\n    use axum_server::{Handle, Server};\n\n    async fn setup_servers(mut config: WebProxyConfig) -> String {\n        let backend_router =\n            Router::new().route(\n                \"/{*path}\",\n                any(|request: axum::extract::Request| async move {\n                    format!(\"backend: {}\", request.uri())\n                }),\n            );\n\n        // The API backend server\n        let backend_handle_handle = Handle::new();\n        let backend_handle_handle_ = backend_handle_handle.clone();\n        tokio::spawn(async move {\n            Server::bind(\"127.0.0.1:0\".parse().unwrap())\n                .handle(backend_handle_handle_)\n                .serve(backend_router.into_make_service())\n                .await\n                .unwrap();\n        });\n\n        // Set the user's config to this dummy API we just built so we can test it\n        let backend_addr = backend_handle_handle.listening().await.unwrap();\n        config.backend = format!(\"http://{}{}\", backend_addr, config.backend);\n\n        // Now set up our actual filesystem server\n        let router = super::add_proxy(Router::new(), &config);\n        let server_handle_handle = Handle::new();\n        let server_handle_handle_ = server_handle_handle.clone();\n        tokio::spawn(async move {\n            Server::bind(\"127.0.0.1:0\".parse().unwrap())\n                .handle(server_handle_handle_)\n                .serve(router.unwrap().into_make_service())\n                .await\n                .unwrap();\n        });\n\n        // Expose *just* the filesystem web server's address\n        server_handle_handle.listening().await.unwrap().to_string()\n    }\n\n    async fn test_proxy_requests(path: String) {\n        let config = WebProxyConfig {\n            // Normally this would be an absolute URL including scheme/host/port,\n            // but in these tests we need to let the OS choose the port so tests\n            // don't conflict, so we'll concatenate the final address and this\n            // path together.\n            // So in day to day usage, use `http://localhost:8000/api` instead!\n            backend: path,\n        };\n\n        let server_addr = setup_servers(config).await;\n\n        assert_eq!(\n            reqwest::get(format!(\"http://{server_addr}/api\"))\n                .await\n                .unwrap()\n                .text()\n                .await\n                .unwrap(),\n            \"backend: /api\"\n        );\n\n        assert_eq!(\n            reqwest::get(format!(\"http://{server_addr}/api/\"))\n                .await\n                .unwrap()\n                .text()\n                .await\n                .unwrap(),\n            \"backend: /api/\"\n        );\n\n        assert_eq!(\n            reqwest::get(format!(\"http://{server_addr}/api/subpath\"))\n                .await\n                .unwrap()\n                .text()\n                .await\n                .unwrap(),\n            \"backend: /api/subpath\"\n        );\n    }\n\n    #[tokio::test]\n    async fn add_proxy() {\n        test_proxy_requests(\"/api\".to_string()).await;\n    }\n\n    #[tokio::test]\n    async fn add_proxy_trailing_slash() {\n        test_proxy_requests(\"/api/\".to_string()).await;\n    }\n}\n"
  },
  {
    "path": "packages/cli/src/serve/proxy_ws.rs",
    "content": "use crate::logging::TraceSrc;\nuse crate::serve::proxy::handle_proxy_error;\nuse anyhow::Context;\nuse axum::body::Body;\nuse axum::extract::ws::{CloseFrame as ClientCloseFrame, Message as ClientMessage};\nuse axum::extract::{FromRequestParts, WebSocketUpgrade};\nuse axum::http::request::Parts;\nuse axum::response::IntoResponse;\nuse futures_util::{SinkExt, StreamExt};\nuse hyper::{Request, Response, Uri};\nuse tokio_tungstenite::tungstenite::protocol::{\n    CloseFrame as ServerCloseFrame, Message as ServerMessage,\n};\n\npub(crate) async fn proxy_websocket(\n    mut parts: Parts,\n    req: Request<Body>,\n    backend_url: &Uri,\n) -> Result<Response<Body>, Response<Body>> {\n    let ws = WebSocketUpgrade::from_request_parts(&mut parts, &())\n        .await\n        .map_err(IntoResponse::into_response)?;\n\n    tracing::trace!(dx_src = ?TraceSrc::Dev, \"Proxying websocket connection {req:?}\");\n    let proxied_request = into_proxied_request(req, backend_url).map_err(handle_proxy_error)?;\n    tracing::trace!(dx_src = ?TraceSrc::Dev, \"Connection proxied to {proxied_uri}\", proxied_uri = proxied_request.uri());\n\n    Ok(ws.on_upgrade(move |client_ws| async move {\n        match handle_ws_connection(client_ws, proxied_request).await {\n            Ok(()) => tracing::trace!(dx_src = ?TraceSrc::Dev, \"Websocket connection closed\"),\n            Err(e) => {\n                // Connection resets during shutdown are expected and not worth logging as errors\n                tracing::debug!(dx_src = ?TraceSrc::Dev, \"Error proxying websocket connection: {e}\")\n            }\n        }\n    }))\n}\n\nfn into_proxied_request(\n    req: Request<Body>,\n    backend_url: &Uri,\n) -> crate::Result<tokio_tungstenite::tungstenite::handshake::client::Request> {\n    // ensure headers from original request are preserved\n    let (mut request_parts, _) = req.into_parts();\n    let mut uri_parts = request_parts.uri.into_parts();\n    uri_parts.scheme = uri_parts.scheme.or(\"ws\".parse().ok());\n    uri_parts.authority = backend_url.authority().cloned();\n    request_parts.uri = Uri::from_parts(uri_parts).context(\"Could not construct proxy URI\")?;\n    Ok(Request::from_parts(request_parts, ()))\n}\n\n#[derive(thiserror::Error, Debug)]\nenum WsError {\n    #[error(\"Error connecting to server: {0}\")]\n    Connect(tokio_tungstenite::tungstenite::Error),\n    #[error(\"Error sending message to server: {0}\")]\n    ToServer(tokio_tungstenite::tungstenite::Error),\n    #[error(\"Error receiving message from server: {0}\")]\n    FromServer(tokio_tungstenite::tungstenite::Error),\n    #[error(\"Error sending message to client: {0}\")]\n    ToClient(axum::Error),\n    #[error(\"Error receiving message from client: {0}\")]\n    FromClient(axum::Error),\n}\n\nasync fn handle_ws_connection(\n    mut client_ws: axum::extract::ws::WebSocket,\n    proxied_request: tokio_tungstenite::tungstenite::handshake::client::Request,\n) -> Result<(), WsError> {\n    let (mut server_ws, _) = tokio_tungstenite::connect_async(proxied_request)\n        .await\n        .map_err(WsError::Connect)?;\n\n    let mut closed = false;\n    while !closed {\n        tokio::select! {\n            Some(server_msg) = server_ws.next() => {\n                closed = matches!(server_msg, Ok(ServerMessage::Close(..)));\n                match server_msg.map_err(WsError::FromServer)?.into_msg() {\n                    Ok(msg) => client_ws.send(msg).await.map_err(WsError::ToClient)?,\n                    Err(UnexpectedRawFrame) => tracing::warn!(dx_src = ?TraceSrc::Dev, \"Dropping unexpected raw websocket frame\"),\n                }\n            },\n            Some(client_msg) = client_ws.next() => {\n                closed = matches!(client_msg, Ok(ClientMessage::Close(..)));\n                let Ok(msg) = client_msg.map_err(WsError::FromClient)?.into_msg();\n                server_ws.send(msg).await.map_err(WsError::ToServer)?;\n            },\n            else => break,\n        }\n    }\n\n    Ok(())\n}\n\ntrait IntoMsg<T> {\n    type Error;\n    fn into_msg(self) -> Result<T, Self::Error>;\n}\n\nimpl IntoMsg<ServerMessage> for ClientMessage {\n    type Error = std::convert::Infallible;\n    fn into_msg(self) -> Result<ServerMessage, Self::Error> {\n        use ServerMessage as SM;\n        Ok(match self {\n            Self::Text(v) => SM::Text(v.as_str().into()),\n            Self::Binary(v) => SM::Binary(v),\n            Self::Ping(v) => SM::Ping(v),\n            Self::Pong(v) => SM::Pong(v),\n            Self::Close(v) => SM::Close(v.map(|cf| ServerCloseFrame {\n                code: cf.code.into(),\n                reason: cf.reason.as_str().into(),\n            })),\n        })\n    }\n}\n\nstruct UnexpectedRawFrame;\nimpl IntoMsg<ClientMessage> for ServerMessage {\n    type Error = UnexpectedRawFrame;\n    fn into_msg(self) -> Result<ClientMessage, Self::Error> {\n        use ClientMessage as CM;\n        Ok(match self {\n            Self::Text(v) => CM::Text(v.as_str().into()),\n            Self::Binary(v) => CM::Binary(v),\n            Self::Ping(v) => CM::Ping(v),\n            Self::Pong(v) => CM::Pong(v),\n            Self::Close(v) => CM::Close(v.map(|cf| ClientCloseFrame {\n                code: cf.code.into(),\n                reason: cf.reason.as_str().into(),\n            })),\n            Self::Frame(_) => {\n                // this variant should never be returned by next(), but handle it\n                // gracefully by dropping it instead of panicking out of an abundance of caution\n                return Err(UnexpectedRawFrame);\n            }\n        })\n    }\n}\n"
  },
  {
    "path": "packages/cli/src/serve/runner.rs",
    "content": "use super::{AppBuilder, ServeUpdate, WebServer};\nuse crate::{\n    platform_override::CommandWithPlatformOverrides, BuildArtifacts, BuildId, BuildMode,\n    BuildTargets, BuilderUpdate, BundleFormat, HotpatchModuleCache, Result, ServeArgs, TailwindCli,\n    TraceSrc, Workspace,\n};\nuse anyhow::{bail, Context};\nuse dioxus_core::internal::{\n    HotReloadTemplateWithLocation, HotReloadedTemplate, TemplateGlobalKey,\n};\nuse dioxus_devtools_types::HotReloadMsg;\nuse dioxus_dx_wire_format::BuildStage;\nuse dioxus_html::HtmlCtx;\nuse dioxus_rsx::CallBody;\nuse dioxus_rsx_hotreload::{ChangedRsx, HotReloadResult};\nuse futures_channel::mpsc::{UnboundedReceiver, UnboundedSender};\nuse futures_util::future::OptionFuture;\nuse futures_util::StreamExt;\nuse krates::NodeId;\nuse notify::{\n    event::{MetadataKind, ModifyKind},\n    Config, EventKind, RecursiveMode, Watcher as NotifyWatcher,\n};\nuse std::{\n    collections::{HashMap, HashSet, VecDeque},\n    net::{IpAddr, TcpListener},\n    path::{Path, PathBuf},\n    sync::Arc,\n    time::Duration,\n};\nuse syn::spanned::Spanned;\nuse tokio::process::Command;\n\n/// This is the primary \"state\" object that holds the builds and handles for the running apps.\n///\n/// It also holds the watcher which is used to watch for changes in the filesystem and trigger rebuilds,\n/// hotreloads, asset updates, etc.\n///\n/// Since we resolve the build request before initializing the CLI, it also serves as a place to store\n/// resolved \"serve\" arguments, which is why it takes ServeArgs instead of BuildArgs. Simply wrap the\n/// BuildArgs in a default ServeArgs and pass it in.\npub(crate) struct AppServer {\n    /// the platform of the \"primary\" crate (ie the first)\n    pub(crate) workspace: Arc<Workspace>,\n\n    pub(crate) client: AppBuilder,\n    pub(crate) server: Option<AppBuilder>,\n\n    // Related to the filesystem watcher\n    pub(crate) watcher: Box<dyn notify::Watcher>,\n    pub(crate) _watcher_tx: UnboundedSender<notify::Event>,\n    pub(crate) watcher_rx: UnboundedReceiver<notify::Event>,\n\n    // Tracked state related to open builds and hot reloading\n    pub(crate) applied_client_hot_reload_message: HotReloadMsg,\n    pub(crate) file_map: HashMap<PathBuf, CachedFile>,\n\n    // Resolved args related to how we go about processing the rebuilds and logging\n    pub(crate) use_hotpatch_engine: bool,\n    pub(crate) automatic_rebuilds: bool,\n    pub(crate) interactive: bool,\n    pub(crate) _force_sequential: bool,\n    pub(crate) hot_reload: bool,\n    pub(crate) open_browser: bool,\n    pub(crate) _wsl_file_poll_interval: u16,\n    pub(crate) always_on_top: bool,\n    pub(crate) fullstack: bool,\n    pub(crate) ssg: bool,\n    pub(crate) watch_fs: bool,\n\n    // resolve args related to the webserver\n    pub(crate) devserver_port: u16,\n    pub(crate) devserver_bind_ip: IpAddr,\n    pub(crate) proxied_port: Option<u16>,\n    pub(crate) cross_origin_policy: bool,\n\n    // The arguments that should be forwarded to the client app when it is opened\n    pub(crate) client_args: Vec<String>,\n    // The arguments that should be forwarded to the server app when it is opened\n    pub(crate) server_args: Vec<String>,\n\n    // Additional plugin-type tools\n    pub(crate) tw_watcher: tokio::task::JoinHandle<Result<()>>,\n\n    // File changes that arrived while a build was in progress, to be processed after build completes\n    pub(crate) pending_file_changes: Vec<PathBuf>,\n}\n\npub(crate) struct CachedFile {\n    contents: String,\n    most_recent: Option<String>,\n    templates: HashMap<TemplateGlobalKey, HotReloadedTemplate>,\n}\n\nimpl AppServer {\n    /// Create the AppRunner and then initialize the filemap with the crate directory.\n    pub(crate) async fn new(args: ServeArgs) -> Result<Self> {\n        let workspace = Workspace::current().await?;\n\n        // Resolve the simpler args\n        let interactive = args.is_interactive_tty();\n        let force_sequential = args.platform_args.shared.targets.force_sequential_build();\n        let cross_origin_policy = args.cross_origin_policy;\n\n        // Find the launch args for the client and server\n        let split_args = |args: &str| {\n            args.split_whitespace()\n                .map(|s| s.to_string())\n                .collect::<Vec<_>>()\n        };\n\n        let server_args = args.platform_args.with_server_or_shared(|c| &c.args);\n        let server_args = split_args(server_args);\n        let client_args = args.platform_args.with_client_or_shared(|c| &c.args);\n        let client_args = split_args(client_args);\n\n        // These come from the args but also might come from the workspace settings\n        // We opt to use the manually specified args over the workspace settings\n        let hot_reload = args\n            .hot_reload\n            .unwrap_or_else(|| workspace.settings.always_hot_reload.unwrap_or(true));\n\n        let open_browser = args\n            .open\n            .unwrap_or_else(|| workspace.settings.always_open_browser.unwrap_or(false))\n            && interactive;\n\n        let wsl_file_poll_interval = args\n            .wsl_file_poll_interval\n            .unwrap_or_else(|| workspace.settings.wsl_file_poll_interval.unwrap_or(2));\n\n        let always_on_top = args\n            .always_on_top\n            .unwrap_or_else(|| workspace.settings.always_on_top.unwrap_or(true));\n\n        // Use 127.0.0.1 as the default address if none is specified.\n        // If the user wants to export on the network, they can use `0.0.0.0` instead.\n        let devserver_bind_ip = args.address.addr.unwrap_or(WebServer::SELF_IP);\n\n        // If the user specified a port, use that, otherwise use any available port, preferring 8080\n        let devserver_port = args\n            .address\n            .port\n            .unwrap_or_else(|| get_available_port(devserver_bind_ip, Some(8080)).unwrap_or(8080));\n\n        // Spin up the file watcher\n        let (watcher_tx, watcher_rx) = futures_channel::mpsc::unbounded();\n        let watcher = create_notify_watcher(watcher_tx.clone(), wsl_file_poll_interval as u64);\n\n        let ssg = args.platform_args.shared.targets.ssg;\n        let target_args = CommandWithPlatformOverrides {\n            shared: args.platform_args.shared.targets,\n            server: args.platform_args.server.map(|s| s.targets),\n            client: args.platform_args.client.map(|c| c.targets),\n        };\n        let BuildTargets { client, server } = target_args.into_targets().await?;\n\n        // All servers will end up behind us (the devserver) but on a different port\n        // This is so we can serve a loading screen as well as devtools without anything particularly fancy\n        let fullstack = server.is_some();\n        let should_proxy_port = match client.bundle {\n            BundleFormat::Server => true,\n            _ => fullstack && !ssg,\n        };\n\n        let proxied_port = should_proxy_port\n            .then(|| get_available_port(devserver_bind_ip, None))\n            .flatten();\n\n        let watch_fs = args.watch.unwrap_or(true);\n        let use_hotpatch_engine = args.hot_patch;\n\n        let client = AppBuilder::new(&client)?;\n        let server = server.map(|server| AppBuilder::new(&server)).transpose()?;\n\n        let tw_watcher = TailwindCli::serve(\n            client.build.package_manifest_dir(),\n            client.build.config.application.tailwind_input.clone(),\n            client.build.config.application.tailwind_output.clone(),\n        );\n\n        _ = client.build.start_simulators().await;\n\n        // Encourage the user to update to a new dx version\n        crate::update::log_if_cli_could_update();\n\n        // Create the runner\n        let mut runner = Self {\n            file_map: Default::default(),\n            applied_client_hot_reload_message: Default::default(),\n            automatic_rebuilds: true,\n            watch_fs,\n            use_hotpatch_engine,\n            client,\n            server,\n            hot_reload,\n            open_browser,\n            _wsl_file_poll_interval: wsl_file_poll_interval,\n            always_on_top,\n            workspace,\n            devserver_port,\n            devserver_bind_ip,\n            proxied_port,\n            watcher,\n            watcher_rx,\n            _watcher_tx: watcher_tx,\n            interactive,\n            _force_sequential: force_sequential,\n            cross_origin_policy,\n            fullstack,\n            ssg,\n            tw_watcher,\n            server_args,\n            client_args,\n            pending_file_changes: Vec::new(),\n        };\n\n        // Only register the hot-reload stuff if we're watching the filesystem\n        if runner.watch_fs {\n            // Spin up the notify watcher\n            // When builds load though, we're going to parse their depinfo and add the paths to the watcher\n            runner.watch_filesystem();\n\n            // todo(jon): this might take a while so we should try and background it, or make it lazy somehow\n            // we could spawn a thread to search the FS and then when it returns we can fill the filemap\n            // in testing, if this hits a massive directory, it might take several seconds with no feedback.\n            // really, we should be using depinfo to get the files that are actually used, but the depinfo file might not be around yet\n            // todo(jon): see if we can just guess the depinfo file before it generates. might be stale but at least it catches most of the files\n            runner.load_rsx_filemap();\n        }\n\n        Ok(runner)\n    }\n\n    pub(crate) fn initialize(&mut self) {\n        let build_mode = match self.use_hotpatch_engine {\n            true => BuildMode::Fat,\n            false => BuildMode::Base { run: true },\n        };\n\n        self.client.start(build_mode.clone(), BuildId::PRIMARY);\n        if let Some(server) = self.server.as_mut() {\n            server.start(build_mode, BuildId::SECONDARY);\n        }\n    }\n\n    /// Take any pending file changes that were queued while a build was in progress.\n    /// Returns the files and clears the pending list.\n    pub(crate) fn take_pending_file_changes(&mut self) -> Vec<PathBuf> {\n        std::mem::take(&mut self.pending_file_changes)\n    }\n\n    pub(crate) async fn rebuild_ssg(&mut self, devserver: &WebServer) {\n        if self.client.stage != BuildStage::Success {\n            return;\n        }\n        // Run SSG and cache static routes if the server build is done\n        if let Some(server) = self.server.as_mut() {\n            if !self.ssg || server.stage != BuildStage::Success {\n                return;\n            }\n            if let Err(err) = crate::pre_render_static_routes(\n                Some(devserver.devserver_address()),\n                server,\n                Some(&server.tx.clone()),\n            )\n            .await\n            {\n                tracing::error!(\"Failed to pre-render static routes: {err}\");\n            }\n        }\n    }\n\n    pub(crate) async fn wait(&mut self) -> ServeUpdate {\n        let client = &mut self.client;\n        let server = self.server.as_mut();\n\n        let client_wait = client.wait();\n        let server_wait = OptionFuture::from(server.map(|s| s.wait()));\n        let watcher_wait = self.watcher_rx.next();\n\n        tokio::select! {\n            // Wait for the client to finish\n            client_update = client_wait => {\n                ServeUpdate::BuilderUpdate {\n                    id: BuildId::PRIMARY,\n                    update: client_update,\n                }\n            }\n\n            Some(server_update) = server_wait => {\n                ServeUpdate::BuilderUpdate {\n                    id: BuildId::SECONDARY,\n                    update: server_update,\n                }\n            }\n\n            // Wait for the watcher to send us an event\n            event = watcher_wait => {\n                let mut changes: Vec<_> = event.into_iter().collect();\n\n                // Dequeue in bulk if we can, we might've received a lot of events in one go\n                while let Some(event) = self.watcher_rx.try_next().ok().flatten() {\n                    changes.push(event);\n                }\n\n                // Filter the changes\n                let mut files: Vec<PathBuf> = vec![];\n\n                // Decompose the events into a list of all the files that have changed\n                for event in changes.drain(..) {\n                    // Make sure we add new folders to the watch list, provided they're not matched by the ignore list\n                    // We'll only watch new folders that are found under the crate, and then update our watcher to watch them\n                    // This unfortunately won't pick up new krates added \"at a distance\" - IE krates not within the workspace.\n                    if let EventKind::Create(_create_kind) = event.kind {\n                        // If it's a new folder, watch it\n                        // If it's a new cargo.toml (ie dep on the fly),\n                        // todo(jon) support new folders on the fly\n                    }\n\n                    for path in event.paths {\n                        // Workaround for notify and vscode-like editor:\n                        // - when edit & save a file in vscode, there will be two notifications,\n                        // - the first one is a file with empty content.\n                        // - filter the empty file notification to avoid false rebuild during hot-reload\n                        if let Ok(metadata) = std::fs::metadata(&path) {\n                            if metadata.len() == 0 {\n                                continue;\n                            }\n                        }\n\n                        files.push(path);\n                    }\n                }\n\n                ServeUpdate::FilesChanged { files }\n            }\n\n        }\n    }\n\n    /// Handle an update from the builder\n    pub(crate) async fn new_build_update(&mut self, update: &BuilderUpdate, devserver: &WebServer) {\n        if let BuilderUpdate::BuildReady { .. } = update {\n            // If the build is ready, we need to check if we need to pre-render with ssg\n            self.rebuild_ssg(devserver).await;\n        }\n    }\n\n    /// Handle the list of changed files from the file watcher, attempting to aggressively prevent\n    /// full rebuilds by hot-reloading RSX and hot-patching Rust code.\n    ///\n    /// This will also handle any assets that are linked in the files, and copy them to the bundle\n    /// and send them to the client.\n    pub(crate) async fn handle_file_change(&mut self, files: &[PathBuf], server: &mut WebServer) {\n        // We can attempt to hotpatch if the build is in a bad state, since this patch might be a recovery.\n        if !matches!(\n            self.client.stage,\n            BuildStage::Failed | BuildStage::Aborted | BuildStage::Success\n        ) {\n            // Queue file changes that arrive during a build, so we can process them after the build completes.\n            // This prevents losing changes from tools like stylance, tailwind, or sass that generate files\n            // in response to source changes.\n            tracing::debug!(\n                \"Queueing file change: client is not ready to receive hotreloads. Files: {:#?}\",\n                files\n            );\n            self.pending_file_changes.extend(files.iter().cloned());\n            return;\n        }\n\n        // If we have any changes to the rust files, we need to update the file map\n        let mut templates = vec![];\n\n        // Prepare the hotreload message we need to send\n        let mut assets = Vec::new();\n        let mut needs_full_rebuild = false;\n\n        // We attempt to hotreload rsx blocks without a full rebuild\n        for path in files {\n            // for various assets that might be linked in, we just try to hotreloading them forcefully\n            // That is, unless they appear in an include! macro, in which case we need to a full rebuild....\n            let ext = path\n                .extension()\n                .and_then(|v| v.to_str())\n                .unwrap_or_default();\n\n            // If it's an asset, we want to hotreload it\n            // todo(jon): don't hardcode this here\n            if let Some(bundled_names) = self.client.hotreload_bundled_assets(path).await {\n                for bundled_name in bundled_names {\n                    assets.push(PathBuf::from(\"/assets/\").join(bundled_name));\n                }\n            }\n\n            // If it's in the public dir, we sync it and trigger a full rebuild\n            if self.client.build.path_is_in_public_dir(path) {\n                needs_full_rebuild = true;\n                continue;\n            }\n\n            // If it's a rust file, we want to hotreload it using the filemap\n            if ext == \"rs\" {\n                // And grabout the contents\n                let Ok(new_contents) = std::fs::read_to_string(path) else {\n                    tracing::debug!(\"Failed to read rust file while hotreloading: {:?}\", path);\n                    continue;\n                };\n\n                // Get the cached file if it exists - ignoring if it doesn't exist\n                let Some(cached_file) = self.file_map.get_mut(path) else {\n                    tracing::debug!(\"No entry for file in filemap: {:?}\", path);\n                    tracing::debug!(\"Filemap: {:#?}\", self.file_map.keys());\n                    continue;\n                };\n\n                let Ok(local_path) = path.strip_prefix(self.workspace.workspace_root()) else {\n                    tracing::debug!(\"Skipping file outside workspace dir: {:?}\", path);\n                    continue;\n                };\n\n                // We assume we can parse the old file and the new file, ignoring untracked rust files\n                let old_syn = syn::parse_file(&cached_file.contents);\n                let new_syn = syn::parse_file(&new_contents);\n                let (Ok(old_file), Ok(new_file)) = (old_syn, new_syn) else {\n                    tracing::debug!(\"Diff rsx returned not parseable\");\n                    continue;\n                };\n\n                // Update the most recent version of the file, so when we force a rebuild, we keep operating on the most recent version\n                cached_file.most_recent = Some(new_contents);\n\n                // This assumes the two files are structured similarly. If they're not, we can't diff them\n                let Some(changed_rsx) = dioxus_rsx_hotreload::diff_rsx(&new_file, &old_file) else {\n                    needs_full_rebuild = true;\n                    break;\n                };\n\n                for ChangedRsx { old, new } in changed_rsx {\n                    let old_start = old.span().start();\n\n                    let old_parsed = syn::parse2::<CallBody>(old.tokens);\n                    let new_parsed = syn::parse2::<CallBody>(new.tokens);\n                    let (Ok(old_call_body), Ok(new_call_body)) = (old_parsed, new_parsed) else {\n                        continue;\n                    };\n\n                    // Format the template location, normalizing the path\n                    let file_name: String = local_path\n                        .components()\n                        .map(|c| c.as_os_str().to_string_lossy())\n                        .collect::<Vec<_>>()\n                        .join(\"/\");\n\n                    // Returns a list of templates that are hotreloadable\n                    let results = HotReloadResult::new::<HtmlCtx>(\n                        &old_call_body.body,\n                        &new_call_body.body,\n                        file_name.clone(),\n                    );\n\n                    // If no result is returned, we can't hotreload this file and need to keep the old file\n                    let Some(results) = results else {\n                        needs_full_rebuild = true;\n                        break;\n                    };\n\n                    // Only send down templates that have roots, and ideally ones that have changed\n                    // todo(jon): maybe cache these and don't send them down if they're the same\n                    for (index, template) in results.templates {\n                        if template.roots.is_empty() {\n                            continue;\n                        }\n\n                        // Create the key we're going to use to identify this template\n                        let key = TemplateGlobalKey {\n                            file: file_name.clone(),\n                            line: old_start.line,\n                            column: old_start.column + 1,\n                            index,\n                        };\n\n                        // if the template is the same, don't send its\n                        if cached_file.templates.get(&key) == Some(&template) {\n                            continue;\n                        };\n\n                        cached_file.templates.insert(key.clone(), template.clone());\n                        templates.push(HotReloadTemplateWithLocation { template, key });\n                    }\n                }\n            }\n\n            // If it's not a rust file, then it might be depended on via include! or similar\n            if ext != \"rs\" {\n                if let Some(artifacts) = self.client.artifacts.as_ref() {\n                    if artifacts.depinfo.files.contains(path) {\n                        needs_full_rebuild = true;\n                        break;\n                    }\n                }\n            }\n        }\n\n        // If the client is in a failed state, any changes to rsx should trigger a rebuild/hotpatch\n        if self.client.stage == BuildStage::Failed && !templates.is_empty() {\n            needs_full_rebuild = true\n        }\n\n        // todo - we need to distinguish between hotpatchable rebuilds and true full rebuilds.\n        //        A full rebuild is required when the user modifies static initializers which we haven't wired up yet.\n        if needs_full_rebuild && self.automatic_rebuilds {\n            if self.use_hotpatch_engine {\n                let changed_crates = self.order_changed_crates(files);\n\n                self.client\n                    .patch_rebuild(files.to_vec(), changed_crates.clone(), BuildId::PRIMARY);\n\n                if let Some(server) = self.server.as_mut() {\n                    server.patch_rebuild(files.to_vec(), changed_crates, BuildId::SECONDARY);\n                }\n                self.clear_hot_reload_changes();\n                self.clear_cached_rsx();\n                server.send_patch_start().await;\n            } else {\n                self.client\n                    .start_rebuild(BuildMode::Base { run: true }, BuildId::PRIMARY);\n                if let Some(server) = self.server.as_mut() {\n                    server.start_rebuild(BuildMode::Base { run: true }, BuildId::SECONDARY);\n                }\n                self.clear_hot_reload_changes();\n                self.clear_cached_rsx();\n                server.send_reload_start().await;\n            }\n        } else {\n            let msg = HotReloadMsg {\n                templates,\n                assets,\n                ms_elapsed: 0,\n                jump_table: Default::default(),\n                for_build_id: None,\n                for_pid: None,\n            };\n\n            self.add_hot_reload_message(&msg);\n\n            let file = files[0].display().to_string();\n            let file =\n                file.trim_start_matches(&self.client.build.crate_dir().display().to_string());\n\n            if needs_full_rebuild && !self.automatic_rebuilds {\n                use crate::styles::NOTE_STYLE;\n                tracing::warn!(\n                    \"Ignoring full rebuild for: {NOTE_STYLE}{}{NOTE_STYLE:#}\",\n                    file\n                );\n            }\n\n            // Only send a hotreload message for templates and assets - otherwise we'll just get a full rebuild\n            //\n            // todo: move the android file uploading out of hotreload_bundled_asset and\n            //\n            // Also make sure the builder isn't busy since that might cause issues with hotreloads\n            // https://github.com/DioxusLabs/dioxus/issues/3361\n            if !msg.is_empty() && self.client.can_receive_hotreloads() {\n                use crate::styles::NOTE_STYLE;\n                tracing::info!(dx_src = ?TraceSrc::Dev, \"Hotreloading: {NOTE_STYLE}{}{NOTE_STYLE:#}\", file);\n\n                if !server.has_hotreload_sockets() && self.client.build.bundle != BundleFormat::Web\n                {\n                    tracing::warn!(\"No clients to hotreload - try reloading the app!\");\n                }\n\n                server.send_hotreload(msg).await;\n            } else {\n                tracing::debug!(dx_src = ?TraceSrc::Dev, \"Ignoring file change: {}\", file);\n            }\n        }\n    }\n\n    /// Finally \"bundle\" this app and return a handle to it\n    pub(crate) async fn open(\n        &mut self,\n        artifacts: &BuildArtifacts,\n        devserver: &mut WebServer,\n    ) -> Result<()> {\n        // Make sure to save artifacts regardless of if we're opening the app or not\n        match artifacts.build_id {\n            BuildId::PRIMARY => self.client.artifacts = Some(artifacts.clone()),\n            BuildId::SECONDARY => {\n                if let Some(server) = self.server.as_mut() {\n                    server.artifacts = Some(artifacts.clone());\n                }\n            }\n            _ => {}\n        }\n\n        let should_open = self.client.stage == BuildStage::Success\n            && (self.server.as_ref().map(|s| s.stage == BuildStage::Success)).unwrap_or(true);\n\n        use crate::cli::styles::GLOW_STYLE;\n\n        if should_open {\n            let time_taken = artifacts\n                .time_end\n                .duration_since(artifacts.time_start)\n                .unwrap();\n\n            if self.client.builds_opened == 0 {\n                tracing::info!(\n                    \"Build completed successfully in {GLOW_STYLE}{}{GLOW_STYLE:#}, launching app! 💫\",\n                    format_duration_ms(time_taken)\n                );\n            } else {\n                tracing::info!(\n                    \"Build completed in {GLOW_STYLE}{}{GLOW_STYLE:#}\",\n                    format_duration_ms(time_taken)\n                );\n            }\n\n            let open_browser = self.client.builds_opened == 0 && self.open_browser;\n            self.open_all(devserver, open_browser).await?;\n\n            // Give a second for the server to boot\n            tokio::time::sleep(Duration::from_millis(300)).await;\n\n            // Update the screen + devserver with the new handle info\n            devserver.send_reload_command().await\n        }\n\n        Ok(())\n    }\n\n    /// Open an existing app bundle, if it exists\n    ///\n    /// Will attempt to open the server and client together, in a coordinated way such that the server\n    /// opens first, initializes, and then the client opens.\n    ///\n    /// There's a number of issues we need to be careful to work around:\n    /// - The server failing to boot or crashing on startup (and entering a boot loop)\n    /// -\n    pub(crate) async fn open_all(\n        &mut self,\n        devserver: &WebServer,\n        open_browser: bool,\n    ) -> Result<()> {\n        let devserver_ip = devserver.devserver_address();\n        let fullstack_address = devserver.proxied_server_address();\n        let displayed_address = devserver.displayed_address();\n\n        // Always open the server first after the client has been built\n        // Only open the server if it isn't prerendered and finished building\n        if let Some(server) = self.server.as_mut().filter(|_| !self.ssg) {\n            if server.stage < BuildStage::Success {\n                tracing::trace!(\"Skipping server open: will open once build completes\");\n            } else {\n                tracing::debug!(\"Opening server build\");\n                server.soft_kill().await;\n                server\n                    .open(\n                        devserver_ip,\n                        displayed_address,\n                        fullstack_address,\n                        false,\n                        false,\n                        BuildId::SECONDARY,\n                        &self.server_args,\n                    )\n                    .await?;\n            }\n        }\n\n        // Skip opening native client if still building (web can open anytime)\n        if self.client.build.bundle != BundleFormat::Web && self.client.stage < BuildStage::Success\n        {\n            tracing::trace!(\"Skipping client open: will open once build completes\");\n            return Ok(());\n        }\n\n        // Start the new app before we kill the old one to give it a little bit of time\n        self.client.soft_kill().await;\n        self.client\n            .open(\n                devserver_ip,\n                displayed_address,\n                fullstack_address,\n                open_browser,\n                self.always_on_top,\n                BuildId::PRIMARY,\n                &self.client_args,\n            )\n            .await?;\n\n        Ok(())\n    }\n\n    /// Shutdown all the running processes\n    pub(crate) async fn shutdown(&mut self) -> Result<()> {\n        self.client.soft_kill().await;\n\n        if let Some(server) = self.server.as_mut() {\n            server.soft_kill().await;\n        }\n\n        // If the client is running on Android, we need to remove the port forwarding\n        // todo: use the android tools \"adb\"\n        if matches!(self.client.build.bundle, BundleFormat::Android) {\n            if let Err(err) = Command::new(&self.workspace.android_tools()?.adb)\n                .arg(\"reverse\")\n                .arg(\"--remove\")\n                .arg(format!(\"tcp:{}\", self.devserver_port))\n                .output()\n                .await\n            {\n                tracing::error!(\n                    \"failed to remove forwarded port {}: {err}\",\n                    self.devserver_port\n                );\n            }\n        }\n\n        // force the tailwind watcher to stop - if we don't, it eats our stdin\n        self.tw_watcher.abort();\n\n        Ok(())\n    }\n\n    /// Perform a full rebuild of the app, equivalent to `cargo rustc` from scratch with no incremental\n    /// hot-patch engine integration.\n    pub(crate) async fn full_rebuild(&mut self) {\n        let build_mode = match self.use_hotpatch_engine {\n            true => BuildMode::Fat,\n            false => BuildMode::Base { run: true },\n        };\n\n        self.client\n            .start_rebuild(build_mode.clone(), BuildId::PRIMARY);\n        if let Some(s) = self.server.as_mut() {\n            s.start_rebuild(build_mode, BuildId::SECONDARY);\n        }\n\n        self.clear_hot_reload_changes();\n        self.clear_cached_rsx();\n        self.clear_patches();\n    }\n\n    pub(crate) async fn hotpatch(\n        &mut self,\n        bundle: &BuildArtifacts,\n        id: BuildId,\n        cache: &HotpatchModuleCache,\n        devserver: &mut WebServer,\n    ) -> Result<()> {\n        let elapsed = bundle\n            .time_end\n            .duration_since(bundle.time_start)\n            .unwrap_or_default();\n\n        let jump_table = match id {\n            BuildId::PRIMARY => self.client.hotpatch(bundle, cache).await,\n            BuildId::SECONDARY => {\n                self.server\n                    .as_mut()\n                    .context(\"Server not found\")?\n                    .hotpatch(bundle, cache)\n                    .await\n            }\n            _ => bail!(\"Invalid build id\"),\n        }?;\n\n        if id == BuildId::PRIMARY {\n            self.applied_client_hot_reload_message.jump_table = self.client.patches.last().cloned();\n        }\n\n        // If no server, just send the patch immediately\n        let Some(server) = self.server.as_mut() else {\n            devserver\n                .send_patch(jump_table, elapsed, id, self.client.pid)\n                .await;\n            return Ok(());\n        };\n\n        // If we have a server, we need to wait until both the client and server are ready\n        // Otherwise we end up with an annoying race condition where the client can't actually load the patch\n        if self.client.stage == BuildStage::Success && server.stage == BuildStage::Success {\n            let client_jump_table = self\n                .client\n                .patches\n                .last()\n                .cloned()\n                .context(\"Missing client jump table\")?;\n\n            let server_jump_table = server\n                .patches\n                .last()\n                .cloned()\n                .context(\"Missing server jump table\")?;\n\n            devserver\n                .send_patch(server_jump_table, elapsed, BuildId::SECONDARY, server.pid)\n                .await;\n\n            devserver\n                .send_patch(\n                    client_jump_table,\n                    elapsed,\n                    BuildId::PRIMARY,\n                    self.client.pid,\n                )\n                .await;\n        }\n\n        Ok(())\n    }\n\n    pub(crate) fn get_build(&self, id: BuildId) -> Option<&AppBuilder> {\n        match id {\n            BuildId::PRIMARY => Some(&self.client),\n            BuildId::SECONDARY => self.server.as_ref(),\n            _ => None,\n        }\n    }\n\n    pub(crate) fn client(&self) -> &AppBuilder {\n        &self.client\n    }\n\n    /// The name of the app being served, to display\n    pub(crate) fn app_name(&self) -> &str {\n        self.client.build.executable_name()\n    }\n\n    /// Get any hot reload changes that have been applied since the last full rebuild\n    pub(crate) fn applied_hot_reload_changes(&mut self, build: BuildId) -> HotReloadMsg {\n        let mut msg = self.applied_client_hot_reload_message.clone();\n\n        if build == BuildId::PRIMARY {\n            msg.jump_table = self.client.patches.last().cloned();\n            msg.for_build_id = Some(BuildId::PRIMARY.0 as _);\n            if let Some(lib) = msg.jump_table.as_mut() {\n                lib.lib = PathBuf::from(\"/\").join(lib.lib.clone());\n            }\n        }\n\n        if build == BuildId::SECONDARY {\n            if let Some(server) = self.server.as_mut() {\n                msg.jump_table = server.patches.last().cloned();\n                msg.for_build_id = Some(BuildId::SECONDARY.0 as _);\n            }\n        }\n\n        msg\n    }\n\n    /// Clear the hot reload changes. This should be called any time a new build is starting\n    pub(crate) fn clear_hot_reload_changes(&mut self) {\n        self.applied_client_hot_reload_message = Default::default();\n    }\n\n    pub(crate) fn clear_patches(&mut self) {\n        self.client.patches.clear();\n        if let Some(server) = self.server.as_mut() {\n            server.patches.clear();\n        }\n    }\n\n    pub(crate) async fn client_connected(\n        &mut self,\n        build_id: BuildId,\n        aslr_reference: Option<u64>,\n        pid: Option<u32>,\n    ) {\n        match build_id {\n            BuildId::PRIMARY => {\n                // multiple tabs on web can cause this to be called incorrectly, and it doesn't\n                // make any sense anyways\n                if self.client.build.bundle != BundleFormat::Web {\n                    if let Some(aslr_reference) = aslr_reference {\n                        self.client.aslr_reference = Some(aslr_reference);\n                    }\n                    if let Some(pid) = pid {\n                        self.client.pid = Some(pid);\n                    }\n                }\n            }\n            BuildId::SECONDARY => {\n                if let Some(server) = self.server.as_mut() {\n                    server.aslr_reference = aslr_reference;\n                }\n            }\n            _ => {}\n        }\n\n        // Assign the runtime asset dir to the runner\n        if self.client.build.bundle == BundleFormat::Ios {\n            // xcrun simctl get_app_container booted com.dioxuslabs\n            let res = Command::new(\"xcrun\")\n                .arg(\"simctl\")\n                .arg(\"get_app_container\")\n                .arg(\"booted\")\n                .arg(self.client.build.bundle_identifier())\n                .output()\n                .await;\n\n            if let Ok(res) = res {\n                tracing::trace!(\"Using runtime asset dir: {:?}\", res);\n\n                if let Ok(out) = String::from_utf8(res.stdout) {\n                    let out = out.trim();\n\n                    tracing::trace!(\"Setting Runtime asset dir: {out:?}\");\n                    self.client.runtime_asset_dir = Some(PathBuf::from(out));\n                }\n            }\n        }\n    }\n\n    /// Store the hot reload changes for any future clients that connect\n    fn add_hot_reload_message(&mut self, msg: &HotReloadMsg) {\n        let applied = &mut self.applied_client_hot_reload_message;\n\n        // Merge the assets, unknown files, and templates\n        // We keep the newer change if there is both a old and new change\n        let mut templates: HashMap<TemplateGlobalKey, _> = std::mem::take(&mut applied.templates)\n            .into_iter()\n            .map(|template| (template.key.clone(), template))\n            .collect();\n        let mut assets: HashSet<PathBuf> =\n            std::mem::take(&mut applied.assets).into_iter().collect();\n        for template in &msg.templates {\n            templates.insert(template.key.clone(), template.clone());\n        }\n        assets.extend(msg.assets.iter().cloned());\n        applied.templates = templates.into_values().collect();\n        applied.assets = assets.into_iter().collect();\n        applied.jump_table = self.client.patches.last().cloned();\n    }\n\n    /// Register the files from the workspace into our file watcher.\n    ///\n    /// This very simply looks for all Rust files in the workspace and adds them to the filemap.\n    ///\n    /// Once the builds complete we'll use the depinfo files to get the actual files that are used,\n    /// making our watcher more accurate. Filling the filemap here is intended to catch any file changes\n    /// in between the first build and the depinfo file being generated.\n    ///\n    /// We don't want watch any registry files since that generally causes a huge performance hit -\n    /// we mostly just care about workspace files and local dependencies.\n    ///\n    /// Dep-info file background:\n    /// <https://doc.rust-lang.org/stable/nightly-rustc/cargo/core/compiler/fingerprint/index.html#dep-info-files>\n    fn load_rsx_filemap(&mut self) {\n        self.fill_filemap_from_krate(self.client.build.crate_dir());\n\n        if let Some(server) = self.server.as_ref() {\n            self.fill_filemap_from_krate(server.build.crate_dir());\n        }\n\n        for krate in self.all_watched_crates() {\n            self.fill_filemap_from_krate(krate);\n        }\n    }\n\n    /// Fill the filemap with files from the filesystem, using the given filter to determine which files to include.\n    ///\n    /// You can use the filter with something like a gitignore to only include files that are relevant to your project.\n    /// We'll walk the filesystem from the given path and recursively search for all files that match the filter.\n    ///\n    /// The filter function takes a path and returns true if the file should be included in the filemap.\n    /// Generally this will only be .rs files\n    ///\n    /// If a file couldn't be parsed, we don't fail. Instead, we save the error.\n    ///\n    /// todo: There are known bugs here when handling gitignores.\n    fn fill_filemap_from_krate(&mut self, crate_dir: PathBuf) {\n        for entry in walkdir::WalkDir::new(crate_dir).into_iter().flatten() {\n            if self\n                .workspace\n                .ignore\n                .matched(entry.path(), entry.file_type().is_dir())\n                .is_ignore()\n            {\n                continue;\n            }\n\n            let path = entry.path();\n            if path.extension().and_then(|s| s.to_str()) == Some(\"rs\") {\n                if let Ok(contents) = std::fs::read_to_string(path) {\n                    self.file_map.insert(\n                        path.to_path_buf(),\n                        CachedFile {\n                            contents,\n                            most_recent: None,\n                            templates: Default::default(),\n                        },\n                    );\n                }\n            }\n        }\n    }\n\n    /// Commit the changes to the filemap, overwriting the contents of the files\n    ///\n    /// Removes any cached templates and replaces the contents of the files with the most recent\n    ///\n    /// todo: we should-reparse the contents so we never send a new version, ever\n    fn clear_cached_rsx(&mut self) {\n        for cached_file in self.file_map.values_mut() {\n            if let Some(most_recent) = cached_file.most_recent.take() {\n                cached_file.contents = most_recent;\n            }\n            cached_file.templates.clear();\n        }\n    }\n\n    fn watch_filesystem(&mut self) {\n        // Watch the folders of the crates that we're interested in\n        for path in self.watch_paths(\n            self.client.build.crate_dir(),\n            self.client.build.crate_package,\n        ) {\n            tracing::trace!(\"Watching path {path:?}\");\n\n            if let Err(err) = self.watcher.watch(&path, RecursiveMode::Recursive) {\n                handle_notify_error(err);\n            }\n        }\n\n        if let Some(server) = self.server.as_ref() {\n            // Watch the server's crate directory as well\n            for path in self.watch_paths(server.build.crate_dir(), server.build.crate_package) {\n                tracing::trace!(\"Watching path {path:?}\");\n\n                if let Err(err) = self.watcher.watch(&path, RecursiveMode::Recursive) {\n                    handle_notify_error(err);\n                }\n            }\n        }\n\n        // Also watch the crates themselves, but not recursively, such that we can pick up new folders\n        for krate in self.all_watched_crates() {\n            tracing::trace!(\"Watching path {krate:?}\");\n            if let Err(err) = self.watcher.watch(&krate, RecursiveMode::NonRecursive) {\n                handle_notify_error(err);\n            }\n        }\n\n        // Also watch the workspace dir, non recursively, such that we can pick up new folders there too\n        if let Err(err) = self.watcher.watch(\n            self.workspace.krates.workspace_root().as_std_path(),\n            RecursiveMode::NonRecursive,\n        ) {\n            handle_notify_error(err);\n        }\n    }\n\n    /// Return the list of paths that we should watch for changes.\n    fn watch_paths(&self, crate_dir: PathBuf, crate_package: NodeId) -> Vec<PathBuf> {\n        let mut watched_paths = vec![];\n\n        // Get a list of *all* the crates with Rust code that we need to watch.\n        // This will end up being dependencies in the workspace and non-workspace dependencies on the user's computer.\n        let mut watched_crates = self.local_dependencies(crate_package);\n        watched_crates.push(crate_dir);\n\n        // Watch the `public` directory if this is the client crate\n        if self.client.build.crate_package == crate_package {\n            if let Some(public_dir) = self.client.build.user_public_dir() {\n                if public_dir.exists() {\n                    watched_paths.push(public_dir);\n                }\n            }\n        }\n\n        // Now, watch all the folders in the crates, but respecting their respective ignore files\n        for krate_root in watched_crates {\n            // Build the ignore builder for this crate, but with our default ignore list as well\n            let ignore = self.workspace.ignore_for_krate(&krate_root);\n\n            for entry in krate_root.read_dir().into_iter().flatten() {\n                let Ok(entry) = entry else {\n                    continue;\n                };\n\n                if ignore\n                    .matched(entry.path(), entry.path().is_dir())\n                    .is_ignore()\n                {\n                    continue;\n                }\n\n                watched_paths.push(entry.path().to_path_buf());\n            }\n        }\n\n        watched_paths.dedup();\n\n        watched_paths\n    }\n\n    /// Get all the Manifest paths for dependencies that we should watch. Will not return anything\n    /// in the `.cargo` folder - only local dependencies will be watched.\n    ///\n    /// This returns a list of manifest paths\n    ///\n    /// Extend the watch path to include:\n    ///\n    /// - the assets directory - this is so we can hotreload CSS and other assets by default\n    /// - the Cargo.toml file - this is so we can hotreload the project if the user changes dependencies\n    /// - the Dioxus.toml file - this is so we can hotreload the project if the user changes the Dioxus config\n    fn local_dependencies(&self, crate_package: NodeId) -> Vec<PathBuf> {\n        let mut paths = vec![];\n\n        for (dependency, _edge) in self.workspace.krates.get_deps(crate_package) {\n            let krate = match dependency {\n                krates::Node::Krate { krate, .. } => krate,\n                krates::Node::Feature { krate_index, .. } => {\n                    &self.workspace.krates[krate_index.index()]\n                }\n            };\n\n            if krate\n                .manifest_path\n                .components()\n                .any(|c| c.as_str() == \".cargo\")\n            {\n                continue;\n            }\n\n            paths.push(\n                krate\n                    .manifest_path\n                    .parent()\n                    .unwrap()\n                    .to_path_buf()\n                    .into_std_path_buf(),\n            );\n        }\n\n        paths\n    }\n\n    // todo: we need to make sure we merge this for all the running packages\n    fn all_watched_crates(&self) -> Vec<PathBuf> {\n        let crate_package = self.client().build.crate_package;\n        let crate_dir = self.client().build.crate_dir();\n\n        let mut krates: Vec<PathBuf> = self\n            .local_dependencies(crate_package)\n            .into_iter()\n            .map(|p| {\n                p.parent()\n                    .expect(\"Local manifest to exist and have a parent\")\n                    .to_path_buf()\n            })\n            .chain(Some(crate_dir))\n            .collect();\n\n        if let Some(server) = self.server.as_ref() {\n            let server_crate_package = server.build.crate_package;\n            let server_crate_dir = server.build.crate_dir();\n\n            let server_krates: Vec<PathBuf> = self\n                .local_dependencies(server_crate_package)\n                .into_iter()\n                .map(|p| {\n                    p.parent()\n                        .expect(\"Server manifest to exist and have a parent\")\n                        .to_path_buf()\n                })\n                .chain(Some(server_crate_dir))\n                .collect();\n            krates.extend(server_krates);\n        }\n\n        krates.dedup();\n\n        krates\n    }\n\n    /// Compute the ordered compilation chain from a changed workspace crate to the tip crate.\n    ///\n    /// Returns crate names (underscore-normalized) in compilation order: the changed crate first,\n    /// then each intermediate workspace crate that depends on it, ending with the tip crate.\n    ///\n    /// Uses BFS from the tip crate through its workspace dependencies to find the path.\n    /// If the changed crate IS the tip crate, returns just `[tip]`.\n    pub(crate) fn workspace_dep_chain(&self, changed_crate: &str) -> Vec<String> {\n        let tip_name = self.client.build.main_target.replace('-', \"_\");\n\n        // If the changed crate is the tip, no chain needed\n        if changed_crate == tip_name {\n            return vec![tip_name];\n        }\n\n        // Build a map of workspace crate names to their krates NodeIds\n        let mut name_to_node: HashMap<String, NodeId> = HashMap::new();\n        for member in self.workspace.krates.workspace_members() {\n            if let krates::Node::Krate { id, krate, .. } = member {\n                let normalized = krate.name.replace('-', \"_\");\n                name_to_node.insert(normalized, self.workspace.krates.nid_for_kid(id).unwrap());\n            }\n        }\n\n        // BFS/DFS from tip through workspace deps to find path to changed crate.\n        // We walk the dependency edges (tip → its deps → their deps → ...) looking for changed_crate.\n        let Some(&tip_node) = name_to_node.get(&tip_name) else {\n            return vec![changed_crate.to_string()];\n        };\n\n        // parent[node] = the workspace crate that depends on it (closer to tip)\n        let mut parent: HashMap<NodeId, Option<NodeId>> = HashMap::new();\n        parent.insert(tip_node, None);\n        let mut queue = VecDeque::new();\n        queue.push_back(tip_node);\n\n        let mut target_node = None;\n\n        while let Some(current) = queue.pop_front() {\n            for (dep, _edge) in self.workspace.krates.get_deps(current) {\n                let (dep_name, dep_nid) = match dep {\n                    krates::Node::Krate { id, krate, .. } => {\n                        let normalized = krate.name.replace('-', \"_\");\n                        let nid = self.workspace.krates.nid_for_kid(id).unwrap();\n                        (normalized, nid)\n                    }\n                    _ => continue,\n                };\n\n                // Only traverse workspace members\n                if !name_to_node.contains_key(&dep_name) {\n                    continue;\n                }\n\n                if parent.contains_key(&dep_nid) {\n                    continue; // already visited\n                }\n\n                parent.insert(dep_nid, Some(current));\n\n                if dep_name == changed_crate {\n                    target_node = Some(dep_nid);\n                    break;\n                }\n\n                queue.push_back(dep_nid);\n            }\n\n            if target_node.is_some() {\n                break;\n            }\n        }\n\n        // Reconstruct the path from changed_crate → ... → tip\n        let Some(target) = target_node else {\n            // Changed crate not found in workspace dep graph — just compile it alone\n            return vec![changed_crate.to_string()];\n        };\n\n        let mut chain = vec![];\n        let mut node = target;\n        loop {\n            // Find the crate name for this node\n            let krate = &self.workspace.krates[node];\n            chain.push(krate.name.replace('-', \"_\"));\n\n            match parent.get(&node) {\n                Some(Some(parent_node)) => node = *parent_node,\n                _ => break,\n            }\n        }\n\n        chain\n    }\n\n    /// Order a set of changed workspace crates so that deeper dependencies compile first.\n    ///\n    /// Uses `workspace_dep_chain` to determine the depth of each crate in the dependency graph,\n    /// then sorts so that leaves (deepest deps) compile before crates closer to the tip.\n    fn order_changed_crates(&self, files: &[PathBuf]) -> Vec<String> {\n        // Determine which workspace crates changed based on the file paths.\n        // Order them so deeper deps compile first (leaves before dependents).\n        let changed_set: HashSet<String> = files\n            .iter()\n            .filter_map(|f| self.file_to_workspace_crate(f))\n            .collect();\n\n        let mut crates_with_depth: Vec<_> = changed_set\n            .iter()\n            .map(|c| (c.clone(), self.workspace_dep_chain(c).len()))\n            .collect();\n\n        // Longer chain = deeper in dep tree = should compile first\n        crates_with_depth.sort_by(|a, b| b.1.cmp(&a.1));\n        crates_with_depth.into_iter().map(|(c, _)| c).collect()\n    }\n\n    /// Map a changed file path to the workspace crate it belongs to.\n    ///\n    /// Returns the crate name in rustc convention (hyphens → underscores), matching the\n    /// `--crate-name` arg used by rustc and the keys in `workspace_rustc_args`.\n    ///\n    /// Finds the workspace member whose crate directory is the longest prefix of the file path.\n    fn file_to_workspace_crate(&self, file: &Path) -> Option<String> {\n        let mut best_match: Option<(String, usize)> = None;\n\n        for member in self.workspace.krates.workspace_members() {\n            if let krates::Node::Krate { krate, .. } = member {\n                let Some(crate_dir) = krate.manifest_path.parent() else {\n                    continue;\n                };\n                if let Ok(relative) = file.strip_prefix(crate_dir.as_std_path()) {\n                    let depth = relative.components().count();\n                    let is_better = best_match\n                        .as_ref()\n                        .map_or(true, |(_, best_depth)| depth < *best_depth);\n                    if is_better {\n                        best_match = Some((krate.name.replace('-', \"_\"), depth));\n                    }\n                }\n            }\n        }\n\n        best_match.map(|(name, _)| name)\n    }\n\n    /// Check if this is a fullstack build. This means that there is an additional build with the `server` platform.\n    pub(crate) fn is_fullstack(&self) -> bool {\n        self.fullstack\n    }\n\n    /// Return a number between 0 and 1 representing the progress of the server build\n    pub(crate) fn server_compile_progress(&self) -> f64 {\n        let Some(server) = self.server.as_ref() else {\n            return 0.0;\n        };\n\n        server.compiled_crates as f64 / server.expected_crates as f64\n    }\n\n    pub(crate) async fn open_debugger(&mut self, dev: &WebServer, build: BuildId) {\n        if self.use_hotpatch_engine {\n            tracing::warn!(\"Debugging symbols might not work properly with hotpatching enabled. Consider disabling hotpatching for debugging.\");\n        }\n\n        match build {\n            BuildId::PRIMARY => {\n                _ = self.client.open_debugger(dev).await;\n            }\n            BuildId::SECONDARY => {\n                if let Some(server) = self.server.as_mut() {\n                    _ = server.open_debugger(dev).await;\n                }\n            }\n            _ => {}\n        }\n    }\n}\n\n/// Bind a listener to any point and return it\n/// When the listener is dropped, the socket will be closed, but we'll still have a port that we\n/// can bind our proxy to.\n///\n/// Todo: we might want to do this on every new build in case the OS tries to bind things to this port\n/// and we don't already have something bound to it. There's no great way of \"reserving\" a port.\nfn get_available_port(address: IpAddr, prefer: Option<u16>) -> Option<u16> {\n    // First, try to bind to the preferred port\n    if let Some(port) = prefer {\n        if let Ok(_listener) = TcpListener::bind((address, port)) {\n            return Some(port);\n        }\n    }\n\n    // Otherwise, try to bind to any port and return the first one we can\n    TcpListener::bind((address, 0))\n        .and_then(|listener| listener.local_addr().map(|f| f.port()))\n        .ok()\n}\n\nfn create_notify_watcher(\n    tx: UnboundedSender<notify::Event>,\n    wsl_poll_interval: u64,\n) -> Box<dyn NotifyWatcher> {\n    // Build the event handler for notify.\n    // This has been known to be a source of many problems, unfortunately, since notify handling seems to be flakey across platforms\n    let handler = move |info: notify::Result<notify::Event>| {\n        let Ok(event) = info else {\n            return;\n        };\n\n        let is_allowed_notify_event = match event.kind {\n            EventKind::Modify(ModifyKind::Data(_)) => true,\n            EventKind::Modify(ModifyKind::Name(_)) => true,\n            // The primary modification event on WSL's poll watcher.\n            EventKind::Modify(ModifyKind::Metadata(MetadataKind::WriteTime)) => true,\n            // Catch-all for unknown event types (windows)\n            EventKind::Modify(ModifyKind::Any) => true,\n            EventKind::Modify(ModifyKind::Metadata(_)) => false,\n            // Don't care about anything else.\n            EventKind::Create(_) => true,\n            EventKind::Remove(_) => true,\n            _ => false,\n        };\n\n        if is_allowed_notify_event {\n            _ = tx.unbounded_send(event);\n        }\n    };\n\n    const NOTIFY_ERROR_MSG: &str = \"Failed to create file watcher.\\nEnsure you have the required permissions to watch the specified directories.\";\n\n    // On wsl, we need to poll the filesystem for changes\n    if is_wsl() {\n        return Box::new(\n            notify::PollWatcher::new(\n                handler,\n                Config::default().with_poll_interval(Duration::from_secs(wsl_poll_interval)),\n            )\n            .expect(NOTIFY_ERROR_MSG),\n        );\n    }\n\n    // Otherwise we can use the recommended watcher\n    Box::new(notify::recommended_watcher(handler).expect(NOTIFY_ERROR_MSG))\n}\n\nfn handle_notify_error(err: notify::Error) {\n    tracing::debug!(\"Failed to watch path: {}\", err);\n    match err.kind {\n        notify::ErrorKind::Io(error) if error.kind() == std::io::ErrorKind::PermissionDenied => {\n            tracing::error!(\"Failed to watch path: permission denied. {:?}\", err.paths)\n        }\n        notify::ErrorKind::MaxFilesWatch => {\n            tracing::error!(\"Failed to set up file watcher: too many files to watch\")\n        }\n        _ => {}\n    }\n}\n\n/// Detects if `dx` is being ran in a WSL environment.\n///\n/// We determine this based on whether the keyword `microsoft` or `wsl` is contained within the `WSL_1` or `WSL_2` files.\n/// This may fail in the future as it isn't guaranteed by Microsoft.\n/// See <https://github.com/microsoft/WSL/issues/423#issuecomment-221627364>\nfn is_wsl() -> bool {\n    const WSL_1: &str = \"/proc/sys/kernel/osrelease\";\n    const WSL_2: &str = \"/proc/version\";\n    const WSL_KEYWORDS: [&str; 2] = [\"microsoft\", \"wsl\"];\n\n    // Test 1st File\n    if let Ok(content) = std::fs::read_to_string(WSL_1) {\n        let lowercase = content.to_lowercase();\n        for keyword in WSL_KEYWORDS {\n            if lowercase.contains(keyword) {\n                return true;\n            }\n        }\n    }\n\n    // Test 2nd File\n    if let Ok(content) = std::fs::read_to_string(WSL_2) {\n        let lowercase = content.to_lowercase();\n        for keyword in WSL_KEYWORDS {\n            if lowercase.contains(keyword) {\n                return true;\n            }\n        }\n    }\n\n    false\n}\n\n/// Format a Duration for human-readable output.\nfn format_duration_ms(d: Duration) -> String {\n    let total_ms = d.as_millis() as u64;\n\n    if total_ms < 1000 {\n        format!(\"{total_ms}ms\")\n    } else {\n        let secs = total_ms as f64 / 1000.0;\n        format!(\"{secs:.2}s\")\n    }\n}\n"
  },
  {
    "path": "packages/cli/src/serve/server.rs",
    "content": "use crate::{\n    config::WebHttpsConfig, serve::ServeUpdate, BuildId, BuildStage, BuilderUpdate, BundleFormat,\n    Result, TraceSrc,\n};\nuse anyhow::{bail, Context};\nuse axum::{\n    body::Body,\n    extract::{\n        ws::{Message, WebSocket},\n        Query, Request, State, WebSocketUpgrade,\n    },\n    http::{\n        header::{HeaderName, HeaderValue, CACHE_CONTROL, EXPIRES, PRAGMA},\n        Method, Response, StatusCode,\n    },\n    middleware::{self, Next},\n    response::IntoResponse,\n    routing::{get, get_service},\n    Extension, Router,\n};\nuse dioxus_devtools_types::{DevserverMsg, HotReloadMsg};\nuse futures_channel::mpsc::{UnboundedReceiver, UnboundedSender};\nuse futures_util::{\n    future,\n    stream::{self, FuturesUnordered},\n    StreamExt,\n};\nuse hyper::HeaderMap;\nuse rustls::crypto::{aws_lc_rs::default_provider, CryptoProvider};\nuse serde::{Deserialize, Serialize};\nuse std::{\n    convert::Infallible,\n    fs, io,\n    net::{IpAddr, Ipv4Addr, SocketAddr, TcpListener},\n    path::Path,\n    sync::{Arc, RwLock},\n    time::Duration,\n};\nuse subsecond_types::JumpTable;\nuse tokio::process::Command;\nuse tower_http::{\n    cors::Any,\n    services::fs::{ServeDir, ServeFileSystemResponseBody},\n    ServiceBuilderExt,\n};\n\nuse super::AppServer;\n\n/// The webserver that serves statics assets (if fullstack isn't already doing that) and the websocket\n/// communication layer that we use to send status updates and hotreloads to the client.\n///\n/// todo(jon): we should merge the build status and hotreload sockets into just a \"devtools\" socket\n/// which carries all the message types. This would make it easier for us to add more message types\n/// and better tooling on the pages that we serve.\npub(crate) struct WebServer {\n    devserver_exposed_ip: IpAddr,\n    devserver_bind_ip: IpAddr,\n    devserver_port: u16,\n    proxied_port: Option<u16>,\n    hot_reload_sockets: Vec<ConnectedWsClient>,\n    build_status_sockets: Vec<ConnectedWsClient>,\n    new_hot_reload_sockets: UnboundedReceiver<ConnectedWsClient>,\n    new_build_status_sockets: UnboundedReceiver<ConnectedWsClient>,\n    build_status: SharedStatus,\n    application_name: String,\n    bundle: BundleFormat,\n}\n\npub(crate) struct ConnectedWsClient {\n    socket: WebSocket,\n    build_id: Option<BuildId>,\n    aslr_reference: Option<u64>,\n    pid: Option<u32>,\n}\n\nimpl WebServer {\n    pub const SELF_IP: IpAddr = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1));\n\n    /// Start the development server.\n    /// This will set up the default http server if there's no server specified (usually via fullstack).\n    ///\n    /// This will also start the websocket server that powers the devtools. If you want to communicate\n    /// with connected devtools clients, this is the place to do it.\n    pub(crate) fn start(runner: &AppServer) -> Result<Self> {\n        let (hot_reload_sockets_tx, hot_reload_sockets_rx) = futures_channel::mpsc::unbounded();\n        let (build_status_sockets_tx, build_status_sockets_rx) = futures_channel::mpsc::unbounded();\n\n        // Create the listener that we'll pass into the devserver, but save its IP here so\n        // we can display it to the user in the tui\n        let devserver_bind_ip = runner.devserver_bind_ip;\n        let devserver_port = runner.devserver_port;\n        let proxied_port = runner.proxied_port;\n        let devserver_exposed_ip = devserver_bind_ip;\n\n        let devserver_bind_address = SocketAddr::new(devserver_bind_ip, devserver_port);\n        let listener = std::net::TcpListener::bind(devserver_bind_address).with_context(|| {\n            anyhow::anyhow!(\n                \"Failed to bind server to: {devserver_bind_address}, is there another devserver running?\\nTo run multiple devservers, use the --port flag to specify a different port\"\n            )\n        })?;\n\n        let proxied_address = proxied_port.map(|port| SocketAddr::new(devserver_exposed_ip, port));\n\n        // Set up the router with some shared state that we'll update later to reflect the current state of the build\n        let build_status = SharedStatus::new_with_starting_build();\n        let router = build_devserver_router(\n            runner,\n            hot_reload_sockets_tx,\n            build_status_sockets_tx,\n            proxied_address,\n            build_status.clone(),\n        )?;\n\n        // And finally, start the server mainloop\n        tokio::spawn(devserver_mainloop(\n            runner.client().build.config.web.https.clone(),\n            listener,\n            router,\n        ));\n\n        Ok(Self {\n            build_status,\n            proxied_port,\n            devserver_bind_ip,\n            devserver_exposed_ip,\n            devserver_port,\n            hot_reload_sockets: Default::default(),\n            build_status_sockets: Default::default(),\n            new_hot_reload_sockets: hot_reload_sockets_rx,\n            new_build_status_sockets: build_status_sockets_rx,\n            application_name: runner.app_name().to_string(),\n            bundle: runner.client.build.bundle,\n        })\n    }\n\n    /// Wait for new clients to be connected and then save them\n    pub(crate) async fn wait(&mut self) -> ServeUpdate {\n        let mut new_hot_reload_socket = self.new_hot_reload_sockets.next();\n        let mut new_build_status_socket = self.new_build_status_sockets.next();\n        let mut new_message = self\n            .hot_reload_sockets\n            .iter_mut()\n            .enumerate()\n            .map(|(idx, socket)| async move { (idx, socket.socket.next().await) })\n            .collect::<FuturesUnordered<_>>();\n\n        tokio::select! {\n            new_hot_reload_socket = &mut new_hot_reload_socket => {\n                if let Some(new_socket) = new_hot_reload_socket {\n                    let aslr_reference = new_socket.aslr_reference;\n                    let pid = new_socket.pid;\n                    let id = new_socket.build_id.unwrap_or(BuildId::PRIMARY);\n\n                    drop(new_message);\n                    self.hot_reload_sockets.push(new_socket);\n\n                    return ServeUpdate::NewConnection { aslr_reference, id, pid };\n                } else {\n                    panic!(\"Could not receive a socket - the devtools could not boot - the port is likely already in use\");\n                }\n            }\n            new_build_status_socket = &mut new_build_status_socket => {\n                if let Some(mut new_socket) = new_build_status_socket {\n                    drop(new_message);\n\n                    // Update the socket with project info and current build status\n                    let project_info = SharedStatus::new(Status::ClientInit { application_name: self.application_name.clone(), bundle: self.bundle });\n                    if project_info.send_to(&mut new_socket.socket).await.is_ok() {\n                        _ = self.build_status.send_to(&mut new_socket.socket).await;\n                        self.build_status_sockets.push(new_socket);\n                    }\n                    return future::pending::<ServeUpdate>().await;\n                } else {\n                    panic!(\"Could not receive a socket - the devtools could not boot - the port is likely already in use\");\n                }\n            }\n            Some((idx, message)) = new_message.next() => {\n                match message {\n                    Some(Ok(msg)) => return ServeUpdate::WsMessage { msg, bundle: BundleFormat::Web },\n                    _ => {\n                        drop(new_message);\n                        _ = self.hot_reload_sockets.remove(idx);\n                    }\n                }\n            }\n        }\n\n        future::pending().await\n    }\n\n    pub(crate) async fn shutdown(&mut self) {\n        self.send_shutdown().await;\n        for mut socket in self.hot_reload_sockets.drain(..) {\n            _ = socket.socket.send(Message::Close(None)).await;\n        }\n    }\n\n    /// Sends the current build status to all clients.\n    async fn send_build_status(&mut self) {\n        let mut i = 0;\n        while i < self.build_status_sockets.len() {\n            let socket = &mut self.build_status_sockets[i];\n            if self.build_status.send_to(&mut socket.socket).await.is_err() {\n                self.build_status_sockets.remove(i);\n            } else {\n                i += 1;\n            }\n        }\n    }\n\n    /// Sends a start build message to all clients.\n    pub(crate) async fn start_build(&mut self) {\n        self.build_status.set(Status::Building {\n            progress: 0.0,\n            build_message: \"Starting the build...\".to_string(),\n        });\n        self.send_build_status().await;\n    }\n\n    /// Sends an updated build status to all clients.\n    pub(crate) async fn new_build_update(&mut self, update: &BuilderUpdate) {\n        match update {\n            BuilderUpdate::Progress { stage } => {\n                // Todo(miles): wire up more messages into the splash screen UI\n                match stage {\n                    BuildStage::Success => {}\n                    BuildStage::Failed => self.send_reload_failed().await,\n                    BuildStage::Restarting => self.send_reload_start().await,\n                    BuildStage::Initializing => {}\n                    BuildStage::InstallingTooling => {}\n                    BuildStage::Compiling {\n                        current,\n                        total,\n                        krate,\n                        ..\n                    } => {\n                        if !matches!(\n                            self.build_status.get(),\n                            Status::Ready | Status::BuildError { .. }\n                        ) {\n                            self.build_status.set(Status::Building {\n                                progress: (*current as f64 / *total as f64).clamp(0.0, 1.0),\n                                build_message: format!(\"{krate} compiling\"),\n                            });\n                            self.send_build_status().await;\n                        }\n                    }\n                    BuildStage::OptimizingWasm => {}\n                    BuildStage::Aborted => {}\n                    BuildStage::CopyingAssets { .. } => {}\n                    _ => {}\n                }\n            }\n            BuilderUpdate::CompilerMessage { .. } => {}\n            BuilderUpdate::BuildReady { .. } => {}\n            BuilderUpdate::BuildFailed { err } => {\n                let error = err.to_string();\n                self.build_status.set(Status::BuildError {\n                    error: ansi_to_html::convert(&error).unwrap_or(error),\n                });\n                self.send_reload_failed().await;\n                self.send_build_status().await;\n            }\n            BuilderUpdate::StdoutReceived { .. } => {}\n            BuilderUpdate::StderrReceived { .. } => {}\n            BuilderUpdate::ProcessExited { .. } => {}\n            BuilderUpdate::ProcessWaitFailed { .. } => {}\n        }\n    }\n\n    pub(crate) fn has_hotreload_sockets(&self) -> bool {\n        !self.hot_reload_sockets.is_empty()\n    }\n\n    /// Sends hot reloadable changes to all clients.\n    pub(crate) async fn send_hotreload(&mut self, reload: HotReloadMsg) {\n        if reload.is_empty() {\n            return;\n        }\n\n        tracing::trace!(\"Sending hotreload to clients {:?}\", reload);\n\n        let msg = DevserverMsg::HotReload(reload);\n        let msg = serde_json::to_string(&msg).unwrap();\n\n        // Send the changes to any connected clients\n        let mut i = 0;\n        while i < self.hot_reload_sockets.len() {\n            let socket = &mut self.hot_reload_sockets[i];\n            if socket\n                .socket\n                .send(Message::Text(msg.clone().into()))\n                .await\n                .is_err()\n            {\n                self.hot_reload_sockets.remove(i);\n            } else {\n                i += 1;\n            }\n        }\n    }\n\n    pub(crate) async fn send_patch(\n        &mut self,\n        jump_table: JumpTable,\n        time_taken: Duration,\n        build: BuildId,\n        for_pid: Option<u32>,\n    ) {\n        let msg = DevserverMsg::HotReload(HotReloadMsg {\n            jump_table: Some(jump_table),\n            ms_elapsed: time_taken.as_millis() as u64,\n            templates: vec![],\n            assets: vec![],\n            for_pid,\n            for_build_id: Some(build.0 as _),\n        });\n        self.send_devserver_message_to_all(msg).await;\n        self.set_ready().await;\n    }\n\n    /// Tells all clients that a hot patch has started.\n    pub(crate) async fn send_patch_start(&mut self) {\n        self.send_devserver_message_to_all(DevserverMsg::HotPatchStart)\n            .await;\n    }\n\n    /// Tells all clients that a full rebuild has started.\n    pub(crate) async fn send_reload_start(&mut self) {\n        self.send_devserver_message_to_all(DevserverMsg::FullReloadStart)\n            .await;\n    }\n\n    /// Tells all clients that a full rebuild has failed.\n    pub(crate) async fn send_reload_failed(&mut self) {\n        self.send_devserver_message_to_all(DevserverMsg::FullReloadFailed)\n            .await;\n    }\n\n    /// Tells all clients to reload if possible for new changes.\n    pub(crate) async fn send_reload_command(&mut self) {\n        self.set_ready().await;\n        self.send_devserver_message_to_all(DevserverMsg::FullReloadCommand)\n            .await;\n    }\n\n    /// Send a shutdown message to all connected clients.\n    pub(crate) async fn send_shutdown(&mut self) {\n        self.send_devserver_message_to_all(DevserverMsg::Shutdown)\n            .await;\n    }\n\n    /// Sends a devserver message to all connected clients.\n    async fn send_devserver_message_to_all(&mut self, msg: DevserverMsg) {\n        for socket in self.hot_reload_sockets.iter_mut() {\n            _ = socket\n                .socket\n                .send(Message::Text(serde_json::to_string(&msg).unwrap().into()))\n                .await;\n        }\n    }\n\n    /// Mark the devserver status as ready and notify listeners.\n    async fn set_ready(&mut self) {\n        if matches!(self.build_status.get(), Status::Ready) {\n            return;\n        }\n\n        self.build_status.set(Status::Ready);\n        self.send_build_status().await;\n    }\n\n    /// Get the address the devserver should run on\n    pub fn devserver_address(&self) -> SocketAddr {\n        SocketAddr::new(self.devserver_exposed_ip, self.devserver_port)\n    }\n\n    // Get the address the server should run on if we're serving the user's server\n    pub fn proxied_server_address(&self) -> Option<SocketAddr> {\n        self.proxied_port\n            .map(|port| SocketAddr::new(self.devserver_exposed_ip, port))\n    }\n\n    pub fn server_address(&self) -> Option<SocketAddr> {\n        match self.bundle {\n            BundleFormat::Web | BundleFormat::Server => Some(self.devserver_address()),\n            _ => self.proxied_server_address(),\n        }\n    }\n\n    /// Get the address the server is running - showing 127.0.0.1 if the devserver is bound to 0.0.0.0\n    /// This is designed this way to not confuse users who expect the devserver to be bound to localhost\n    /// ... which it is, but they don't know that 0.0.0.0 also serves localhost.\n    pub fn displayed_address(&self) -> Option<SocketAddr> {\n        let mut address = self.server_address()?;\n\n        // Set the port to the devserver port since that's usually what people expect\n        address.set_port(self.devserver_port);\n\n        if self.devserver_bind_ip == IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)) {\n            address = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), address.port());\n        }\n\n        Some(address)\n    }\n}\n\nasync fn devserver_mainloop(\n    https_cfg: WebHttpsConfig,\n    listener: TcpListener,\n    router: Router,\n) -> Result<()> {\n    // We have a native listener that we're going to give to tokio, so we need to make it non-blocking\n    let _ = listener.set_nonblocking(true);\n\n    // If we're not using rustls, just use regular axum\n    if https_cfg.enabled != Some(true) {\n        axum::serve(\n            tokio::net::TcpListener::from_std(listener).unwrap(),\n            router.into_make_service(),\n        )\n        .await?;\n        return Ok(());\n    }\n\n    // If we're using rustls, we need to install the provider, get the cert/key paths, and then set up rustls\n    if let Err(provider) = CryptoProvider::install_default(default_provider()) {\n        bail!(\"Failed to install default CryptoProvider: {provider:?}\");\n    }\n    let (cert_path, key_path) = get_rustls(&https_cfg).await?;\n    let rustls = axum_server::tls_rustls::RustlsConfig::from_pem_file(cert_path, key_path).await?;\n\n    axum_server::from_tcp_rustls(listener, rustls)\n        .serve(router.into_make_service())\n        .await?;\n\n    Ok(())\n}\n\n/// Sets up and returns a router\n///\n/// Steps include:\n/// - Setting up cors\n/// - Setting up the proxy to the endpoint specified in the config\n/// - Setting up the file serve service\n/// - Setting up the websocket endpoint for devtools\nfn build_devserver_router(\n    runner: &AppServer,\n    hot_reload_sockets: UnboundedSender<ConnectedWsClient>,\n    build_status_sockets: UnboundedSender<ConnectedWsClient>,\n    fullstack_address: Option<SocketAddr>,\n    build_status: SharedStatus,\n) -> Result<Router> {\n    let mut router = Router::new();\n    let build = runner.client();\n\n    // Setup proxy for the endpoint specified in the config\n    for proxy_config in build.build.config.web.proxy.iter() {\n        router = super::proxy::add_proxy(router, proxy_config)?;\n    }\n\n    // For fullstack, liveview, and server, forward all requests to the inner server\n    if runner.proxied_port.is_some() {\n        tracing::debug!(\"Proxying requests to fullstack server at {fullstack_address:?}\");\n        let address = fullstack_address.context(\"No fullstack address specified\")?;\n        tracing::debug!(\"Proxying requests to fullstack server at {address}\");\n        router = router.fallback_service(super::proxy::proxy_to(\n            format!(\"http://{address}\").parse().unwrap(),\n            true,\n            |error| {\n                Response::builder()\n                    .status(StatusCode::INTERNAL_SERVER_ERROR)\n                    .body(Body::from(format!(\n                        \"Backend connection failed. The backend is likely still starting up. Please try again in a few seconds. Error: {error:#?}\"\n                    )))\n                    .unwrap()\n            },\n        ));\n    } else {\n        // Otherwise, just serve the dir ourselves\n        // Route file service to output the .wasm and assets if this is a web build\n        let base_path = format!(\n            \"/{}\",\n            runner\n                .client()\n                .build\n                .base_path()\n                .unwrap_or_default()\n                .trim_matches('/')\n        );\n        if base_path == \"/\" {\n            router = router.fallback_service(build_serve_dir(runner));\n        } else {\n            router = router.nest_service(&base_path, build_serve_dir(runner));\n        }\n    }\n\n    // Setup middleware to intercept html requests if the build status is \"Building\"\n    router = router.layer(middleware::from_fn_with_state(\n        build_status,\n        build_status_middleware,\n    ));\n\n    #[derive(Deserialize, Debug)]\n    struct ConnectionQuery {\n        aslr_reference: Option<u64>,\n        build_id: Option<BuildId>,\n        pid: Option<u32>,\n    }\n\n    // Setup websocket endpoint - and pass in the extension layer immediately after\n    router = router.nest(\n        \"/_dioxus\",\n        Router::new()\n            .route(\n                \"/\",\n                get(\n                    |ws: WebSocketUpgrade, ext: Extension<UnboundedSender<ConnectedWsClient>>, query: Query<ConnectionQuery>| async move {\n                        tracing::debug!(\"New devtool websocket connection: {:?}\", query);\n                        ws.on_upgrade(move |socket| async move { _ = ext.0.unbounded_send(ConnectedWsClient { socket, aslr_reference: query.aslr_reference, build_id: query.build_id, pid: query.pid }) })\n                    },\n                ),\n            )\n            .layer(Extension(hot_reload_sockets))\n            .route(\n                \"/build_status\",\n                get(\n                    |ws: WebSocketUpgrade, ext: Extension<UnboundedSender<ConnectedWsClient>>| async move {\n                        ws.on_upgrade(move |socket| async move { _ = ext.0.unbounded_send(ConnectedWsClient { socket, aslr_reference: None, build_id: None, pid: None }) })\n                    },\n                ),\n            )\n            .layer(Extension(build_status_sockets)),\n    );\n\n    // Setup cors\n    router = router.layer(\n        tower_http::cors::CorsLayer::new()\n            // allow `GET` and `POST` when accessing the resource\n            .allow_methods([Method::GET, Method::POST])\n            // allow requests from any origin\n            .allow_origin(Any)\n            .allow_headers(Any),\n    );\n\n    Ok(router)\n}\n\nfn build_serve_dir(runner: &AppServer) -> axum::routing::MethodRouter {\n    use tower::ServiceBuilder;\n\n    static CORS_UNSAFE: (HeaderValue, HeaderValue) = (\n        HeaderValue::from_static(\"unsafe-none\"),\n        HeaderValue::from_static(\"unsafe-none\"),\n    );\n\n    static CORS_REQUIRE: (HeaderValue, HeaderValue) = (\n        HeaderValue::from_static(\"require-corp\"),\n        HeaderValue::from_static(\"same-origin\"),\n    );\n\n    let (coep, coop) = match runner.cross_origin_policy {\n        true => CORS_REQUIRE.clone(),\n        false => CORS_UNSAFE.clone(),\n    };\n\n    let app = &runner.client;\n    let cfg = &runner.client.build.config;\n\n    let out_dir = app.build.root_dir();\n    let index_on_404: bool = cfg.web.watcher.index_on_404;\n\n    get_service(\n        ServiceBuilder::new()\n            .override_response_header(\n                HeaderName::from_static(\"cross-origin-embedder-policy\"),\n                coep,\n            )\n            .override_response_header(HeaderName::from_static(\"cross-origin-opener-policy\"), coop)\n            .and_then({\n                let out_dir = out_dir.clone();\n                move |response| async move { Ok(no_cache(index_on_404, &out_dir, response)) }\n            })\n            .service(ServeDir::new(&out_dir)),\n    )\n    .handle_error(|error: Infallible| async move {\n        (\n            StatusCode::INTERNAL_SERVER_ERROR,\n            format!(\"Unhandled internal error: {error}\"),\n        )\n    })\n}\n\nfn no_cache(\n    index_on_404: bool,\n    out_dir: &Path,\n    response: Response<ServeFileSystemResponseBody>,\n) -> Response<Body> {\n    // By default we just decompose into the response\n    let mut response = response.into_response();\n\n    // If there's a 404 and we're supposed to index on 404, upgrade that failed request to the index.html\n    // We might want to isnert a header here saying we *did* that but oh well\n    if response.status() == StatusCode::NOT_FOUND && index_on_404 {\n        let fallback = out_dir.join(\"index.html\");\n        let contents = std::fs::read_to_string(fallback).unwrap_or_else(|_| {\n            String::from(\n                r#\"\n            <!DOCTYPE html>\n            <html>\n                <head>\n                    <title>Err 404 - dx is not serving a web app</title>\n                </head>\n                <body>\n                <p>Err 404 - dioxus is not currently serving a web app</p>\n                </body>\n            </html>\n            \"#,\n            )\n        });\n        let body = Body::from(contents);\n\n        response = Response::builder()\n            .status(StatusCode::OK)\n            .body(body)\n            .unwrap();\n    };\n\n    insert_no_cache_headers(response.headers_mut());\n\n    response\n}\n\npub(crate) fn insert_no_cache_headers(headers: &mut HeaderMap) {\n    headers.insert(CACHE_CONTROL, HeaderValue::from_static(\"no-cache\"));\n    headers.insert(PRAGMA, HeaderValue::from_static(\"no-cache\"));\n    headers.insert(EXPIRES, HeaderValue::from_static(\"0\"));\n}\n\nasync fn get_rustls(web_config: &WebHttpsConfig) -> Result<(String, String)> {\n    // If we're not using mkcert, just use the cert/key paths given to use in the config\n    if !web_config.mkcert.unwrap_or(false) {\n        if let (Some(key), Some(cert)) = (web_config.key_path.clone(), web_config.cert_path.clone())\n        {\n            return Ok((cert, key));\n        } else {\n            bail!(\"https is enabled but cert or key path is missing\");\n        }\n    }\n\n    const DEFAULT_KEY_PATH: &str = \"ssl/key.pem\";\n    const DEFAULT_CERT_PATH: &str = \"ssl/cert.pem\";\n\n    // Get paths to store certs, otherwise use ssl/item.pem\n    let key_path = web_config\n        .key_path\n        .clone()\n        .unwrap_or(DEFAULT_KEY_PATH.to_string());\n\n    let cert_path = web_config\n        .cert_path\n        .clone()\n        .unwrap_or(DEFAULT_CERT_PATH.to_string());\n\n    // Create ssl directory if using defaults\n    if key_path == DEFAULT_KEY_PATH && cert_path == DEFAULT_CERT_PATH {\n        _ = fs::create_dir(\"ssl\");\n    }\n\n    let cmd = Command::new(\"mkcert\")\n        .args([\n            \"-install\",\n            \"-key-file\",\n            &key_path,\n            \"-cert-file\",\n            &cert_path,\n            \"localhost\",\n            \"::1\",\n            \"127.0.0.1\",\n        ])\n        .spawn();\n\n    match cmd {\n        Err(e) => {\n            match e.kind() {\n                io::ErrorKind::NotFound => {\n                    tracing::error!(dx_src = ?TraceSrc::Dev, \"`mkcert` is not installed. See https://github.com/FiloSottile/mkcert#installation for installation instructions.\")\n                }\n                e => {\n                    tracing::error!(dx_src = ?TraceSrc::Dev, \"An error occurred while generating mkcert certificates: {}\", e.to_string())\n                }\n            };\n            bail!(\"failed to generate mkcert certificates\");\n        }\n        Ok(mut cmd) => {\n            cmd.wait().await?;\n        }\n    }\n\n    Ok((cert_path, key_path))\n}\n\n/// Middleware that intercepts html requests if the status is \"Building\" and returns a loading page instead\nasync fn build_status_middleware(\n    state: State<SharedStatus>,\n    request: Request,\n    next: Next,\n) -> axum::response::Response {\n    // If the request is for html, and the status is \"Building\", return the loading page instead of the contents of the response\n    let accepts = request.headers().get(hyper::header::ACCEPT);\n    let accepts_html = accepts\n        .and_then(|v| v.to_str().ok())\n        .map(|v| v.contains(\"text/html\"));\n\n    if let Some(true) = accepts_html {\n        let status = state.get();\n        if status != Status::Ready {\n            let html = include_str!(\"../../assets/web/dev.loading.html\");\n            return axum::response::Response::builder()\n                .status(StatusCode::OK)\n                // Load the html loader then keep loading forever\n                // We never close the stream so any headless testing framework (like playwright) will wait until the real build is done\n                .body(Body::from_stream(\n                    stream::once(async move { Ok::<_, std::convert::Infallible>(html) })\n                        .chain(stream::pending()),\n                ))\n                .unwrap();\n        }\n    }\n\n    next.run(request).await\n}\n\n#[derive(Debug, Clone)]\nstruct SharedStatus(Arc<RwLock<Status>>);\n\n#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]\n#[serde(tag = \"type\", content = \"data\")]\nenum Status {\n    ClientInit {\n        application_name: String,\n        bundle: BundleFormat,\n    },\n    Building {\n        progress: f64,\n        build_message: String,\n    },\n    BuildError {\n        error: String,\n    },\n    Ready,\n}\n\nimpl SharedStatus {\n    fn new(status: Status) -> Self {\n        Self(Arc::new(RwLock::new(status)))\n    }\n\n    fn new_with_starting_build() -> Self {\n        Self::new(Status::Building {\n            progress: 0.0,\n            build_message: \"Starting the build...\".to_string(),\n        })\n    }\n\n    fn set(&self, status: Status) {\n        *self.0.write().unwrap() = status;\n    }\n\n    fn get(&self) -> Status {\n        self.0.read().unwrap().clone()\n    }\n\n    async fn send_to(&self, socket: &mut WebSocket) -> Result<(), axum::Error> {\n        let msg = serde_json::to_string(&self.get()).unwrap();\n        socket.send(Message::Text(msg.into())).await\n    }\n}\n"
  },
  {
    "path": "packages/cli/src/serve/update.rs",
    "content": "use crate::{BuildId, BuilderUpdate, BundleFormat, Error, TraceMsg};\nuse axum::extract::ws::Message as WsMessage;\nuse std::path::PathBuf;\n\n/// One fat enum to rule them all....\n///\n/// Thanks to libraries like winit for the inspiration\n#[allow(clippy::large_enum_variant)]\npub(crate) enum ServeUpdate {\n    NewConnection {\n        id: BuildId,\n        aslr_reference: Option<u64>,\n        pid: Option<u32>,\n    },\n    WsMessage {\n        bundle: BundleFormat,\n        msg: WsMessage,\n    },\n\n    /// An update regarding the state of the build and running app from an AppBuilder\n    BuilderUpdate {\n        id: BuildId,\n        update: BuilderUpdate,\n    },\n\n    FilesChanged {\n        files: Vec<PathBuf>,\n    },\n\n    OpenApp,\n\n    RequestRebuild,\n\n    ToggleShouldRebuild,\n\n    OpenDebugger {\n        id: BuildId,\n    },\n\n    Redraw,\n\n    TracingLog {\n        log: TraceMsg,\n    },\n\n    Exit {\n        error: Option<Error>,\n    },\n}\n"
  },
  {
    "path": "packages/cli/src/settings.rs",
    "content": "use crate::{Result, TraceSrc};\nuse anyhow::bail;\nuse serde::{Deserialize, Serialize};\nuse std::sync::LazyLock;\nuse std::{fs, path::PathBuf, sync::Arc};\nuse tracing::{error, trace, warn};\n\n/// Describes cli settings from project or global level.\n/// The order of priority goes:\n/// 1. CLI Flags/Arguments\n/// 2. Project-level Settings\n/// 3. Global-level settings.\n///\n/// This allows users to control the cli settings with ease.\n#[derive(Debug, Clone, Serialize, Deserialize, Default)]\npub(crate) struct CliSettings {\n    /// Describes whether hot reload should always be on.\n    pub(crate) always_hot_reload: Option<bool>,\n    /// Describes whether the CLI should always open the browser for Web targets.\n    pub(crate) always_open_browser: Option<bool>,\n    /// Describes whether desktop apps in development will be pinned always-on-top.\n    pub(crate) always_on_top: Option<bool>,\n    /// Describes the interval in seconds that the CLI should poll for file changes on WSL.\n    #[serde(default = \"default_wsl_file_poll_interval\")]\n    pub(crate) wsl_file_poll_interval: Option<u16>,\n    /// Use tooling from path rather than downloading them.\n    pub(crate) no_downloads: Option<bool>,\n    /// Ignore updates for this version\n    pub(crate) ignore_version_update: Option<String>,\n    /// Disable telemetry\n    pub(crate) disable_telemetry: Option<bool>,\n}\n\nimpl CliSettings {\n    /// Load the settings from the local, global, or default config in that order\n    pub(crate) fn load() -> Arc<Self> {\n        static SETTINGS: LazyLock<Arc<CliSettings>> =\n            LazyLock::new(|| Arc::new(CliSettings::global_or_default()));\n        SETTINGS.clone()\n    }\n\n    pub fn global_or_default() -> Self {\n        CliSettings::from_global().unwrap_or_default()\n    }\n\n    /// Get the path to the settings toml file.\n    pub(crate) fn get_settings_path() -> PathBuf {\n        crate::Workspace::global_settings_file()\n    }\n\n    /// Get the current settings structure from global.\n    pub(crate) fn from_global() -> Option<Self> {\n        let settings = crate::Workspace::global_settings_file();\n\n        if !settings.exists() {\n            trace!(\"global settings file does not exist, returning None\");\n            return None;\n        }\n\n        let Some(data) = fs::read_to_string(&settings).ok() else {\n            warn!(\"failed to read global settings file\");\n            return None;\n        };\n\n        let Some(data) = toml::from_str::<CliSettings>(&data).ok() else {\n            warn!(\"failed to parse global settings file\");\n            return None;\n        };\n\n        Some(data)\n    }\n\n    /// Save the current structure to the global settings toml.\n    /// This does not save to project-level settings.\n    pub(crate) fn save(&self) -> Result<()> {\n        let path = Self::get_settings_path();\n\n        let data = toml::to_string_pretty(&self).map_err(|e| {\n            error!(dx_src = ?TraceSrc::Dev, ?self, \"failed to parse config into toml\");\n            anyhow::anyhow!(\"failed to parse config into toml: {e}\")\n        })?;\n\n        // Create the directory structure if it doesn't exist.\n        let parent_path = path.parent().unwrap();\n        if let Err(e) = fs::create_dir_all(parent_path) {\n            error!(\n                dx_src = ?TraceSrc::Dev,\n                ?data,\n                ?path,\n                \"failed to create directories for settings file\"\n            );\n            bail!(\"failed to create directories for settings file: {e}\");\n        }\n\n        // Write the data.\n        let result = fs::write(&path, data.clone());\n        if let Err(e) = result {\n            error!(?data, ?path, \"failed to save global cli settings\");\n            bail!(\"failed to save global cli settings: {e}\");\n        }\n\n        Ok(())\n    }\n\n    /// Modify the settings toml file - doesn't change the settings for this session\n    pub(crate) fn modify_settings(with: impl FnOnce(&mut CliSettings)) -> Result<()> {\n        let mut _settings = CliSettings::load();\n        let settings: &mut CliSettings = Arc::make_mut(&mut _settings);\n        with(settings);\n        settings.save()?;\n\n        Ok(())\n    }\n\n    /// Check if we should prefer to use the no-downloads feature\n    pub(crate) fn prefer_no_downloads() -> bool {\n        if cfg!(feature = \"no-downloads\") && !cfg!(debug_assertions) {\n            return true;\n        }\n\n        if std::env::var(\"NO_DOWNLOADS\").is_ok() {\n            return true;\n        }\n\n        CliSettings::load().no_downloads.unwrap_or_default()\n    }\n\n    /// Check if telemetry is disabled\n    pub(crate) fn telemetry_disabled() -> bool {\n        use std::env::var;\n\n        static TELEMETRY_DISABLED: LazyLock<bool> = LazyLock::new(|| {\n            if cfg!(feature = \"disable-telemetry\") {\n                return true;\n            }\n\n            if matches!(var(\"DX_TELEMETRY_ENABLED\"), Ok(val) if val.eq_ignore_ascii_case(\"false\") || val == \"0\")\n            {\n                return true;\n            }\n\n            if matches!(var(\"TELEMETRY\"), Ok(val) if val.eq_ignore_ascii_case(\"false\") || val == \"0\")\n            {\n                return true;\n            }\n\n            CliSettings::load().disable_telemetry.unwrap_or_default()\n        });\n\n        *TELEMETRY_DISABLED\n    }\n\n    pub(crate) fn is_ci() -> bool {\n        static CI: LazyLock<bool> = LazyLock::new(|| {\n            if matches!(std::env::var(\"CI\"), Ok(val) if val.eq_ignore_ascii_case(\"true\") || val == \"1\")\n            {\n                return true;\n            }\n\n            if matches!(std::env::var(\"DX_CI\"), Ok(val) if val.eq_ignore_ascii_case(\"true\") || val == \"1\")\n            {\n                return true;\n            }\n\n            false\n        });\n\n        *CI\n    }\n}\n\nfn default_wsl_file_poll_interval() -> Option<u16> {\n    Some(2)\n}\n"
  },
  {
    "path": "packages/cli/src/tailwind.rs",
    "content": "use crate::{CliSettings, Result, Workspace};\nuse anyhow::{anyhow, Context};\nuse std::{\n    path::{Path, PathBuf},\n    process::Stdio,\n};\nuse tokio::process::Command;\n\n#[derive(Debug)]\npub(crate) struct TailwindCli {\n    version: String,\n}\n\nimpl TailwindCli {\n    const V3_TAG: &'static str = \"v3.4.15\";\n    const V4_TAG: &'static str = \"v4.1.5\";\n\n    pub(crate) fn new(version: String) -> Self {\n        Self { version }\n    }\n\n    pub(crate) async fn run_once(\n        manifest_dir: PathBuf,\n        input_path: Option<PathBuf>,\n        output_path: Option<PathBuf>,\n    ) -> Result<()> {\n        let Some(tailwind) = Self::autodetect(&manifest_dir, &input_path) else {\n            return Ok(());\n        };\n\n        if !tailwind.get_binary_path()?.exists() {\n            tracing::info!(\"Installing tailwindcss@{}\", tailwind.version);\n            tailwind.install_github().await?;\n        }\n\n        let output = tailwind\n            .run(&manifest_dir, input_path, output_path, false)?\n            .wait_with_output()\n            .await?;\n\n        if !output.stderr.is_empty() {\n            tracing::warn!(\n                \"Warnings while running tailwind: {}\",\n                String::from_utf8_lossy(&output.stdout)\n            );\n        }\n\n        Ok(())\n    }\n\n    pub(crate) fn serve(\n        manifest_dir: PathBuf,\n        input_path: Option<PathBuf>,\n        output_path: Option<PathBuf>,\n    ) -> tokio::task::JoinHandle<Result<()>> {\n        tokio::spawn(async move {\n            let Some(tailwind) = Self::autodetect(&manifest_dir, &input_path) else {\n                return Ok(());\n            };\n\n            if !tailwind.get_binary_path()?.exists() {\n                tracing::info!(\"Installing tailwindcss@{}\", tailwind.version);\n                tailwind.install_github().await?;\n            }\n\n            // the tw watcher blocks on stdin, and `.wait()` will drop stdin\n            // unfortunately the tw watcher just deadlocks in this case, so we take the stdin manually\n            let mut proc = tailwind.run(&manifest_dir, input_path, output_path, true)?;\n            let stdin = proc.stdin.take();\n            proc.wait().await?;\n            drop(stdin);\n\n            Ok(())\n        })\n    }\n\n    /// Use the correct tailwind version based on the manifest directory.\n    ///\n    /// - If `tailwind.config.js` or `tailwind.config.ts` exists, use v3.\n    /// - If `tailwind.css` exists, use v4.\n    ///\n    /// Note that v3 still uses the tailwind.css file, but usually the accompanying js file indicates\n    /// that the project is using v3.\n    pub(crate) fn autodetect(manifest_dir: &Path, input_path: &Option<PathBuf>) -> Option<Self> {\n        let dir = input_path\n            .as_ref()\n            .map(|p| manifest_dir.join(p))\n            .and_then(|p| p.parent().map(|parent| parent.to_path_buf()))\n            .unwrap_or(manifest_dir.to_path_buf());\n\n        if dir.join(\"tailwind.config.js\").exists() || dir.join(\"tailwind.config.ts\").exists() {\n            return Some(Self::v3());\n        }\n\n        if input_path\n            .as_ref()\n            .map(|p| manifest_dir.join(p).exists())\n            .unwrap_or_else(|| manifest_dir.join(\"tailwind.css\").exists())\n        {\n            return Some(Self::v4());\n        }\n\n        None\n    }\n\n    pub(crate) fn v4() -> Self {\n        Self::new(Self::V4_TAG.to_string())\n    }\n\n    pub(crate) fn v3() -> Self {\n        Self::new(Self::V3_TAG.to_string())\n    }\n\n    pub(crate) fn run(\n        &self,\n        manifest_dir: &Path,\n        input_path: Option<PathBuf>,\n        output_path: Option<PathBuf>,\n        watch: bool,\n    ) -> Result<tokio::process::Child> {\n        let binary_path = self.get_binary_path()?;\n\n        let input_path = input_path.unwrap_or_else(|| manifest_dir.join(\"tailwind.css\"));\n        let output_path =\n            output_path.unwrap_or_else(|| manifest_dir.join(\"assets\").join(\"tailwind.css\"));\n\n        if !output_path.exists() {\n            std::fs::create_dir_all(output_path.parent().unwrap())\n                .context(\"failed to create tailwindcss output directory\")?;\n        }\n\n        tracing::debug!(\"Spawning tailwindcss@{} with args: {:?}\", self.version, {\n            [\n                binary_path.to_string_lossy().to_string(),\n                \"--input\".to_string(),\n                input_path.to_string_lossy().to_string(),\n                \"--output\".to_string(),\n                output_path.to_string_lossy().to_string(),\n                \"--watch\".to_string(),\n            ]\n        });\n\n        let mut cmd = Command::new(binary_path);\n        let proc = cmd\n            .arg(\"--input\")\n            .arg(input_path)\n            .arg(\"--output\")\n            .arg(output_path)\n            .args(watch.then_some(\"--watch\"))\n            .current_dir(manifest_dir)\n            .kill_on_drop(true)\n            .stdin(Stdio::piped())\n            .stdout(Stdio::null())\n            .stderr(Stdio::null())\n            .spawn()?;\n\n        Ok(proc)\n    }\n\n    pub fn get_binary_path(&self) -> anyhow::Result<PathBuf> {\n        if CliSettings::prefer_no_downloads() {\n            which::which(\"tailwindcss\").map_err(|_| anyhow!(\"Missing tailwindcss@{}\", self.version))\n        } else {\n            let installed_name = self.installed_bin_name();\n            let install_dir = self.install_dir()?;\n            Ok(install_dir.join(installed_name))\n        }\n    }\n\n    fn installed_bin_name(&self) -> String {\n        let mut name = format!(\"tailwindcss-{}\", self.version);\n        if cfg!(windows) {\n            name = format!(\"{name}.exe\");\n        }\n        name\n    }\n\n    async fn install_github(&self) -> anyhow::Result<()> {\n        tracing::debug!(\n            \"Attempting to install tailwindcss@{} from GitHub\",\n            self.version\n        );\n\n        let url = self.git_install_url().ok_or_else(|| {\n            anyhow!(\n                \"no available GitHub binary for tailwindcss@{}\",\n                self.version\n            )\n        })?;\n\n        // Get the final binary location.\n        let binary_path = self.get_binary_path()?;\n\n        // Download then extract tailwindcss.\n        let bytes = reqwest::get(url).await?.bytes().await?;\n\n        std::fs::create_dir_all(binary_path.parent().unwrap())\n            .context(\"failed to create tailwindcss directory\")?;\n\n        std::fs::write(&binary_path, &bytes).context(\"failed to write tailwindcss binary\")?;\n\n        // Make the binary executable.\n        #[cfg(unix)]\n        {\n            use std::os::unix::fs::PermissionsExt;\n            let mut perms = binary_path.metadata()?.permissions();\n            perms.set_mode(0o755);\n            std::fs::set_permissions(&binary_path, perms)?;\n        }\n\n        Ok(())\n    }\n\n    fn downloaded_bin_name(&self) -> Option<String> {\n        let platform = match target_lexicon::HOST.operating_system {\n            target_lexicon::OperatingSystem::Linux => \"linux\",\n            target_lexicon::OperatingSystem::Darwin(_) => \"macos\",\n            target_lexicon::OperatingSystem::Windows => \"windows\",\n            _ => return None,\n        };\n\n        let arch = match target_lexicon::HOST.architecture {\n            target_lexicon::Architecture::X86_64 if platform == \"windows\" => \"x64.exe\",\n            target_lexicon::Architecture::X86_64 => \"x64\",\n            // you would think this would be arm64.exe, but tailwind doesn't distribute arm64 binaries\n            target_lexicon::Architecture::Aarch64(_) if platform == \"windows\" => \"x64.exe\",\n            target_lexicon::Architecture::Aarch64(_) => \"arm64\",\n            _ => return None,\n        };\n\n        Some(format!(\"tailwindcss-{platform}-{arch}\"))\n    }\n\n    fn install_dir(&self) -> Result<PathBuf> {\n        let bindgen_dir = Workspace::dioxus_data_dir().join(\"tailwind/\");\n        Ok(bindgen_dir)\n    }\n\n    fn git_install_url(&self) -> Option<String> {\n        // eg:\n        //\n        // https://github.com/tailwindlabs/tailwindcss/releases/download/v4.1.5/tailwindcss-linux-arm64\n        //\n        // tailwindcss-linux-arm64\n        // tailwindcss-linux-x64\n        // tailwindcss-macos-arm64\n        // tailwindcss-macos-x64\n        // tailwindcss-windows-x64.exe\n        // tailwindcss-linux-arm64-musl\n        // tailwindcss-linux-x64-musl\n        Some(format!(\n            \"https://github.com/tailwindlabs/tailwindcss/releases/download/{}/{}\",\n            self.version,\n            self.downloaded_bin_name()?\n        ))\n    }\n}\n"
  },
  {
    "path": "packages/cli/src/test_harnesses.rs",
    "content": "use crate::{BuildTargets, BundleFormat, Cli, Commands, Workspace};\nuse anyhow::Result;\nuse clap::Parser;\nuse futures_util::{stream::FuturesUnordered, StreamExt};\nuse std::{\n    collections::HashSet,\n    fmt::Write,\n    path::{Path, PathBuf},\n    pin::Pin,\n    prelude::rust_2024::Future,\n};\nuse target_lexicon::Triple;\nuse tracing_subscriber::{prelude::*, util::SubscriberInitExt, EnvFilter, Layer};\n\n#[tokio::test]\nasync fn run_harness() {\n    test_harnesses().await;\n}\n\n#[allow(dead_code)]\nasync fn test_harnesses() {\n    tracing_subscriber::registry()\n        .with(tracing_subscriber::fmt::layer().with_filter(EnvFilter::new(\"error,dx=debug,dioxus_cli=debug,manganis_cli_support=debug,wasm_split_cli=debug,subsecond_cli_support=debug\",)))\n        .init();\n\n    TestHarnessBuilder::run(vec![\n        TestHarnessBuilder::new(\"harness-simple-web\")\n            .deps(r#\"dioxus = { workspace = true, features = [\"web\"] }\"#)\n            .asrt(r#\"dx build\"#, |targets| async move {\n                let t = targets.unwrap();\n                assert_eq!(t.client.bundle, BundleFormat::Web);\n                assert_eq!(t.client.triple, \"wasm32-unknown-unknown\".parse().unwrap());\n                assert!(t.server.is_none());\n            }),\n        TestHarnessBuilder::new(\"harness-simple-desktop\")\n            .deps(r#\"dioxus = { workspace = true, features = [\"desktop\"] }\"#)\n            .asrt(r#\"dx build\"#, |targets| async move {\n                let t = targets.unwrap();\n                assert_eq!(t.client.bundle, BundleFormat::host());\n                assert_eq!(t.client.triple, Triple::host());\n                assert!(t.server.is_none());\n            }),\n        TestHarnessBuilder::new(\"harness-simple-mobile\")\n            .deps(r#\"dioxus = { workspace = true, features = [\"mobile\"] }\"#)\n            .asrt(\n                \"dx build\",\n                |targets| async move { assert!(targets.is_err()) },\n            ),\n        TestHarnessBuilder::new(\"harness-simple-fullstack\")\n            .deps(r#\"dioxus = { workspace = true, features = [\"fullstack\"] }\"#)\n            .fetr(r#\"web=[\"dioxus/web\"]\"#)\n            .fetr(r#\"server=[\"dioxus/server\"]\"#)\n            .asrt(r#\"dx build\"#, |targets| async move {\n                let t = targets.unwrap();\n                assert_eq!(t.client.bundle, BundleFormat::Web);\n                let server = t.server.unwrap();\n                assert_eq!(server.bundle, BundleFormat::Server);\n                assert_eq!(server.triple, Triple::host());\n            }),\n        TestHarnessBuilder::new(\"harness-simple-fullstack-with-default\")\n            .deps(r#\"dioxus = { workspace = true, features = [\"fullstack\"] }\"#)\n            .fetr(r#\"default=[\"web\", \"server\"]\"#)\n            .fetr(r#\"web=[\"dioxus/web\"]\"#)\n            .fetr(r#\"server=[\"dioxus/server\"]\"#)\n            .asrt(r#\"dx build\"#, |targets| async move {\n                let t = targets.unwrap();\n                assert_eq!(t.client.bundle, BundleFormat::Web);\n                let server = t.server.unwrap();\n                assert_eq!(server.bundle, BundleFormat::Server);\n                assert_eq!(server.triple, Triple::host());\n            }),\n        TestHarnessBuilder::new(\"harness-simple-fullstack-native-with-default\")\n            .deps(r#\"dioxus = { workspace = true, features = [\"fullstack\"] }\"#)\n            .fetr(r#\"default=[\"native\", \"server\"]\"#)\n            .fetr(r#\"native=[\"dioxus/native\"]\"#)\n            .fetr(r#\"server=[\"dioxus/server\"]\"#)\n            .asrt(r#\"dx build\"#, |targets| async move {\n                let t = targets.unwrap();\n                assert_eq!(t.client.bundle, BundleFormat::host());\n                assert_eq!(t.client.features.len(), 1);\n                assert_eq!(t.client.features[0], \"native\");\n                let server = t.server.unwrap();\n                assert_eq!(server.bundle, BundleFormat::Server);\n                assert_eq!(server.triple, Triple::host());\n            }),\n        TestHarnessBuilder::new(\"harness-fullstack-multi-target\")\n            .deps(r#\"dioxus = { workspace = true, features = [\"fullstack\"] }\"#)\n            .fetr(r#\"default=[\"web\", \"desktop\", \"mobile\", \"server\"]\"#)\n            .fetr(r#\"web=[\"dioxus/web\"]\"#)\n            .fetr(r#\"desktop=[\"dioxus/desktop\"]\"#)\n            .fetr(r#\"mobile=[\"dioxus/mobile\"]\"#)\n            .fetr(r#\"server=[\"dioxus/server\"]\"#)\n            .asrt(r#\"dx build\"#, |t| async move { assert!(t.is_err()) })\n            .asrt(r#\"dx build --web\"#, |targets| async move {\n                let t = targets.unwrap();\n                assert_eq!(t.client.bundle, BundleFormat::Web);\n            })\n            .asrt(r#\"dx build --desktop\"#, |targets| async move {\n                let t = targets.unwrap();\n                assert_eq!(t.client.bundle, BundleFormat::host());\n            })\n            .asrt(r#\"dx build --ios\"#, |targets| async move {\n                let t = targets.unwrap();\n                assert_eq!(t.client.bundle, BundleFormat::Ios);\n                assert_eq!(t.client.triple, TestHarnessBuilder::host_ios_triple_sim());\n            })\n            .asrt(r#\"dx build --ios --device\"#, |targets| async move {\n                let targets = targets.unwrap();\n                assert_eq!(targets.client.bundle, BundleFormat::Ios);\n                assert_eq!(targets.client.triple, \"aarch64-apple-ios\".parse().unwrap());\n            })\n            .asrt(r#\"dx build --android --device\"#, |targets| async move {\n                let t = targets.unwrap();\n                assert_eq!(t.client.bundle, BundleFormat::Android);\n                assert_eq!(t.client.triple, \"aarch64-linux-android\".parse().unwrap());\n            }),\n        TestHarnessBuilder::new(\"harness-fullstack-multi-target-no-default\")\n            .deps(r#\"dioxus = { workspace = true, features = [\"fullstack\"] }\"#)\n            .fetr(r#\"web=[\"dioxus/web\"]\"#)\n            .fetr(r#\"desktop=[\"dioxus/desktop\"]\"#)\n            .fetr(r#\"mobile=[\"dioxus/mobile\"]\"#)\n            .fetr(r#\"server=[\"dioxus/server\"]\"#)\n            .asrt(r#\"dx build\"#, |targets| async move {\n                assert!(targets.is_err())\n            })\n            .asrt(r#\"dx build --desktop\"#, |targets| async move {\n                let t = targets.unwrap();\n                assert_eq!(t.client.bundle, BundleFormat::host());\n                let server = t.server.unwrap();\n                assert_eq!(server.bundle, BundleFormat::Server);\n                assert_eq!(server.triple, Triple::host());\n            })\n            .asrt(r#\"dx build --ios\"#, |targets| async move {\n                let t = targets.unwrap();\n                assert_eq!(t.client.bundle, BundleFormat::Ios);\n                assert_eq!(t.client.triple, TestHarnessBuilder::host_ios_triple_sim());\n                let server = t.server.unwrap();\n                assert_eq!(server.bundle, BundleFormat::Server);\n                assert_eq!(server.triple, Triple::host());\n            }),\n        TestHarnessBuilder::new(\"harness-fullstack-desktop\")\n            .deps(r#\"dioxus = { workspace = true, features = [\"fullstack\"] }\"#)\n            .fetr(r#\"desktop=[\"dioxus/desktop\"]\"#)\n            .fetr(r#\"server=[\"dioxus/server\"]\"#)\n            .asrt(r#\"dx build\"#, |targets| async move {\n                let t = targets.unwrap();\n                assert_eq!(t.client.bundle, BundleFormat::host());\n                let server = t.server.unwrap();\n                assert_eq!(server.bundle, BundleFormat::Server);\n                assert_eq!(server.triple, Triple::host());\n            }),\n        TestHarnessBuilder::new(\"harness-fullstack-desktop-with-features\")\n            .deps(r#\"dioxus = { workspace = true, features = [\"fullstack\"] }\"#)\n            .deps(r#\"anyhow = { workspace = true, optional = true }\"#)\n            .fetr(r#\"desktop=[\"dioxus/desktop\", \"has_anyhow\"]\"#)\n            .fetr(r#\"has_anyhow=[\"dep:anyhow\"]\"#)\n            .fetr(r#\"server=[\"dioxus/server\"]\"#)\n            .asrt(r#\"dx build\"#, |targets| async move {\n                let t = targets.unwrap();\n                assert_eq!(t.client.bundle, BundleFormat::host());\n                let server = t.server.unwrap();\n                assert_eq!(server.bundle, BundleFormat::Server);\n                assert_eq!(server.triple, Triple::host());\n            }),\n        TestHarnessBuilder::new(\"harness-fullstack-desktop-with-default\")\n            .deps(r#\"dioxus = { workspace = true, features = [\"fullstack\"] }\"#)\n            .deps(r#\"anyhow = { workspace = true, optional = true }\"#)\n            .fetr(r#\"default=[\"desktop\"]\"#)\n            .fetr(r#\"desktop=[\"dioxus/desktop\", \"has_anyhow\"]\"#)\n            .fetr(r#\"has_anyhow=[\"dep:anyhow\"]\"#)\n            .fetr(r#\"server=[\"dioxus/server\"]\"#)\n            .asrt(r#\"dx build\"#, |targets| async move {\n                let t = targets.unwrap();\n                assert_eq!(t.client.bundle, BundleFormat::host());\n                let server = t.server.unwrap();\n                assert_eq!(server.bundle, BundleFormat::Server);\n                assert_eq!(server.triple, Triple::host());\n            }),\n        TestHarnessBuilder::new(\"harness-no-dioxus\")\n            .deps(r#\"anyhow = { workspace = true, optional = true }\"#)\n            .fetr(r#\"web=[\"dep:anyhow\"]\"#)\n            .fetr(r#\"server=[]\"#)\n            .asrt(r#\"dx build\"#, |targets| async move {\n                let t = targets.unwrap();\n                assert_eq!(t.client.bundle, BundleFormat::host());\n                assert!(t.server.is_none());\n            }),\n        TestHarnessBuilder::new(\"harness-simple-dedicated-server\"),\n        TestHarnessBuilder::new(\"harness-simple-dedicated-client\")\n            .deps(r#\"dioxus = { workspace = true, features = [\"web\"] }\"#)\n            .asrt(r#\"dx build\"#, |targets| async move {\n                let t = targets.unwrap();\n                assert_eq!(t.client.bundle, BundleFormat::Web);\n                assert!(t.server.is_none());\n            })\n            .asrt(r#\"dx build @client --package harness-simple-dedicated-client @server --package harness-simple-dedicated-server\"#, |targets| async move {\n                    let t = targets.unwrap();\n                    assert_eq!(t.client.bundle, BundleFormat::Web);\n                    let s = t.server.unwrap();\n                    assert_eq!(s.bundle, BundleFormat::Server);\n                    assert_eq!(s.triple, Triple::host());\n                },\n            )\n            .asrt(r#\"dx build @client --package harness-simple-dedicated-client @server --package harness-simple-dedicated-server --target wasm32-unknown-unknown\"#, |targets| async move {\n                let t = targets.unwrap();\n                assert_eq!(t.client.bundle, BundleFormat::Web);\n                let s = t.server.unwrap();\n                assert_eq!(s.bundle, BundleFormat::Server);\n                assert_eq!(s.triple, \"wasm32-unknown-unknown\".parse().unwrap());\n            }),\n        TestHarnessBuilder::new(\"harness-renderer-swap\")\n            .deps(r#\"dioxus = { workspace = true, features = [\"fullstack\"] }\"#)\n            .fetr(r#\"default=[\"desktop\", \"server\"]\"#)\n            .fetr(r#\"desktop=[\"dioxus/desktop\"]\"#)\n            .fetr(r#\"native=[\"dioxus/native\"]\"#)\n            .fetr(r#\"server=[\"dioxus/server\"]\"#)\n            .asrt(\n                r#\"dx build --desktop --renderer native\"#,\n                |targets| async move {\n                    let t = targets.unwrap();\n                    assert_eq!(t.client.bundle, BundleFormat::host());\n                    let server = t.server.unwrap();\n                    assert_eq!(server.bundle, BundleFormat::Server);\n                    assert_eq!(server.triple, Triple::host());\n                },\n            ),\n        TestHarnessBuilder::new(\"harness-default-to-non-default\")\n            .deps(r#\"dioxus = { workspace = true, features = [] }\"#)\n            .fetr(r#\"default=[\"web\"]\"#)\n            .fetr(r#\"web=[\"dioxus/web\"]\"#)\n            .asrt(\n                r#\"dx build --ios\"#,\n                |targets| async move {\n                    let t = targets.unwrap();\n                    assert!(t.server.is_none());\n                    assert_eq!(t.client.bundle, BundleFormat::Ios);\n                    assert_eq!(t.client.triple, TestHarnessBuilder::host_ios_triple_sim());\n                    assert!(t.client.no_default_features);\n                },\n            ),\n        TestHarnessBuilder::new(\"harness-fullstack-with-optional-tokio\")\n            .deps(r#\"dioxus = { workspace = true, features = [\"fullstack\"] }\"#)\n            .deps(r#\"serde = \"1.0.219\"\"#)\n            .deps(r#\"tokio = { workspace = true, features = [\"full\"], optional = true }\"#)\n            .fetr(r#\"default = []\"#)\n            .fetr(r#\"server = [\"dioxus/server\", \"dep:tokio\"]\"#)\n            .fetr(r#\"web = [\"dioxus/web\"]\"#)\n            // .asrt(r#\"dx build\"#, |targets| async move {\n            //     assert!(targets.is_err())\n            // })\n            .asrt(r#\"dx build --web\"#, |targets| async move {\n                let t = targets.unwrap();\n                assert_eq!(t.client.bundle, BundleFormat::Web);\n                assert_eq!(t.client.triple, \"wasm32-unknown-unknown\".parse().unwrap());\n                let server = t.server.unwrap();\n                assert_eq!(server.bundle, BundleFormat::Server);\n                assert_eq!(server.triple, Triple::host());\n            }),\n        TestHarnessBuilder::new(\"harness-web-with-no-default-features\")\n            .deps(r#\"dioxus = { workspace = true }\"#)\n            .fetr(r#\"default=[\"other\"]\"#)\n            .fetr(r#\"other=[]\"#)\n            .asrt(r#\"dx build --no-default-features --web\"#, |targets| async move {\n                let t = targets.unwrap();\n                assert_eq!(t.client.bundle, BundleFormat::Web);\n                assert_eq!(t.client.features, vec![\"dioxus/web\"]);\n                assert!(t.server.is_none());\n            }),\n        TestHarnessBuilder::new(\"harness-web-with-default-features\")\n            .deps(r#\"dioxus = { workspace = true }\"#)\n            .fetr(r#\"default=[\"other\"]\"#)\n            .fetr(r#\"other=[]\"#)\n            .asrt(r#\"dx build --web\"#, |targets| async move {\n                let t = targets.unwrap();\n                assert_eq!(t.client.bundle, BundleFormat::Web);\n                assert_eq!(t.client.features.iter().map(|s| s.as_str()).collect::<HashSet<_>>(), [\"dioxus/web\", \"other\"].into_iter().collect::<HashSet<_>>());\n                assert!(t.server.is_none());\n            }),\n    ])\n    .await;\n}\n\n#[derive(Default)]\nstruct TestHarnessBuilder {\n    name: String,\n    dependencies: String,\n    features: String,\n    futures: Vec<TestHarnessTestCase>,\n}\n\nstruct TestHarnessTestCase {\n    args: String,\n    #[allow(clippy::type_complexity)]\n    callback: Box<dyn FnOnce(Result<BuildTargets>) -> Pin<Box<dyn Future<Output = ()>>>>,\n}\n\nimpl TestHarnessBuilder {\n    fn new(name: &str) -> Self {\n        Self {\n            name: name.into(),\n            dependencies: Default::default(),\n            features: Default::default(),\n            futures: Default::default(),\n        }\n    }\n\n    /// Add a dependency to the test harness.\n    fn deps(mut self, dependencies: impl Into<String>) -> Self {\n        writeln!(&mut self.dependencies, \"{}\", dependencies.into()).unwrap();\n        self\n    }\n\n    /// Add a feature to the test harness.\n    fn fetr(mut self, features: impl Into<String>) -> Self {\n        writeln!(&mut self.features, \"{}\", features.into()).unwrap();\n        self\n    }\n\n    /// Assert the expected behavior of the test harness.\n    fn asrt<F>(\n        mut self,\n        args: impl Into<String>,\n        future: impl FnOnce(Result<BuildTargets>) -> F + 'static,\n    ) -> Self\n    where\n        F: Future<Output = ()> + 'static,\n    {\n        self.futures.push(TestHarnessTestCase {\n            args: args.into(),\n            callback: Box::new(move |args| Box::pin(future(args))),\n        });\n        self\n    }\n\n    /// Write the test harness to the filesystem.\n    fn build(&self, harness_dir: &Path) {\n        let name = self.name.clone();\n        let dependencies = self.dependencies.clone();\n        let features = self.features.clone();\n\n        let test_dir = harness_dir.join(&name);\n\n        _ = std::fs::remove_dir_all(&test_dir);\n        std::fs::create_dir_all(&test_dir).unwrap();\n        std::fs::create_dir_all(test_dir.join(\"src\")).unwrap();\n\n        let cargo_toml = format!(\n            r#\"[package]\nname = \"{name}\"\nversion = \"0.0.1\"\nedition = \"2021\"\nlicense = \"MIT OR Apache-2.0\"\npublish = false\n\n[dependencies]\n{dependencies}\n\n[features]\n{features}\n    \"#,\n            name = name,\n            dependencies = dependencies,\n            features = features\n        );\n\n        std::fs::write(test_dir.join(\"Cargo.toml\"), cargo_toml).unwrap();\n\n        let contents = if features.contains(\"dioxus\") {\n            r#\"use dioxus::prelude::*;\nfn main() {\n    dioxus::launch(|| rsx! { \"hello world!\" })\n}\n\"#\n        } else {\n            r#\"fn main() {\n    println!(\"Hello, world!\");\n}\n\"#\n        };\n\n        std::fs::write(test_dir.join(\"src/main.rs\"), contents).unwrap();\n    }\n\n    async fn run(harnesses: Vec<Self>) {\n        _ = crate::VERBOSITY.set(crate::Verbosity {\n            verbose: true,\n            trace: true,\n            json_output: false,\n            log_to_file: None,\n            locked: false,\n            offline: false,\n            frozen: false,\n        });\n\n        let cargo_manifest_dir = std::env::var(\"CARGO_MANIFEST_DIR\")\n            .map(PathBuf::from)\n            .unwrap();\n\n        let harness_dir = cargo_manifest_dir.parent().unwrap().join(\"cli-harnesses\");\n\n        // make sure we don't start deleting random stuff.\n        if !harness_dir.exists() {\n            panic!(\n                \"cli-harnesses directory does not exist, aborting: {:?}\",\n                harness_dir\n            );\n        }\n\n        // Erase old entries in the harness directory, but keep files (ie README.md) around\n        for entry in std::fs::read_dir(&harness_dir).unwrap() {\n            let entry = entry.unwrap();\n            if entry.file_type().unwrap().is_dir() {\n                std::fs::remove_dir_all(entry.path()).unwrap();\n            }\n        }\n\n        // Now that the harnesses are written to the filesystem, we can call cargo_metadata\n        // It will be cached from here\n        let mut futures = FuturesUnordered::new();\n        let mut seen_names = HashSet::new();\n\n        for harness in harnesses {\n            if !seen_names.insert(harness.name.clone()) {\n                panic!(\"Duplicate test harness name found: {}\", harness.name);\n            }\n\n            harness.build(&harness_dir);\n\n            for case in harness.futures {\n                let mut escaped = shell_words::split(&case.args).unwrap();\n                if !(escaped.contains(&\"--package\".to_string())\n                    || escaped.contains(&\"@server\".to_string())\n                    || escaped.contains(&\"@client\".to_string()))\n                {\n                    escaped.push(\"--package\".to_string());\n                    escaped.push(harness.name.clone());\n                }\n                let args = Cli::try_parse_from(escaped).unwrap();\n                let Commands::Build(build_args) = args.action else {\n                    panic!(\"Expected build command\");\n                };\n\n                futures.push(async move {\n                    let targets = build_args.into_targets().await;\n                    (case.callback)(targets).await;\n                });\n            }\n        }\n\n        // Give a moment for fs to catch up\n        std::thread::sleep(std::time::Duration::from_secs(1));\n\n        let _workspace = Workspace::current().await.unwrap();\n\n        while let Some(_res) = futures.next().await {}\n    }\n\n    fn host_ios_triple_sim() -> Triple {\n        if cfg!(target_arch = \"aarch64\") {\n            \"aarch64-apple-ios-sim\".parse().unwrap()\n        } else {\n            \"x86_64-apple-ios\".parse().unwrap()\n        }\n    }\n}\n"
  },
  {
    "path": "packages/cli/src/wasm_bindgen.rs",
    "content": "use crate::{CliSettings, Result, Workspace};\nuse anyhow::{anyhow, Context};\nuse flate2::read::GzDecoder;\nuse std::path::{Path, PathBuf};\nuse tar::Archive;\nuse tempfile::TempDir;\nuse tokio::process::Command;\n\npub(crate) struct WasmBindgen {\n    version: String,\n    input_path: PathBuf,\n    out_dir: PathBuf,\n    out_name: String,\n    target: String,\n    debug: bool,\n    keep_debug: bool,\n    demangle: bool,\n    remove_name_section: bool,\n    remove_producers_section: bool,\n    keep_lld_exports: bool,\n}\n\nimpl WasmBindgen {\n    pub(crate) fn new(version: &str) -> Self {\n        Self {\n            version: version.to_string(),\n            input_path: PathBuf::new(),\n            out_dir: PathBuf::new(),\n            out_name: String::new(),\n            target: String::new(),\n            debug: true,\n            keep_debug: true,\n            demangle: true,\n            remove_name_section: false,\n            remove_producers_section: false,\n            keep_lld_exports: false,\n        }\n    }\n\n    pub(crate) fn input_path(self, input_path: &Path) -> Self {\n        Self {\n            input_path: input_path.to_path_buf(),\n            ..self\n        }\n    }\n\n    pub(crate) fn out_dir(self, out_dir: &Path) -> Self {\n        Self {\n            out_dir: out_dir.to_path_buf(),\n            ..self\n        }\n    }\n\n    pub(crate) fn out_name(self, out_name: &str) -> Self {\n        Self {\n            out_name: out_name.to_string(),\n            ..self\n        }\n    }\n\n    pub(crate) fn target(self, target: &str) -> Self {\n        Self {\n            target: target.to_string(),\n            ..self\n        }\n    }\n\n    pub(crate) fn debug(self, debug: bool) -> Self {\n        Self { debug, ..self }\n    }\n\n    pub(crate) fn keep_debug(self, keep_debug: bool) -> Self {\n        Self { keep_debug, ..self }\n    }\n\n    pub(crate) fn demangle(self, demangle: bool) -> Self {\n        Self { demangle, ..self }\n    }\n\n    pub(crate) fn remove_name_section(self, remove_name_section: bool) -> Self {\n        Self {\n            remove_name_section,\n            ..self\n        }\n    }\n\n    pub(crate) fn remove_producers_section(self, remove_producers_section: bool) -> Self {\n        Self {\n            remove_producers_section,\n            ..self\n        }\n    }\n\n    pub(crate) fn keep_lld_sections(self, keep_lld_sections: bool) -> Self {\n        Self {\n            keep_lld_exports: keep_lld_sections,\n            ..self\n        }\n    }\n\n    /// Run the bindgen command with the current settings\n    pub(crate) async fn run(&self) -> Result<std::process::Output> {\n        let binary = self.get_binary_path()?;\n\n        let mut args = Vec::new();\n\n        // Target\n        args.push(\"--target\");\n        args.push(&self.target);\n\n        // Options\n        if self.debug {\n            args.push(\"--debug\");\n        }\n\n        if !self.demangle {\n            args.push(\"--no-demangle\");\n        }\n\n        if self.keep_debug {\n            args.push(\"--keep-debug\");\n        }\n\n        if self.remove_name_section {\n            args.push(\"--remove-name-section\");\n        }\n\n        if self.remove_producers_section {\n            args.push(\"--remove-producers-section\");\n        }\n\n        if self.keep_lld_exports {\n            args.push(\"--keep-lld-exports\");\n        }\n\n        // Out name\n        args.push(\"--out-name\");\n        args.push(&self.out_name);\n\n        // wbg generates typescript bindnings by default - we don't want those\n        args.push(\"--no-typescript\");\n\n        // Out dir\n        let out_dir = self\n            .out_dir\n            .to_str()\n            .expect(\"input_path should be valid utf8\");\n\n        args.push(\"--out-dir\");\n        args.push(out_dir);\n\n        // Input path\n        let input_path = self\n            .input_path\n            .to_str()\n            .expect(\"input_path should be valid utf8\");\n        args.push(input_path);\n\n        tracing::debug!(\"wasm-bindgen: {:#?}\", args);\n\n        // Run bindgen\n        let output = Command::new(binary).args(args).output().await?;\n\n        // Check for errors\n        if !output.stderr.is_empty() {\n            if output.status.success() {\n                tracing::debug!(\n                    \"wasm-bindgen warnings: {}\",\n                    String::from_utf8_lossy(&output.stderr)\n                );\n            } else {\n                tracing::error!(\n                    \"wasm-bindgen error: {}\",\n                    String::from_utf8_lossy(&output.stderr)\n                );\n            }\n        }\n\n        Ok(output)\n    }\n\n    /// Verify the installed version of wasm-bindgen-cli\n    ///\n    /// For local installations, this will check that the installed version matches the specified version.\n    /// For managed installations, this will check that the version managed by `dx` is the specified version.\n    pub async fn verify_install(version: &str) -> anyhow::Result<()> {\n        let settings = Self::new(version);\n        if CliSettings::prefer_no_downloads() {\n            settings.verify_local_install().await\n        } else {\n            settings.verify_managed_install().await\n        }\n    }\n\n    /// Install the specified wasm-bingen version.\n    ///\n    /// This will overwrite any existing wasm-bindgen binaries of the same version.\n    ///\n    /// This will attempt to install wasm-bindgen from:\n    /// 1. Direct GitHub release download.\n    /// 2. `cargo binstall` if installed.\n    /// 3. Compile from source with `cargo install`.\n    async fn install(&self) -> anyhow::Result<()> {\n        tracing::info!(\"Installing wasm-bindgen-cli@{}...\", self.version);\n\n        // Attempt installation from GitHub\n        if let Err(e) = self.install_github().await {\n            tracing::error!(\"Failed to install wasm-bindgen-cli@{}: {e}\", self.version);\n        } else {\n            tracing::info!(\n                \"wasm-bindgen-cli@{} was successfully installed from GitHub.\",\n                self.version\n            );\n            return Ok(());\n        }\n\n        // Attempt installation from binstall.\n        if let Err(e) = self.install_binstall().await {\n            tracing::error!(\"Failed to install wasm-bindgen-cli@{}: {e}\", self.version);\n            tracing::info!(\"Failed to install prebuilt binary for wasm-bindgen-cli@{}. Compiling from source instead. This may take a while.\", self.version);\n        } else {\n            tracing::info!(\n                \"wasm-bindgen-cli@{} was successfully installed from cargo-binstall.\",\n                self.version\n            );\n            return Ok(());\n        }\n\n        // Attempt installation from cargo.\n        self.install_cargo()\n            .await\n            .context(\"failed to install wasm-bindgen-cli from cargo\")?;\n\n        tracing::info!(\n            \"wasm-bindgen-cli@{} was successfully installed from source.\",\n            self.version\n        );\n\n        Ok(())\n    }\n\n    async fn install_github(&self) -> anyhow::Result<()> {\n        tracing::debug!(\n            \"Attempting to install wasm-bindgen-cli@{} from GitHub\",\n            self.version\n        );\n\n        let url = self.git_install_url().ok_or_else(|| {\n            anyhow!(\n                \"no available GitHub binary for wasm-bindgen-cli@{}\",\n                self.version\n            )\n        })?;\n\n        // Get the final binary location.\n        let binary_path = self.get_binary_path()?;\n\n        // Download then extract wasm-bindgen-cli.\n        let bytes = reqwest::get(url).await?.bytes().await?;\n\n        // Unpack the first tar entry to the final binary location\n        Archive::new(GzDecoder::new(bytes.as_ref()))\n            .entries()?\n            .find(|entry| {\n                entry\n                    .as_ref()\n                    .map(|e| {\n                        e.path_bytes()\n                            .ends_with(self.downloaded_bin_name().as_bytes())\n                    })\n                    .unwrap_or(false)\n            })\n            .context(\"Failed to find entry\")??\n            .unpack(&binary_path)\n            .context(\"failed to unpack wasm-bindgen-cli binary\")?;\n\n        Ok(())\n    }\n\n    async fn install_binstall(&self) -> anyhow::Result<()> {\n        tracing::debug!(\n            \"Attempting to install wasm-bindgen-cli@{} from cargo-binstall\",\n            self.version\n        );\n\n        let package = self.cargo_bin_name();\n        let tempdir = TempDir::new()?;\n\n        // Run install command\n        Command::new(\"cargo\")\n            .args([\n                \"binstall\",\n                &package,\n                \"--no-confirm\",\n                \"--force\",\n                \"--no-track\",\n                \"--install-path\",\n            ])\n            .arg(tempdir.path())\n            .output()\n            .await?;\n\n        std::fs::copy(\n            tempdir.path().join(self.downloaded_bin_name()),\n            self.get_binary_path()?,\n        )?;\n\n        Ok(())\n    }\n\n    async fn install_cargo(&self) -> anyhow::Result<()> {\n        tracing::debug!(\n            \"Attempting to install wasm-bindgen-cli@{} from cargo-install\",\n            self.version\n        );\n        let package = self.cargo_bin_name();\n        let tempdir = TempDir::new()?;\n\n        // Run install command\n        Command::new(\"cargo\")\n            .args([\n                \"install\",\n                &package,\n                \"--bin\",\n                \"wasm-bindgen\",\n                \"--no-track\",\n                \"--force\",\n                \"--root\",\n            ])\n            .arg(tempdir.path())\n            .output()\n            .await\n            .context(\"failed to install wasm-bindgen-cli from cargo-install\")?;\n\n        tracing::info!(\"Copying into path: {}\", tempdir.path().display());\n\n        // copy the wasm-bindgen out of the tempdir to the final location\n        std::fs::copy(\n            tempdir.path().join(\"bin\").join(self.downloaded_bin_name()),\n            self.get_binary_path()?,\n        )\n        .context(\"failed to copy wasm-bindgen binary\")?;\n\n        Ok(())\n    }\n\n    async fn verify_local_install(&self) -> anyhow::Result<()> {\n        tracing::trace!(\n            \"Verifying wasm-bindgen-cli@{} is installed in the path\",\n            self.version\n        );\n\n        let binary = self.get_binary_path()?;\n        let output = Command::new(binary)\n            .args([\"--version\"])\n            .output()\n            .await\n            .context(\"Failed to check wasm-bindgen-cli version\")?;\n\n        let stdout = String::from_utf8(output.stdout)\n            .context(\"Failed to extract wasm-bindgen-cli output\")?;\n\n        let installed_version = stdout.trim_start_matches(\"wasm-bindgen\").trim();\n        if installed_version != self.version {\n            return Err(anyhow!(\n                \"Incorrect wasm-bindgen-cli version: project requires version {} but version {} is installed\",\n                self.version,\n                installed_version,\n            ));\n        }\n\n        Ok(())\n    }\n\n    async fn verify_managed_install(&self) -> anyhow::Result<()> {\n        tracing::trace!(\n            \"Verifying wasm-bindgen-cli@{} is installed in the tool directory\",\n            self.version\n        );\n\n        let binary_name = self.installed_bin_name();\n        let path = self.install_dir()?.join(binary_name);\n\n        if !path.exists() {\n            self.install().await?;\n        }\n\n        Ok(())\n    }\n\n    pub fn get_binary_path(&self) -> anyhow::Result<PathBuf> {\n        if CliSettings::prefer_no_downloads() {\n            which::which(\"wasm-bindgen\")\n                .map_err(|_| anyhow!(\"Missing wasm-bindgen-cli@{}\", self.version))\n        } else {\n            let installed_name = self.installed_bin_name();\n            let install_dir = self.install_dir()?;\n            Ok(install_dir.join(installed_name))\n        }\n    }\n\n    fn install_dir(&self) -> anyhow::Result<PathBuf> {\n        let bindgen_dir = Workspace::dioxus_data_dir().join(\"wasm-bindgen/\");\n        std::fs::create_dir_all(&bindgen_dir)?;\n        Ok(bindgen_dir)\n    }\n\n    fn installed_bin_name(&self) -> String {\n        let mut name = format!(\"wasm-bindgen-{}\", self.version);\n        if cfg!(windows) {\n            name = format!(\"{name}.exe\");\n        }\n        name\n    }\n\n    fn cargo_bin_name(&self) -> String {\n        format!(\"wasm-bindgen-cli@{}\", self.version)\n    }\n\n    fn downloaded_bin_name(&self) -> &'static str {\n        if cfg!(windows) {\n            \"wasm-bindgen.exe\"\n        } else {\n            \"wasm-bindgen\"\n        }\n    }\n\n    fn git_install_url(&self) -> Option<String> {\n        let platform = if cfg!(all(target_os = \"windows\", target_arch = \"x86_64\")) {\n            \"x86_64-pc-windows-msvc\"\n        } else if cfg!(all(target_os = \"linux\", target_arch = \"x86_64\")) {\n            \"x86_64-unknown-linux-musl\"\n        } else if cfg!(all(target_os = \"linux\", target_arch = \"aarch64\")) {\n            \"aarch64-unknown-linux-gnu\"\n        } else if cfg!(all(target_os = \"macos\", target_arch = \"x86_64\")) {\n            \"x86_64-apple-darwin\"\n        } else if cfg!(all(target_os = \"macos\", target_arch = \"aarch64\")) {\n            \"aarch64-apple-darwin\"\n        } else {\n            return None;\n        };\n\n        Some(format!(\n            \"https://github.com/rustwasm/wasm-bindgen/releases/download/{}/wasm-bindgen-{}-{}.tar.gz\",\n            self.version, self.version, platform\n        ))\n    }\n}\n\n#[cfg(test)]\nmod test {\n    use super::*;\n    const VERSION: &str = \"0.2.99\";\n\n    /// Test the github installer.\n    #[tokio::test]\n    async fn test_github_install() {\n        if !crate::devcfg::test_installs() {\n            return;\n        }\n        let binary = WasmBindgen::new(VERSION);\n        reset_test().await;\n        binary.install_github().await.unwrap();\n        test_verify_install().await;\n        verify_installation(&binary).await;\n    }\n\n    /// Test the cargo installer.\n    #[tokio::test]\n    async fn test_cargo_install() {\n        if !crate::devcfg::test_installs() {\n            return;\n        }\n        let binary = WasmBindgen::new(VERSION);\n        reset_test().await;\n        binary.install_cargo().await.unwrap();\n        test_verify_install().await;\n        verify_installation(&binary).await;\n    }\n\n    // CI doesn't have binstall.\n    // Test the binstall installer\n    #[tokio::test]\n    async fn test_binstall_install() {\n        if !crate::devcfg::test_installs() {\n            return;\n        }\n        let binary = WasmBindgen::new(VERSION);\n        reset_test().await;\n        binary.install_binstall().await.unwrap();\n        test_verify_install().await;\n        verify_installation(&binary).await;\n    }\n\n    /// Helper to test `verify_install` after an installation.\n    async fn test_verify_install() {\n        WasmBindgen::verify_install(VERSION).await.unwrap();\n    }\n\n    /// Helper to test that the installed binary actually exists.\n    async fn verify_installation(binary: &WasmBindgen) {\n        let path = binary.install_dir().unwrap();\n        let name = binary.installed_bin_name();\n        let binary_path = path.join(name);\n        assert!(\n            binary_path.exists(),\n            \"wasm-bindgen binary doesn't exist after installation\"\n        );\n    }\n\n    /// Delete the installed binary. The temp folder should be automatically deleted.\n    async fn reset_test() {\n        let binary = WasmBindgen::new(VERSION);\n        let path = binary.install_dir().unwrap();\n        let name = binary.installed_bin_name();\n        let binary_path = path.join(name);\n        let _ = std::fs::remove_file(binary_path);\n    }\n}\n"
  },
  {
    "path": "packages/cli/src/wasm_opt.rs",
    "content": "use crate::config::WasmOptLevel;\nuse crate::{CliSettings, Result, WasmOptConfig, Workspace};\nuse anyhow::{anyhow, bail, Context};\nuse flate2::read::GzDecoder;\nuse std::path::{Path, PathBuf};\nuse tar::Archive;\nuse tempfile::NamedTempFile;\n\n/// Write these wasm bytes with a particular set of optimizations\npub async fn write_wasm(bytes: &[u8], output_path: &Path, cfg: &WasmOptConfig) -> Result<()> {\n    std::fs::write(output_path, bytes)?;\n    optimize(output_path, output_path, cfg).await?;\n    Ok(())\n}\n\npub async fn optimize(input_path: &Path, output_path: &Path, cfg: &WasmOptConfig) -> Result<()> {\n    let wasm_opt = WasmOpt::new(input_path, output_path, cfg)\n        .await\n        .context(\"Failed to create wasm-opt instance\")?;\n    wasm_opt\n        .optimize()\n        .await\n        .context(\"Failed to run wasm-opt\")?;\n\n    Ok(())\n}\n\nstruct WasmOpt {\n    path: PathBuf,\n    input_path: PathBuf,\n    temporary_output_path: NamedTempFile,\n    output_path: PathBuf,\n    cfg: WasmOptConfig,\n}\n\nimpl WasmOpt {\n    pub async fn new(\n        input_path: &Path,\n        output_path: &Path,\n        cfg: &WasmOptConfig,\n    ) -> anyhow::Result<Self> {\n        let path = get_binary_path().await?;\n        Ok(Self {\n            path,\n            input_path: input_path.to_path_buf(),\n            temporary_output_path: tempfile::NamedTempFile::new()?,\n            output_path: output_path.to_path_buf(),\n            cfg: cfg.clone(),\n        })\n    }\n\n    /// Create the command to run wasm-opt\n    fn build_command(&self) -> tokio::process::Command {\n        // defaults needed by wasm-opt.\n        // wasm is a moving target, and we add these by default since they progressively get enabled by default.\n        let mut args = vec![\n            \"--enable-reference-types\",\n            \"--enable-bulk-memory\",\n            \"--enable-mutable-globals\",\n            \"--enable-nontrapping-float-to-int\",\n            \"--enable-threads\",\n        ];\n\n        if self.cfg.memory_packing {\n            // needed for our current approach to bundle splitting to work properly\n            // todo(jon): emit the main module's data section in chunks instead of all at once\n            args.push(\"--memory-packing\");\n        }\n\n        if !self.cfg.debug {\n            args.push(\"--strip-debug\");\n        } else {\n            args.push(\"--debuginfo\");\n        }\n\n        for extra in &self.cfg.extra_features {\n            args.push(extra);\n        }\n\n        let level = match self.cfg.level {\n            WasmOptLevel::Z => \"-Oz\",\n            WasmOptLevel::S => \"-Os\",\n            WasmOptLevel::Zero => \"-O0\",\n            WasmOptLevel::One => \"-O1\",\n            WasmOptLevel::Two => \"-O2\",\n            WasmOptLevel::Three => \"-O3\",\n            WasmOptLevel::Four => \"-O4\",\n        };\n\n        tracing::debug!(\n            \"Running wasm-opt: {} {} {} -o {} {}\",\n            self.path.to_string_lossy(),\n            self.input_path.to_string_lossy(),\n            level,\n            self.temporary_output_path.path().to_string_lossy(),\n            args.join(\" \")\n        );\n        let mut command = tokio::process::Command::new(&self.path);\n        command\n            .arg(&self.input_path)\n            .arg(level)\n            .arg(\"-o\")\n            .arg(self.temporary_output_path.path())\n            .args(args);\n        command\n    }\n\n    pub async fn optimize(&self) -> Result<()> {\n        let mut command = self.build_command();\n        let res = command.output().await?;\n\n        if !res.status.success() {\n            let err = String::from_utf8_lossy(&res.stderr);\n            tracing::error!(\n                telemetry = %serde_json::json!({ \"event\": \"wasm_opt_failed\" }),\n                \"wasm-opt failed with status code {}\\nstderr: {}\\nstdout: {}\",\n                res.status,\n                err,\n                String::from_utf8_lossy(&res.stdout)\n            );\n            // A failing wasm-opt execution may leave behind an empty file so copy the original file instead.\n            if self.input_path != self.output_path {\n                std::fs::copy(&self.input_path, &self.output_path).unwrap();\n            }\n        } else {\n            std::fs::copy(self.temporary_output_path.path(), &self.output_path).unwrap();\n        }\n\n        Ok(())\n    }\n}\n\n// Find the URL for the latest binaryen release that contains wasm-opt\nasync fn find_latest_wasm_opt_download_url() -> anyhow::Result<String> {\n    // Find the platform identifier based on the current OS and architecture\n    // hardcoded for now to get around github api rate limits\n    if cfg!(all(target_os = \"windows\", target_arch = \"x86_64\")) {\n        return Ok(\"https://github.com/WebAssembly/binaryen/releases/download/version_123/binaryen-version_123-x86_64-windows.tar.gz\".to_string());\n    } else if cfg!(all(target_os = \"linux\", target_arch = \"x86_64\")) {\n        return Ok(\"https://github.com/WebAssembly/binaryen/releases/download/version_123/binaryen-version_123-x86_64-linux.tar.gz\".to_string());\n    } else if cfg!(all(target_os = \"linux\", target_arch = \"aarch64\")) {\n        return Ok(\"https://github.com/WebAssembly/binaryen/releases/download/version_123/binaryen-version_123-aarch64-linux.tar.gz\".to_string());\n    } else if cfg!(all(target_os = \"macos\", target_arch = \"x86_64\")) {\n        return Ok(\"https://github.com/WebAssembly/binaryen/releases/download/version_123/binaryen-version_123-x86_64-macos.tar.gz\".to_string());\n    } else if cfg!(all(target_os = \"macos\", target_arch = \"aarch64\")) {\n        return Ok(\"https://github.com/WebAssembly/binaryen/releases/download/version_123/binaryen-version_123-arm64-macos.tar.gz\".to_string());\n    };\n\n    let url = \"https://api.github.com/repos/WebAssembly/binaryen/releases/latest\";\n    let client = reqwest::Client::new();\n    let response = client\n        .get(url)\n        .header(\"User-Agent\", \"dioxus-cli\")\n        .send()\n        .await?\n        .json::<serde_json::Value>()\n        .await?;\n\n    tracing::trace!(\"Response from GitHub: {:#?}\", response);\n\n    let assets = response\n        .get(\"assets\")\n        .and_then(|assets| assets.as_array())\n        .ok_or_else(|| anyhow::anyhow!(\"Failed to parse assets\"))?;\n\n    // Find the platform identifier based on the current OS and architecture\n    let platform = if cfg!(all(target_os = \"windows\", target_arch = \"x86_64\")) {\n        \"x86_64-windows\"\n    } else if cfg!(all(target_os = \"linux\", target_arch = \"x86_64\")) {\n        \"x86_64-linux\"\n    } else if cfg!(all(target_os = \"linux\", target_arch = \"aarch64\")) {\n        \"aarch64-linux\"\n    } else if cfg!(all(target_os = \"macos\", target_arch = \"x86_64\")) {\n        \"x86_64-macos\"\n    } else if cfg!(all(target_os = \"macos\", target_arch = \"aarch64\")) {\n        \"arm64-macos\"\n    } else {\n        bail!(\"Unknown platform for wasm-opt installation. Please install wasm-opt manually from https://github.com/WebAssembly/binaryen/releases and add it to your PATH.\");\n    };\n\n    // Find the first asset with a name that contains the platform string\n    let asset = assets\n        .iter()\n        .find(|asset| {\n            asset\n                .get(\"name\")\n                .and_then(|name| name.as_str())\n                .is_some_and(|name| name.contains(platform) && !name.ends_with(\"sha256\"))\n        })\n        .with_context(|| anyhow!(\n            \"No suitable wasm-opt binary found for platform: {}. Please install wasm-opt manually from https://github.com/WebAssembly/binaryen/releases and add it to your PATH.\",\n            platform\n        ))?;\n\n    // Extract the download URL from the asset\n    let download_url = asset\n        .get(\"browser_download_url\")\n        .and_then(|url| url.as_str())\n        .ok_or_else(|| anyhow::anyhow!(\"Failed to get download URL for wasm-opt\"))?;\n\n    Ok(download_url.to_string())\n}\n\n/// Get the path to the wasm-opt binary, downloading it if necessary\npub async fn get_binary_path() -> anyhow::Result<PathBuf> {\n    let install_dir = install_dir();\n    let install_path = installed_bin_path(&install_dir);\n\n    if install_path.exists() {\n        return Ok(install_path);\n    }\n\n    if CliSettings::prefer_no_downloads() {\n        if let Ok(existing) = which::which(\"wasm-opt\") {\n            return Ok(existing);\n        } else {\n            return Err(anyhow!(\"Missing wasm-opt\"));\n        }\n    }\n\n    tracing::info!(\"Installing wasm-opt\");\n    install_github(&install_dir).await?;\n    tracing::info!(\"wasm-opt installed from Github\");\n\n    Ok(install_path)\n}\n\npub fn installed_location() -> Option<PathBuf> {\n    let install_dir = install_dir();\n    let install_path = installed_bin_path(&install_dir);\n\n    if install_path.exists() {\n        return Some(install_path);\n    }\n\n    if CliSettings::prefer_no_downloads() {\n        if let Ok(existing) = which::which(\"wasm-opt\") {\n            return Some(existing);\n        } else {\n            return None;\n        }\n    }\n\n    None\n}\n\nfn install_dir() -> PathBuf {\n    Workspace::dioxus_data_dir().join(\"binaryen\")\n}\n\nfn installed_bin_name() -> &'static str {\n    if cfg!(windows) {\n        \"wasm-opt.exe\"\n    } else {\n        \"wasm-opt\"\n    }\n}\n\nfn installed_bin_path(install_dir: &Path) -> PathBuf {\n    install_dir.join(\"bin\").join(installed_bin_name())\n}\n\n/// Install wasm-opt from GitHub releases into the specified directory\nasync fn install_github(install_dir: &Path) -> anyhow::Result<()> {\n    tracing::trace!(\"Attempting to install wasm-opt from GitHub\");\n\n    std::fs::create_dir_all(install_dir)?;\n\n    let url = find_latest_wasm_opt_download_url()\n        .await\n        .context(\"Failed to find latest wasm-opt download URL\")?;\n    tracing::trace!(\"Downloading wasm-opt from {}\", url);\n\n    // Download the binaryen release archive into memory\n    let bytes = reqwest::get(url).await?.bytes().await?;\n\n    // We don't need the whole gzip archive, just the wasm-opt binary and the lib folder. We\n    // just extract those files from the archive.\n    let installed_bin_path = installed_bin_path(install_dir);\n    let lib_folder_name = \"lib\";\n    let installed_lib_path = install_dir.join(lib_folder_name);\n\n    // Create the lib and bin directories if they don't exist\n    for path in [installed_bin_path.parent(), Some(&installed_lib_path)]\n        .into_iter()\n        .flatten()\n    {\n        std::fs::create_dir_all(path)\n            .context(format!(\"Failed to create directory: {}\", path.display()))?;\n    }\n\n    let mut archive = Archive::new(GzDecoder::new(bytes.as_ref()));\n\n    // Unpack the binary and library files from the archive\n    for mut entry in archive.entries()?.flatten() {\n        // Unpack the wasm-opt binary\n        if entry\n            .path_bytes()\n            .ends_with(installed_bin_name().as_bytes())\n        {\n            entry.unpack(&installed_bin_path)?;\n        }\n        // Unpack any files in the lib folder\n        else if let Ok(path) = entry.path() {\n            if path.components().any(|c| c.as_os_str() == lib_folder_name) {\n                if let Some(file_name) = path.file_name() {\n                    entry.unpack(installed_lib_path.join(file_name))?;\n                }\n            }\n        }\n    }\n\n    Ok(())\n}\n"
  },
  {
    "path": "packages/cli/src/workspace.rs",
    "content": "use crate::styles::GLOW_STYLE;\nuse crate::CliSettings;\nuse crate::Result;\nuse crate::{config::DioxusConfig, AndroidTools};\nuse anyhow::{bail, Context};\nuse ignore::gitignore::Gitignore;\nuse krates::{semver::Version, KrateDetails, LockOptions};\nuse krates::{Cmd, Krates, NodeId};\nuse std::sync::Arc;\nuse std::{collections::HashSet, path::Path};\nuse std::{path::PathBuf, time::Duration};\nuse target_lexicon::Triple;\nuse tokio::process::Command;\n\npub struct Workspace {\n    pub(crate) krates: Krates,\n    pub(crate) settings: CliSettings,\n    pub(crate) wasm_opt: Option<PathBuf>,\n    pub(crate) sysroot: PathBuf,\n    pub(crate) rustc_version: String,\n    pub(crate) ignore: Gitignore,\n    pub(crate) cargo_toml: cargo_toml::Manifest,\n    pub(crate) android_tools: Option<Arc<AndroidTools>>,\n}\n\nimpl Workspace {\n    /// Load the workspace from the current directory. This is cached and will only be loaded once.\n    pub async fn current() -> Result<Arc<Workspace>> {\n        static WS: tokio::sync::Mutex<Option<Arc<Workspace>>> = tokio::sync::Mutex::const_new(None);\n\n        // Lock the workspace to prevent multiple threads from loading it at the same time\n        // If loading the workspace failed the first time, it won't be set and therefore permeate an error.\n        let mut lock = WS.lock().await;\n        if let Some(ws) = lock.as_ref() {\n            return Ok(ws.clone());\n        }\n\n        let krates_future = tokio::task::spawn_blocking(|| {\n            let manifest_options = crate::logging::VERBOSITY.get().unwrap();\n            let lock_options = LockOptions {\n                frozen: manifest_options.frozen,\n                locked: manifest_options.locked,\n                offline: manifest_options.offline,\n            };\n\n            let mut cmd = Cmd::new();\n            cmd.lock_opts(lock_options);\n\n            let mut builder = krates::Builder::new();\n            builder.workspace(true);\n            let res = builder.build(cmd, |_| {})?;\n\n            if !lock_options.offline {\n                if let Ok(res) = std::env::var(\"SIMULATE_SLOW_NETWORK\") {\n                    std::thread::sleep(Duration::from_secs(res.parse().unwrap_or(5)));\n                }\n            }\n\n            Ok(res) as Result<Krates, krates::Error>\n        });\n\n        let spin_future = async move {\n            tokio::time::sleep(Duration::from_millis(1000)).await;\n            eprintln!(\"{GLOW_STYLE}warning{GLOW_STYLE:#}: Waiting for cargo-metadata...\");\n            tokio::time::sleep(Duration::from_millis(2000)).await;\n            for x in 1..=100 {\n                tokio::time::sleep(Duration::from_millis(2000)).await;\n                eprintln!(\"{GLOW_STYLE}warning{GLOW_STYLE:#}: (Try {x}) Taking a while...\");\n\n                if x % 10 == 0 {\n                    eprintln!(\"{GLOW_STYLE}warning{GLOW_STYLE:#}: maybe check your network connection or build lock?\");\n                }\n            }\n        };\n\n        let krates = tokio::select! {\n            f = krates_future => {\n                let res = f?;\n                if let Err(krates::Error::Metadata(e)) = res {\n                    bail!(\"{e}\");\n                }\n                res?\n            },\n            _ = spin_future => bail!(\"cargo metadata took too long to respond, try again with --offline\"),\n        };\n\n        let settings = CliSettings::global_or_default();\n        let sysroot = Self::get_rustc_sysroot()\n            .await\n            .context(\"Failed to get rustc sysroot\")?;\n        let rustc_version = Self::get_rustc_version()\n            .await\n            .context(\"Failed to get rustc version\")?;\n\n        let wasm_opt = which::which(\"wasm-opt\").ok();\n\n        let ignore = Self::workspace_gitignore(krates.workspace_root().as_std_path());\n\n        let cargo_toml = crate::cargo_toml::load_manifest_from_path(\n            krates.workspace_root().join(\"Cargo.toml\").as_std_path(),\n        )\n        .context(\"Failed to load Cargo.toml\")?;\n\n        let android_tools = crate::build::get_android_tools();\n\n        let workspace = Arc::new(Self {\n            krates,\n            settings,\n            wasm_opt,\n            sysroot: sysroot.trim().into(),\n            rustc_version: rustc_version.trim().into(),\n            ignore,\n            cargo_toml,\n            android_tools,\n        });\n\n        tracing::debug!(\n            r#\"Initialized workspace:\n               • sysroot: {sysroot}\n               • rustc version: {rustc_version}\n               • workspace root: {workspace_root}\n               • dioxus versions: [{dioxus_versions:?}]\"#,\n            sysroot = workspace.sysroot.display(),\n            rustc_version = workspace.rustc_version,\n            workspace_root = workspace.workspace_root().display(),\n            dioxus_versions = workspace\n                .dioxus_versions()\n                .iter()\n                .map(|v| v.to_string())\n                .collect::<Vec<_>>()\n                .join(\", \")\n        );\n\n        lock.replace(workspace.clone());\n\n        Ok(workspace)\n    }\n\n    pub fn android_tools(&self) -> Result<Arc<AndroidTools>> {\n        self\n            .android_tools\n            .clone()\n            .context(\"Android not installed properly. Please set the `ANDROID_NDK_HOME` environment variable to the root of your NDK installation.\")\n    }\n\n    pub fn is_release_profile(&self, profile: &str) -> bool {\n        // If the profile is \"release\" or ends with \"-release\" like the default platform release profiles,\n        // always put it in the release category.\n        if profile == \"release\" || profile.ends_with(\"-release\") {\n            return true;\n        }\n\n        // Check if the profile inherits from release by traversing the `inherits` chain\n        let mut current_profile_name = profile;\n\n        // Try to find the current profile in the custom profiles section\n        while let Some(profile_settings) = self.cargo_toml.profile.custom.get(current_profile_name)\n        {\n            // Check what this profile inherits from\n            match &profile_settings.inherits {\n                // Otherwise, continue checking the profile it inherits from\n                Some(inherits_name) => current_profile_name = inherits_name,\n\n                // This profile doesn't explicitly inherit anything, so the chain ends here.\n                // Since it didn't lead to \"release\", return false.\n                None => break,\n            }\n\n            if current_profile_name == \"release\" {\n                return true;\n            }\n        }\n\n        false\n    }\n\n    pub fn check_dioxus_version_against_cli(&self) {\n        let dx_semver = Version::parse(env!(\"CARGO_PKG_VERSION\")).unwrap();\n        let dioxus_versions = self.dioxus_versions();\n\n        tracing::trace!(\"dx version: {}\", dx_semver);\n        tracing::trace!(\"dioxus versions: {:?}\", dioxus_versions);\n\n        // if there are no dioxus versions in the workspace, we don't need to check anything\n        // dx is meant to be compatible with non-dioxus projects too.\n        if dioxus_versions.is_empty() {\n            return;\n        }\n\n        let min = dioxus_versions.iter().min().unwrap();\n        let max = dioxus_versions.iter().max().unwrap();\n\n        // If the minimum dioxus version is greater than the current cli version, warn the user\n        if min > &dx_semver\n            || max < &dx_semver\n            || dioxus_versions.iter().any(|f| f.pre != dx_semver.pre)\n        {\n            tracing::error!(\n                r#\"🚫dx and dioxus versions are incompatible!\n                  • dx version: {dx_semver}\n                  • dioxus versions: [{}]\"#,\n                dioxus_versions\n                    .iter()\n                    .map(|v| v.to_string())\n                    .collect::<Vec<_>>()\n                    .join(\", \")\n            );\n        }\n    }\n\n    /// Get all the versions of dioxus in the workspace\n    pub fn dioxus_versions(&self) -> Vec<Version> {\n        let mut versions = HashSet::new();\n        for krate in self.krates.krates() {\n            if krate.name == \"dioxus\" {\n                versions.insert(krate.version.clone());\n            }\n        }\n        let mut versions = versions.into_iter().collect::<Vec<_>>();\n        versions.sort();\n        versions\n    }\n\n    #[allow(unused)]\n    pub fn rust_lld(&self) -> PathBuf {\n        self.sysroot\n            .join(\"lib\")\n            .join(\"rustlib\")\n            .join(Triple::host().to_string())\n            .join(\"bin\")\n            .join(\"rust-lld\")\n    }\n\n    /// Return the path to the `cc` compiler\n    ///\n    /// This is used for the patching system to run the linker.\n    /// We could also just use lld given to us by rust itself.\n    pub fn cc(&self) -> PathBuf {\n        PathBuf::from(\"cc\")\n    }\n\n    /// The windows linker\n    pub fn lld_link(&self) -> PathBuf {\n        self.gcc_ld_dir().join(\"lld-link\")\n    }\n\n    pub fn wasm_ld(&self) -> PathBuf {\n        self.gcc_ld_dir().join(\"wasm-ld\")\n    }\n\n    pub fn select_ranlib() -> Option<PathBuf> {\n        // prefer the modern llvm-ranlib if they have it\n        which::which(\"llvm-ranlib\")\n            .or_else(|_| which::which(\"ranlib\"))\n            .ok()\n    }\n\n    /// Return the version of the wasm-bindgen crate if it exists\n    pub fn wasm_bindgen_version(&self) -> Option<String> {\n        self.krates\n            .krates_by_name(\"wasm-bindgen\")\n            .next()\n            .map(|krate| krate.krate.version.to_string())\n    }\n\n    // wasm-ld: ./rustup/toolchains/nightly-x86_64-unknown-linux-gnu/bin/wasm-ld\n    // rust-lld: ./rustup/toolchains/nightly-x86_64-unknown-linux-gnu/bin/rust-lld\n    fn gcc_ld_dir(&self) -> PathBuf {\n        self.sysroot\n            .join(\"lib\")\n            .join(\"rustlib\")\n            .join(Triple::host().to_string())\n            .join(\"bin\")\n            .join(\"gcc-ld\")\n    }\n\n    // wasm-ld: ./rustup/toolchains/nightly-x86_64-unknown-linux-gnu/bin/wasm-ld\n    // rust-lld: ./rustup/toolchains/nightly-x86_64-unknown-linux-gnu/bin/rust-lld\n    pub fn rustc_objcopy(&self) -> PathBuf {\n        self.sysroot\n            .join(\"lib\")\n            .join(\"rustlib\")\n            .join(Triple::host().to_string())\n            .join(\"bin\")\n            .join(\"rust-objcopy\")\n    }\n\n    // ./rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib\n    pub fn rustc_objcopy_dylib_path(&self) -> PathBuf {\n        self.sysroot.join(\"lib\")\n    }\n\n    /// Find the \"main\" package in the workspace. There might not be one!\n    pub fn find_main_package(&self, package: Option<String>) -> Result<NodeId> {\n        if let Some(package) = package {\n            let mut workspace_members = self.krates.workspace_members();\n            let found = workspace_members.find_map(|node| {\n                if let krates::Node::Krate { id, krate, .. } = node {\n                    if krate.name == package {\n                        return Some(id);\n                    }\n                }\n                None\n            });\n\n            if found.is_none() {\n                tracing::error!(\"Could not find package {package} in the workspace. Did you forget to add it to the workspace?\");\n                tracing::error!(\"Packages in the workspace:\");\n                for package in self.krates.workspace_members() {\n                    if let krates::Node::Krate { krate, .. } = package {\n                        tracing::error!(\"{}\", krate.name());\n                    }\n                }\n            }\n\n            let kid = found.ok_or_else(|| anyhow::anyhow!(\"Failed to find package {package}\"))?;\n\n            return Ok(self.krates.nid_for_kid(kid).unwrap());\n        };\n\n        // if we have default members specified, try them first\n        if let Some(ws) = &self.cargo_toml.workspace {\n            for default in &ws.default_members {\n                let mut workspace_members = self.krates.workspace_members();\n                let default_member_path = std::fs::canonicalize(default).unwrap();\n\n                let found = workspace_members.find_map(|node| {\n                    if let krates::Node::Krate { id, krate, .. } = node {\n                        // Skip this default member if it doesn't have any binary targets\n                        if !krate\n                            .targets\n                            .iter()\n                            .any(|t| t.kind.contains(&krates::cm::TargetKind::Bin))\n                        {\n                            return None;\n                        }\n                        let member_path =\n                            std::fs::canonicalize(krate.manifest_path.parent().unwrap()).unwrap();\n                        if member_path == default_member_path {\n                            return Some(id);\n                        }\n                    }\n                    None\n                });\n\n                if let Some(kid) = found {\n                    return Ok(self.krates.nid_for_kid(kid).unwrap());\n                }\n            }\n        }\n\n        // Otherwise find the package that is the closest parent of the current directory\n        let current_dir = std::env::current_dir()?;\n        let current_dir = current_dir.as_path();\n\n        // Go through each member and find the path that is a parent of the current directory\n        let mut closest_parent = None;\n        for member in self.krates.workspace_members() {\n            if let krates::Node::Krate { id, krate, .. } = member {\n                let member_path = krate.manifest_path.parent().unwrap();\n                if let Ok(path) = current_dir.strip_prefix(member_path.as_std_path()) {\n                    let len = path.components().count();\n                    match closest_parent {\n                        Some((_, closest_parent_len)) => {\n                            if len < closest_parent_len {\n                                closest_parent = Some((id, len));\n                            }\n                        }\n                        None => {\n                            closest_parent = Some((id, len));\n                        }\n                    }\n                }\n            }\n        }\n\n        let kid = closest_parent\n        .map(|(id, _)| id)\n        .with_context(|| {\n            let bin_targets = self.krates.workspace_members().filter_map(|krate|match krate {\n                krates::Node::Krate { krate, .. } if krate.targets.iter().any(|t| t.kind.contains(&krates::cm::TargetKind::Bin))=> {\n                    Some(format!(\"- {}\", krate.name))\n                }\n                _ => None\n            }).collect::<Vec<_>>();\n            format!(\"Failed to find binary package to build.\\nYou need to either run dx from inside a binary crate or specify a binary package to build with the `--package` flag. Try building again with one of the binary packages in the workspace:\\n{}\", bin_targets.join(\"\\n\"))\n        })?;\n\n        let package = self.krates.nid_for_kid(kid).unwrap();\n        Ok(package)\n    }\n\n    /// Load the Dioxus.toml configuration for a package.\n    ///\n    /// Optionally accepts a source file path to extract inline configuration from doc comments.\n    /// Inline config is merged with the base Dioxus.toml, with inline values taking precedence.\n    ///\n    /// This allows examples and binaries to embed config in their doc comments:\n    /// ```rust,ignore\n    /// //! ```dioxus.toml\n    /// //! [bundle]\n    /// //! identifier = \"com.example.app\"\n    /// //! ```\n    /// ```\n    pub fn load_dioxus_config(\n        &self,\n        package: NodeId,\n        source_file: Option<&Path>,\n    ) -> Result<Option<DioxusConfig>> {\n        // Walk up from the cargo.toml to the root of the workspace looking for Dioxus.toml\n        let mut current_dir = self.krates[package]\n            .manifest_path\n            .parent()\n            .unwrap()\n            .as_std_path()\n            .to_path_buf()\n            .canonicalize()?;\n\n        let workspace_path = self\n            .krates\n            .workspace_root()\n            .as_std_path()\n            .to_path_buf()\n            .canonicalize()?;\n\n        let mut dioxus_conf_file = None;\n        while current_dir.starts_with(&workspace_path) {\n            let config = [\"Dioxus.toml\", \"dioxus.toml\"]\n                .into_iter()\n                .map(|file| current_dir.join(file))\n                .find(|path| path.is_file());\n\n            // Try to find Dioxus.toml in the current directory\n            if let Some(new_config) = config {\n                dioxus_conf_file = Some(new_config.as_path().to_path_buf());\n                break;\n            }\n            // If we can't find it, go up a directory\n            current_dir = current_dir\n                .parent()\n                .context(\"Failed to find Dioxus.toml\")?\n                .to_path_buf();\n        }\n\n        // Load base config from Dioxus.toml (if it exists)\n        let base_config: Option<DioxusConfig> = match &dioxus_conf_file {\n            Some(path) => {\n                let content = std::fs::read_to_string(path)?;\n                Some(toml::from_str(&content).map_err(|err| {\n                    anyhow::anyhow!(\"Failed to parse Dioxus.toml at {path:?}: {err}\")\n                })?)\n            }\n            None => None,\n        };\n\n        // Extract inline config from source file (if provided)\n        let inline_config = source_file.and_then(crate::config::extract_inline_config_from_file);\n\n        // Merge configs: inline overrides base\n        match (base_config, inline_config) {\n            (Some(base), Some(inline)) => crate::config::merge_with_inline_config(&base, inline)\n                .map(Some)\n                .map_err(|err| anyhow::anyhow!(\"Failed to merge inline config: {err}\")),\n            (Some(base), None) => Ok(Some(base)),\n            (None, Some(inline)) => {\n                // No Dioxus.toml, but we have inline config - use defaults + inline\n                let base = DioxusConfig::default();\n                crate::config::merge_with_inline_config(&base, inline)\n                    .map(Some)\n                    .map_err(|err| anyhow::anyhow!(\"Failed to merge inline config: {err}\"))\n            }\n            (None, None) => Ok(None),\n        }\n    }\n\n    /// Create a new gitignore map for this target crate\n    ///\n    /// todo(jon): this is a bit expensive to build, so maybe we should cache it?\n    pub fn workspace_gitignore(workspace_dir: &Path) -> Gitignore {\n        let mut ignore_builder = ignore::gitignore::GitignoreBuilder::new(workspace_dir);\n        ignore_builder.add(workspace_dir.join(\".gitignore\"));\n\n        for path in Self::default_ignore_list() {\n            ignore_builder\n                .add_line(None, path)\n                .expect(\"failed to add path to file excluded\");\n        }\n\n        ignore_builder.build().unwrap()\n    }\n\n    pub fn ignore_for_krate(&self, path: &Path) -> ignore::gitignore::Gitignore {\n        let mut ignore_builder = ignore::gitignore::GitignoreBuilder::new(path);\n        for path in Self::default_ignore_list() {\n            ignore_builder\n                .add_line(None, path)\n                .expect(\"failed to add path to file excluded\");\n        }\n        ignore_builder.build().unwrap()\n    }\n\n    pub fn default_ignore_list() -> Vec<&'static str> {\n        vec![\n            \".git\",\n            \".github\",\n            \".vscode\",\n            \"target\",\n            \"node_modules\",\n            \"dist\",\n            \"*~\",\n            \".*\",\n            \"*.lock\",\n            \"*.log\",\n        ]\n    }\n\n    pub(crate) fn workspace_root(&self) -> PathBuf {\n        self.krates.workspace_root().as_std_path().to_path_buf()\n    }\n\n    /// Returns the root of the crate that the command is run from, without calling `cargo metadata`\n    ///\n    /// If the command is run from the workspace root, this will return the top-level Cargo.toml\n    pub(crate) fn crate_root_from_path() -> Result<PathBuf> {\n        /// How many parent folders are searched for a `Cargo.toml`\n        const MAX_ANCESTORS: u32 = 10;\n\n        /// Checks if the directory contains `Cargo.toml`\n        fn contains_manifest(path: &Path) -> bool {\n            std::fs::read_dir(path)\n                .map(|entries| {\n                    entries\n                        .filter_map(Result::ok)\n                        .any(|ent| &ent.file_name() == \"Cargo.toml\")\n                })\n                .unwrap_or(false)\n        }\n\n        // From the current directory we work our way up, looking for `Cargo.toml`\n        std::env::current_dir()\n            .ok()\n            .and_then(|mut wd| {\n                for _ in 0..MAX_ANCESTORS {\n                    if contains_manifest(&wd) {\n                        return Some(wd);\n                    }\n                    if !wd.pop() {\n                        break;\n                    }\n                }\n                None\n            })\n            .context(\"Failed to find directory containing Cargo.toml\")\n    }\n\n    pub async fn get_xcode_path() -> Option<PathBuf> {\n        let xcode = Command::new(\"xcode-select\")\n            .arg(\"-p\")\n            .output()\n            .await\n            .ok()\n            .map(|s| String::from_utf8_lossy(&s.stdout).trim().to_string().into());\n        xcode\n    }\n\n    pub async fn get_rustc_sysroot() -> Result<String, anyhow::Error> {\n        let sysroot = Command::new(\"rustc\")\n            .args([\"--print\", \"sysroot\"])\n            .output()\n            .await\n            .map(|out| String::from_utf8(out.stdout).map(|s| s.trim().to_string()))?\n            .context(\"Failed to extract rustc sysroot output\")?;\n        Ok(sysroot)\n    }\n\n    pub async fn get_rustc_version() -> Result<String> {\n        let rustc_version = Command::new(\"rustc\")\n            .args([\"--version\"])\n            .output()\n            .await\n            .map(|out| String::from_utf8(out.stdout))?\n            .context(\"Failed to extract rustc version output\")?;\n        Ok(rustc_version)\n    }\n\n    /// Returns the properly canonicalized path to the dx executable, used for linking and wrapping rustc\n    pub(crate) fn path_to_dx() -> Result<PathBuf> {\n        dunce::canonicalize(std::env::current_exe().context(\"Failed to find dx\")?)\n            .context(\"Failed to find dx\")\n    }\n\n    /// Returns the path to the dioxus data directory, used to install tools, store configs, and other things\n    ///\n    /// On macOS, we prefer to not put this dir in Application Support, but rather in the home directory.\n    /// On Windows, we prefer to keep it in the home directory so the `dx` install dir matches the install script.\n    pub(crate) fn dioxus_data_dir() -> PathBuf {\n        static DX_HOME: std::sync::OnceLock<PathBuf> = std::sync::OnceLock::new();\n        DX_HOME\n            .get_or_init(|| {\n                if let Some(path) = std::env::var_os(\"DX_HOME\") {\n                    return PathBuf::from(path);\n                }\n\n                if cfg!(target_os = \"macos\") || cfg!(target_os = \"windows\") {\n                    dirs::home_dir().unwrap().join(\".dx\")\n                } else {\n                    dirs::data_dir()\n                        .or_else(dirs::home_dir)\n                        .unwrap()\n                        .join(\".dx\")\n                }\n            })\n            .to_path_buf()\n    }\n\n    pub(crate) fn global_settings_file() -> PathBuf {\n        Self::dioxus_data_dir().join(\"settings.toml\")\n    }\n\n    /// The path where components downloaded from git are cached\n    pub(crate) fn component_cache_dir() -> PathBuf {\n        Self::dioxus_data_dir().join(\"components\")\n    }\n\n    /// Get the path to a specific component in the cache\n    pub(crate) fn component_cache_path(git: &str, rev: Option<&str>) -> PathBuf {\n        use std::hash::Hasher;\n\n        let mut hasher = std::hash::DefaultHasher::new();\n        std::hash::Hash::hash(git, &mut hasher);\n        if let Some(rev) = rev {\n            std::hash::Hash::hash(rev, &mut hasher);\n        }\n        let hash = hasher.finish();\n        Self::component_cache_dir().join(format!(\"{hash:016x}\"))\n    }\n}\n\nimpl std::fmt::Debug for Workspace {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_struct(\"Workspace\")\n            .field(\"krates\", &\"..\")\n            .field(\"settings\", &self.settings)\n            .field(\"rustc_version\", &self.rustc_version)\n            .field(\"sysroot\", &self.sysroot)\n            .field(\"wasm_opt\", &self.wasm_opt)\n            .finish()\n    }\n}\n"
  },
  {
    "path": "packages/cli-config/.gitignore",
    "content": "/target\nCargo.lock\n.DS_Store\n.idea/\n"
  },
  {
    "path": "packages/cli-config/Cargo.toml",
    "content": "[package]\nname = \"dioxus-cli-config\"\nedition = \"2021\"\nversion.workspace = true\nauthors = [\"Jonathan Kelley\", \"Evan Almloff\"]\nlicense = \"MIT OR Apache-2.0\"\nrepository = \"https://github.com/DioxusLabs/dioxus/\"\nhomepage = \"https://dioxuslabs.com\"\ndescription = \"CLI Configuration for dioxus-cli\"\nkeywords = [\"web\", \"desktop\", \"mobile\", \"gui\", \"wasm\"]\n\n[dependencies]\nwasm-bindgen = { workspace = true, optional = true }\n\n[features]\nweb = [\"dep:wasm-bindgen\"]\n"
  },
  {
    "path": "packages/cli-config/README.md",
    "content": "# dioxus-cli-config\n\nA crate that provides key/value names and types for configuring Dioxus applications at runtime.\n\nThis crate exists for us to very cleanly define the exact fields we want to pass down to Dioxus applications at runtime but without exposing the entire config object.\n\nThis leads to faster compile times, smaller binaries, and a clearer distinction between the config and the application.\n"
  },
  {
    "path": "packages/cli-config/src/lib.rs",
    "content": "//! <div align=\"center\">\n//!     <img\n//!         src=\"https://github.com/user-attachments/assets/6c7e227e-44ff-4e53-824a-67949051149c\"\n//!         alt=\"Build web, desktop, and mobile apps with a single codebase.\"\n//!         width=\"100%\"\n//!         class=\"darkmode-image\"\n//!     >\n//! </div>\n//!\n//! # Dioxus CLI configuration\n//!\n//! This crate exposes the various configuration options that the Dioxus CLI sets when running the\n//! application during development.\n//!\n//! Note that these functions will return a different value when running under the CLI, so make sure\n//! not to rely on them when running in a production environment.\n//!\n//! ## Constants\n//!\n//! The various constants here are the names of the environment variables that the CLI sets. We recommend\n//! using the functions in this crate to access the values of these environment variables indirectly.\n//!\n//! The CLI uses this crate and the associated constants to *set* the environment variables, but as\n//! a consumer of the CLI, you would want to read the values of these environment variables using\n//! the provided functions.\n//!\n//! ## Example Usage\n//!\n//! We recommend using the functions here to access the values of the environment variables set by the CLI.\n//! For example, you might use the [`fullstack_address_or_localhost`] function to get the address that\n//! the CLI is requesting the application to be served on.\n//!\n//! ```rust, ignore\n//! async fn launch_axum(app: axum::Router<()>) {\n//!     // Read the PORT and ADDR environment variables set by the CLI\n//!     let addr = dioxus_cli_config::fullstack_address_or_localhost();\n//!\n//!     // Bind to the address and serve the application\n//!     let listener = tokio::net::TcpListener::bind(&addr).await.unwrap();\n//!     axum::serve(listener, app.into_make_service())\n//!         .await\n//!         .unwrap();\n//! }\n//! ```\n//!\n//! ## Stability\n//!\n//! The *values* that these functions return are *not* guaranteed to be stable between patch releases\n//! of Dioxus. At any time, we might change the values that the CLI sets or the way that they are read.\n//!\n//! We also don't guarantee the stability of the env var names themselves. If you want to rely on a\n//! particular env var, use the defined constants in your code.\n\nuse std::{\n    net::{IpAddr, Ipv4Addr, SocketAddr},\n    path::PathBuf,\n};\n\npub const CLI_ENABLED_ENV: &str = \"DIOXUS_CLI_ENABLED\";\npub const SERVER_IP_ENV: &str = \"IP\";\npub const SERVER_PORT_ENV: &str = \"PORT\";\npub const DEVSERVER_IP_ENV: &str = \"DIOXUS_DEVSERVER_IP\";\npub const DEVSERVER_PORT_ENV: &str = \"DIOXUS_DEVSERVER_PORT\";\npub const ALWAYS_ON_TOP_ENV: &str = \"DIOXUS_ALWAYS_ON_TOP\";\npub const ASSET_ROOT_ENV: &str = \"DIOXUS_ASSET_ROOT\";\npub const APP_TITLE_ENV: &str = \"DIOXUS_APP_TITLE\";\npub const PRODUCT_NAME_ENV: &str = \"DIOXUS_PRODUCT_NAME\";\n\n#[deprecated(since = \"0.6.0\", note = \"The CLI currently does not set this.\")]\n#[doc(hidden)]\npub const OUT_DIR: &str = \"DIOXUS_OUT_DIR\";\npub const SESSION_CACHE_DIR: &str = \"DIOXUS_SESSION_CACHE_DIR\";\npub const BUILD_ID: &str = \"DIOXUS_BUILD_ID\";\n\n/// Reads an environment variable at runtime in debug mode or at compile time in\n/// release mode. When bundling in release mode, we will not be running under the\n/// environment variables that the CLI sets, so we need to read them at compile time.\nmacro_rules! read_env_config {\n    ($name:expr) => {{\n        #[cfg(debug_assertions)]\n        {\n            // In debug mode, read the environment variable set by the CLI at runtime\n            std::env::var($name).ok()\n        }\n\n        #[cfg(not(debug_assertions))]\n        {\n            // In release mode, read the environment variable set by the CLI at compile time\n            // This means the value will still be available when running the application\n            // standalone.\n            // We don't always read the environment variable at compile time to avoid rebuilding\n            // this crate when the environment variable changes.\n            option_env!($name).map(ToString::to_string)\n        }\n    }};\n}\n\n/// Get the address of the devserver for use over a raw socket\n///\n/// This returns a [`SocketAddr`], meaning that you still need to connect to it using a socket with\n/// the appropriate protocol and path.\n///\n/// For reference, the devserver typically lives on `127.0.0.1:8080` and serves the devserver websocket\n/// on `127.0.0.1:8080/_dioxus`.\npub fn devserver_raw_addr() -> Option<SocketAddr> {\n    let port = std::env::var(DEVSERVER_PORT_ENV).ok();\n\n    if cfg!(target_os = \"android\") {\n        // Since `adb reverse` is used for Android, the 127.0.0.1 will always be\n        // the correct IP address.\n        let port = port.unwrap_or(\"8080\".to_string());\n        return Some(format!(\"127.0.0.1:{}\", port).parse().unwrap());\n    }\n\n    let port = port?;\n    let ip = std::env::var(DEVSERVER_IP_ENV).ok()?;\n\n    format!(\"{}:{}\", ip, port).parse().ok()\n}\n\n/// Get the address of the devserver for use over a websocket\n///\n/// This is meant for internal use, though if you are building devtools around Dioxus, this would be\n/// useful to connect as a \"listener\" to the devserver.\n///\n/// Unlike [`devserver_raw_addr`], this returns a string that can be used directly to connect to the\n/// devserver over a websocket. IE `ws://127.0.0.1:8080/_dioxus`.\npub fn devserver_ws_endpoint() -> Option<String> {\n    let addr = devserver_raw_addr()?;\n    Some(format!(\"ws://{addr}/_dioxus\"))\n}\n\n/// Get the IP that the server should be bound to.\n///\n/// This is set by the CLI and is used to bind the server to a specific address.\n/// You can manually set the ip by setting the `IP` environment variable.\n///\n/// ```sh\n/// IP=0.0.0.0 ./server\n/// ```\npub fn server_ip() -> Option<IpAddr> {\n    std::env::var(SERVER_IP_ENV)\n        .ok()\n        .and_then(|s| s.parse().ok())\n}\n\n/// Get the port that the server should listen on.\n///\n/// This is set by the CLI and is used to bind the server to a specific port.\n/// You can manually set the port by setting the `PORT` environment variable.\n///\n/// ```sh\n/// PORT=8081 ./server\n/// ```\npub fn server_port() -> Option<u16> {\n    std::env::var(SERVER_PORT_ENV)\n        .ok()\n        .and_then(|s| s.parse().ok())\n}\n\n/// Get the full address that the server should listen on.\n///\n/// This is a convenience function that combines the `server_ip` and `server_port` functions and then\n/// falls back to `localhost:8080` if the environment variables are not set.\n///\n/// ## Example\n///\n/// ```rust, ignore\n/// async fn launch_axum(app: axum::Router<()>) {\n///     // Read the PORT and ADDR environment variables set by the CLI\n///     let addr = dioxus_cli_config::fullstack_address_or_localhost();\n///\n///     // Bind to the address and serve the application\n///     let listener = tokio::net::TcpListener::bind(&addr).await.unwrap();\n///     axum::serve(listener, app.into_make_service())\n///         .await\n///         .unwrap();\n/// }\n/// ```\n///\n/// ## Stability\n///\n/// In the future, we might change the address from 127.0.0.1 to 0.0.0.0.\npub fn fullstack_address_or_localhost() -> SocketAddr {\n    let ip = server_ip().unwrap_or_else(|| IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)));\n    let port = server_port().unwrap_or(8080);\n    SocketAddr::new(ip, port)\n}\n\n/// Get the title of the application, usually set by the Dioxus.toml.\n///\n/// This is used to set the title of the desktop window if the app itself doesn't set it.\npub fn app_title() -> Option<String> {\n    read_env_config!(\"DIOXUS_APP_TITLE\")\n}\n\n/// Check if the application should forced to \"float\" on top of other windows.\n///\n/// The CLI sets this based on the `--always-on-top` flag and the settings system.\npub fn always_on_top() -> Option<bool> {\n    std::env::var(ALWAYS_ON_TOP_ENV)\n        .ok()\n        .and_then(|s| s.parse().ok())\n}\n\n/// Check if the CLI is enabled when running the application.\n///\n/// The CLI *always* sets this value to true when running the application.\n///\n/// ## Note\n///\n/// On Android and the Web, this *might* not be reliable since there isn't always a consistent way to\n/// pass off the CLI environment variables to the application.\npub fn is_cli_enabled() -> bool {\n    // todo: (jon) - on android and web we should fix this...\n    std::env::var(CLI_ENABLED_ENV).is_ok()\n}\n\n/// Get the path where the application will be served from.\n///\n/// This is used by the router to format the URLs. For example, an app with a base path of `dogapp` will\n/// be served at `http://localhost:8080/dogapp`.\n///\n/// All assets will be served from this base path as well, ie `http://localhost:8080/dogapp/assets/logo.png`.\n#[allow(unreachable_code)]\npub fn base_path() -> Option<String> {\n    // This may trigger when compiling to the server if you depend on another crate that pulls in\n    // the web feature. It might be better for the renderers to provide the current platform\n    // as a global context\n    #[cfg(all(feature = \"web\", target_arch = \"wasm32\"))]\n    {\n        return web_base_path();\n    }\n\n    read_env_config!(\"DIOXUS_ASSET_ROOT\")\n}\n\n#[cfg(feature = \"web\")]\n#[wasm_bindgen::prelude::wasm_bindgen(inline_js = r#\"\n        export function getMetaContents(meta_name) {\n            const selector = document.querySelector(`meta[name=\"${meta_name}\"]`);\n            if (!selector) {\n                return null;\n            }\n            return selector.content;\n        }\n    \"#)]\nextern \"C\" {\n    #[wasm_bindgen(js_name = getMetaContents)]\n    pub fn get_meta_contents(selector: &str) -> Option<String>;\n}\n\n/// Get the path where the application is served from in the browser.\n///\n/// This uses wasm_bindgen on the browser to extract the base path from a meta element.\n#[cfg(feature = \"web\")]\npub fn web_base_path() -> Option<String> {\n    // In debug mode, we get the base path from the meta element which can be hot reloaded and changed without recompiling\n    #[cfg(debug_assertions)]\n    {\n        thread_local! {\n            static BASE_PATH: std::cell::OnceCell<Option<String>> = const { std::cell::OnceCell::new() };\n        }\n        BASE_PATH.with(|f| f.get_or_init(|| get_meta_contents(ASSET_ROOT_ENV)).clone())\n    }\n\n    // In release mode, we get the base path from the environment variable\n    #[cfg(not(debug_assertions))]\n    {\n        option_env!(\"DIOXUS_ASSET_ROOT\").map(ToString::to_string)\n    }\n}\n\n/// Format a meta element for the base path to be used in the output HTML\n#[doc(hidden)]\npub fn format_base_path_meta_element(base_path: &str) -> String {\n    format!(r#\"<meta name=\"{ASSET_ROOT_ENV}\" content=\"{base_path}\">\"#,)\n}\n\n/// Get the path to the output directory where the application is being built.\n///\n/// This might not return a valid path - we don't recommend relying on this.\n#[doc(hidden)]\n#[deprecated(\n    since = \"0.6.0\",\n    note = \"The does not set the OUT_DIR environment variable.\"\n)]\npub fn out_dir() -> Option<PathBuf> {\n    #[allow(deprecated)]\n    {\n        std::env::var(OUT_DIR).ok().map(PathBuf::from)\n    }\n}\n\n/// Get the directory where this app can write to for this session that's guaranteed to be stable\n/// between reloads of the same app. This is useful for emitting state like window position and size\n/// so the app can restore it when it's next opened.\n///\n/// Note that this cache dir is really only useful for platforms that can access it. Web/Android\n/// don't have access to this directory, so it's not useful for them.\n///\n/// This is designed with desktop executables in mind.\npub fn session_cache_dir() -> Option<PathBuf> {\n    if cfg!(target_os = \"android\") {\n        return Some(android_session_cache_dir());\n    }\n\n    std::env::var(SESSION_CACHE_DIR).ok().map(PathBuf::from)\n}\n\n/// The session cache directory for android\npub fn android_session_cache_dir() -> PathBuf {\n    PathBuf::from(\"/data/local/tmp/dx/\")\n}\n\n/// The unique build id for this application, used to disambiguate between different builds of the same\n/// application.\npub fn build_id() -> u64 {\n    #[cfg(target_arch = \"wasm32\")]\n    {\n        0\n    }\n\n    #[cfg(not(target_arch = \"wasm32\"))]\n    {\n        std::env::var(BUILD_ID)\n            .ok()\n            .and_then(|s| s.parse().ok())\n            .unwrap_or(0)\n    }\n}\n\n/// The product name of the bundled application.\npub fn product_name() -> Option<String> {\n    read_env_config!(\"DIOXUS_PRODUCT_NAME\")\n}\n"
  },
  {
    "path": "packages/cli-harnesses/.gitignore",
    "content": "!README.md\n!.gitignore\n"
  },
  {
    "path": "packages/cli-harnesses/README.md",
    "content": "# Test harnesses\n\nThis folder contains a series of CLI harnesses that represent common structures of dioxus apps.\n\nWe test these projects as a form of smoke testing to ensure our argument resolution works properly.\n\nThese projects might not be functionally useful, but they do have interesting properties that the CLI tests.\n\nThe tests for the CLI are contained within the CLI itself.\n\n## This Folder\n\nThe items in this folder are procedurally generated by tests in the CLI crate. We will delete EVERYTHING that ends up here.\n\n## How platform resolution works\n\nDetermine which features, triple, profile, etc to pass to the build.\n\nMost of the time, users should use `dx serve --<platform>` where the platform name directly\ncorresponds to the feature in their cargo.toml. So,\n- `dx serve --web` will enable the `web` feature\n- `dx serve --mobile` will enable the `mobile` feature\n- `dx serve --desktop` will enable the `desktop` feature\n\nIn this case, we set default-features to false and then add back the default features that\naren't renderers, and then add the feature for the given renderer (i.e., web/desktop/mobile).\nWe call this \"no-default-features-stripped.\"\n\nThere are a few cases where the user doesn't need to pass a platform.\n- they selected one via `dioxus = { features = [\"web\"] }`\n- they have a single platform in their default features `default = [\"web\"]`\n- there is only a single non-server renderer as a feature `web = [\"dioxus/web\"], server = [\"dioxus/server\"]`\n- they compose the super triple via triple + bundleformat + features\n\nNote that we only use the names of the features to correspond with the platform.\nPlatforms are \"super triples\", meaning they contain information about\n- bundle format\n- target triple\n- how to serve\n- enabled features\n\nBy default, the --platform presets correspond to:\n- web:          bundle(web), triple(wasm32), serve(http-serve), features(\"web\")\n- desktop:      alias to mac/win/linux\n- mac:          bundle(mac), triple(host), serve(appbundle-open), features(\"desktop\")\n- windows:      bundle(exefolder), triple(host), serve(run-exe), features(\"desktop\")\n- linux:        bundle(appimage), triple(host), serve(run-exe), features(\"desktop\")\n- ios:          bundle(ios), triple(arm64-apple-ios), serve(ios-simulator/xcrun), features(\"mobile\")\n- android:      bundle(android), triple(arm64-apple-ios), serve(android-emulator/adb), features(\"mobile\")\n- server:       bundle(server), triple(host), serve(run-exe), features(\"server\")\n- liveview:     bundle(liveview), triple(host), serve(run-exe), features(\"liveview\")\n- unknown:      <auto or default to desktop>\n\nFullstack usage is inferred from the presence of the fullstack feature or --fullstack.\n\n## List of harnesses (brainstorming)\n\n- app that doesn't use dioxus\n- simple app where the renderer is used as an explicit feature\n- simple app, same as above, but with fullstack enabled\n  - since no `server` feature is present, we don't run the server\n- simple app, same as above, but with a `server` feature\n  - server should launch and take precedence over the client\n- simple server-only app\n  - IE you're doing SSR with dioxus\n"
  },
  {
    "path": "packages/cli-harnesses/harness-default-to-non-default/Cargo.toml",
    "content": "[package]\nname = \"harness-default-to-non-default\"\nversion = \"0.0.1\"\nedition = \"2021\"\nlicense = \"MIT OR Apache-2.0\"\npublish = false\n\n[dependencies]\ndioxus = { workspace = true, features = [] }\n\n\n[features]\ndefault=[\"web\"]\nweb=[\"dioxus/web\"]\n\n    "
  },
  {
    "path": "packages/cli-harnesses/harness-default-to-non-default/src/main.rs",
    "content": "use dioxus::prelude::*;\nfn main() {\n    dioxus::launch(|| rsx! { \"hello world!\" })\n}\n"
  },
  {
    "path": "packages/cli-harnesses/harness-fullstack-desktop/Cargo.toml",
    "content": "[package]\nname = \"harness-fullstack-desktop\"\nversion = \"0.0.1\"\nedition = \"2021\"\nlicense = \"MIT OR Apache-2.0\"\npublish = false\n\n[dependencies]\ndioxus = { workspace = true, features = [\"fullstack\"] }\n\n\n[features]\ndesktop=[\"dioxus/desktop\"]\nserver=[\"dioxus/server\"]\n\n    "
  },
  {
    "path": "packages/cli-harnesses/harness-fullstack-desktop/src/main.rs",
    "content": "use dioxus::prelude::*;\nfn main() {\n    dioxus::launch(|| rsx! { \"hello world!\" })\n}\n"
  },
  {
    "path": "packages/cli-harnesses/harness-fullstack-desktop-with-default/Cargo.toml",
    "content": "[package]\nname = \"harness-fullstack-desktop-with-default\"\nversion = \"0.0.1\"\nedition = \"2021\"\nlicense = \"MIT OR Apache-2.0\"\npublish = false\n\n[dependencies]\ndioxus = { workspace = true, features = [\"fullstack\"] }\nanyhow = { workspace = true, optional = true }\n\n\n[features]\ndefault=[\"desktop\"]\ndesktop=[\"dioxus/desktop\", \"has_anyhow\"]\nhas_anyhow=[\"dep:anyhow\"]\nserver=[\"dioxus/server\"]\n\n    "
  },
  {
    "path": "packages/cli-harnesses/harness-fullstack-desktop-with-default/src/main.rs",
    "content": "use dioxus::prelude::*;\nfn main() {\n    dioxus::launch(|| rsx! { \"hello world!\" })\n}\n"
  },
  {
    "path": "packages/cli-harnesses/harness-fullstack-desktop-with-features/Cargo.toml",
    "content": "[package]\nname = \"harness-fullstack-desktop-with-features\"\nversion = \"0.0.1\"\nedition = \"2021\"\nlicense = \"MIT OR Apache-2.0\"\npublish = false\n\n[dependencies]\ndioxus = { workspace = true, features = [\"fullstack\"] }\nanyhow = { workspace = true, optional = true }\n\n\n[features]\ndesktop=[\"dioxus/desktop\", \"has_anyhow\"]\nhas_anyhow=[\"dep:anyhow\"]\nserver=[\"dioxus/server\"]\n\n    "
  },
  {
    "path": "packages/cli-harnesses/harness-fullstack-desktop-with-features/src/main.rs",
    "content": "use dioxus::prelude::*;\nfn main() {\n    dioxus::launch(|| rsx! { \"hello world!\" })\n}\n"
  },
  {
    "path": "packages/cli-harnesses/harness-fullstack-multi-target/Cargo.toml",
    "content": "[package]\nname = \"harness-fullstack-multi-target\"\nversion = \"0.0.1\"\nedition = \"2021\"\nlicense = \"MIT OR Apache-2.0\"\npublish = false\n\n[dependencies]\ndioxus = { workspace = true, features = [\"fullstack\"] }\n\n\n[features]\ndefault=[\"web\", \"desktop\", \"mobile\", \"server\"]\nweb=[\"dioxus/web\"]\ndesktop=[\"dioxus/desktop\"]\nmobile=[\"dioxus/mobile\"]\nserver=[\"dioxus/server\"]\n\n    "
  },
  {
    "path": "packages/cli-harnesses/harness-fullstack-multi-target/src/main.rs",
    "content": "use dioxus::prelude::*;\nfn main() {\n    dioxus::launch(|| rsx! { \"hello world!\" })\n}\n"
  },
  {
    "path": "packages/cli-harnesses/harness-fullstack-multi-target-no-default/Cargo.toml",
    "content": "[package]\nname = \"harness-fullstack-multi-target-no-default\"\nversion = \"0.0.1\"\nedition = \"2021\"\nlicense = \"MIT OR Apache-2.0\"\npublish = false\n\n[dependencies]\ndioxus = { workspace = true, features = [\"fullstack\"] }\n\n\n[features]\nweb=[\"dioxus/web\"]\ndesktop=[\"dioxus/desktop\"]\nmobile=[\"dioxus/mobile\"]\nserver=[\"dioxus/server\"]\n\n    "
  },
  {
    "path": "packages/cli-harnesses/harness-fullstack-multi-target-no-default/src/main.rs",
    "content": "use dioxus::prelude::*;\nfn main() {\n    dioxus::launch(|| rsx! { \"hello world!\" })\n}\n"
  },
  {
    "path": "packages/cli-harnesses/harness-fullstack-with-optional-tokio/Cargo.toml",
    "content": "[package]\nname = \"harness-fullstack-with-optional-tokio\"\nversion = \"0.0.1\"\nedition = \"2021\"\nlicense = \"MIT OR Apache-2.0\"\npublish = false\n\n[dependencies]\ndioxus = { workspace = true, features = [\"fullstack\"] }\nserde = \"1.0.219\"\ntokio = { workspace = true, features = [\"full\"], optional = true }\n\n\n[features]\ndefault = []\nserver = [\"dioxus/server\", \"dep:tokio\"]\nweb = [\"dioxus/web\"]\n\n    "
  },
  {
    "path": "packages/cli-harnesses/harness-fullstack-with-optional-tokio/src/main.rs",
    "content": "use dioxus::prelude::*;\nfn main() {\n    dioxus::launch(|| rsx! { \"hello world!\" })\n}\n"
  },
  {
    "path": "packages/cli-harnesses/harness-no-dioxus/Cargo.toml",
    "content": "[package]\nname = \"harness-no-dioxus\"\nversion = \"0.0.1\"\nedition = \"2021\"\nlicense = \"MIT OR Apache-2.0\"\npublish = false\n\n[dependencies]\nanyhow = { workspace = true, optional = true }\n\n\n[features]\nweb=[\"dep:anyhow\"]\nserver=[]\n\n    "
  },
  {
    "path": "packages/cli-harnesses/harness-no-dioxus/src/main.rs",
    "content": "fn main() {\n    println!(\"Hello, world!\");\n}\n"
  },
  {
    "path": "packages/cli-harnesses/harness-renderer-swap/Cargo.toml",
    "content": "[package]\nname = \"harness-renderer-swap\"\nversion = \"0.0.1\"\nedition = \"2021\"\nlicense = \"MIT OR Apache-2.0\"\npublish = false\n\n[dependencies]\ndioxus = { workspace = true, features = [\"fullstack\"] }\n\n\n[features]\ndefault=[\"desktop\", \"server\"]\ndesktop=[\"dioxus/desktop\"]\nnative=[\"dioxus/native\"]\nserver=[\"dioxus/server\"]\n\n    "
  },
  {
    "path": "packages/cli-harnesses/harness-renderer-swap/src/main.rs",
    "content": "use dioxus::prelude::*;\nfn main() {\n    dioxus::launch(|| rsx! { \"hello world!\" })\n}\n"
  },
  {
    "path": "packages/cli-harnesses/harness-simple-dedicated-client/Cargo.toml",
    "content": "[package]\nname = \"harness-simple-dedicated-client\"\nversion = \"0.0.1\"\nedition = \"2021\"\nlicense = \"MIT OR Apache-2.0\"\npublish = false\n\n[dependencies]\ndioxus = { workspace = true, features = [\"web\"] }\n\n\n[features]\n\n    "
  },
  {
    "path": "packages/cli-harnesses/harness-simple-dedicated-client/src/main.rs",
    "content": "fn main() {\n    println!(\"Hello, world!\");\n}\n"
  },
  {
    "path": "packages/cli-harnesses/harness-simple-dedicated-server/Cargo.toml",
    "content": "[package]\nname = \"harness-simple-dedicated-server\"\nversion = \"0.0.1\"\nedition = \"2021\"\nlicense = \"MIT OR Apache-2.0\"\npublish = false\n\n[dependencies]\n\n\n[features]\n\n    "
  },
  {
    "path": "packages/cli-harnesses/harness-simple-dedicated-server/src/main.rs",
    "content": "fn main() {\n    println!(\"Hello, world!\");\n}\n"
  },
  {
    "path": "packages/cli-harnesses/harness-simple-desktop/Cargo.toml",
    "content": "[package]\nname = \"harness-simple-desktop\"\nversion = \"0.0.1\"\nedition = \"2021\"\nlicense = \"MIT OR Apache-2.0\"\npublish = false\n\n[dependencies]\ndioxus = { workspace = true, features = [\"desktop\"] }\n\n\n[features]\n\n    "
  },
  {
    "path": "packages/cli-harnesses/harness-simple-desktop/src/main.rs",
    "content": "fn main() {\n    println!(\"Hello, world!\");\n}\n"
  },
  {
    "path": "packages/cli-harnesses/harness-simple-fullstack/Cargo.toml",
    "content": "[package]\nname = \"harness-simple-fullstack\"\nversion = \"0.0.1\"\nedition = \"2021\"\nlicense = \"MIT OR Apache-2.0\"\npublish = false\n\n[dependencies]\ndioxus = { workspace = true, features = [\"fullstack\"] }\n\n\n[features]\nweb=[\"dioxus/web\"]\nserver=[\"dioxus/server\"]\n\n    "
  },
  {
    "path": "packages/cli-harnesses/harness-simple-fullstack/src/main.rs",
    "content": "use dioxus::prelude::*;\nfn main() {\n    dioxus::launch(|| rsx! { \"hello world!\" })\n}\n"
  },
  {
    "path": "packages/cli-harnesses/harness-simple-fullstack-native-with-default/Cargo.toml",
    "content": "[package]\nname = \"harness-simple-fullstack-native-with-default\"\nversion = \"0.0.1\"\nedition = \"2021\"\nlicense = \"MIT OR Apache-2.0\"\npublish = false\n\n[dependencies]\ndioxus = { workspace = true, features = [\"fullstack\"] }\n\n\n[features]\ndefault=[\"native\", \"server\"]\nnative=[\"dioxus/native\"]\nserver=[\"dioxus/server\"]\n\n    "
  },
  {
    "path": "packages/cli-harnesses/harness-simple-fullstack-native-with-default/src/main.rs",
    "content": "use dioxus::prelude::*;\nfn main() {\n    dioxus::launch(|| rsx! { \"hello world!\" })\n}\n"
  },
  {
    "path": "packages/cli-harnesses/harness-simple-fullstack-with-default/Cargo.toml",
    "content": "[package]\nname = \"harness-simple-fullstack-with-default\"\nversion = \"0.0.1\"\nedition = \"2021\"\nlicense = \"MIT OR Apache-2.0\"\npublish = false\n\n[dependencies]\ndioxus = { workspace = true, features = [\"fullstack\"] }\n\n\n[features]\ndefault=[\"web\", \"server\"]\nweb=[\"dioxus/web\"]\nserver=[\"dioxus/server\"]\n\n    "
  },
  {
    "path": "packages/cli-harnesses/harness-simple-fullstack-with-default/src/main.rs",
    "content": "use dioxus::prelude::*;\nfn main() {\n    dioxus::launch(|| rsx! { \"hello world!\" })\n}\n"
  },
  {
    "path": "packages/cli-harnesses/harness-simple-mobile/Cargo.toml",
    "content": "[package]\nname = \"harness-simple-mobile\"\nversion = \"0.0.1\"\nedition = \"2021\"\nlicense = \"MIT OR Apache-2.0\"\npublish = false\n\n[dependencies]\ndioxus = { workspace = true, features = [\"mobile\"] }\n\n\n[features]\n\n    "
  },
  {
    "path": "packages/cli-harnesses/harness-simple-mobile/src/main.rs",
    "content": "fn main() {\n    println!(\"Hello, world!\");\n}\n"
  },
  {
    "path": "packages/cli-harnesses/harness-simple-web/Cargo.toml",
    "content": "[package]\nname = \"harness-simple-web\"\nversion = \"0.0.1\"\nedition = \"2021\"\nlicense = \"MIT OR Apache-2.0\"\npublish = false\n\n[dependencies]\ndioxus = { workspace = true, features = [\"web\"] }\n\n\n[features]\n\n    "
  },
  {
    "path": "packages/cli-harnesses/harness-simple-web/src/main.rs",
    "content": "fn main() {\n    println!(\"Hello, world!\");\n}\n"
  },
  {
    "path": "packages/cli-harnesses/harness-web-with-default-features/Cargo.toml",
    "content": "[package]\nname = \"harness-web-with-default-features\"\nversion = \"0.0.1\"\nedition = \"2021\"\nlicense = \"MIT OR Apache-2.0\"\npublish = false\n\n[dependencies]\ndioxus = { workspace = true }\n\n\n[features]\ndefault=[\"other\"]\nother=[]\n\n    "
  },
  {
    "path": "packages/cli-harnesses/harness-web-with-default-features/src/main.rs",
    "content": "fn main() {\n    println!(\"Hello, world!\");\n}\n"
  },
  {
    "path": "packages/cli-harnesses/harness-web-with-no-default-features/Cargo.toml",
    "content": "[package]\nname = \"harness-web-with-no-default-features\"\nversion = \"0.0.1\"\nedition = \"2021\"\nlicense = \"MIT OR Apache-2.0\"\npublish = false\n\n[dependencies]\ndioxus = { workspace = true }\n\n\n[features]\ndefault=[\"other\"]\nother=[]\n\n    "
  },
  {
    "path": "packages/cli-harnesses/harness-web-with-no-default-features/src/main.rs",
    "content": "fn main() {\n    println!(\"Hello, world!\");\n}\n"
  },
  {
    "path": "packages/cli-opt/Cargo.toml",
    "content": "[package]\nname = \"dioxus-cli-opt\"\nedition = \"2021\"\nversion.workspace = true\nauthors = [\"Jonathan Kelley\", \"Evan Almloff\"]\nlicense = \"MIT OR Apache-2.0\"\nrepository = \"https://github.com/DioxusLabs/dioxus/\"\nhomepage = \"https://dioxuslabs.com\"\ndescription = \"CLI optimizations for dioxus-cli\"\nkeywords = [\"dom\", \"ui\", \"gui\", \"react\"]\n\n[dependencies]\nanyhow = { workspace = true }\nmanganis = { workspace = true }\nmanganis-core = { workspace = true }\nobject = { workspace = true, features = [\"wasm\"] }\nserde = { workspace = true, features = [\"derive\"] }\nserde_json = { workspace = true }\nconst-serialize = { workspace = true, features = [\"serde\"] }\ntracing = { workspace = true }\nrayon = \"1.10.0\"\n\n# Image compression/conversion\n# - JPEG\nmozjpeg = { version = \"0.10.13\", default-features = false, features = [\n    \"parallel\",\n] }\n# - PNG\nimagequant = \"4.4.0\"\npng = \"0.17.16\"\n\n# Image format/conversion\nimage = { version = \"0.25\", features = [\"avif\"] }\n\n# CSS Minification\nlightningcss = { version = \"1.0.0-alpha.67\", features = [\n    \"browserslist\",\n    \"into_owned\",\n] }\n\n# SCSS Processing\ngrass = \"0.13.4\"\ncodemap = \"0.1.3\"\n\n# Js minification - swc has introduces minor versions with breaking changes in the past so we pin all of their crates\n# Root dependencies\nswc_bundler = { version = \"=24.0.0\", default-features = false }\nswc_common = { version = \"=13.0.4\", features = [\n    \"tty-emitter\",\n], default-features = false }\nswc_ecma_ast = { version = \"=13.0.1\", default-features = false }\nswc_ecma_codegen = { version = \"=15.0.2\", default-features = false }\nswc_ecma_loader = { version = \"=13.0.0\", features = [\n    \"cache\",\n    \"node\",\n], default-features = false }\nswc_ecma_minifier = { version = \"=25.0.0\", default-features = false }\nswc_ecma_parser = { version = \"=19.0.0\", default-features = false }\nswc_ecma_transforms_base = { version = \"=20.0.0\", default-features = false }\nswc_ecma_visit = { version = \"=13.0.0\", default-features = false }\n\n# Pinned from cargo tree --prefix none | grep -Eo 'swc[^ ]+ [^ ]+' | sort -u\nswc_allocator = { version = \"4.0.1\" }\nswc_atoms = { version = \"6.0.1\" }\nswc_config = { version = \"3.1.1\" }\nswc_config_macro = { version = \"1.0.1\" }\nswc_ecma_codegen_macros = { version = \"2.0.2\" }\nswc_ecma_lexer = { version = \"19.0.1\" }\nswc_ecma_transforms_optimization = { version = \"21.0.0\" }\nswc_ecma_usage_analyzer = { version = \"19.0.2\" }\nswc_ecma_utils = { version = \"18.1.0\" }\nswc_eq_ignore_macros = { version = \"1.0.1\" }\nswc_graph_analyzer = { version = \"14.0.1\" }\nswc_macros_common = { version = \"1.0.1\" }\nswc_sourcemap = { version = \"9.3.2\" }\nswc_timer = { version = \"1.0.0\" }\nswc_visit = { version = \"2.0.1\" }\nbrowserslist-rs = { version = \"=0.19.0\" }\n\n[build-dependencies]\nbuilt = { version = \"0.8.0\", features = [\"git2\"] }\n"
  },
  {
    "path": "packages/cli-opt/build.rs",
    "content": "fn main() {\n    built::write_built_file().expect(\"Failed to acquire build-time information\");\n}\n"
  },
  {
    "path": "packages/cli-opt/src/build_info.rs",
    "content": "// The file has been placed there by the build script.\ninclude!(concat!(env!(\"OUT_DIR\"), \"/built.rs\"));\n\npub(crate) fn version() -> String {\n    format!(\n        \"{} ({})\",\n        PKG_VERSION,\n        GIT_COMMIT_HASH_SHORT.unwrap_or(\"was built without git repository\")\n    )\n}\n"
  },
  {
    "path": "packages/cli-opt/src/css.rs",
    "content": "use std::{hash::Hasher, path::Path};\n\nuse anyhow::{anyhow, Context};\nuse codemap::SpanLoc;\nuse grass::OutputStyle;\nuse lightningcss::{\n    printer::PrinterOptions,\n    stylesheet::{MinifyOptions, ParserOptions, StyleSheet},\n    targets::{Browsers, Targets},\n};\nuse manganis_core::{create_module_hash, transform_css, CssAssetOptions, CssModuleAssetOptions};\n\npub(crate) fn process_css(\n    css_options: &CssAssetOptions,\n    source: &Path,\n    output_path: &Path,\n) -> anyhow::Result<()> {\n    let css = std::fs::read_to_string(source)?;\n\n    let css = if css_options.minified() {\n        // Try to minify the css. If we fail, log the error and use the unminified css\n        match minify_css(&css) {\n            Ok(minified) => minified,\n            Err(err) => {\n                tracing::error!(\n                    \"Failed to minify css; Falling back to unminified css. Error: {}\",\n                    err\n                );\n                css\n            }\n        }\n    } else {\n        css\n    };\n\n    std::fs::write(output_path, css).with_context(|| {\n        format!(\n            \"Failed to write css to output location: {}\",\n            output_path.display()\n        )\n    })?;\n\n    Ok(())\n}\n\npub(crate) fn process_css_module(\n    css_options: &CssModuleAssetOptions,\n    source: &Path,\n    output_path: &Path,\n) -> anyhow::Result<()> {\n    let css = std::fs::read_to_string(source)?;\n\n    // Collect the file hash name.\n    let mut src_name = source\n        .file_name()\n        .and_then(|x| x.to_str())\n        .ok_or_else(|| {\n            anyhow!(\n                \"Failed to read name of css module file `{}`.\",\n                source.display()\n            )\n        })?\n        .strip_suffix(\".css\")\n        .ok_or_else(|| {\n            anyhow!(\n                \"Css module file `{}` should end with a `.css` suffix.\",\n                source.display(),\n            )\n        })?\n        .to_string();\n\n    src_name.push('-');\n\n    let hash = create_module_hash(source);\n    let css = transform_css(css.as_str(), hash.as_str()).map_err(|error| {\n        anyhow!(\n            \"Invalid css for file `{}`\\nError:\\n{}\",\n            source.display(),\n            error\n        )\n    })?;\n\n    // Minify CSS\n    let css = if css_options.minified() {\n        // Try to minify the css. If we fail, log the error and use the unminified css\n        match minify_css(&css) {\n            Ok(minified) => minified,\n            Err(err) => {\n                tracing::error!(\n                    \"Failed to minify css module; Falling back to unminified css. Error: {}\",\n                    err\n                );\n                css\n            }\n        }\n    } else {\n        css\n    };\n\n    std::fs::write(output_path, css).with_context(|| {\n        format!(\n            \"Failed to write css module to output location: {}\",\n            output_path.display()\n        )\n    })?;\n\n    Ok(())\n}\n\npub(crate) fn minify_css(css: &str) -> anyhow::Result<String> {\n    let options = ParserOptions {\n        error_recovery: true,\n        ..Default::default()\n    };\n    let mut stylesheet = StyleSheet::parse(css, options).map_err(|err| err.into_owned())?;\n\n    // We load the browser list from the standard browser list file or use the browserslist default if we don't find any\n    // settings. Without the browser lists default, lightningcss will default to supporting only the newest versions of\n    // browsers.\n    let browsers_list = match Browsers::load_browserslist()? {\n        Some(browsers) => Some(browsers),\n        None => {\n            Browsers::from_browserslist([\"defaults\"]).expect(\"borwserslists should have defaults\")\n        }\n    };\n\n    let targets = Targets {\n        browsers: browsers_list,\n        ..Default::default()\n    };\n\n    stylesheet.minify(MinifyOptions {\n        targets,\n        ..Default::default()\n    })?;\n    let printer = PrinterOptions {\n        targets,\n        minify: true,\n        ..Default::default()\n    };\n    let res = stylesheet.to_css(printer)?;\n    Ok(res.code)\n}\n\n/// Compile scss with grass\npub(crate) fn compile_scss(\n    scss_options: &CssAssetOptions,\n    source: &Path,\n) -> anyhow::Result<String> {\n    let style = match scss_options.minified() {\n        true => OutputStyle::Compressed,\n        false => OutputStyle::Expanded,\n    };\n\n    let options = grass::Options::default()\n        .style(style)\n        .quiet(false)\n        .logger(&ScssLogger {});\n\n    let css = grass::from_path(source, &options)\n        .with_context(|| format!(\"Failed to compile scss file: {}\", source.display()))?;\n    Ok(css)\n}\n\n/// Process an scss/sass file into css.\npub(crate) fn process_scss(\n    scss_options: &CssAssetOptions,\n    source: &Path,\n    output_path: &Path,\n) -> anyhow::Result<()> {\n    let css = compile_scss(scss_options, source)?;\n    let minified = minify_css(&css)?;\n\n    std::fs::write(output_path, minified).with_context(|| {\n        format!(\n            \"Failed to write css to output location: {}\",\n            output_path.display()\n        )\n    })?;\n\n    Ok(())\n}\n\n/// Logger for Grass that re-uses their StdLogger formatting but with tracing.\n#[derive(Debug)]\nstruct ScssLogger {}\n\nimpl grass::Logger for ScssLogger {\n    fn debug(&self, location: SpanLoc, message: &str) {\n        tracing::debug!(\n            \"{}:{} DEBUG: {}\",\n            location.file.name(),\n            location.begin.line + 1,\n            message\n        );\n    }\n\n    fn warn(&self, location: SpanLoc, message: &str) {\n        tracing::warn!(\n            \"Warning: {}\\n    ./{}:{}:{}\",\n            message,\n            location.file.name(),\n            location.begin.line + 1,\n            location.begin.column + 1\n        );\n    }\n}\n\n/// Hash the inputs to the scss file\npub(crate) fn hash_scss(\n    scss_options: &CssAssetOptions,\n    source: &Path,\n    hasher: &mut impl Hasher,\n) -> anyhow::Result<()> {\n    // Grass doesn't expose the ast for us to traverse the imports in the file. Instead of parsing scss ourselves\n    // we just hash the expanded version of the file for now\n    let css = compile_scss(scss_options, source)?;\n\n    // Hash the compiled css\n    hasher.write(css.as_bytes());\n\n    Ok(())\n}\n"
  },
  {
    "path": "packages/cli-opt/src/file.rs",
    "content": "use anyhow::Context;\nuse manganis::{AssetOptions, CssModuleAssetOptions, FolderAssetOptions};\nuse manganis_core::{AssetVariant, CssAssetOptions, ImageAssetOptions, JsAssetOptions};\nuse std::path::Path;\n\nuse crate::css::{process_css_module, process_scss};\n\nuse super::{\n    css::process_css, folder::process_folder, image::process_image, js::process_js,\n    json::process_json,\n};\n\n/// Process a specific file asset with the given options reading from the source and writing to the output path\npub fn process_file_to(\n    options: &AssetOptions,\n    source: &Path,\n    output_path: &Path,\n) -> anyhow::Result<()> {\n    process_file_to_with_options(options, source, output_path, false)\n}\n\n/// Process a specific file asset with additional options\npub(crate) fn process_file_to_with_options(\n    options: &AssetOptions,\n    source: &Path,\n    output_path: &Path,\n    in_folder: bool,\n) -> anyhow::Result<()> {\n    // If the file already exists and this is a hashed asset, then we must have a file\n    // with the same hash already. The hash has the file contents and options, so if we\n    // find a file with the same hash, we probably already created this file in the past\n    if output_path.exists() && options.hash_suffix() {\n        return Ok(());\n    }\n    if let Some(parent) = output_path.parent() {\n        if !parent.exists() {\n            std::fs::create_dir_all(parent).context(\"Failed to create directory\")?;\n        }\n    }\n\n    // Processing can be slow. Write to a temporary file first and then rename it to the final output path. If everything\n    // goes well. Without this, the user could quit in the middle of processing and the file will look complete to the\n    // caching system even though it is empty.\n    let temp_path = output_path.with_file_name(format!(\n        \"partial.{}\",\n        output_path\n            .file_name()\n            .unwrap_or_default()\n            .to_string_lossy()\n    ));\n    let resolved_options = resolve_asset_options(source, options.variant());\n\n    match &resolved_options {\n        ResolvedAssetType::Css(options) => {\n            process_css(options, source, &temp_path)?;\n        }\n        ResolvedAssetType::CssModule(options) => {\n            process_css_module(options, source, &temp_path)?;\n        }\n        ResolvedAssetType::Scss(options) => {\n            process_scss(options, source, &temp_path)?;\n        }\n        ResolvedAssetType::Js(options) => {\n            process_js(options, source, &temp_path, !in_folder)?;\n        }\n        ResolvedAssetType::Image(options) => {\n            process_image(options, source, &temp_path)?;\n        }\n        ResolvedAssetType::Json => {\n            process_json(source, &temp_path)?;\n        }\n        ResolvedAssetType::Folder(_) => {\n            process_folder(source, &temp_path)?;\n        }\n        ResolvedAssetType::File => {\n            let source_file = std::fs::File::open(source)?;\n            let mut reader = std::io::BufReader::new(source_file);\n            let output_file = std::fs::File::create(&temp_path)?;\n            let mut writer = std::io::BufWriter::new(output_file);\n            std::io::copy(&mut reader, &mut writer).with_context(|| {\n                format!(\n                    \"Failed to write file to output location: {}\",\n                    temp_path.display()\n                )\n            })?;\n        }\n    }\n\n    // Remove the existing output file if it exists\n    if output_path.exists() {\n        if output_path.is_file() {\n            std::fs::remove_file(output_path).context(\"Failed to remove previous output file\")?;\n        } else if output_path.is_dir() {\n            std::fs::remove_dir_all(output_path)\n                .context(\"Failed to remove previous output file\")?;\n        }\n    }\n\n    // If everything was successful, rename the temp file to the final output path\n    std::fs::rename(temp_path, output_path)\n        .with_context(|| format!(\"Failed to rename output file to: {}\", output_path.display()))?;\n\n    Ok(())\n}\n\npub(crate) enum ResolvedAssetType {\n    /// An image asset\n    Image(ImageAssetOptions),\n    /// A css asset\n    Css(CssAssetOptions),\n    /// A css module asset\n    CssModule(CssModuleAssetOptions),\n    /// A SCSS asset\n    Scss(CssAssetOptions),\n    /// A javascript asset\n    Js(JsAssetOptions),\n    /// A json asset\n    Json,\n    /// A folder asset\n    Folder(FolderAssetOptions),\n    /// A generic file\n    File,\n}\n\npub(crate) fn resolve_asset_options(source: &Path, options: &AssetVariant) -> ResolvedAssetType {\n    match options {\n        AssetVariant::Image(image) => ResolvedAssetType::Image(*image),\n        AssetVariant::Css(css) => ResolvedAssetType::Css(*css),\n        AssetVariant::CssModule(css) => ResolvedAssetType::CssModule(*css),\n        AssetVariant::Js(js) => ResolvedAssetType::Js(*js),\n        AssetVariant::Folder(folder) => ResolvedAssetType::Folder(*folder),\n        AssetVariant::Unknown => resolve_unknown_asset_options(source),\n        _ => {\n            tracing::warn!(\"Unknown asset options... you may need to update the Dioxus CLI. Defaulting to a generic file: {:?}\", options);\n            resolve_unknown_asset_options(source)\n        }\n    }\n}\n\nfn resolve_unknown_asset_options(source: &Path) -> ResolvedAssetType {\n    match source.extension().map(|e| e.to_string_lossy()).as_deref() {\n        Some(\"scss\" | \"sass\") => ResolvedAssetType::Scss(CssAssetOptions::default()),\n        Some(\"css\") => ResolvedAssetType::Css(CssAssetOptions::default()),\n        Some(\"js\") => ResolvedAssetType::Js(JsAssetOptions::default()),\n        Some(\"json\") => ResolvedAssetType::Json,\n        Some(\"jpg\" | \"jpeg\" | \"png\" | \"webp\" | \"avif\") => {\n            ResolvedAssetType::Image(ImageAssetOptions::default())\n        }\n        _ if source.is_dir() => ResolvedAssetType::Folder(FolderAssetOptions::default()),\n        _ => ResolvedAssetType::File,\n    }\n}\n"
  },
  {
    "path": "packages/cli-opt/src/folder.rs",
    "content": "use std::path::Path;\n\nuse rayon::iter::{IntoParallelRefIterator, ParallelIterator};\n\nuse crate::file::process_file_to_with_options;\n\n/// Process a folder, optimizing and copying all assets into the output folder\npub fn process_folder(source: &Path, output_folder: &Path) -> anyhow::Result<()> {\n    // Create the folder\n    std::fs::create_dir_all(output_folder)?;\n\n    // Then optimize children\n    let files: Vec<_> = std::fs::read_dir(source)\n        .into_iter()\n        .flatten()\n        .flatten()\n        .collect();\n\n    files.par_iter().try_for_each(|file| {\n        let file = file.path();\n        let metadata = file.metadata()?;\n        let output_path = output_folder.join(file.strip_prefix(source)?);\n        if metadata.is_dir() {\n            process_folder(&file, &output_path)\n        } else {\n            process_file_minimal(&file, &output_path)\n        }\n    })?;\n\n    Ok(())\n}\n\n/// Optimize a file without changing any of its contents significantly (e.g. by changing the extension)\nfn process_file_minimal(input_path: &Path, output_path: &Path) -> anyhow::Result<()> {\n    process_file_to_with_options(\n        &manganis_core::AssetOptions::builder().into_asset_options(),\n        input_path,\n        output_path,\n        true,\n    )?;\n    Ok(())\n}\n"
  },
  {
    "path": "packages/cli-opt/src/hash.rs",
    "content": "//! Utilities for creating hashed paths to assets in Manganis. This module defines [`AssetHash`] which is used to create a hashed path to an asset in both the CLI and the macro.\n\nuse std::{\n    hash::{Hash, Hasher},\n    io::Read,\n    path::{Path, PathBuf},\n};\n\nuse crate::{\n    css::hash_scss,\n    file::{resolve_asset_options, ResolvedAssetType},\n    js::hash_js,\n};\nuse manganis::{AssetOptions, BundledAsset};\n\n/// The opaque hash type manganis uses to identify assets. Each time an asset or asset options change, this hash will\n/// change. This hash is included in the URL of the bundled asset for cache busting.\nstruct AssetHash {\n    /// We use a wrapper type here to hide the exact size of the hash so we can switch to a sha hash in a minor version bump\n    hash: [u8; 8],\n}\n\nimpl AssetHash {\n    /// Create a new asset hash\n    const fn new(hash: u64) -> Self {\n        Self {\n            hash: hash.to_le_bytes(),\n        }\n    }\n\n    /// Get the hash bytes\n    pub const fn bytes(&self) -> &[u8] {\n        &self.hash\n    }\n\n    /// Create a new asset hash for a file. The input file to this function should be fully resolved\n    pub fn hash_file_contents(\n        options: &AssetOptions,\n        file_path: impl AsRef<Path>,\n    ) -> anyhow::Result<AssetHash> {\n        hash_file(options, file_path.as_ref())\n    }\n}\n\n/// Process a specific file asset with the given options reading from the source and writing to the output path\nfn hash_file(options: &AssetOptions, source: &Path) -> anyhow::Result<AssetHash> {\n    // Create a hasher\n    let mut hash = std::collections::hash_map::DefaultHasher::new();\n    options.hash(&mut hash);\n\n    // Hash the version of CLI opt\n    hash.write(crate::build_info::version().as_bytes());\n    hash_file_with_options(options, source, &mut hash, false)?;\n\n    let hash = hash.finish();\n    Ok(AssetHash::new(hash))\n}\n\n/// Process a specific file asset with additional options\npub(crate) fn hash_file_with_options(\n    options: &AssetOptions,\n    source: &Path,\n    hasher: &mut impl Hasher,\n    in_folder: bool,\n) -> anyhow::Result<()> {\n    let resolved_options = resolve_asset_options(source, options.variant());\n\n    match &resolved_options {\n        // Scss and JS can import files during the bundling process. We need to hash\n        // both the files themselves and any imports they have\n        ResolvedAssetType::Scss(options) => {\n            hash_scss(options, source, hasher)?;\n        }\n\n        ResolvedAssetType::Js(options) => {\n            hash_js(options, source, hasher, !in_folder)?;\n        }\n\n        // Otherwise, we can just hash the file contents\n        ResolvedAssetType::CssModule(_)\n        | ResolvedAssetType::Css(_)\n        | ResolvedAssetType::Image(_)\n        | ResolvedAssetType::Json\n        | ResolvedAssetType::File => {\n            hash_file_contents(source, hasher)?;\n        }\n\n        // Or the folder contents recursively\n        ResolvedAssetType::Folder(_) => {\n            for file in std::fs::read_dir(source)?.flatten() {\n                let path = file.path();\n                hash_file_with_options(\n                    // We can't reuse the options here since they contain the source variant which no\n                    // longer applies to the nested files\n                    //\n                    // We don't hash nested files either since we assume the parent here is already being hashed\n                    // (or being opted out of hashing)\n                    &AssetOptions::builder()\n                        .with_hash_suffix(false)\n                        .into_asset_options(),\n                    &path,\n                    hasher,\n                    true,\n                )?;\n            }\n        }\n    }\n\n    Ok(())\n}\n\npub(crate) fn hash_file_contents(source: &Path, hasher: &mut impl Hasher) -> anyhow::Result<()> {\n    // Otherwise, open the file to get its contents\n    let mut file = std::fs::File::open(source)?;\n\n    // We add a hash to the end of the file so it is invalidated when the bundled version of the file changes\n    // The hash includes the file contents, the options, and the version of manganis. From the macro, we just\n    // know the file contents, so we only include that hash\n    let mut buffer = [0; 8192];\n    loop {\n        let read = file.read(&mut buffer)?;\n        if read == 0 {\n            break;\n        }\n        hasher.write(&buffer[..read]);\n    }\n    Ok(())\n}\n\n/// Add a hash to the asset, or log an error if it fails\npub fn add_hash_to_asset(asset: &mut BundledAsset) {\n    let source = asset.absolute_source_path();\n    match AssetHash::hash_file_contents(asset.options(), source) {\n        Ok(hash) => {\n            let options = *asset.options();\n\n            // Set the bundled path to the source path with the hash appended before the extension\n            let source_path = PathBuf::from(source);\n            let Some(file_name) = source_path.file_name() else {\n                tracing::error!(\"Failed to get file name from path: {source}\");\n                return;\n            };\n\n            // The output extension path is the extension set by the options\n            // or the extension of the source file if we don't recognize the file\n            let mut ext = asset.options().extension().map(Into::into).or_else(|| {\n                source_path\n                    .extension()\n                    .map(|ext| ext.to_string_lossy().to_string())\n            });\n\n            // Rewrite scss as css\n            if let Some(\"scss\" | \"sass\") = ext.as_deref() {\n                ext = Some(\"css\".to_string());\n            }\n\n            let hash = hash.bytes();\n            let hash = hash\n                .iter()\n                .map(|byte| format!(\"{byte:x}\"))\n                .collect::<String>();\n            let file_stem = source_path.file_stem().unwrap_or(file_name);\n            let mut bundled_path = if asset.options().hash_suffix() {\n                PathBuf::from(format!(\"{}-dxh{hash}\", file_stem.to_string_lossy()))\n            } else {\n                PathBuf::from(file_stem)\n            };\n\n            if let Some(ext) = ext {\n                // Push the extension to the bundled path. There may be multiple extensions (e.g. .js.map)\n                // with one left after the file_stem is extracted above so we need to push the extension\n                // instead of setting it\n                bundled_path.as_mut_os_string().push(format!(\".{ext}\"));\n            }\n\n            let bundled_path = bundled_path.to_string_lossy().to_string();\n\n            *asset = BundledAsset::new(source, &bundled_path, options);\n        }\n        Err(err) => {\n            tracing::error!(\"Failed to hash asset {source}: {err}\");\n        }\n    }\n}\n"
  },
  {
    "path": "packages/cli-opt/src/image/jpg.rs",
    "content": "use image::{DynamicImage, EncodableLayout};\nuse std::{\n    io::{BufWriter, Write},\n    path::Path,\n};\n\npub(crate) fn compress_jpg(image: DynamicImage, output_location: &Path) -> anyhow::Result<()> {\n    let mut comp = mozjpeg::Compress::new(mozjpeg::ColorSpace::JCS_EXT_RGBX);\n    let width = image.width() as usize;\n    let height = image.height() as usize;\n\n    comp.set_size(width, height);\n    let mut comp = comp.start_compress(Vec::new())?; // any io::Write will work\n\n    comp.write_scanlines(image.to_rgba8().as_bytes())?;\n\n    let jpeg_bytes = comp.finish()?;\n\n    let file = std::fs::File::create(output_location)?;\n    let w = &mut BufWriter::new(file);\n    w.write_all(&jpeg_bytes)?;\n    Ok(())\n}\n"
  },
  {
    "path": "packages/cli-opt/src/image/mod.rs",
    "content": "use std::path::Path;\n\nuse anyhow::Context;\nuse jpg::compress_jpg;\nuse manganis_core::{ImageAssetOptions, ImageFormat, ImageSize};\nuse png::compress_png;\n\nmod jpg;\nmod png;\n\npub(crate) fn process_image(\n    image_options: &ImageAssetOptions,\n    source: &Path,\n    output_path: &Path,\n) -> anyhow::Result<()> {\n    let mut image = image::ImageReader::new(std::io::Cursor::new(&*std::fs::read(source)?))\n        .with_guessed_format()\n        .context(\"Failed to guess image format\")?\n        .decode();\n\n    if let Ok(image) = &mut image {\n        if let ImageSize::Manual { width, height } = image_options.size() {\n            *image = image.resize_exact(width, height, image::imageops::FilterType::Lanczos3);\n        }\n    }\n\n    match (image, image_options.format()) {\n        (image, ImageFormat::Png) => {\n            compress_png(image.context(\"Failed to decode image\")?, output_path);\n        }\n        (image, ImageFormat::Jpg) => {\n            compress_jpg(image.context(\"Failed to decode image\")?, output_path)?;\n        }\n        (Ok(image), ImageFormat::Avif) => {\n            if let Err(error) = image.save(output_path) {\n                tracing::error!(\"Failed to save avif image: {} with path {}. You must have the avif feature enabled to use avif assets\", error, output_path.display());\n            }\n        }\n        (Ok(image), ImageFormat::Webp) => {\n            if let Err(err) = image.save(output_path) {\n                tracing::error!(\"Failed to save webp image: {}. You must have the avif feature enabled to use webp assets\", err);\n            }\n        }\n        (Ok(image), _) => {\n            image.save(output_path).with_context(|| {\n                format!(\n                    \"Failed to save image (from {}) with path {}\",\n                    source.display(),\n                    output_path.display()\n                )\n            })?;\n        }\n        // If we can't decode the image or it is of an unknown type, we just copy the file\n        _ => {\n            let source_file = std::fs::File::open(source).context(\"Failed to open source file\")?;\n            let mut reader = std::io::BufReader::new(source_file);\n            let output_file = std::fs::File::create(output_path).with_context(|| {\n                format!(\"Failed to create output file: {}\", output_path.display())\n            })?;\n            let mut writer = std::io::BufWriter::new(output_file);\n            std::io::copy(&mut reader, &mut writer)\n                .with_context(|| {\n                    format!(\n                        \"Failed to write image to output location: {}\",\n                        output_path.display()\n                    )\n                })\n                .context(\"Failed to copy image data\")?;\n        }\n    }\n\n    Ok(())\n}\n"
  },
  {
    "path": "packages/cli-opt/src/image/png.rs",
    "content": "use std::{io::BufWriter, path::Path};\n\nuse image::DynamicImage;\n\npub(crate) fn compress_png(image: DynamicImage, output_location: &Path) {\n    // Image loading/saving is outside scope of this library\n    let width = image.width() as usize;\n    let height = image.height() as usize;\n    let bitmap: Vec<_> = image\n        .into_rgba8()\n        .pixels()\n        .map(|px| imagequant::RGBA::new(px[0], px[1], px[2], px[3]))\n        .collect();\n\n    // Configure the library\n    let mut liq = imagequant::new();\n    liq.set_speed(5).unwrap();\n    liq.set_quality(0, 99).unwrap();\n\n    // Describe the bitmap\n    let mut img = liq.new_image(&bitmap[..], width, height, 0.0).unwrap();\n\n    // The magic happens in quantize()\n    let mut res = match liq.quantize(&mut img) {\n        Ok(res) => res,\n        Err(err) => panic!(\"Quantization failed, because: {err:?}\"),\n    };\n\n    let (palette, pixels) = res.remapped(&mut img).unwrap();\n\n    let file = std::fs::File::create(output_location).unwrap();\n    let w = &mut BufWriter::new(file);\n\n    let mut encoder = png::Encoder::new(w, width as u32, height as u32);\n    encoder.set_color(png::ColorType::Rgba);\n    let mut flattened_palette = Vec::new();\n    let mut alpha_palette = Vec::new();\n    for px in palette {\n        flattened_palette.push(px.r);\n        flattened_palette.push(px.g);\n        flattened_palette.push(px.b);\n        alpha_palette.push(px.a);\n    }\n    encoder.set_palette(flattened_palette);\n    encoder.set_trns(alpha_palette);\n    encoder.set_depth(png::BitDepth::Eight);\n    encoder.set_color(png::ColorType::Indexed);\n    encoder.set_compression(png::Compression::Best);\n    let mut writer = encoder.write_header().unwrap();\n    writer.write_image_data(&pixels).unwrap();\n    writer.finish().unwrap();\n}\n"
  },
  {
    "path": "packages/cli-opt/src/js.rs",
    "content": "use std::hash::Hasher;\nuse std::path::Path;\nuse std::path::PathBuf;\n\nuse anyhow::Context;\nuse manganis_core::JsAssetOptions;\nuse swc_common::errors::Emitter;\nuse swc_common::errors::Handler;\nuse swc_common::input::SourceFileInput;\nuse swc_ecma_minifier::option::{ExtraOptions, MinifyOptions};\nuse swc_ecma_parser::lexer::Lexer;\nuse swc_ecma_parser::Parser;\nuse swc_ecma_transforms_base::fixer::fixer;\nuse swc_ecma_visit::VisitMutWith;\n\nuse std::collections::HashMap;\n\nuse anyhow::Error;\nuse swc_bundler::{Bundler, Config, Load, ModuleData, ModuleRecord};\nuse swc_common::{\n    errors::HANDLER, sync::Lrc, FileName, FilePathMapping, Globals, Mark, SourceMap, Span, GLOBALS,\n};\nuse swc_ecma_ast::*;\nuse swc_ecma_codegen::text_writer::JsWriter;\nuse swc_ecma_loader::{resolvers::node::NodeModulesResolver, TargetEnv};\nuse swc_ecma_parser::{parse_file_as_module, Syntax};\n\nuse crate::hash::hash_file_contents;\n\nstruct TracingEmitter;\n\nimpl Emitter for TracingEmitter {\n    fn emit(&mut self, db: &mut swc_common::errors::DiagnosticBuilder<'_>) {\n        match db.level {\n            swc_common::errors::Level::Bug\n            | swc_common::errors::Level::Fatal\n            | swc_common::errors::Level::PhaseFatal\n            | swc_common::errors::Level::Error => tracing::error!(\"{}\", db.message()),\n            swc_common::errors::Level::Warning\n            | swc_common::errors::Level::FailureNote\n            | swc_common::errors::Level::Cancelled => tracing::warn!(\"{}\", db.message()),\n            swc_common::errors::Level::Note | swc_common::errors::Level::Help => {\n                tracing::trace!(\"{}\", db.message())\n            }\n        }\n    }\n}\n\n/// Run a closure with the swc globals and handler set up\nfn inside_handler<O>(f: impl FnOnce(&Globals, Lrc<SourceMap>) -> O) -> O {\n    let globals = Globals::new();\n    let cm = Lrc::new(SourceMap::new(FilePathMapping::empty()));\n    let handler = Handler::with_emitter_and_flags(Box::new(TracingEmitter), Default::default());\n    GLOBALS.set(&globals, || HANDLER.set(&handler, || f(&globals, cm)))\n}\n\nfn bundle_js_to_writer(\n    file: PathBuf,\n    bundle: bool,\n    minify: bool,\n    write_to: &mut impl std::io::Write,\n) -> anyhow::Result<()> {\n    inside_handler(|globals, cm| {\n        bundle_js_to_writer_inside_handler(globals, cm, file, bundle, minify, write_to)\n    })\n}\n\nfn resolve_js_inside_handler(\n    globals: &Globals,\n    file: PathBuf,\n    bundle: bool,\n    cm: &Lrc<SourceMap>,\n) -> anyhow::Result<Module> {\n    if bundle {\n        let node_resolver = NodeModulesResolver::new(TargetEnv::Browser, Default::default(), true);\n        let mut bundler = Bundler::new(\n            globals,\n            cm.clone(),\n            PathLoader { cm: cm.clone() },\n            node_resolver,\n            Config {\n                require: true,\n                ..Default::default()\n            },\n            Box::new(Hook),\n        );\n        let mut entries = HashMap::default();\n        entries.insert(\"main\".to_string(), FileName::Real(file));\n\n        let mut bundles = bundler\n            .bundle(entries)\n            .context(\"failed to bundle javascript with swc\")?;\n        // Since we only inserted one entry, there should only be one bundle in the output\n        let bundle = bundles\n            .pop()\n            .ok_or_else(|| anyhow::anyhow!(\"swc did not output any bundles\"))?;\n        Ok(bundle.module)\n    } else {\n        let fm = cm.load_file(Path::new(&file)).expect(\"Failed to load file\");\n\n        let lexer = Lexer::new(\n            Default::default(),\n            Default::default(),\n            SourceFileInput::from(&*fm),\n            None,\n        );\n        let mut parser = Parser::new_from(lexer);\n\n        parser.parse_module().map_err(|err| {\n            HANDLER.with(|handler| {\n                let mut error = err.into_diagnostic(handler);\n                // swc errors panic on drop if you don't cancel them\n                error.cancel();\n                anyhow::anyhow!(\"{}\", error.message())\n            })\n        })\n    }\n}\n\nfn bundle_js_to_writer_inside_handler(\n    globals: &Globals,\n    cm: Lrc<SourceMap>,\n    file: PathBuf,\n    bundle: bool,\n    minify: bool,\n    write_to: &mut impl std::io::Write,\n) -> anyhow::Result<()> {\n    let mut module = resolve_js_inside_handler(globals, file, bundle, &cm)?;\n\n    if minify {\n        module = swc_ecma_minifier::optimize(\n            std::mem::take(&mut module).into(),\n            cm.clone(),\n            None,\n            None,\n            &MinifyOptions {\n                rename: true,\n                compress: None,\n                mangle: None,\n                ..Default::default()\n            },\n            &ExtraOptions {\n                unresolved_mark: Mark::new(),\n                top_level_mark: Mark::new(),\n                mangle_name_cache: None,\n            },\n        )\n        .expect_module();\n        module.visit_mut_with(&mut fixer(None));\n    }\n\n    let mut emitter = swc_ecma_codegen::Emitter {\n        cfg: swc_ecma_codegen::Config::default().with_minify(minify),\n        cm: cm.clone(),\n        comments: None,\n        wr: Box::new(JsWriter::new(cm, \"\\n\", write_to, None)),\n    };\n\n    emitter.emit_module(&module)?;\n\n    Ok(())\n}\n\nstruct PathLoader {\n    cm: Lrc<SourceMap>,\n}\n\nimpl Load for PathLoader {\n    fn load(&self, file: &FileName) -> anyhow::Result<ModuleData> {\n        let file = match file {\n            FileName::Real(v) => v,\n            _ => anyhow::bail!(\"Only real files are supported\"),\n        };\n\n        let fm = self.cm.load_file(file)?;\n\n        let module = HANDLER.with(|handler| {\n            parse_file_as_module(\n                &fm,\n                Syntax::Es(Default::default()),\n                Default::default(),\n                None,\n                &mut Vec::new(),\n            )\n            .map_err(|err| {\n                let mut error = err.into_diagnostic(handler);\n                // swc errors panic on drop if you don't cancel them\n                error.cancel();\n                anyhow::anyhow!(\"{}\", error.message())\n            })\n            .context(\"Failed to parse javascript\")\n        })?;\n\n        Ok(ModuleData {\n            fm,\n            module,\n            helpers: Default::default(),\n        })\n    }\n}\n\n// Adapted from https://github.com/swc-project/swc/blob/624680b7896cef9d8e30bd5ff910538298016974/bindings/binding_core_node/src/bundle.rs#L266-L302\nstruct Hook;\n\nimpl swc_bundler::Hook for Hook {\n    fn get_import_meta_props(\n        &self,\n        span: Span,\n        module_record: &ModuleRecord,\n    ) -> Result<Vec<KeyValueProp>, Error> {\n        let file_name = module_record.file_name.to_string();\n\n        Ok(vec![\n            KeyValueProp {\n                key: PropName::Ident(IdentName::new(\"url\".into(), span)),\n                value: Box::new(Expr::Lit(Lit::Str(Str {\n                    span,\n                    raw: None,\n                    value: file_name.into(),\n                }))),\n            },\n            KeyValueProp {\n                key: PropName::Ident(IdentName::new(\"main\".into(), span)),\n                value: Box::new(if module_record.is_entry {\n                    Expr::Member(MemberExpr {\n                        span,\n                        obj: Box::new(Expr::MetaProp(MetaPropExpr {\n                            span,\n                            kind: MetaPropKind::ImportMeta,\n                        })),\n                        prop: MemberProp::Ident(IdentName::new(\"main\".into(), span)),\n                    })\n                } else {\n                    Expr::Lit(Lit::Bool(Bool { span, value: false }))\n                }),\n            },\n        ])\n    }\n}\n\npub(crate) fn process_js(\n    js_options: &JsAssetOptions,\n    source: &Path,\n    output_path: &Path,\n    bundle: bool,\n) -> anyhow::Result<()> {\n    let mut writer = std::io::BufWriter::new(std::fs::File::create(output_path)?);\n    if js_options.minified() {\n        if let Err(err) = bundle_js_to_writer(source.to_path_buf(), bundle, true, &mut writer) {\n            tracing::error!(\"Failed to minify js. Falling back to non-minified: {err}\");\n        } else {\n            return Ok(());\n        }\n    }\n    let mut source_file = std::fs::File::open(source)?;\n    std::io::copy(&mut source_file, &mut writer).with_context(|| {\n        format!(\n            \"Failed to write js to output location: {}\",\n            output_path.display()\n        )\n    })?;\n\n    Ok(())\n}\n\nfn hash_js_module(file: PathBuf, hasher: &mut impl Hasher, bundle: bool) -> anyhow::Result<()> {\n    inside_handler(|globals, cm| {\n        _ = resolve_js_inside_handler(globals, file, bundle, &cm)?;\n\n        for file in cm.files().iter() {\n            let hash = file.src_hash;\n            hasher.write(&hash.to_le_bytes());\n        }\n\n        Ok(())\n    })\n}\n\npub(crate) fn hash_js(\n    js_options: &JsAssetOptions,\n    source: &Path,\n    hasher: &mut impl Hasher,\n    bundle: bool,\n) -> anyhow::Result<()> {\n    if js_options.minified() {\n        if let Err(err) = hash_js_module(source.to_path_buf(), hasher, bundle) {\n            tracing::error!(\"Failed to minify js. Falling back to non-minified: {err}\");\n            hash_file_contents(source, hasher)?;\n        }\n    } else {\n        hash_file_contents(source, hasher)?;\n    }\n\n    Ok(())\n}\n"
  },
  {
    "path": "packages/cli-opt/src/json.rs",
    "content": "use std::{io::Read, path::Path};\n\nuse anyhow::Context;\n\npub(crate) fn minify_json(source: &str) -> anyhow::Result<String> {\n    // First try to parse the json\n    let json: serde_json::Value = serde_json::from_str(source)?;\n    // Then print it in a minified format\n    let json = serde_json::to_string(&json)?;\n    Ok(json)\n}\n\npub(crate) fn process_json(source: &Path, output_path: &Path) -> anyhow::Result<()> {\n    let mut source_file = std::fs::File::open(source)?;\n    let mut source = String::new();\n    source_file.read_to_string(&mut source)?;\n    let json = match minify_json(&source) {\n        Ok(json) => json,\n        Err(err) => {\n            tracing::error!(\"Failed to minify json: {}\", err);\n            source\n        }\n    };\n\n    std::fs::write(output_path, json).with_context(|| {\n        format!(\n            \"Failed to write json to output location: {}\",\n            output_path.display()\n        )\n    })?;\n\n    Ok(())\n}\n"
  },
  {
    "path": "packages/cli-opt/src/lib.rs",
    "content": "use anyhow::Context;\nuse manganis::AssetOptions;\nuse manganis_core::BundledAsset;\nuse rayon::iter::{IntoParallelRefIterator, ParallelIterator};\nuse serde::{Deserialize, Serialize};\nuse std::collections::{BTreeMap, HashSet};\nuse std::path::{Path, PathBuf};\nuse std::sync::{Arc, RwLock};\n\nmod build_info;\nmod css;\nmod file;\nmod folder;\nmod hash;\nmod image;\nmod js;\nmod json;\n\npub use file::process_file_to;\npub use hash::add_hash_to_asset;\n\n/// A manifest of all assets collected from dependencies\n///\n/// This will be filled in primarily by incremental compilation artifacts.\n#[derive(Debug, PartialEq, Default, Clone, Serialize, Deserialize)]\npub struct AssetManifest {\n    /// Map of bundled asset name to the asset itself\n    assets: BTreeMap<PathBuf, HashSet<BundledAsset>>,\n}\n\nimpl AssetManifest {\n    /// Manually add an asset to the manifest\n    pub fn register_asset(\n        &mut self,\n        asset_path: &Path,\n        options: manganis::AssetOptions,\n    ) -> anyhow::Result<BundledAsset> {\n        let output_path_str = asset_path.to_str().ok_or(anyhow::anyhow!(\n            \"Failed to convert wasm bindgen output path to string\"\n        ))?;\n\n        let mut bundled_asset =\n            manganis::macro_helpers::create_bundled_asset(output_path_str, options);\n        add_hash_to_asset(&mut bundled_asset);\n\n        self.assets\n            .entry(asset_path.to_path_buf())\n            .or_default()\n            .insert(bundled_asset);\n\n        Ok(bundled_asset)\n    }\n\n    /// Insert an existing bundled asset to the manifest\n    pub fn insert_asset(&mut self, asset: BundledAsset) {\n        let asset_path = asset.absolute_source_path();\n        self.assets\n            .entry(asset_path.into())\n            .or_default()\n            .insert(asset);\n    }\n\n    /// Get any assets that are tied to a specific source file\n    pub fn get_assets_for_source(&self, path: &Path) -> Option<&HashSet<BundledAsset>> {\n        self.assets.get(path)\n    }\n\n    /// Get the first asset that matches the given source path\n    pub fn get_first_asset_for_source(&self, path: &Path) -> Option<&BundledAsset> {\n        self.assets\n            .get(path)\n            .and_then(|assets| assets.iter().next())\n    }\n\n    /// Check if the manifest contains a specific asset\n    pub fn contains(&self, asset: &BundledAsset) -> bool {\n        self.assets\n            .get(&PathBuf::from(asset.absolute_source_path()))\n            .is_some_and(|assets| assets.contains(asset))\n    }\n\n    /// Iterate over all the assets with unique output paths in the manifest. This will not include\n    /// assets that have different source paths, but the same file contents.\n    pub fn unique_assets(&self) -> impl Iterator<Item = &BundledAsset> {\n        let mut seen = HashSet::new();\n        self.assets\n            .values()\n            .flat_map(|assets| assets.iter())\n            .filter(move |asset| seen.insert(asset.bundled_path()))\n    }\n\n    pub fn load_from_file(path: &Path) -> anyhow::Result<Self> {\n        let src = std::fs::read_to_string(path)?;\n\n        serde_json::from_str(&src)\n            .with_context(|| format!(\"Failed to parse asset manifest from {path:?}\\n{src}\"))\n    }\n}\n\n/// Optimize a list of assets in parallel\npub fn optimize_all_assets(\n    assets_to_transfer: Vec<(PathBuf, PathBuf, AssetOptions)>,\n    on_optimization_start: impl FnMut(&Path, &Path, &AssetOptions) + Sync + Send,\n    on_optimization_end: impl FnMut(&Path, &Path, &AssetOptions) + Sync + Send,\n) -> anyhow::Result<()> {\n    let on_optimization_start = Arc::new(RwLock::new(on_optimization_start));\n    let on_optimization_end = Arc::new(RwLock::new(on_optimization_end));\n    assets_to_transfer\n        .par_iter()\n        .try_for_each(|(from, to, options)| {\n            {\n                let mut on_optimization_start = on_optimization_start.write().unwrap();\n                on_optimization_start(from, to, options);\n            }\n\n            let res = process_file_to(options, from, to);\n            if let Err(err) = res.as_ref() {\n                tracing::error!(\"Failed to copy asset {from:?}: {err}\");\n            }\n\n            {\n                let mut on_optimization_end = on_optimization_end.write().unwrap();\n                on_optimization_end(from, to, options);\n            }\n\n            res.map(|_| ())\n        })\n}\n"
  },
  {
    "path": "packages/cli-telemetry/Cargo.toml",
    "content": "[package]\nname = \"dioxus-cli-telemetry\"\nedition = \"2021\"\nversion.workspace = true\ndescription = \"Wire format for the dioxus CLI telemetry type\"\nauthors = [\"Jonathan Kelley\", \"Evan Almloff\"]\nlicense = \"MIT OR Apache-2.0\"\nrepository = \"https://github.com/DioxusLabs/dioxus/\"\nhomepage = \"https://dioxuslabs.com\"\nkeywords = [\"dom\", \"ui\", \"gui\", \"react\"]\n\n[dependencies]\nserde = { workspace = true, features = [\"derive\"] }\nchrono = { workspace = true, features = [\"serde\"] }\nrand = { workspace = true }\nserde_json = { workspace = true }\ndirs = { workspace = true }\nuuid = { workspace = true, features = [\"serde\", \"v4\"] }\n"
  },
  {
    "path": "packages/cli-telemetry/src/lib.rs",
    "content": "//! # Telemetry for the Dioxus CLI\n//!\n//! Dioxus uses telemetry in the CLI to get insight into metrics like performance, panics, and usage\n//! of various arguments. This data helps us track down bugs and improve quality of the tooling.\n//!\n//! Usage of telemetry in open source products can be controversial. Our goal here is to collect\n//! minimally invasive data used exclusively to improve our tooling. Github issues only show *some*\n//! of the problem, but many users stumble into issues which go unreported.\n//!\n//! Our policy follows:\n//! - minimally invasive\n//! - anonymous\n//! - periodic\n//! - transparent\n//! - easy to disable\n//!\n//! We send a heartbeat when the CLI is executed and then rollups of logs over time.\n//! - Heartbeat: helps us track version distribution of the CLI and critical \"failures on launch\" useful during new version rollouts.\n//! - Rollups: helps us track performance and issues over time, as well as usage of various commands.\n//!\n//! Rollups are not done in background processes, but rather directly by the `dx` CLI.\n//! If you don't run the CLI, then we won't send any data.\n//!\n//! We don't collect any PII, but we do collect three \"controversial\" pieces of data:\n//! - the target triple of your system (OS, arch, etc)\n//! - a session ID which is a random number generated on each run\n//! - a distinct ID per `.dx` installation which is a random number generated on initial run.\n//!\n//! The distinct ID is used to track the same installation over time, but it is not tied to any user\n//! account or PII. Since `dx` doesn't have any accounts or authentication mechanism, this ID is used\n//! as a \"best effort\" identifier. If you still want to participate in telemetry but don't want a\n//! distinct ID, you can replace the stable_id.json file in the `.dx` directory with an empty string.\n//!\n//! In the CLI, you can disable this by using any of the methods:\n//! - installing with the \"disable-telemetry\" feature flag\n//! - setting TELEMETRY=false in your env\n//! - setting `dx config set disable-telemetry true`\n\nuse chrono::{DateTime, Utc};\nuse serde::{Deserialize, Serialize};\nuse std::{\n    collections::{BTreeMap, HashMap},\n    time::SystemTime,\n};\n\n/// An event's data, corresponding roughly to data collected from an individual trace.\n///\n/// This can be something like a build, bundle, translate, etc\n/// We collect the phases of the build in a list of events to get a better sense of how long\n/// it took.\n///\n/// Note that this is just the data and does not include the reporter information.\n///\n/// On the analytics, side, we reconstruct the trace messages into a sequence of events, using\n/// the stage as a marker.\n///\n/// If the event contains a stack trace, it is considered a crash event and will be sent to the crash reporting service.\n///\n/// We store this type on disk without the reporter information or any information about the CLI.\n#[derive(Serialize, Deserialize, Debug, Clone)]\npub struct TelemetryEventData {\n    /// The name of the command that was run, e.g. \"dx build\", \"dx bundle\", \"dx serve\"\n    pub command: String,\n\n    /// The action that was taken, e.g. \"build\", \"bundle\", \"cli_invoked\", \"cli_crashed\" etc\n    pub action: String,\n\n    /// An additional message to include in the event, e.g. \"start\", \"end\", \"error\", etc\n    pub message: String,\n\n    /// The \"name\" of the error. In our case, usually\" \"RustError\" or \"RustPanic\". In other languages\n    /// this might be the exception type. In Rust, this is usually the name of the error type. (e.g. \"std::io::Error\", etc)\n    pub error_type: Option<String>,\n\n    /// Whether the event was handled or not. Unhandled errors are the default, but some we recover from (like hotpatching issues).\n    pub error_handled: bool,\n\n    /// Additional values to include in the event, e.g. \"duration\", \"enabled\", etc.\n    pub values: HashMap<String, serde_json::Value>,\n\n    /// Timestamp of the event, in UTC, derived from the user's system time. Might not be reliable.\n    pub time: DateTime<Utc>,\n\n    /// The module where the event occurred, stripped of paths for privacy.\n    pub module: Option<String>,\n\n    /// The file or module where the event occurred, stripped of paths for privacy, relative to the monorepo root.\n    pub file: Option<String>,\n\n    /// The line and column where the event occurred, if applicable.\n    pub line: Option<u32>,\n\n    /// The column where the event occurred, if applicable.\n    pub column: Option<u32>,\n\n    /// The stack frames of the event, if applicable.\n    #[serde(default, skip_serializing_if = \"Vec::is_empty\")]\n    pub stack_frames: Vec<StackFrame>,\n}\n\nimpl TelemetryEventData {\n    pub fn new(name: impl ToString, message: impl ToString) -> Self {\n        Self {\n            command: std::env::args()\n                .nth(1)\n                .unwrap_or_else(|| \"unknown\".to_string()),\n            action: strip_paths(&name.to_string()),\n            message: strip_paths(&message.to_string()),\n            file: None,\n            module: None,\n            time: DateTime::<Utc>::from(SystemTime::now()),\n            values: HashMap::new(),\n            error_type: None,\n            column: None,\n            line: None,\n            stack_frames: vec![],\n            error_handled: false,\n        }\n    }\n\n    pub fn with_value<K: ToString, V: serde::Serialize>(mut self, key: K, value: V) -> Self {\n        let mut value = serde_json::to_value(value).unwrap();\n        strip_paths_value(&mut value);\n        self.values.insert(key.to_string(), value);\n        self\n    }\n\n    pub fn with_module(mut self, module: impl ToString) -> Self {\n        self.module = Some(strip_paths(&module.to_string()));\n        self\n    }\n\n    pub fn with_file(mut self, file: impl ToString) -> Self {\n        self.file = Some(strip_paths(&file.to_string()));\n        self\n    }\n\n    pub fn with_line_column(mut self, line: u32, column: u32) -> Self {\n        self.line = Some(line);\n        self.column = Some(column);\n        self\n    }\n\n    pub fn with_error_handled(mut self, error_handled: bool) -> Self {\n        self.error_handled = error_handled;\n        self\n    }\n\n    pub fn with_error_type(mut self, error_type: String) -> Self {\n        self.error_type = Some(error_type);\n        self\n    }\n\n    pub fn with_stack_frames(mut self, stack_frames: Vec<StackFrame>) -> Self {\n        self.stack_frames = stack_frames;\n        self\n    }\n\n    pub fn with_values(mut self, fields: serde_json::Map<String, serde_json::Value>) -> Self {\n        for (key, value) in fields {\n            self = self.with_value(key, value);\n        }\n        self\n    }\n\n    pub fn to_json(&self) -> serde_json::Value {\n        serde_json::to_value(self).unwrap()\n    }\n}\n\n/// Display implementation for TelemetryEventData, such that you can use it in tracing macros with the \"%\" syntax.\nimpl std::fmt::Display for TelemetryEventData {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(f, \"{}\", serde_json::to_string(self).unwrap())\n    }\n}\n\n/// A serialized stack frame, in a format that matches PostHog's stack frame format.\n///\n/// Read more:\n/// <https://github.com/PostHog/posthog-js/blob/6e35a639a4d06804f6844cbde15adf11a069b92b/packages/node/src/extensions/error-tracking/types.ts#L55>\n///\n/// Supposedly, this is compatible with Sentry's stack frames as well. In the CLI we use sentry-backtrace\n/// even though we don't actually use sentry.\n#[derive(Serialize, Deserialize, Debug, Clone)]\n#[serde(rename_all = \"snake_case\")]\npub struct StackFrame {\n    pub raw_id: String,\n\n    pub mangled_name: String,\n\n    pub resolved_name: String,\n\n    pub lang: String,\n\n    pub resolved: bool,\n\n    pub platform: String,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    pub filename: Option<String>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    pub function: Option<String>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    pub module: Option<String>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    pub lineno: Option<u64>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    pub colno: Option<u64>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    pub abs_path: Option<String>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    pub context_line: Option<String>,\n\n    #[serde(default, skip_serializing_if = \"Vec::is_empty\")]\n    pub pre_context: Vec<String>,\n\n    #[serde(default, skip_serializing_if = \"Vec::is_empty\")]\n    pub post_context: Vec<String>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    pub in_app: Option<bool>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    pub instruction_addr: Option<String>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    pub addr_mode: Option<String>,\n\n    #[serde(default, skip_serializing_if = \"BTreeMap::is_empty\")]\n    pub vars: BTreeMap<String, serde_json::Value>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    pub chunk_id: Option<String>,\n}\n\n// If the CLI is compiled locally, it can contain backtraces which contain the home path with the username in it.\npub fn strip_paths(string: &str) -> String {\n    // Strip the home path from any paths in the backtrace\n    let home_dir = dirs::home_dir().unwrap_or_default();\n\n    // Strip every path between the current path and the home directory\n    let mut cwd = std::env::current_dir().unwrap_or_default();\n    let mut string = string.to_string();\n    loop {\n        string = string.replace(&*cwd.to_string_lossy(), \"<stripped>\");\n        let Some(parent) = cwd.parent() else {\n            break;\n        };\n        cwd = parent.to_path_buf();\n        if cwd == home_dir {\n            break;\n        }\n    }\n\n    // Finally, strip the home directory itself (in case the cwd is outside the home directory)\n    string.replace(&*home_dir.to_string_lossy(), \"~\")\n}\n\nfn strip_paths_value(value: &mut serde_json::Value) {\n    match value {\n        serde_json::Value::String(s) => *s = strip_paths(s),\n        serde_json::Value::Object(map) => map.values_mut().for_each(strip_paths_value),\n        serde_json::Value::Array(arr) => arr.iter_mut().for_each(strip_paths_value),\n        _ => {}\n    }\n}\n"
  },
  {
    "path": "packages/component-manifest/Cargo.toml",
    "content": "[package]\nname = \"dioxus-component-manifest\"\nedition = \"2021\"\nversion.workspace = true\ndescription = \"Wire format for the dioxus CLI telemetry type\"\nauthors = [\"Evan Almloff\"]\nlicense = \"MIT OR Apache-2.0\"\nrepository = \"https://github.com/DioxusLabs/dioxus/\"\nhomepage = \"https://dioxuslabs.com\"\nkeywords = [\"dom\", \"ui\", \"gui\", \"react\"]\n\n[dependencies]\nschemars = \"1.0.4\"\nserde = { version = \"1.0.219\", features = [\"derive\"] }\nserde_json = \"1.0.143\"\n"
  },
  {
    "path": "packages/component-manifest/src/lib.rs",
    "content": "use std::process::Command;\n\nuse schemars::{schema_for, JsonSchema, Schema};\nuse serde::{Deserialize, Serialize};\n\n/// A component compatible with the dioxus components system.\n/// This may be a \"virtual\" component which is empty except for a list of members.\n#[derive(Deserialize, Serialize, JsonSchema, Clone, Debug, PartialEq, Eq, Hash)]\n#[serde(rename_all = \"camelCase\", deny_unknown_fields)]\npub struct Component {\n    pub name: String,\n\n    #[serde(default)]\n    pub description: String,\n\n    #[serde(default, skip_serializing_if = \"Vec::is_empty\")]\n    pub authors: Vec<String>,\n\n    #[serde(default, skip_serializing_if = \"Vec::is_empty\")]\n    pub component_dependencies: Vec<ComponentDependency>,\n\n    #[serde(default, skip_serializing_if = \"Vec::is_empty\")]\n    pub cargo_dependencies: Vec<CargoDependency>,\n\n    #[serde(default, skip_serializing_if = \"Vec::is_empty\")]\n    pub members: Vec<String>,\n\n    #[serde(default, skip_serializing_if = \"Vec::is_empty\")]\n    pub exclude: Vec<String>,\n\n    #[serde(default, skip_serializing_if = \"Vec::is_empty\")]\n    pub global_assets: Vec<String>,\n}\n\n/// A dependency on another component, either built-in or third-party.\n#[derive(Deserialize, Serialize, JsonSchema, Clone, Debug, PartialEq, Eq, Hash)]\n#[serde(untagged)]\npub enum ComponentDependency {\n    Builtin(String),\n    ThirdParty {\n        name: String,\n        git: String,\n        #[serde(default)]\n        rev: Option<String>,\n    },\n}\n\n/// A dependency on a cargo crate required for a component.\n#[derive(Deserialize, Serialize, JsonSchema, Clone, Debug, PartialEq, Eq, Hash)]\n#[serde(untagged)]\npub enum CargoDependency {\n    Simple(String),\n    Detailed {\n        name: String,\n        #[serde(default)]\n        version: Option<String>,\n        #[serde(default, skip_serializing_if = \"Vec::is_empty\")]\n        features: Vec<String>,\n        #[serde(default, skip_serializing_if = \"std::ops::Not::not\")]\n        default_features: bool,\n        #[serde(default, skip_serializing_if = \"Option::is_none\")]\n        git: Option<String>,\n        #[serde(default, skip_serializing_if = \"Option::is_none\")]\n        rev: Option<String>,\n    },\n}\n\nimpl CargoDependency {\n    /// Get the `cargo add` command for this dependency.\n    pub fn add_command(&self) -> Command {\n        let mut cmd = Command::new(\"cargo\");\n        cmd.arg(\"add\");\n        match self {\n            CargoDependency::Simple(name) => {\n                cmd.arg(name);\n            }\n            CargoDependency::Detailed {\n                name,\n                version,\n                features,\n                default_features,\n                git,\n                rev,\n            } => {\n                cmd.arg(format!(\n                    \"{name}{}\",\n                    version\n                        .as_ref()\n                        .map(|version| format!(\"@{version}\"))\n                        .unwrap_or_default()\n                ));\n                if !features.is_empty() {\n                    cmd.arg(\"--features\").arg(features.join(\",\"));\n                }\n                if !*default_features {\n                    cmd.arg(\"--no-default-features\");\n                }\n                if let Some(git) = git {\n                    cmd.arg(\"--git\").arg(git);\n                }\n                if let Some(rev) = rev {\n                    cmd.arg(\"--rev\").arg(rev);\n                }\n            }\n        }\n        cmd\n    }\n\n    /// Get the name of the dependency.\n    pub fn name(&self) -> &str {\n        match self {\n            CargoDependency::Simple(name) => name,\n            CargoDependency::Detailed { name, .. } => name,\n        }\n    }\n}\n\n/// Get the JSON schema for the `Component` struct.\npub fn component_manifest_schema() -> Schema {\n    schema_for!(Component)\n}\n"
  },
  {
    "path": "packages/config-macro/Cargo.toml",
    "content": "[package]\nname = \"dioxus-config-macro\"\nversion = { workspace = true }\nauthors = [\"Jonathan Kelley\"]\nedition = \"2021\"\ndescription = \"Configuration macros for Dioxus\"\nlicense = \"MIT OR Apache-2.0\"\nrepository = \"https://github.com/DioxusLabs/dioxus/\"\nhomepage = \"https://dioxuslabs.com\"\nkeywords = [\"web\", \"desktop\", \"mobile\", \"gui\", \"wasm\"]\n\n[lib]\nproc-macro = true\n\n[dependencies]\nproc-macro2 = { workspace = true }\nquote = { workspace = true }\n\n[features]\ndefault = []\nfullstack = []\ndesktop = []\nnative = []\nmobile = []\nweb = []\nssr = []\nliveview = []\n\n[package.metadata.docs.rs]\ncargo-args = [\"-Zunstable-options\", \"-Zrustdoc-scrape-examples\"]\n"
  },
  {
    "path": "packages/config-macro/README.md",
    "content": "# dioxus-config-macro\n\n[![Crates.io][crates-badge]][crates-url]\n[![MIT licensed][mit-badge]][mit-url]\n[![Build Status][actions-badge]][actions-url]\n[![Discord chat][discord-badge]][discord-url]\n\n[crates-badge]: https://img.shields.io/crates/v/dioxus-config-macro.svg\n[crates-url]: https://crates.io/crates/dioxus-config-macro\n[mit-badge]: https://img.shields.io/badge/license-MIT-blue.svg\n[mit-url]: https://github.com/dioxuslabs/dioxus/blob/main/LICENSE-MIT\n[actions-badge]: https://github.com/dioxuslabs/dioxus/actions/workflows/main.yml/badge.svg\n[actions-url]: https://github.com/dioxuslabs/dioxus/actions?query=workflow%3ACI+branch%3Amaster\n[discord-badge]: https://img.shields.io/discord/899851952891002890.svg?logo=discord&style=flat-square\n[discord-url]: https://discord.gg/XgGxMSkvUM\n\n[Website](https://dioxuslabs.com) |\n[Guide](https://dioxuslabs.com/learn/0.7/) |\n[API Docs](https://docs.rs/dioxus-config-macro/latest/dioxus_config_macro/) |\n[Chat](https://discord.gg/XgGxMSkvUM)\n\n## Overview\n\n`dioxus-config-macro` provides a handful of helpful macros to make it easier to work with optional sections of the launch builder.\n\n## Contributing\n\n- Report issues on our [issue tracker](https://github.com/dioxuslabs/dioxus/issues).\n- Join the discord and ask questions!\n\n## License\n\nThis project is licensed under the [MIT license].\n\n[mit license]: https://github.com/dioxuslabs/dioxus/blob/main/LICENSE-MIT\n\nUnless you explicitly state otherwise, any contribution intentionally submitted\nfor inclusion in Dioxus by you shall be licensed as MIT without any additional\nterms or conditions.\n"
  },
  {
    "path": "packages/config-macro/src/lib.rs",
    "content": "#![doc = include_str!(\"../README.md\")]\n#![doc(html_logo_url = \"https://avatars.githubusercontent.com/u/79236386\")]\n#![doc(html_favicon_url = \"https://avatars.githubusercontent.com/u/79236386\")]\n\nuse proc_macro::TokenStream;\nuse proc_macro2::TokenStream as TokenStream2;\nuse quote::quote;\n\nmacro_rules! define_config_macro {\n    ($name:ident if $($cfg:tt)+) => {\n        #[proc_macro]\n        pub fn $name(input: TokenStream) -> TokenStream {\n            if cfg!($($cfg)+) {\n                let input = TokenStream2::from(input);\n                quote! {\n                    {\n                        #input\n                    }\n                }\n            } else {\n                quote! {\n                    ()\n                }\n            }\n            .into()\n        }\n    };\n}\n\ndefine_config_macro!(server_only if any(feature = \"ssr\", feature = \"liveview\"));\ndefine_config_macro!(client if any(feature = \"desktop\", feature = \"web\", feature = \"mobile\"));\ndefine_config_macro!(web if feature = \"web\");\ndefine_config_macro!(desktop if feature = \"desktop\");\ndefine_config_macro!(native if feature = \"native\");\ndefine_config_macro!(mobile if feature = \"mobile\");\ndefine_config_macro!(fullstack if feature = \"fullstack\");\ndefine_config_macro!(ssr if feature = \"ssr\");\ndefine_config_macro!(liveview if feature = \"liveview\");\n"
  },
  {
    "path": "packages/config-macros/Cargo.toml",
    "content": "[package]\nname = \"dioxus-config-macros\"\nedition = \"2021\"\nversion.workspace = true\nauthors = [\"Dioxus Labs\"]\ndescription = \"Macros used internally by codegen\"\nlicense = \"MIT OR Apache-2.0\"\nrepository = \"https://github.com/DioxusLabs/dioxus/\"\nhomepage = \"https://dioxuslabs.com\"\nkeywords = [\"web\", \"desktop\", \"mobile\", \"gui\", \"wasm\"]\nrust-version = \"1.79.0\"\n\n[dependencies]\n\n[features]\ndefault = []\nwasm-split = []\n"
  },
  {
    "path": "packages/config-macros/README.md",
    "content": "# Dioxus Config Macros\n\nThese macros are used internally by codegen and are not intended for general use.\n\nDioxus will export its feature flags into this crate, allowing downstream codegen to use them under the \"dioxus\" namespace.\n\n## License\n\nThis project is licensed under the [MIT license].\n\n[mit license]: https://github.com/dioxuslabs/dioxus/blob/main/LICENSE-MIT\n\nUnless you explicitly state otherwise, any contribution intentionally submitted\nfor inclusion in Dioxus by you shall be licensed as MIT without any additional\nterms or conditions.\n"
  },
  {
    "path": "packages/config-macros/src/lib.rs",
    "content": "/// A macro for deciding whether or not to split the wasm bundle.\n/// Used by the internal router-macro code. The contents here are considered to be semver exempt.\n///\n/// Only on wasm with the wasm-split feature will we prefer the `maybe_wasm_split` variant that emits\n/// the \"lefthand\" tokens. Otherwise, we emit the non-wasm_split tokens\n#[doc(hidden)]\n#[cfg(all(feature = \"wasm-split\", target_arch = \"wasm32\"))]\n#[macro_export]\nmacro_rules! maybe_wasm_split {\n    (\n        if wasm_split {\n            $left:tt\n        } else {\n            $right:tt\n        }\n    ) => {\n        $left\n    };\n}\n\n/// A macro for deciding whether or not to split the wasm bundle.\n/// Used by the internal router-macro code. The contents here are considered to be semver exempt.\n///\n/// Only on wasm with the wasm-split feature will we prefer the `maybe_wasm_split` variant that emits\n/// the \"lefthand\" tokens. Otherwise, we emit the non-wasm_split tokens\n#[doc(hidden)]\n#[cfg(any(not(feature = \"wasm-split\"), not(target_arch = \"wasm32\")))]\n#[macro_export]\nmacro_rules! maybe_wasm_split {\n    (\n        if wasm_split {\n            $left:tt\n        } else {\n            $right:tt\n        }\n    ) => {\n        $right\n    };\n}\n"
  },
  {
    "path": "packages/const-serialize/.gitignore",
    "content": "target"
  },
  {
    "path": "packages/const-serialize/Cargo.toml",
    "content": "[package]\nname = \"const-serialize\"\nversion = \"0.8.0-alpha.0\"\nauthors = [\"Evan Almloff\"]\nedition = \"2021\"\ndescription = \"A serialization framework that works in const contexts\"\nlicense = \"MIT OR Apache-2.0\"\nrepository = \"https://github.com/dioxuslabs/dioxus\"\nhomepage = \"https://dioxuslabs.com/learn/0.5/getting_started\"\nkeywords = [\"const\", \"serialize\"]\nrust-version = \"1.83.0\"\n\n[dependencies]\nconst-serialize-macro = { workspace = true }\nserde = { workspace = true, features = [\"derive\"], optional = true }\nconst-serialize-07 = { package = \"const-serialize\", version = \"=0.7.2\", optional = true }\n\n[dev-dependencies]\nrand = { workspace = true }\n\n[features]\ntest-big-endian = []\nserde = [\"dep:serde\"]\n# Enables serialization of the ConstStr with the older const-serialize-0.7 crate\nconst-serialize-07 = [\"dep:const-serialize-07\"]\n"
  },
  {
    "path": "packages/const-serialize/README.md",
    "content": "A rust serialization library that works in const with complex(ish) types like enums, nested structs and arrays. Const rust does not have an allocator, so this library cannot work in a cross architecture environment with Vecs, slices or strings.\n\n```rust\nuse const_serialize::{deserialize_const, serialize_const, serialize_eq, ConstVec, SerializeConst};\n#[derive(Clone, Copy, Debug, PartialEq, SerializeConst)]\nstruct Struct {\n    a: u32,\n    b: u8,\n    c: u32,\n    d: Enum,\n}\n\n#[derive(Clone, Copy, Debug, PartialEq, SerializeConst)]\n#[repr(C, u8)]\nenum Enum {\n    A { one: u32, two: u16 },\n    B { one: u8, two: u16 } = 15,\n}\n\nconst {\n    let data = [Struct {\n        a: 0x11111111,\n        b: 0x22,\n        c: 0x33333333,\n        d: Enum::A {\n            one: 0x44444444,\n            two: 0x5555,\n        },\n    }; 3];\n    let mut buf = ConstVec::new();\n    buf = serialize_const(&data, buf);\n    let buf = buf.as_ref();\n    let (buf, deserialized) = match deserialize_const!([Struct; 3], buf) {\n        Some(data) => data,\n        None => panic!(\"data mismatch\"),\n    };\n    if !serialize_eq(&data, &deserialized) {\n        panic!(\"data mismatch\");\n    }\n}\n```\n\n## How it works\n\n`const-serialize` relies heavily on well defined layouts for the types you want to serialize. The serialization format is the linear sequence of unaligned bytes stored in the order of the fields, items or variants of the type. Numbers are stored in little endian order.\n\nIn order to support complex nested types, serialization is done using a trait. Since functions in traits cannot be const, `const-serialize` uses a macro to generate constant associated items that describe the memory layout of the type. That layout is then used to read all of the bytes in the type into the serialized buffer.\n\nThe deserialization is done in a similar way, but the layout is used to write the bytes from the serialized buffer into the type.\n\nThe rust [nomicon](https://doc.rust-lang.org/nomicon/data.html) defines the memory layout of different types. It is used as a reference for the layout of the types implemented in `const-serialize`.\n\n## Limitations\n\n- Only constant sized types are supported. This means that you can't serialize a type like `Vec<T>`. These types are difficult to create in const contexts in general\n- Only types with a well defined memory layout are supported (see <https://github.com/rust-lang/rfcs/pull/3727> and <https://onevariable.com/blog/pods-from-scratch>). `repr(Rust)` enums don't have a well defined layout, so they are not supported. `repr(C, u8)` enums can be used instead\n- Const rust does not support mutable references or points, so this crate leans heavily on functional data structures for data processing.\n"
  },
  {
    "path": "packages/const-serialize/src/array.rs",
    "content": "use crate::*;\n\n/// The layout for a constant sized array. The array layout is just a length and an item layout.\n#[derive(Debug, Copy, Clone)]\npub struct ArrayLayout {\n    pub(crate) len: usize,\n    pub(crate) item_layout: &'static Layout,\n}\n\nimpl ArrayLayout {\n    /// Create a new array layout\n    pub const fn new(len: usize, item_layout: &'static Layout) -> Self {\n        Self { len, item_layout }\n    }\n}\n\nunsafe impl<const N: usize, T: SerializeConst> SerializeConst for [T; N] {\n    const MEMORY_LAYOUT: Layout = Layout::Array(ArrayLayout {\n        len: N,\n        item_layout: &T::MEMORY_LAYOUT,\n    });\n}\n\n/// Serialize a constant sized array that is stored at the pointer passed in\npub(crate) const unsafe fn serialize_const_array(\n    ptr: *const (),\n    mut to: ConstVec<u8>,\n    layout: &ArrayLayout,\n) -> ConstVec<u8> {\n    let len = layout.len;\n    let mut i = 0;\n    to = write_array(to, len);\n    while i < len {\n        let field = ptr.wrapping_byte_offset((i * layout.item_layout.size()) as _);\n        to = serialize_const_ptr(field, to, layout.item_layout);\n        i += 1;\n    }\n    to\n}\n\n/// Deserialize an array type into the out buffer at the offset passed in. Returns a new version of the buffer with the data added.\npub(crate) const fn deserialize_const_array<'a>(\n    from: &'a [u8],\n    layout: &ArrayLayout,\n    mut out: &mut [MaybeUninit<u8>],\n) -> Option<&'a [u8]> {\n    let item_layout = layout.item_layout;\n    let Ok((_, mut from)) = take_array(from) else {\n        return None;\n    };\n    let mut i = 0;\n    while i < layout.len {\n        let Some(new_from) = deserialize_const_ptr(from, item_layout, out) else {\n            return None;\n        };\n        let Some((_, item_out)) = out.split_at_mut_checked(item_layout.size()) else {\n            return None;\n        };\n        out = item_out;\n        from = new_from;\n        i += 1;\n    }\n    Some(from)\n}\n"
  },
  {
    "path": "packages/const-serialize/src/cbor.rs",
    "content": "//! Const serialization utilities for the CBOR data format.\n//!\n//! ## Overview of the format\n//!\n//! Const serialize only supports a subset of the CBOR format, specifically the major types:\n//! - UnsignedInteger\n//! - NegativeInteger\n//! - Bytes\n//! - String\n//! - Array\n//!\n//! Each item in CBOR starts with a leading byte, which determines the type of the item and additional information.\n//! The additional information is encoded in the lower 5 bits of the leading byte and generally indicates either a\n//! small number or how many of the next bytes are part of the first number.\n//!\n//! Resources:\n//! The spec: <https://www.rfc-editor.org/rfc/rfc8949.html>\n//! A playground to check examples against: <https://cbor.me/>\n\nuse crate::ConstVec;\n\n/// Each item in CBOR starts with a leading byte, which determines the type of the item and additional information.\n///\n/// The first 3 bits of the leading byte are the major type, which indicates the type of the item.\n#[repr(u8)]\n#[derive(PartialEq)]\nenum MajorType {\n    /// An unsigned integer in the range 0..2^64. The value of the number is encoded in the remaining bits of the leading byte and any additional bytes.\n    UnsignedInteger = 0,\n    /// An unsigned integer in the range -2^64..-1. The value of the number is encoded in the remaining bits of the leading byte and any additional bytes\n    NegativeInteger = 1,\n    /// A byte sequence. The number of bytes in the sequence is encoded in the remaining bits of the leading byte and any additional bytes.\n    Bytes = 2,\n    /// A text sequence. The number of bytes in the sequence is encoded in the remaining bits of the leading byte and any additional bytes.\n    Text = 3,\n    /// A dynamically sized array of non-uniform data items. The number of items in the array is encoded in the remaining bits of the leading byte and any additional bytes.\n    Array = 4,\n    /// A map of pairs of data items. The first item in each pair is the key and the second item is the value. The number of items in the array is encoded in the remaining bits of the leading byte and any additional bytes.\n    Map = 5,\n    /// Tagged values - not supported\n    Tagged = 6,\n    /// Floating point values - not supported\n    Float = 7,\n}\n\nimpl MajorType {\n    /// The bitmask for the major type in the leading byte\n    const MASK: u8 = 0b0001_1111;\n\n    const fn from_byte(byte: u8) -> Self {\n        match byte >> 5 {\n            0 => MajorType::UnsignedInteger,\n            1 => MajorType::NegativeInteger,\n            2 => MajorType::Bytes,\n            3 => MajorType::Text,\n            4 => MajorType::Array,\n            5 => MajorType::Map,\n            6 => MajorType::Tagged,\n            7 => MajorType::Float,\n            _ => panic!(\"Invalid major type\"),\n        }\n    }\n}\n\n/// Get the length of the item in bytes without deserialization.\nconst fn item_length(bytes: &[u8]) -> Result<usize, ()> {\n    let [head, rest @ ..] = bytes else {\n        return Err(());\n    };\n    let major = MajorType::from_byte(*head);\n    let additional_information = *head & MajorType::MASK;\n    let length_of_item = match major {\n        // The length of the number is the total of:\n        // - The length of the number (which may be 0 if the number is encoded in additional information)\n        MajorType::UnsignedInteger | MajorType::NegativeInteger => {\n            get_length_of_number(additional_information) as usize\n        }\n        // The length of the text or bytes is the total of:\n        // - The length of the number that denotes the length of the text or bytes\n        // - The length of the text or bytes themselves\n        MajorType::Text | MajorType::Bytes => {\n            let length_of_number = get_length_of_number(additional_information);\n            let Ok((length_of_bytes, _)) =\n                grab_u64_with_byte_length(rest, length_of_number, additional_information)\n            else {\n                return Err(());\n            };\n            length_of_number as usize + length_of_bytes as usize\n        }\n        // The length of the map is the total of:\n        // - The length of the number that denotes the number of items\n        // - The length of the pairs of items themselves\n        MajorType::Array | MajorType::Map => {\n            let length_of_number = get_length_of_number(additional_information);\n            let Ok((length_of_items, _)) =\n                grab_u64_with_byte_length(rest, length_of_number, additional_information)\n            else {\n                return Err(());\n            };\n            let mut total_length = length_of_number as usize;\n            let mut items_left = length_of_items * if let MajorType::Map = major { 2 } else { 1 };\n            while items_left > 0 {\n                let Some((_, after)) = rest.split_at_checked(total_length) else {\n                    return Err(());\n                };\n                let Ok(item_length) = item_length(after) else {\n                    return Err(());\n                };\n                total_length += item_length;\n                items_left -= 1;\n            }\n            total_length\n        }\n        _ => return Err(()),\n    };\n    let length_of_head = 1;\n    Ok(length_of_head + length_of_item)\n}\n\n/// Read a number from the buffer, returning the number and the remaining bytes.\npub(crate) const fn take_number(bytes: &[u8]) -> Result<(i64, &[u8]), ()> {\n    let [head, rest @ ..] = bytes else {\n        return Err(());\n    };\n    let major = MajorType::from_byte(*head);\n    let additional_information = *head & MajorType::MASK;\n    match major {\n        MajorType::UnsignedInteger => {\n            let Ok((number, rest)) = grab_u64(rest, additional_information) else {\n                return Err(());\n            };\n            Ok((number as i64, rest))\n        }\n        MajorType::NegativeInteger => {\n            let Ok((number, rest)) = grab_u64(rest, additional_information) else {\n                return Err(());\n            };\n            Ok((-(1 + number as i64), rest))\n        }\n        _ => Err(()),\n    }\n}\n\n/// Write a number to the buffer\npub(crate) const fn write_number<const MAX_SIZE: usize>(\n    vec: ConstVec<u8, MAX_SIZE>,\n    number: i64,\n) -> ConstVec<u8, MAX_SIZE> {\n    match number {\n        0.. => write_major_type_and_u64(vec, MajorType::UnsignedInteger, number as u64),\n        ..0 => write_major_type_and_u64(vec, MajorType::NegativeInteger, (-(number + 1)) as u64),\n    }\n}\n\n/// Write the major type along with a number to the buffer. The first byte\n/// contains both the major type and the additional information which contains\n/// either the number itself or the number of extra bytes the number occupies.\nconst fn write_major_type_and_u64<const MAX_SIZE: usize>(\n    vec: ConstVec<u8, MAX_SIZE>,\n    major: MajorType,\n    number: u64,\n) -> ConstVec<u8, MAX_SIZE> {\n    let major = (major as u8) << 5;\n    match number {\n        // For numbers less than 24, store the number in the lower bits\n        // of the first byte\n        0..24 => {\n            let additional_information = number as u8;\n            let byte = major | additional_information;\n            vec.push(byte)\n        }\n        // For larger numbers, store the number of extra bytes the number occupies\n        24.. => {\n            let log2_additional_bytes = log2_bytes_for_number(number);\n            let additional_bytes = 1 << log2_additional_bytes;\n            let additional_information = log2_additional_bytes + 24;\n            let byte = major | additional_information;\n            let mut vec = vec.push(byte);\n            let mut byte = 0;\n            while byte < additional_bytes {\n                vec = vec.push((number >> ((additional_bytes - byte - 1) * 8)) as u8);\n                byte += 1;\n            }\n            vec\n        }\n    }\n}\n\n/// Find the number of bytes required to store a number and return the log2 of the number of bytes.\n/// This is the number stored in the additional information field if the number is more than 24.\nconst fn log2_bytes_for_number(number: u64) -> u8 {\n    let required_bytes = ((64 - number.leading_zeros()).div_ceil(8)) as u8;\n    #[allow(clippy::match_overlapping_arm)]\n    match required_bytes {\n        ..=1 => 0,\n        ..=2 => 1,\n        ..=4 => 2,\n        _ => 3,\n    }\n}\n\n/// Take bytes from a slice and return the bytes and the remaining slice.\npub(crate) const fn take_bytes(bytes: &[u8]) -> Result<(&[u8], &[u8]), ()> {\n    let [head, rest @ ..] = bytes else {\n        return Err(());\n    };\n    let major = MajorType::from_byte(*head);\n    let additional_information = *head & MajorType::MASK;\n    if let MajorType::Bytes = major {\n        take_bytes_from(rest, additional_information)\n    } else {\n        Err(())\n    }\n}\n\n/// Write bytes to a buffer and return the new buffer.\npub(crate) const fn write_bytes<const MAX_SIZE: usize>(\n    vec: ConstVec<u8, MAX_SIZE>,\n    bytes: &[u8],\n) -> ConstVec<u8, MAX_SIZE> {\n    let vec = write_major_type_and_u64(vec, MajorType::Bytes, bytes.len() as u64);\n    vec.extend(bytes)\n}\n\n/// Take a string from a buffer and return the string and the remaining buffer.\npub(crate) const fn take_str(bytes: &[u8]) -> Result<(&str, &[u8]), ()> {\n    let [head, rest @ ..] = bytes else {\n        return Err(());\n    };\n    let major = MajorType::from_byte(*head);\n    let additional_information = *head & MajorType::MASK;\n    if let MajorType::Text = major {\n        let Ok((bytes, rest)) = take_bytes_from(rest, additional_information) else {\n            return Err(());\n        };\n        let Ok(string) = std::str::from_utf8(bytes) else {\n            return Err(());\n        };\n        Ok((string, rest))\n    } else {\n        Err(())\n    }\n}\n\n/// Write a string to a buffer and return the new buffer.\npub(crate) const fn write_str<const MAX_SIZE: usize>(\n    vec: ConstVec<u8, MAX_SIZE>,\n    string: &str,\n) -> ConstVec<u8, MAX_SIZE> {\n    let vec = write_major_type_and_u64(vec, MajorType::Text, string.len() as u64);\n    vec.extend(string.as_bytes())\n}\n\n/// Take the length and header of an array from a buffer and return the length and the remaining buffer.\n/// You must loop over the elements of the array and parse them outside of this method.\npub(crate) const fn take_array(bytes: &[u8]) -> Result<(usize, &[u8]), ()> {\n    let [head, rest @ ..] = bytes else {\n        return Err(());\n    };\n    let major = MajorType::from_byte(*head);\n    let additional_information = *head & MajorType::MASK;\n    if let MajorType::Array = major {\n        let Ok((length, rest)) = take_len_from(rest, additional_information) else {\n            return Err(());\n        };\n        Ok((length as usize, rest))\n    } else {\n        Err(())\n    }\n}\n\n/// Write the header and length of an array.\npub(crate) const fn write_array<const MAX_SIZE: usize>(\n    vec: ConstVec<u8, MAX_SIZE>,\n    len: usize,\n) -> ConstVec<u8, MAX_SIZE> {\n    write_major_type_and_u64(vec, MajorType::Array, len as u64)\n}\n\n/// Write the header and length of a map.\npub(crate) const fn write_map<const MAX_SIZE: usize>(\n    vec: ConstVec<u8, MAX_SIZE>,\n    len: usize,\n) -> ConstVec<u8, MAX_SIZE> {\n    // We write 2 * len as the length of the map because each key-value pair is a separate entry.\n    write_major_type_and_u64(vec, MajorType::Map, len as u64)\n}\n\n/// Write the key of a map entry.\npub(crate) const fn write_map_key<const MAX_SIZE: usize>(\n    value: ConstVec<u8, MAX_SIZE>,\n    key: &str,\n) -> ConstVec<u8, MAX_SIZE> {\n    write_str(value, key)\n}\n\n/// Take a map from the byte slice and return the map reference and the remaining bytes.\npub(crate) const fn take_map<'a>(bytes: &'a [u8]) -> Result<(MapRef<'a>, &'a [u8]), ()> {\n    let [head, rest @ ..] = bytes else {\n        return Err(());\n    };\n    let major = MajorType::from_byte(*head);\n    let additional_information = *head & MajorType::MASK;\n    if let MajorType::Map = major {\n        let Ok((length, rest)) = take_len_from(rest, additional_information) else {\n            return Err(());\n        };\n        let mut after_map = rest;\n        let mut items_left = length * 2;\n        while items_left > 0 {\n            // Skip the value\n            let Ok(len) = item_length(after_map) else {\n                return Err(());\n            };\n            let Some((_, rest)) = after_map.split_at_checked(len) else {\n                return Err(());\n            };\n            after_map = rest;\n            items_left -= 1;\n        }\n        Ok((MapRef::new(rest, length as usize), after_map))\n    } else {\n        Err(())\n    }\n}\n\n/// A reference to a CBOR map.\npub(crate) struct MapRef<'a> {\n    /// The bytes of the map.\n    pub(crate) bytes: &'a [u8],\n    /// The length of the map.\n    pub(crate) len: usize,\n}\n\nimpl<'a> MapRef<'a> {\n    /// Create a new map reference.\n    const fn new(bytes: &'a [u8], len: usize) -> Self {\n        Self { bytes, len }\n    }\n\n    /// Find a key in the map and return the buffer associated with it.\n    pub(crate) const fn find(&self, key: &str) -> Result<Option<&[u8]>, ()> {\n        let mut bytes = self.bytes;\n        let mut items_left = self.len;\n        while items_left > 0 {\n            let Ok((str, rest)) = take_str(bytes) else {\n                return Err(());\n            };\n            if str_eq(key, str) {\n                return Ok(Some(rest));\n            }\n            // Skip the value associated with the key we don't care about\n            let Ok(len) = item_length(rest) else {\n                return Err(());\n            };\n            let Some((_, rest)) = rest.split_at_checked(len) else {\n                return Err(());\n            };\n            bytes = rest;\n            items_left -= 1;\n        }\n        Ok(None)\n    }\n}\n\n/// Compare two strings for equality at compile time.\npub(crate) const fn str_eq(a: &str, b: &str) -> bool {\n    let a_bytes = a.as_bytes();\n    let b_bytes = b.as_bytes();\n    let a_len = a_bytes.len();\n    let b_len = b_bytes.len();\n    if a_len != b_len {\n        return false;\n    }\n    let mut index = 0;\n    while index < a_len {\n        if a_bytes[index] != b_bytes[index] {\n            return false;\n        }\n        index += 1;\n    }\n    true\n}\n\n/// Take the length from the additional information byte and return it along with the remaining bytes.\nconst fn take_len_from(rest: &[u8], additional_information: u8) -> Result<(u64, &[u8]), ()> {\n    match additional_information {\n        // If additional_information < 24, the argument's value is the value of the additional information.\n        0..24 => Ok((additional_information as u64, rest)),\n        // If additional_information is between 24 and 28, the argument's value is held in the n following bytes.\n        24..28 => {\n            let Ok((number, rest)) = grab_u64(rest, additional_information) else {\n                return Err(());\n            };\n            Ok((number, rest))\n        }\n        _ => Err(()),\n    }\n}\n\n/// Take a list of bytes from the byte slice and the additional information byte\n/// and return the bytes and the remaining bytes.\npub(crate) const fn take_bytes_from(\n    rest: &[u8],\n    additional_information: u8,\n) -> Result<(&[u8], &[u8]), ()> {\n    let Ok((number, rest)) = grab_u64(rest, additional_information) else {\n        return Err(());\n    };\n    let Some((bytes, rest)) = rest.split_at_checked(number as usize) else {\n        return Err(());\n    };\n    Ok((bytes, rest))\n}\n\n/// Find the length of the number based on the additional information byte.\nconst fn get_length_of_number(additional_information: u8) -> u8 {\n    match additional_information {\n        0..24 => 0,\n        24..28 => 1 << (additional_information - 24),\n        _ => 0,\n    }\n}\n\n/// Read a u64 from the byte slice and the additional information byte.\nconst fn grab_u64(rest: &[u8], additional_information: u8) -> Result<(u64, &[u8]), ()> {\n    grab_u64_with_byte_length(\n        rest,\n        get_length_of_number(additional_information),\n        additional_information,\n    )\n}\n\n/// Read a u64 from the byte slice and the additional information byte along with the byte length.\nconst fn grab_u64_with_byte_length(\n    mut rest: &[u8],\n    byte_length: u8,\n    additional_information: u8,\n) -> Result<(u64, &[u8]), ()> {\n    match byte_length {\n        0 => Ok((additional_information as u64, rest)),\n        n => {\n            let mut value = 0;\n            let mut count = 0;\n            while count < n {\n                let [next, remaining @ ..] = rest else {\n                    return Err(());\n                };\n                value = (value << 8) | *next as u64;\n                rest = remaining;\n                count += 1;\n            }\n            Ok((value, rest))\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn test_parse_byte() {\n        for byte in 0..=255 {\n            let bytes = if byte < 24 { [byte, 0] } else { [24, byte] };\n            let (item, _) = take_number(&bytes).unwrap();\n            assert_eq!(item, byte as _);\n        }\n        for byte in 1..=255 {\n            let bytes = if byte < 24 {\n                [(byte - 1) | 0b0010_0000, 0]\n            } else {\n                [0b0010_0000 | 24, byte - 1]\n            };\n            let (item, _) = take_number(&bytes).unwrap();\n            assert_eq!(item, -(byte as i64));\n        }\n    }\n\n    #[test]\n    fn test_byte_roundtrip() {\n        for byte in 0..=255 {\n            let vec = write_number(ConstVec::new(), byte as _);\n            println!(\"{vec:?}\");\n            let (item, _) = take_number(vec.as_ref()).unwrap();\n            assert_eq!(item, byte as _);\n        }\n        for byte in 0..=255 {\n            let vec = write_number(ConstVec::new(), -(byte as i64));\n            let (item, _) = take_number(vec.as_ref()).unwrap();\n            assert_eq!(item, -(byte as i64));\n        }\n    }\n\n    #[test]\n    fn test_number_roundtrip() {\n        for _ in 0..100 {\n            let value = rand::random::<i64>();\n            let vec = write_number(ConstVec::new(), value);\n            let (item, _) = take_number(vec.as_ref()).unwrap();\n            assert_eq!(item, value);\n        }\n    }\n\n    #[test]\n    fn test_bytes_roundtrip() {\n        for _ in 0..100 {\n            let len = (rand::random::<u8>() % 100) as usize;\n            let bytes = rand::random::<[u8; 100]>();\n            let vec = write_bytes(ConstVec::new(), &bytes[..len]);\n            let (item, _) = take_bytes(vec.as_ref()).unwrap();\n            assert_eq!(item, &bytes[..len]);\n        }\n    }\n\n    #[test]\n    fn test_array_roundtrip() {\n        for _ in 0..100 {\n            let len = (rand::random::<u8>() % 100) as usize;\n            let mut vec = write_array(ConstVec::new(), len);\n            for i in 0..len {\n                vec = write_number(vec, i as _);\n            }\n            let (len, mut remaining) = take_array(vec.as_ref()).unwrap();\n            for i in 0..len {\n                let (item, rest) = take_number(remaining).unwrap();\n                remaining = rest;\n                assert_eq!(item, i as i64);\n            }\n        }\n    }\n\n    #[test]\n    fn test_map_roundtrip() {\n        use rand::prelude::SliceRandom;\n        for _ in 0..100 {\n            let len = (rand::random::<u8>() % 10) as usize;\n            let mut vec = write_map(ConstVec::new(), len);\n            let mut random_order_indexes = (0..len).collect::<Vec<_>>();\n            random_order_indexes.shuffle(&mut rand::rng());\n            for &i in &random_order_indexes {\n                vec = write_map_key(vec, &i.to_string());\n                vec = write_number(vec, i as _);\n            }\n            println!(\"len: {}\", len);\n            println!(\"Map: {:?}\", vec);\n            let (map, remaining) = take_map(vec.as_ref()).unwrap();\n            println!(\"remaining: {:?}\", remaining);\n            assert!(remaining.is_empty());\n            for i in 0..len {\n                let key = i.to_string();\n                let key_location = map\n                    .find(&key)\n                    .expect(\"encoding is valid\")\n                    .expect(\"key exists\");\n                let (value, _) = take_number(key_location).unwrap();\n                assert_eq!(value, i as i64);\n            }\n        }\n    }\n\n    #[test]\n    fn test_item_length_str() {\n        #[rustfmt::skip]\n    let input = [\n        /* text(1) */ 0x61,\n        /* \"1\" */     0x31,\n        /* text(1) */ 0x61,\n        /* \"1\" */     0x31,\n    ];\n        let Ok(length) = item_length(&input) else {\n            panic!(\"Failed to calculate length\");\n        };\n        assert_eq!(length, 2);\n    }\n\n    #[test]\n    fn test_item_length_map() {\n        #[rustfmt::skip]\n    let input = [\n        /* map(1) */              0xA1,\n        /* text(1) */             0x61,\n        /* \"A\" */                 0x41,\n        /* map(2) */              0xA2,\n        /* text(3) */             0x63,\n        /* \"one\" */               0x6F, 0x6E, 0x65,\n        /* unsigned(286331153) */ 0x1A, 0x11, 0x11, 0x11, 0x11,\n        /* text(3) */             0x63,\n        /* \"two\" */               0x74, 0x77, 0x6F,\n        /* unsigned(34) */        0x18, 0x22,\n    ];\n        let Ok(length) = item_length(&input) else {\n            panic!(\"Failed to calculate length\");\n        };\n        assert_eq!(length, input.len());\n    }\n}\n"
  },
  {
    "path": "packages/const-serialize/src/const_buffers.rs",
    "content": "/// A buffer that can be read from at compile time. This is very similar to [Cursor](std::io::Cursor) but is\n/// designed to be used in const contexts.\n#[derive(Debug, Clone, Copy, PartialEq)]\npub struct ConstReadBuffer<'a> {\n    location: usize,\n    memory: &'a [u8],\n}\n\nimpl<'a> ConstReadBuffer<'a> {\n    /// Create a new buffer from a byte slice\n    pub const fn new(memory: &'a [u8]) -> Self {\n        Self {\n            location: 0,\n            memory,\n        }\n    }\n\n    /// Get the next byte from the buffer. Returns `None` if the buffer is empty.\n    /// This will return the new version of the buffer with the first byte removed.\n    pub const fn get(mut self) -> Option<(Self, u8)> {\n        if self.location >= self.memory.len() {\n            return None;\n        }\n        let value = self.memory[self.location];\n        self.location += 1;\n        Some((self, value))\n    }\n\n    /// Get a reference to the underlying byte slice\n    pub const fn as_ref(&self) -> &[u8] {\n        self.memory\n    }\n\n    /// Get a slice of the buffer from the current location to the end of the buffer\n    pub const fn remaining(&self) -> &[u8] {\n        self.memory.split_at(self.location).1\n    }\n}\n"
  },
  {
    "path": "packages/const-serialize/src/const_vec.rs",
    "content": "#![allow(dead_code)]\nuse std::{fmt::Debug, hash::Hash, mem::MaybeUninit};\n\nuse crate::ConstReadBuffer;\n\nconst DEFAULT_MAX_SIZE: usize = 2usize.pow(10);\n\n/// [`ConstVec`] is a version of [`Vec`] that is usable in const contexts. It has\n/// a fixed maximum size, but it can grow and shrink within that size limit\n/// as needed.\n///\n/// # Example\n/// ```rust\n/// # use const_serialize::ConstVec;\n/// const EMPTY: ConstVec<u8> = ConstVec::new();\n/// // Methods that mutate the vector will return a new vector\n/// const ONE: ConstVec<u8> = EMPTY.push(1);\n/// const TWO: ConstVec<u8> = ONE.push(2);\n/// const THREE: ConstVec<u8> = TWO.push(3);\n/// const FOUR: ConstVec<u8> = THREE.push(4);\n/// // If a value is also returned, that will be placed in a tuple in the return value\n/// // along with the new vector\n/// const POPPED: (ConstVec<u8>, Option<u8>) = FOUR.pop();\n/// assert_eq!(POPPED.0, THREE);\n/// assert_eq!(POPPED.1.unwrap(), 4);\n/// ```\npub struct ConstVec<T, const MAX_SIZE: usize = DEFAULT_MAX_SIZE> {\n    memory: [MaybeUninit<T>; MAX_SIZE],\n    len: u32,\n}\n\nimpl<T: Clone, const MAX_SIZE: usize> Clone for ConstVec<T, MAX_SIZE> {\n    fn clone(&self) -> Self {\n        let mut cloned = Self::new_with_max_size();\n        for i in 0..self.len as usize {\n            cloned = cloned.push(self.get(i).unwrap().clone());\n        }\n        cloned\n    }\n}\n\nimpl<T: Copy, const MAX_SIZE: usize> Copy for ConstVec<T, MAX_SIZE> {}\n\nimpl<T: PartialEq, const MAX_SIZE: usize> PartialEq for ConstVec<T, MAX_SIZE> {\n    fn eq(&self, other: &Self) -> bool {\n        self.as_ref() == other.as_ref()\n    }\n}\n\nimpl<T: Hash, const MAX_SIZE: usize> Hash for ConstVec<T, MAX_SIZE> {\n    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {\n        self.as_ref().hash(state)\n    }\n}\n\nimpl<T, const MAX_SIZE: usize> Default for ConstVec<T, MAX_SIZE> {\n    fn default() -> Self {\n        Self::new_with_max_size()\n    }\n}\n\nimpl<T: Debug, const MAX_SIZE: usize> Debug for ConstVec<T, MAX_SIZE> {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_struct(\"ConstVec\")\n            .field(\"len\", &self.len)\n            .field(\"memory\", &self.as_ref())\n            .finish()\n    }\n}\n\nimpl<T> ConstVec<T> {\n    /// Create a new empty [`ConstVec`]\n    pub const fn new() -> Self {\n        Self::new_with_max_size()\n    }\n}\n\nimpl<T, const MAX_SIZE: usize> ConstVec<T, MAX_SIZE> {\n    /// Create a new empty [`ConstVec`] with a custom maximum size\n    ///\n    /// # Example\n    /// ```rust\n    /// # use const_serialize::ConstVec;\n    /// const EMPTY: ConstVec<u8, 10> = ConstVec::new_with_max_size();\n    /// ```\n    pub const fn new_with_max_size() -> Self {\n        Self {\n            memory: [const { MaybeUninit::uninit() }; MAX_SIZE],\n            len: 0,\n        }\n    }\n\n    /// Push a value onto the end of the [`ConstVec`]\n    ///\n    /// # Example\n    /// ```rust\n    /// # use const_serialize::ConstVec;\n    /// const EMPTY: ConstVec<u8> = ConstVec::new();\n    /// const ONE: ConstVec<u8> = EMPTY.push(1);\n    /// assert_eq!(ONE.as_ref(), &[1]);\n    /// ```\n    pub const fn push(mut self, value: T) -> Self {\n        self.memory[self.len as usize] = MaybeUninit::new(value);\n        self.len += 1;\n        self\n    }\n\n    /// Extend the [`ConstVec`] with the contents of a slice\n    ///\n    /// # Example\n    /// ```rust\n    /// # use const_serialize::ConstVec;\n    /// const EMPTY: ConstVec<u8> = ConstVec::new();\n    /// const ONE: ConstVec<u8> = EMPTY.extend(&[1, 2, 3]);\n    /// assert_eq!(ONE.as_ref(), &[1, 2, 3]);\n    /// ```\n    pub const fn extend(mut self, other: &[T]) -> Self\n    where\n        T: Copy,\n    {\n        let mut i = 0;\n        while i < other.len() {\n            self = self.push(other[i]);\n            i += 1;\n        }\n        self\n    }\n\n    /// Get a reference to the value at the given index\n    ///\n    /// # Example\n    /// ```rust\n    /// # use const_serialize::ConstVec;\n    /// const EMPTY: ConstVec<u8> = ConstVec::new();\n    /// const ONE: ConstVec<u8> = EMPTY.push(1);\n    /// assert_eq!(ONE.get(0), Some(&1));\n    /// ```\n    pub const fn get(&self, index: usize) -> Option<&T> {\n        if index < self.len as usize {\n            Some(unsafe { &*self.memory[index].as_ptr() })\n        } else {\n            None\n        }\n    }\n\n    /// Get the length of the [`ConstVec`]\n    ///\n    /// # Example\n    /// ```rust\n    /// # use const_serialize::ConstVec;\n    /// const EMPTY: ConstVec<u8> = ConstVec::new();\n    /// const ONE: ConstVec<u8> = EMPTY.push(1);\n    /// assert_eq!(ONE.len(), 1);\n    /// ```\n    pub const fn len(&self) -> usize {\n        self.len as usize\n    }\n\n    /// Check if the [`ConstVec`] is empty\n    ///\n    /// # Example\n    /// ```rust\n    /// # use const_serialize::ConstVec;\n    /// const EMPTY: ConstVec<u8> = ConstVec::new();\n    /// assert!(EMPTY.is_empty());\n    /// const ONE: ConstVec<u8> = EMPTY.push(1);\n    /// assert!(!ONE.is_empty());\n    /// ```\n    pub const fn is_empty(&self) -> bool {\n        self.len == 0\n    }\n\n    /// Get a reference to the underlying slice\n    ///\n    /// # Example\n    /// ```rust\n    /// # use const_serialize::ConstVec;\n    /// const EMPTY: ConstVec<u8> = ConstVec::new();\n    /// const ONE: ConstVec<u8> = EMPTY.push(1);\n    /// assert_eq!(ONE.as_ref(), &[1]);\n    /// ```\n    pub const fn as_ref(&self) -> &[T] {\n        unsafe {\n            &*(self.memory.split_at(self.len as usize).0 as *const [MaybeUninit<T>] as *const [T])\n        }\n    }\n\n    /// Swap the values at the given indices\n    ///\n    /// # Example\n    /// ```rust\n    /// # use const_serialize::ConstVec;\n    /// const EMPTY: ConstVec<u8> = ConstVec::new();\n    /// const ONE: ConstVec<u8> = EMPTY.push(1);\n    /// const TWO: ConstVec<u8> = ONE.push(2);\n    /// const THREE: ConstVec<u8> = TWO.swap(0, 1);\n    /// assert_eq!(THREE.as_ref(), &[2, 1]);\n    /// ```\n    pub const fn swap(mut self, first: usize, second: usize) -> Self\n    where\n        T: Copy,\n    {\n        assert!(first < self.len as usize);\n        assert!(second < self.len as usize);\n        let temp = self.memory[first];\n        self.memory[first] = self.memory[second];\n        self.memory[second] = temp;\n        self\n    }\n\n    /// Pop a value off the end of the [`ConstVec`]\n    ///\n    /// # Example\n    /// ```rust\n    /// # use const_serialize::ConstVec;\n    /// const EMPTY: ConstVec<u8> = ConstVec::new();\n    /// const ONE: ConstVec<u8> = EMPTY.push(1);\n    /// const TWO: ConstVec<u8> = ONE.push(2);\n    /// const THREE: ConstVec<u8> = TWO.push(3);\n    /// const POPPED: (ConstVec<u8>, Option<u8>) = THREE.pop();\n    /// assert_eq!(POPPED.0, TWO);\n    /// assert_eq!(POPPED.1.unwrap(), 3);\n    /// ```\n    pub const fn pop(mut self) -> (Self, Option<T>)\n    where\n        T: Copy,\n    {\n        let value = if self.len > 0 {\n            self.len -= 1;\n            let last = self.len as usize;\n            let last_value = unsafe { self.memory[last].assume_init() };\n            Some(last_value)\n        } else {\n            None\n        };\n        (self, value)\n    }\n\n    /// Remove the value at the given index\n    ///\n    /// # Example\n    /// ```rust\n    /// # use const_serialize::ConstVec;\n    /// const EMPTY: ConstVec<u8> = ConstVec::new();\n    /// const ONE: ConstVec<u8> = EMPTY.push(1);\n    /// const TWO: ConstVec<u8> = ONE.push(2);\n    /// const THREE: ConstVec<u8> = TWO.push(3);\n    /// const REMOVED: (ConstVec<u8>, Option<u8>) = THREE.remove(1);\n    /// assert_eq!(REMOVED.0.as_ref(), &[1, 3]);\n    /// assert_eq!(REMOVED.1.unwrap(), 2);\n    /// ```\n    pub const fn remove(mut self, index: usize) -> (Self, Option<T>)\n    where\n        T: Copy,\n    {\n        let value = if index < self.len as usize {\n            let value = unsafe { self.memory[index].assume_init() };\n            let mut swap_index = index;\n            while swap_index + 1 < self.len as usize {\n                self.memory[swap_index] = self.memory[swap_index + 1];\n                swap_index += 1;\n            }\n            self.len -= 1;\n            Some(value)\n        } else {\n            None\n        };\n\n        (self, value)\n    }\n\n    /// Set the value at the given index\n    ///\n    /// # Example\n    /// ```rust\n    /// # use const_serialize::ConstVec;\n    /// const EMPTY: ConstVec<u8> = ConstVec::new();\n    /// const ONE: ConstVec<u8> = EMPTY.push(1);\n    /// const TWO: ConstVec<u8> = ONE.set(0, 2);\n    /// assert_eq!(TWO.as_ref(), &[2]);\n    /// ```\n    pub const fn set(mut self, index: usize, value: T) -> Self {\n        if index >= self.len as usize {\n            panic!(\"Out of bounds\")\n        }\n        self.memory[index] = MaybeUninit::new(value);\n        self\n    }\n\n    pub(crate) const fn into_parts(self) -> ([MaybeUninit<T>; MAX_SIZE], usize) {\n        (self.memory, self.len as usize)\n    }\n\n    /// Split the [`ConstVec`] into two at the given index\n    ///\n    /// # Example\n    /// ```rust\n    /// # use const_serialize::ConstVec;\n    /// const EMPTY: ConstVec<u8> = ConstVec::new();\n    /// const ONE: ConstVec<u8> = EMPTY.push(1);\n    /// const TWO: ConstVec<u8> = ONE.push(2);\n    /// const THREE: ConstVec<u8> = TWO.push(3);\n    /// const SPLIT: (ConstVec<u8>, ConstVec<u8>) = THREE.split_at(1);\n    /// assert_eq!(SPLIT.0.as_ref(), &[1]);\n    /// assert_eq!(SPLIT.1.as_ref(), &[2, 3]);\n    /// ```\n    pub const fn split_at(&self, index: usize) -> (Self, Self)\n    where\n        T: Copy,\n    {\n        assert!(index <= self.len as usize);\n        let slice = self.as_ref();\n        let (left, right) = slice.split_at(index);\n        let mut left_vec = Self::new_with_max_size();\n        let mut i = 0;\n        while i < left.len() {\n            left_vec = left_vec.push(left[i]);\n            i += 1;\n        }\n        let mut right_vec = Self::new_with_max_size();\n        i = 0;\n        while i < right.len() {\n            right_vec = right_vec.push(right[i]);\n            i += 1;\n        }\n        (left_vec, right_vec)\n    }\n}\n\nimpl<const MAX_SIZE: usize> ConstVec<u8, MAX_SIZE> {\n    /// Convert the [`ConstVec`] into a [`ConstReadBuffer`]\n    ///\n    /// # Example\n    /// ```rust\n    /// # use const_serialize::{ConstVec, ConstReadBuffer};\n    /// const EMPTY: ConstVec<u8> = ConstVec::new();\n    /// const ONE: ConstVec<u8> = EMPTY.push(1);\n    /// const TWO: ConstVec<u8> = ONE.push(2);\n    /// const READ: ConstReadBuffer = TWO.read();\n    /// ```\n    pub const fn read(&self) -> ConstReadBuffer<'_> {\n        ConstReadBuffer::new(self.as_ref())\n    }\n}\n\n#[test]\nfn test_const_vec() {\n    const VEC: ConstVec<u32> = {\n        let mut vec = ConstVec::new();\n        vec = vec.push(1234);\n        vec = vec.push(5678);\n        vec\n    };\n    assert_eq!(VEC.as_ref(), &[1234, 5678]);\n    let vec = VEC;\n    let (vec, value) = vec.pop();\n    assert_eq!(value, Some(5678));\n    let (vec, value) = vec.pop();\n    assert_eq!(value, Some(1234));\n    let (vec, value) = vec.pop();\n    assert_eq!(value, None);\n    assert_eq!(vec.as_ref(), &[]);\n}\n\n#[test]\nfn test_const_vec_len() {\n    const VEC: ConstVec<u32> = {\n        let mut vec = ConstVec::new();\n        vec = vec.push(1234);\n        vec = vec.push(5678);\n        vec\n    };\n    assert_eq!(VEC.len(), 2);\n}\n\n#[test]\nfn test_const_vec_get() {\n    const VEC: ConstVec<u32> = {\n        let mut vec = ConstVec::new();\n        vec = vec.push(1234);\n        vec = vec.push(5678);\n        vec\n    };\n    assert_eq!(VEC.get(0), Some(&1234));\n    assert_eq!(VEC.get(1), Some(&5678));\n    assert_eq!(VEC.get(2), None);\n}\n\n#[test]\nfn test_const_vec_swap() {\n    const VEC: ConstVec<u32> = {\n        let mut vec = ConstVec::new();\n        vec = vec.push(1234);\n        vec = vec.push(5678);\n        vec\n    };\n    let mut vec = VEC;\n    assert_eq!(vec.as_ref(), &[1234, 5678]);\n    vec = vec.swap(0, 1);\n    assert_eq!(vec.as_ref(), &[5678, 1234]);\n    vec = vec.swap(0, 1);\n    assert_eq!(vec.as_ref(), &[1234, 5678]);\n}\n\n#[test]\nfn test_const_vec_remove() {\n    const VEC: ConstVec<u32> = {\n        let mut vec = ConstVec::new();\n        vec = vec.push(1234);\n        vec = vec.push(5678);\n        vec\n    };\n    let vec = VEC;\n    println!(\"{:?}\", vec);\n    assert_eq!(vec.as_ref(), &[1234, 5678]);\n    let (vec, value) = vec.remove(0);\n    assert_eq!(value, Some(1234));\n    assert_eq!(vec.as_ref(), &[5678]);\n    let (vec, value) = vec.remove(0);\n    assert_eq!(value, Some(5678));\n    assert_eq!(vec.as_ref(), &[]);\n}\n\n#[test]\nfn test_const_vec_extend() {\n    const VEC: ConstVec<u32> = {\n        let mut vec = ConstVec::new();\n        vec = vec.push(1234);\n        vec = vec.push(5678);\n        vec = vec.extend(&[91011, 1213]);\n        vec\n    };\n    let vec = VEC;\n    println!(\"{:?}\", vec);\n    assert_eq!(vec.as_ref(), &[1234, 5678, 91011, 1213]);\n}\n"
  },
  {
    "path": "packages/const-serialize/src/enum.rs",
    "content": "use crate::*;\n\n/// Serialize an enum that is stored at the pointer passed in\npub(crate) const unsafe fn serialize_const_enum(\n    ptr: *const (),\n    mut to: ConstVec<u8>,\n    layout: &EnumLayout,\n) -> ConstVec<u8> {\n    let byte_ptr = ptr as *const u8;\n    let discriminant = layout.discriminant.read(byte_ptr);\n\n    let mut i = 0;\n    while i < layout.variants.len() {\n        // If the variant is the discriminated one, serialize it\n        let EnumVariant {\n            tag, name, data, ..\n        } = &layout.variants[i];\n        if discriminant == *tag {\n            to = write_map(to, 1);\n            to = write_map_key(to, name);\n            let data_ptr = ptr.wrapping_byte_offset(layout.variants_offset as _);\n            to = serialize_const_struct(data_ptr, to, data);\n            break;\n        }\n        i += 1;\n    }\n    to\n}\n\n/// The layout for an enum. The enum layout is just a discriminate size and a tag layout.\n#[derive(Debug, Copy, Clone)]\npub struct EnumLayout {\n    pub(crate) size: usize,\n    discriminant: PrimitiveLayout,\n    variants_offset: usize,\n    variants: &'static [EnumVariant],\n}\n\nimpl EnumLayout {\n    /// Create a new enum layout\n    pub const fn new(\n        size: usize,\n        discriminant: PrimitiveLayout,\n        variants: &'static [EnumVariant],\n    ) -> Self {\n        let mut max_align = 1;\n        let mut i = 0;\n        while i < variants.len() {\n            let EnumVariant { align, .. } = &variants[i];\n            if *align > max_align {\n                max_align = *align;\n            }\n            i += 1;\n        }\n\n        let variants_offset_raw = discriminant.size;\n        let padding = (max_align - (variants_offset_raw % max_align)) % max_align;\n        let variants_offset = variants_offset_raw + padding;\n\n        assert!(variants_offset % max_align == 0);\n\n        Self {\n            size,\n            discriminant,\n            variants_offset,\n            variants,\n        }\n    }\n}\n\n/// The layout for an enum variant. The enum variant layout is just a struct layout with a tag and alignment.\n#[derive(Debug, Copy, Clone)]\npub struct EnumVariant {\n    name: &'static str,\n    // Note: tags may not be sequential\n    tag: u32,\n    data: StructLayout,\n    align: usize,\n}\n\nimpl EnumVariant {\n    /// Create a new enum variant layout\n    pub const fn new(name: &'static str, tag: u32, data: StructLayout, align: usize) -> Self {\n        Self {\n            name,\n            tag,\n            data,\n            align,\n        }\n    }\n}\n\n/// Deserialize an enum type into the out buffer at the offset passed in. Returns a new version of the buffer with the data added.\npub(crate) const fn deserialize_const_enum<'a>(\n    from: &'a [u8],\n    layout: &EnumLayout,\n    out: &mut [MaybeUninit<u8>],\n) -> Option<&'a [u8]> {\n    // First, deserialize the map\n    let Ok((map, remaining)) = take_map(from) else {\n        return None;\n    };\n\n    // Then get the only field which is the tag\n    let Ok((deserilized_name, from)) = take_str(map.bytes) else {\n        return None;\n    };\n\n    // Then, deserialize the variant\n    let mut i = 0;\n    let mut matched_variant = false;\n    while i < layout.variants.len() {\n        // If the variant is the discriminated one, deserialize it\n        let EnumVariant {\n            name, data, tag, ..\n        } = &layout.variants[i];\n        if str_eq(deserilized_name, name) {\n            layout.discriminant.write(*tag, out);\n            let Some((_, out)) = out.split_at_mut_checked(layout.variants_offset) else {\n                return None;\n            };\n            if deserialize_const_struct(from, data, out).is_none() {\n                return None;\n            }\n            matched_variant = true;\n            break;\n        }\n        i += 1;\n    }\n    if !matched_variant {\n        return None;\n    }\n\n    Some(remaining)\n}\n"
  },
  {
    "path": "packages/const-serialize/src/lib.rs",
    "content": "#![doc = include_str!(\"../README.md\")]\n#![warn(missing_docs)]\n\nuse std::mem::MaybeUninit;\n\nmod const_buffers;\npub use const_buffers::ConstReadBuffer;\nmod cbor;\nmod const_vec;\nmod r#enum;\npub use r#enum::*;\nmod r#struct;\npub use r#struct::*;\nmod primitive;\npub use primitive::*;\nmod list;\npub use list::*;\nmod array;\npub use array::*;\nmod str;\npub use str::*;\n\npub use const_serialize_macro::SerializeConst;\npub use const_vec::ConstVec;\n\nuse crate::cbor::{\n    str_eq, take_array, take_bytes, take_map, take_number, take_str, write_array, write_bytes,\n    write_map, write_map_key, write_number,\n};\n\n/// The layout for a type. This layout defines a sequence of locations and reversed or not bytes. These bytes will be copied from during serialization and copied into during deserialization.\n#[derive(Debug, Copy, Clone)]\npub enum Layout {\n    /// An enum layout\n    Enum(EnumLayout),\n    /// A struct layout\n    Struct(StructLayout),\n    /// An array layout\n    Array(ArrayLayout),\n    /// A primitive layout\n    Primitive(PrimitiveLayout),\n    /// A dynamically sized list layout\n    List(ListLayout),\n}\n\nimpl Layout {\n    /// The size of the type in bytes.\n    pub const fn size(&self) -> usize {\n        match self {\n            Layout::Enum(layout) => layout.size,\n            Layout::Struct(layout) => layout.size,\n            Layout::Array(layout) => layout.len * layout.item_layout.size(),\n            Layout::List(layout) => layout.size,\n            Layout::Primitive(layout) => layout.size,\n        }\n    }\n}\n\n/// A trait for types that can be serialized and deserialized in const.\n///\n/// # Safety\n/// The layout must accurately describe the memory layout of the type\npub unsafe trait SerializeConst: Sized {\n    /// The memory layout of the type. This type must have plain old data; no pointers or references.\n    const MEMORY_LAYOUT: Layout;\n    /// Assert that the memory layout of the type is the same as the size of the type\n    const _ASSERT: () = assert!(Self::MEMORY_LAYOUT.size() == std::mem::size_of::<Self>());\n}\n\n/// Serialize a pointer to a type that is stored at the pointer passed in\nconst unsafe fn serialize_const_ptr(\n    ptr: *const (),\n    to: ConstVec<u8>,\n    layout: &Layout,\n) -> ConstVec<u8> {\n    match layout {\n        Layout::Enum(layout) => serialize_const_enum(ptr, to, layout),\n        Layout::Struct(layout) => serialize_const_struct(ptr, to, layout),\n        Layout::Array(layout) => serialize_const_array(ptr, to, layout),\n        Layout::List(layout) => serialize_const_list(ptr, to, layout),\n        Layout::Primitive(layout) => serialize_const_primitive(ptr, to, layout),\n    }\n}\n\n/// Serialize a type into a buffer\n///\n/// # Example\n///\n/// ```rust\n/// use const_serialize::{ConstVec, SerializeConst, serialize_const};\n///\n/// #[derive(Clone, Copy, Debug, PartialEq, SerializeConst)]\n/// struct Struct {\n///     a: u32,\n///     b: u8,\n///     c: u32,\n/// }\n///\n/// let mut buffer = ConstVec::new();\n/// buffer = serialize_const(&Struct {\n///     a: 0x11111111,\n///     b: 0x22,\n///     c: 0x33333333,\n/// }, buffer);\n/// assert_eq!(buffer.as_ref(), &[0xa3, 0x61, 0x61, 0x1a, 0x11, 0x11, 0x11, 0x11, 0x61, 0x62, 0x18, 0x22, 0x61, 0x63, 0x1a, 0x33, 0x33, 0x33, 0x33]);\n/// ```\n#[must_use = \"The data is serialized into the returned buffer\"]\npub const fn serialize_const<T: SerializeConst>(data: &T, to: ConstVec<u8>) -> ConstVec<u8> {\n    let ptr = data as *const T as *const ();\n    // SAFETY: The pointer is valid and the layout is correct\n    unsafe { serialize_const_ptr(ptr, to, &T::MEMORY_LAYOUT) }\n}\n\n/// Deserialize a type into the out buffer at the offset passed in. Returns a new version of the buffer with the data added.\nconst fn deserialize_const_ptr<'a>(\n    from: &'a [u8],\n    layout: &Layout,\n    out: &mut [MaybeUninit<u8>],\n) -> Option<&'a [u8]> {\n    match layout {\n        Layout::Enum(layout) => deserialize_const_enum(from, layout, out),\n        Layout::Struct(layout) => deserialize_const_struct(from, layout, out),\n        Layout::Array(layout) => deserialize_const_array(from, layout, out),\n        Layout::List(layout) => deserialize_const_list(from, layout, out),\n        Layout::Primitive(layout) => deserialize_const_primitive(from, layout, out),\n    }\n}\n\n/// Deserialize a type into the output buffer. Accepts `(type, ConstVec<u8>)` as input and returns `Option<(&'a [u8], Instance of type)>`\n///\n/// # Example\n/// ```rust\n/// # use const_serialize::{deserialize_const, serialize_const, ConstVec, SerializeConst};\n/// #[derive(Clone, Copy, Debug, PartialEq, SerializeConst)]\n/// struct Struct {\n///     a: u32,\n///     b: u8,\n///     c: u32,\n///     d: u32,\n/// }\n///\n/// let mut buffer = ConstVec::new();\n/// buffer = serialize_const(&Struct {\n///     a: 0x11111111,\n///     b: 0x22,\n///     c: 0x33333333,\n///     d: 0x44444444,\n/// }, buffer);\n/// let buf = buffer.as_ref();\n/// assert_eq!(deserialize_const!(Struct, buf).unwrap().1, Struct {\n///     a: 0x11111111,\n///     b: 0x22,\n///     c: 0x33333333,\n///     d: 0x44444444,\n/// });\n/// ```\n#[macro_export]\nmacro_rules! deserialize_const {\n    ($type:ty, $buffer:expr) => {\n        unsafe {\n            const __SIZE: usize = std::mem::size_of::<$type>();\n            $crate::deserialize_const_raw::<__SIZE, $type>($buffer)\n        }\n    };\n}\n\n/// Deserialize a buffer into a type. This will return None if the buffer doesn't have enough data to fill the type.\n/// # Safety\n/// N must be `std::mem::size_of::<T>()`\n#[must_use = \"The data is deserialized from the input buffer\"]\npub const unsafe fn deserialize_const_raw<const N: usize, T: SerializeConst>(\n    from: &[u8],\n) -> Option<(&[u8], T)> {\n    // Create uninitized memory with the size of the type\n    let mut out = [MaybeUninit::uninit(); N];\n    // Fill in the bytes into the buffer for the type\n    let Some(from) = deserialize_const_ptr(from, &T::MEMORY_LAYOUT, &mut out) else {\n        return None;\n    };\n    // Now that the memory is filled in, transmute it into the type\n    Some((from, unsafe {\n        std::mem::transmute_copy::<[MaybeUninit<u8>; N], T>(&out)\n    }))\n}\n\n/// Check if the serialized representation of two items are the same\npub const fn serialize_eq<T: SerializeConst>(first: &T, second: &T) -> bool {\n    let first_serialized = ConstVec::<u8>::new();\n    let first_serialized = serialize_const(first, first_serialized);\n    let second_serialized = ConstVec::<u8>::new();\n    let second_serialized = serialize_const(second, second_serialized);\n    let first_buf = first_serialized.as_ref();\n    let second_buf = second_serialized.as_ref();\n    if first_buf.len() != second_buf.len() {\n        return false;\n    }\n    let mut i = 0;\n    while i < first_buf.len() {\n        if first_buf[i] != second_buf[i] {\n            return false;\n        }\n        i += 1;\n    }\n    true\n}\n"
  },
  {
    "path": "packages/const-serialize/src/list.rs",
    "content": "use crate::*;\n\n/// The layout for a dynamically sized list. The list layout is just a length and an item layout.\n#[derive(Debug, Copy, Clone)]\npub struct ListLayout {\n    /// The size of the struct backing the list\n    pub(crate) size: usize,\n    /// The byte offset of the length field\n    len_offset: usize,\n    /// The layout of the length field\n    len_layout: PrimitiveLayout,\n    /// The byte offset of the data field\n    data_offset: usize,\n    /// The layout of the data field\n    data_layout: ArrayLayout,\n}\n\nimpl ListLayout {\n    /// Create a new list layout\n    pub const fn new(\n        size: usize,\n        len_offset: usize,\n        len_layout: PrimitiveLayout,\n        data_offset: usize,\n        data_layout: ArrayLayout,\n    ) -> Self {\n        Self {\n            size,\n            len_offset,\n            len_layout,\n            data_offset,\n            data_layout,\n        }\n    }\n}\n\n/// Serialize a dynamically sized list that is stored at the pointer passed in\npub(crate) const unsafe fn serialize_const_list(\n    ptr: *const (),\n    mut to: ConstVec<u8>,\n    layout: &ListLayout,\n) -> ConstVec<u8> {\n    // Read the length of the list\n    let len_ptr = ptr.wrapping_byte_offset(layout.len_offset as _);\n    let len = layout.len_layout.read(len_ptr as *const u8) as usize;\n\n    let data_ptr = ptr.wrapping_byte_offset(layout.data_offset as _);\n    let item_layout = layout.data_layout.item_layout;\n    // If the item size is 1, deserialize as bytes directly\n    if item_layout.size() == 1 {\n        let slice = std::slice::from_raw_parts(data_ptr as *const u8, len);\n        to = write_bytes(to, slice);\n    }\n    // Otherwise, deserialize as a list of items\n    else {\n        let mut i = 0;\n        to = write_array(to, len);\n        while i < len {\n            let item = data_ptr.wrapping_byte_offset((i * item_layout.size()) as _);\n            to = serialize_const_ptr(item, to, item_layout);\n            i += 1;\n        }\n    }\n    to\n}\n\n/// Deserialize a list type into the out buffer at the offset passed in. Returns a new version of the buffer with the data added.\npub(crate) const fn deserialize_const_list<'a>(\n    from: &'a [u8],\n    layout: &ListLayout,\n    out: &mut [MaybeUninit<u8>],\n) -> Option<&'a [u8]> {\n    let Some((_, len_out)) = out.split_at_mut_checked(layout.len_offset) else {\n        return None;\n    };\n\n    // If the list items are only one byte, serialize as bytes directly\n    let item_layout = layout.data_layout.item_layout;\n    if item_layout.size() == 1 {\n        let Ok((bytes, new_from)) = take_bytes(from) else {\n            return None;\n        };\n        // Write out the length of the list\n        layout.len_layout.write(bytes.len() as u32, len_out);\n        let Some((_, data_out)) = out.split_at_mut_checked(layout.data_offset) else {\n            return None;\n        };\n        let mut offset = 0;\n        while offset < bytes.len() {\n            data_out[offset] = MaybeUninit::new(bytes[offset]);\n            offset += 1;\n        }\n        Some(new_from)\n    }\n    // Otherwise, serialize as an list of objects\n    else {\n        let Ok((len, mut from)) = take_array(from) else {\n            return None;\n        };\n        // Write out the length of the list\n        layout.len_layout.write(len as u32, len_out);\n        let Some((_, mut data_out)) = out.split_at_mut_checked(layout.data_offset) else {\n            return None;\n        };\n        let mut i = 0;\n        while i < len {\n            let Some(new_from) = deserialize_const_ptr(from, item_layout, data_out) else {\n                return None;\n            };\n            let Some((_, item_out)) = data_out.split_at_mut_checked(item_layout.size()) else {\n                return None;\n            };\n            data_out = item_out;\n            from = new_from;\n            i += 1;\n        }\n        Some(from)\n    }\n}\n"
  },
  {
    "path": "packages/const-serialize/src/primitive.rs",
    "content": "use crate::*;\nuse std::mem::MaybeUninit;\n\n/// The layout for a primitive type. The bytes will be reversed if the target is big endian.\n#[derive(Debug, Copy, Clone)]\npub struct PrimitiveLayout {\n    pub(crate) size: usize,\n}\n\nimpl PrimitiveLayout {\n    /// Create a new primitive layout\n    pub const fn new(size: usize) -> Self {\n        Self { size }\n    }\n\n    /// Read the value from the given pointer\n    ///\n    /// # Safety\n    /// The pointer must be valid for reads of `self.size` bytes.\n    pub const unsafe fn read(self, byte_ptr: *const u8) -> u32 {\n        let mut value = 0;\n        let mut offset = 0;\n        while offset < self.size {\n            // If the bytes are reversed, walk backwards from the end of the number when pushing bytes\n            let byte = if cfg!(target_endian = \"big\") {\n                unsafe {\n                    byte_ptr\n                        .wrapping_byte_add((self.size - offset - 1) as _)\n                        .read()\n                }\n            } else {\n                unsafe { byte_ptr.wrapping_byte_add(offset as _).read() }\n            };\n            value |= (byte as u32) << (offset * 8);\n            offset += 1;\n        }\n        value\n    }\n\n    /// Write the value to the given buffer\n    pub const fn write(self, value: u32, out: &mut [MaybeUninit<u8>]) {\n        let bytes = value.to_ne_bytes();\n        let mut offset = 0;\n        while offset < self.size {\n            out[offset] = MaybeUninit::new(bytes[offset]);\n            offset += 1;\n        }\n    }\n}\n\nmacro_rules! impl_serialize_const {\n    ($type:ty) => {\n        unsafe impl SerializeConst for $type {\n            const MEMORY_LAYOUT: Layout = Layout::Primitive(PrimitiveLayout {\n                size: std::mem::size_of::<$type>(),\n            });\n        }\n    };\n}\n\nimpl_serialize_const!(u8);\nimpl_serialize_const!(u16);\nimpl_serialize_const!(u32);\nimpl_serialize_const!(u64);\nimpl_serialize_const!(i8);\nimpl_serialize_const!(i16);\nimpl_serialize_const!(i32);\nimpl_serialize_const!(i64);\nimpl_serialize_const!(bool);\nimpl_serialize_const!(f32);\nimpl_serialize_const!(f64);\n\n/// Serialize a primitive type that is stored at the pointer passed in\npub(crate) const unsafe fn serialize_const_primitive(\n    ptr: *const (),\n    to: ConstVec<u8>,\n    layout: &PrimitiveLayout,\n) -> ConstVec<u8> {\n    let ptr = ptr as *const u8;\n    let mut offset = 0;\n    let mut i64_bytes = [0u8; 8];\n    while offset < layout.size {\n        // If the bytes are reversed, walk backwards from the end of the number when pushing bytes\n        let byte = unsafe {\n            if cfg!(any(target_endian = \"big\", feature = \"test-big-endian\")) {\n                ptr.wrapping_byte_offset((layout.size - offset - 1) as _)\n                    .read()\n            } else {\n                ptr.wrapping_byte_offset(offset as _).read()\n            }\n        };\n        i64_bytes[offset] = byte;\n        offset += 1;\n    }\n    let number = i64::from_ne_bytes(i64_bytes);\n    write_number(to, number)\n}\n\n/// Deserialize a primitive type into the out buffer at the offset passed in. Returns a new version of the buffer with the data added.\npub(crate) const fn deserialize_const_primitive<'a>(\n    from: &'a [u8],\n    layout: &PrimitiveLayout,\n    out: &mut [MaybeUninit<u8>],\n) -> Option<&'a [u8]> {\n    let mut offset = 0;\n    let Ok((number, from)) = take_number(from) else {\n        return None;\n    };\n    let bytes = number.to_le_bytes();\n    while offset < layout.size {\n        // If the bytes are reversed, walk backwards from the end of the number when filling in bytes\n        let byte = bytes[offset];\n        if cfg!(any(target_endian = \"big\", feature = \"test-big-endian\")) {\n            out[layout.size - offset - 1] = MaybeUninit::new(byte);\n        } else {\n            out[offset] = MaybeUninit::new(byte);\n        }\n        offset += 1;\n    }\n    Some(from)\n}\n"
  },
  {
    "path": "packages/const-serialize/src/str.rs",
    "content": "use crate::*;\nuse std::{char, fmt::Debug, hash::Hash, mem::MaybeUninit};\n\nconst MAX_STR_SIZE: usize = 256;\n\n/// A string that is stored in a constant sized buffer that can be serialized and deserialized at compile time\n#[derive(Clone, Copy)]\npub struct ConstStr {\n    bytes: [MaybeUninit<u8>; MAX_STR_SIZE],\n    len: u32,\n}\n\nimpl Debug for ConstStr {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_struct(\"ConstStr\")\n            .field(\"str\", &self.as_str())\n            .finish()\n    }\n}\n\n#[cfg(feature = \"serde\")]\nmod serde_bytes {\n    use serde::{Deserialize, Serialize, Serializer};\n\n    use crate::ConstStr;\n\n    impl Serialize for ConstStr {\n        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>\n        where\n            S: Serializer,\n        {\n            serializer.serialize_str(self.as_str())\n        }\n    }\n\n    impl<'de> Deserialize<'de> for ConstStr {\n        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>\n        where\n            D: serde::Deserializer<'de>,\n        {\n            let s = String::deserialize(deserializer)?;\n            Ok(ConstStr::new(&s))\n        }\n    }\n}\n\nunsafe impl SerializeConst for ConstStr {\n    const MEMORY_LAYOUT: Layout = Layout::List(ListLayout::new(\n        std::mem::size_of::<Self>(),\n        std::mem::offset_of!(Self, len),\n        PrimitiveLayout {\n            size: std::mem::size_of::<u32>(),\n        },\n        std::mem::offset_of!(Self, bytes),\n        ArrayLayout {\n            len: MAX_STR_SIZE,\n            item_layout: &Layout::Primitive(PrimitiveLayout {\n                size: std::mem::size_of::<u8>(),\n            }),\n        },\n    ));\n}\n\n#[cfg(feature = \"const-serialize-07\")]\nunsafe impl const_serialize_07::SerializeConst for ConstStr {\n    const MEMORY_LAYOUT: const_serialize_07::Layout =\n        const_serialize_07::Layout::Struct(const_serialize_07::StructLayout::new(\n            std::mem::size_of::<Self>(),\n            &[\n                const_serialize_07::StructFieldLayout::new(\n                    std::mem::offset_of!(Self, bytes),\n                    const_serialize_07::Layout::List(const_serialize_07::ListLayout::new(\n                        MAX_STR_SIZE,\n                        &const_serialize_07::Layout::Primitive(\n                            const_serialize_07::PrimitiveLayout::new(std::mem::size_of::<u8>()),\n                        ),\n                    )),\n                ),\n                const_serialize_07::StructFieldLayout::new(\n                    std::mem::offset_of!(Self, len),\n                    const_serialize_07::Layout::Primitive(\n                        const_serialize_07::PrimitiveLayout::new(std::mem::size_of::<u32>()),\n                    ),\n                ),\n            ],\n        ));\n}\n\nimpl ConstStr {\n    /// Create a new constant string\n    pub const fn new(s: &str) -> Self {\n        let str_bytes = s.as_bytes();\n        // This is serialized as a constant sized array in const-serialize-07 which requires all memory to be initialized\n        let mut bytes = if cfg!(feature = \"const-serialize-07\") {\n            [MaybeUninit::new(0); MAX_STR_SIZE]\n        } else {\n            [MaybeUninit::uninit(); MAX_STR_SIZE]\n        };\n        let mut i = 0;\n        while i < str_bytes.len() {\n            bytes[i] = MaybeUninit::new(str_bytes[i]);\n            i += 1;\n        }\n        Self {\n            bytes,\n            len: str_bytes.len() as u32,\n        }\n    }\n\n    /// Get the bytes of the initialized portion of the string\n    const fn bytes(&self) -> &[u8] {\n        // Safety: All bytes up to the pointer are initialized\n        unsafe {\n            &*(self.bytes.split_at(self.len as usize).0 as *const [MaybeUninit<u8>]\n                as *const [u8])\n        }\n    }\n\n    /// Get a reference to the string\n    pub const fn as_str(&self) -> &str {\n        let str_bytes = self.bytes();\n        match std::str::from_utf8(str_bytes) {\n            Ok(s) => s,\n            Err(_) => panic!(\n                \"Invalid utf8; ConstStr should only ever be constructed from valid utf8 strings\"\n            ),\n        }\n    }\n\n    /// Get the length of the string\n    pub const fn len(&self) -> usize {\n        self.len as usize\n    }\n\n    /// Check if the string is empty\n    pub const fn is_empty(&self) -> bool {\n        self.len == 0\n    }\n\n    /// Push a character onto the string\n    pub const fn push(self, byte: char) -> Self {\n        assert!(byte.is_ascii(), \"Only ASCII bytes are supported\");\n        let (bytes, len) = char_to_bytes(byte);\n        let (str, _) = bytes.split_at(len);\n        let Ok(str) = std::str::from_utf8(str) else {\n            panic!(\"Invalid utf8; char_to_bytes should always return valid utf8 bytes\")\n        };\n        self.push_str(str)\n    }\n\n    /// Push a str onto the string\n    pub const fn push_str(self, str: &str) -> Self {\n        let Self { mut bytes, len } = self;\n        assert!(\n            str.len() + len as usize <= MAX_STR_SIZE,\n            \"String is too long\"\n        );\n        let str_bytes = str.as_bytes();\n        let new_len = len as usize + str_bytes.len();\n        let mut i = 0;\n        while i < str_bytes.len() {\n            bytes[len as usize + i] = MaybeUninit::new(str_bytes[i]);\n            i += 1;\n        }\n        Self {\n            bytes,\n            len: new_len as u32,\n        }\n    }\n\n    /// Split the string at a byte index. The byte index must be a char boundary\n    pub const fn split_at(self, index: usize) -> (Self, Self) {\n        let (left, right) = self.bytes().split_at(index);\n        let left = match std::str::from_utf8(left) {\n            Ok(s) => s,\n            Err(_) => {\n                panic!(\"Invalid utf8; you cannot split at a byte that is not a char boundary\")\n            }\n        };\n        let right = match std::str::from_utf8(right) {\n            Ok(s) => s,\n            Err(_) => {\n                panic!(\"Invalid utf8; you cannot split at a byte that is not a char boundary\")\n            }\n        };\n        (Self::new(left), Self::new(right))\n    }\n\n    /// Split the string at the last occurrence of a character\n    pub const fn rsplit_once(&self, char: char) -> Option<(Self, Self)> {\n        let str = self.as_str();\n        let mut index = str.len() - 1;\n        // First find the bytes we are searching for\n        let (char_bytes, len) = char_to_bytes(char);\n        let (char_bytes, _) = char_bytes.split_at(len);\n        let bytes = str.as_bytes();\n\n        // Then walk backwards from the end of the string\n        loop {\n            let byte = bytes[index];\n            // Look for char boundaries in the string and check if the bytes match\n            if let Some(char_boundary_len) = utf8_char_boundary_to_char_len(byte) {\n                // Split up the string into three sections: [before_char, in_char, after_char]\n                let (before_char, after_index) = bytes.split_at(index);\n                let (in_char, after_char) = after_index.split_at(char_boundary_len as usize);\n                if in_char.len() != char_boundary_len as usize {\n                    panic!(\"in_char.len() should always be equal to char_boundary_len as usize\")\n                }\n                // Check if the bytes for the current char and the target char match\n                let mut in_char_eq = true;\n                let mut i = 0;\n                let min_len = if in_char.len() < char_bytes.len() {\n                    in_char.len()\n                } else {\n                    char_bytes.len()\n                };\n                while i < min_len {\n                    in_char_eq &= in_char[i] == char_bytes[i];\n                    i += 1;\n                }\n                // If they do, convert the bytes to strings and return the split strings\n                if in_char_eq {\n                    let Ok(before_char_str) = std::str::from_utf8(before_char) else {\n                        panic!(\"Invalid utf8; utf8_char_boundary_to_char_len should only return Some when the byte is a character boundary\")\n                    };\n                    let Ok(after_char_str) = std::str::from_utf8(after_char) else {\n                        panic!(\"Invalid utf8; utf8_char_boundary_to_char_len should only return Some when the byte is a character boundary\")\n                    };\n                    return Some((Self::new(before_char_str), Self::new(after_char_str)));\n                }\n            }\n            match index.checked_sub(1) {\n                Some(new_index) => index = new_index,\n                None => return None,\n            }\n        }\n    }\n\n    /// Split the string at the first occurrence of a character\n    pub const fn split_once(&self, char: char) -> Option<(Self, Self)> {\n        let str = self.as_str();\n        let mut index = 0;\n        // First find the bytes we are searching for\n        let (char_bytes, len) = char_to_bytes(char);\n        let (char_bytes, _) = char_bytes.split_at(len);\n        let bytes = str.as_bytes();\n\n        // Then walk forwards from the start of the string\n        while index < bytes.len() {\n            let byte = bytes[index];\n            // Look for char boundaries in the string and check if the bytes match\n            if let Some(char_boundary_len) = utf8_char_boundary_to_char_len(byte) {\n                // Split up the string into three sections: [before_char, in_char, after_char]\n                let (before_char, after_index) = bytes.split_at(index);\n                let (in_char, after_char) = after_index.split_at(char_boundary_len as usize);\n                if in_char.len() != char_boundary_len as usize {\n                    panic!(\"in_char.len() should always be equal to char_boundary_len as usize\")\n                }\n                // Check if the bytes for the current char and the target char match\n                let mut in_char_eq = true;\n                let mut i = 0;\n                let min_len = if in_char.len() < char_bytes.len() {\n                    in_char.len()\n                } else {\n                    char_bytes.len()\n                };\n                while i < min_len {\n                    in_char_eq &= in_char[i] == char_bytes[i];\n                    i += 1;\n                }\n                // If they do, convert the bytes to strings and return the split strings\n                if in_char_eq {\n                    let Ok(before_char_str) = std::str::from_utf8(before_char) else {\n                        panic!(\"Invalid utf8; utf8_char_boundary_to_char_len should only return Some when the byte is a character boundary\")\n                    };\n                    let Ok(after_char_str) = std::str::from_utf8(after_char) else {\n                        panic!(\"Invalid utf8; utf8_char_boundary_to_char_len should only return Some when the byte is a character boundary\")\n                    };\n                    return Some((Self::new(before_char_str), Self::new(after_char_str)));\n                }\n            }\n            index += 1\n        }\n        None\n    }\n}\n\nimpl PartialEq for ConstStr {\n    fn eq(&self, other: &Self) -> bool {\n        self.as_str() == other.as_str()\n    }\n}\n\nimpl Eq for ConstStr {}\n\nimpl PartialOrd for ConstStr {\n    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {\n        Some(self.cmp(other))\n    }\n}\n\nimpl Ord for ConstStr {\n    fn cmp(&self, other: &Self) -> std::cmp::Ordering {\n        self.as_str().cmp(other.as_str())\n    }\n}\n\nimpl Hash for ConstStr {\n    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {\n        self.as_str().hash(state);\n    }\n}\n\n#[test]\nfn test_rsplit_once() {\n    let str = ConstStr::new(\"hello world\");\n    assert_eq!(\n        str.rsplit_once(' '),\n        Some((ConstStr::new(\"hello\"), ConstStr::new(\"world\")))\n    );\n\n    let unicode_str = ConstStr::new(\"hi😀hello😀world😀world\");\n    assert_eq!(\n        unicode_str.rsplit_once('😀'),\n        Some((ConstStr::new(\"hi😀hello😀world\"), ConstStr::new(\"world\")))\n    );\n    assert_eq!(unicode_str.rsplit_once('❌'), None);\n\n    for _ in 0..100 {\n        let random_str: String = (0..rand::random::<u8>() % 50)\n            .map(|_| rand::random::<char>())\n            .collect();\n        let konst = ConstStr::new(&random_str);\n        let mut seen_chars = std::collections::HashSet::new();\n        for char in random_str.chars().rev() {\n            let (char_bytes, len) = char_to_bytes(char);\n            let char_bytes = &char_bytes[..len];\n            assert_eq!(char_bytes, char.to_string().as_bytes());\n            if seen_chars.contains(&char) {\n                continue;\n            }\n            seen_chars.insert(char);\n            let (correct_left, correct_right) = random_str.rsplit_once(char).unwrap();\n            let (left, right) = konst.rsplit_once(char).unwrap();\n            println!(\"splitting {random_str:?} at {char:?}\");\n            assert_eq!(left.as_str(), correct_left);\n            assert_eq!(right.as_str(), correct_right);\n        }\n    }\n}\n\nconst CONTINUED_CHAR_MASK: u8 = 0b10000000;\nconst BYTE_CHAR_BOUNDARIES: [u8; 4] = [0b00000000, 0b11000000, 0b11100000, 0b11110000];\n\n// Const version of https://doc.rust-lang.org/src/core/char/methods.rs.html#1765-1797\nconst fn char_to_bytes(char: char) -> ([u8; 4], usize) {\n    let code = char as u32;\n    let len = char.len_utf8();\n    let mut bytes = [0; 4];\n    match len {\n        1 => {\n            bytes[0] = code as u8;\n        }\n        2 => {\n            bytes[0] = ((code >> 6) & 0x1F) as u8 | BYTE_CHAR_BOUNDARIES[1];\n            bytes[1] = (code & 0x3F) as u8 | CONTINUED_CHAR_MASK;\n        }\n        3 => {\n            bytes[0] = ((code >> 12) & 0x0F) as u8 | BYTE_CHAR_BOUNDARIES[2];\n            bytes[1] = ((code >> 6) & 0x3F) as u8 | CONTINUED_CHAR_MASK;\n            bytes[2] = (code & 0x3F) as u8 | CONTINUED_CHAR_MASK;\n        }\n        4 => {\n            bytes[0] = ((code >> 18) & 0x07) as u8 | BYTE_CHAR_BOUNDARIES[3];\n            bytes[1] = ((code >> 12) & 0x3F) as u8 | CONTINUED_CHAR_MASK;\n            bytes[2] = ((code >> 6) & 0x3F) as u8 | CONTINUED_CHAR_MASK;\n            bytes[3] = (code & 0x3F) as u8 | CONTINUED_CHAR_MASK;\n        }\n        _ => panic!(\n            \"encode_utf8: need more than 4 bytes to encode the unicode character, but the buffer has 4 bytes\"\n        ),\n    };\n    (bytes, len)\n}\n\n#[test]\nfn fuzz_char_to_bytes() {\n    use std::char;\n    for _ in 0..100 {\n        let char = rand::random::<char>();\n        let (bytes, len) = char_to_bytes(char);\n        let str = std::str::from_utf8(&bytes[..len]).unwrap();\n        assert_eq!(char.to_string(), str);\n    }\n}\n\nconst fn utf8_char_boundary_to_char_len(byte: u8) -> Option<u8> {\n    match byte {\n        0b00000000..=0b01111111 => Some(1),\n        0b11000000..=0b11011111 => Some(2),\n        0b11100000..=0b11101111 => Some(3),\n        0b11110000..=0b11111111 => Some(4),\n        _ => None,\n    }\n}\n\n#[test]\nfn fuzz_utf8_byte_to_char_len() {\n    for _ in 0..100 {\n        let random_string: String = (0..rand::random::<u8>())\n            .map(|_| rand::random::<char>())\n            .collect();\n        let bytes = random_string.as_bytes();\n        let chars: std::collections::HashMap<_, _> = random_string.char_indices().collect();\n        for (i, byte) in bytes.iter().enumerate() {\n            match utf8_char_boundary_to_char_len(*byte) {\n                Some(char_len) => {\n                    let char = chars\n                        .get(&i)\n                        .unwrap_or_else(|| panic!(\"{byte:b} is not a character boundary\"));\n                    assert_eq!(char.len_utf8(), char_len as usize);\n                }\n                None => {\n                    assert!(!chars.contains_key(&i), \"{byte:b} is a character boundary\");\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "packages/const-serialize/src/struct.rs",
    "content": "use crate::*;\n\n/// Plain old data for a field. Stores the offset of the field in the struct and the layout of the field.\n#[derive(Debug, Copy, Clone)]\npub struct StructFieldLayout {\n    name: &'static str,\n    offset: usize,\n    layout: Layout,\n}\n\nimpl StructFieldLayout {\n    /// Create a new struct field layout\n    pub const fn new(name: &'static str, offset: usize, layout: Layout) -> Self {\n        Self {\n            name,\n            offset,\n            layout,\n        }\n    }\n}\n\n/// Layout for a struct. The struct layout is just a list of fields with offsets\n#[derive(Debug, Copy, Clone)]\npub struct StructLayout {\n    pub(crate) size: usize,\n    pub(crate) data: &'static [StructFieldLayout],\n}\n\nimpl StructLayout {\n    /// Create a new struct layout\n    pub const fn new(size: usize, data: &'static [StructFieldLayout]) -> Self {\n        Self { size, data }\n    }\n}\n\n/// Serialize a struct that is stored at the pointer passed in\npub(crate) const unsafe fn serialize_const_struct(\n    ptr: *const (),\n    to: ConstVec<u8>,\n    layout: &StructLayout,\n) -> ConstVec<u8> {\n    let mut i = 0;\n    let field_count = layout.data.len();\n    let mut to = write_map(to, field_count);\n    while i < field_count {\n        // Serialize the field at the offset pointer in the struct\n        let StructFieldLayout {\n            name,\n            offset,\n            layout,\n        } = &layout.data[i];\n        to = write_map_key(to, name);\n        let field = ptr.wrapping_byte_add(*offset as _);\n        to = serialize_const_ptr(field, to, layout);\n        i += 1;\n    }\n    to\n}\n\n/// Deserialize a struct type into the out buffer at the offset passed in. Returns a new version of the buffer with the data added.\npub(crate) const fn deserialize_const_struct<'a>(\n    from: &'a [u8],\n    layout: &StructLayout,\n    out: &mut [MaybeUninit<u8>],\n) -> Option<&'a [u8]> {\n    let Ok((map, from)) = take_map(from) else {\n        return None;\n    };\n    let mut i = 0;\n    while i < layout.data.len() {\n        // Deserialize the field at the offset pointer in the struct\n        let StructFieldLayout {\n            name,\n            offset,\n            layout,\n        } = &layout.data[i];\n        let Ok(Some(from)) = map.find(name) else {\n            return None;\n        };\n        let Some((_, field_bytes)) = out.split_at_mut_checked(*offset) else {\n            return None;\n        };\n        if deserialize_const_ptr(from, layout, field_bytes).is_none() {\n            return None;\n        }\n        i += 1;\n    }\n    Some(from)\n}\n\nmacro_rules! impl_serialize_const_tuple {\n    ($($generic:ident: $generic_number:expr),*) => {\n        impl_serialize_const_tuple!(@impl ($($generic,)*) = $($generic: $generic_number),*);\n    };\n    (@impl $inner:ty = $($generic:ident: $generic_number:expr),*) => {\n        unsafe impl<$($generic: SerializeConst),*> SerializeConst for ($($generic,)*) {\n            const MEMORY_LAYOUT: Layout = {\n                Layout::Struct(StructLayout {\n                    size: std::mem::size_of::<($($generic,)*)>(),\n                    data: &[\n                        $(\n                            StructFieldLayout::new(stringify!($generic_number), std::mem::offset_of!($inner, $generic_number), $generic::MEMORY_LAYOUT),\n                        )*\n                    ],\n                })\n            };\n        }\n    };\n}\n\nimpl_serialize_const_tuple!(T1: 0);\nimpl_serialize_const_tuple!(T1: 0, T2: 1);\nimpl_serialize_const_tuple!(T1: 0, T2: 1, T3: 2);\nimpl_serialize_const_tuple!(T1: 0, T2: 1, T3: 2, T4: 3);\nimpl_serialize_const_tuple!(T1: 0, T2: 1, T3: 2, T4: 3, T5: 4);\nimpl_serialize_const_tuple!(T1: 0, T2: 1, T3: 2, T4: 3, T5: 4, T6: 5);\nimpl_serialize_const_tuple!(T1: 0, T2: 1, T3: 2, T4: 3, T5: 4, T6: 5, T7: 6);\nimpl_serialize_const_tuple!(T1: 0, T2: 1, T3: 2, T4: 3, T5: 4, T6: 5, T7: 6, T8: 7);\nimpl_serialize_const_tuple!(T1: 0, T2: 1, T3: 2, T4: 3, T5: 4, T6: 5, T7: 6, T8: 7, T9: 8);\nimpl_serialize_const_tuple!(T1: 0, T2: 1, T3: 2, T4: 3, T5: 4, T6: 5, T7: 6, T8: 7, T9: 8, T10: 9);\n"
  },
  {
    "path": "packages/const-serialize/tests/enum.rs",
    "content": "use const_serialize::{deserialize_const, serialize_const, ConstVec, SerializeConst};\nuse std::mem::MaybeUninit;\n\n#[test]\nfn test_transmute_bytes_to_enum() {\n    #[derive(Clone, Copy, Debug, PartialEq)]\n    #[repr(C, u8)]\n    enum Enum<T> {\n        A { one: u32, two: u16 },\n        B { one: u8, two: T } = 15,\n    }\n\n    #[repr(C)]\n    #[derive(Debug, PartialEq)]\n    struct A {\n        one: u32,\n        two: u16,\n    }\n\n    #[repr(C)]\n    #[derive(Debug, PartialEq)]\n    struct B<T> {\n        one: u8,\n        two: T,\n    }\n\n    const SIZE: usize = std::mem::size_of::<Enum<u16>>();\n    let mut out = [MaybeUninit::uninit(); SIZE];\n    let discriminate_size = std::mem::size_of::<u8>();\n    let tag_align = 0;\n    let union_alignment = std::mem::align_of::<A>().max(std::mem::align_of::<B<u16>>());\n    let data_align = (discriminate_size / union_alignment) + union_alignment;\n    let a_one_align = std::mem::offset_of!(A, one);\n    let a_two_align = std::mem::offset_of!(A, two);\n    let b_one_align = std::mem::offset_of!(B<u16>, one);\n    let b_two_align = std::mem::offset_of!(B<u16>, two);\n\n    let one = 1234u32;\n    let two = 5678u16;\n    let first = Enum::A { one, two };\n    for (i, byte) in one.to_le_bytes().iter().enumerate() {\n        out[data_align + i + a_one_align] = MaybeUninit::new(*byte);\n    }\n    for (i, byte) in two.to_le_bytes().iter().enumerate() {\n        out[data_align + i + a_two_align] = MaybeUninit::new(*byte);\n    }\n    out[tag_align] = MaybeUninit::new(0);\n    let out = unsafe { std::mem::transmute_copy::<[MaybeUninit<u8>; SIZE], Enum<u16>>(&out) };\n    assert_eq!(out, first);\n\n    let mut out = [MaybeUninit::uninit(); SIZE];\n    let one = 123u8;\n    let two = 58u16;\n    let second = Enum::B { one, two };\n    for (i, byte) in one.to_le_bytes().iter().enumerate() {\n        out[data_align + i + b_one_align] = MaybeUninit::new(*byte);\n    }\n    for (i, byte) in two.to_le_bytes().iter().enumerate() {\n        out[data_align + i + b_two_align] = MaybeUninit::new(*byte);\n    }\n    out[tag_align] = MaybeUninit::new(15);\n    let out = unsafe { std::mem::transmute_copy::<[MaybeUninit<u8>; SIZE], Enum<u16>>(&out) };\n    assert_eq!(out, second);\n}\n\n#[test]\nfn test_serialize_enum() {\n    #[derive(Clone, Copy, Debug, PartialEq, SerializeConst)]\n    #[repr(C, u8)]\n    enum Enum {\n        A { one: u32, two: u16 },\n        B { one: u8, two: u16 } = 15,\n    }\n\n    println!(\"{:#?}\", Enum::MEMORY_LAYOUT);\n\n    let data = Enum::A {\n        one: 0x11111111,\n        two: 0x22,\n    };\n    let mut buf = ConstVec::new();\n    buf = serialize_const(&data, buf);\n    println!(\"{:?}\", buf.as_ref());\n    let buf = buf.as_ref();\n    assert_eq!(deserialize_const!(Enum, buf).unwrap().1, data);\n\n    let data = Enum::B {\n        one: 0x11,\n        two: 0x2233,\n    };\n    let mut buf = ConstVec::new();\n    buf = serialize_const(&data, buf);\n    println!(\"{:?}\", buf.as_ref());\n    let buf = buf.as_ref();\n    assert_eq!(deserialize_const!(Enum, buf).unwrap().1, data);\n}\n\n#[test]\nfn test_serialize_list_of_lopsided_enums() {\n    #[derive(Clone, Copy, Debug, PartialEq, SerializeConst)]\n    #[repr(C, u8)]\n    enum Enum {\n        A,\n        B { one: u8, two: u16 } = 15,\n    }\n\n    println!(\"{:#?}\", Enum::MEMORY_LAYOUT);\n\n    let data = [Enum::A, Enum::A];\n    let mut buf = ConstVec::new();\n    buf = serialize_const(&data, buf);\n    println!(\"{:?}\", buf.as_ref());\n    let buf = buf.as_ref();\n    assert_eq!(deserialize_const!([Enum; 2], buf).unwrap().1, data);\n\n    let data = [\n        Enum::B {\n            one: 0x11,\n            two: 0x2233,\n        },\n        Enum::B {\n            one: 0x12,\n            two: 0x2244,\n        },\n    ];\n    let mut buf = ConstVec::new();\n    buf = serialize_const(&data, buf);\n    println!(\"{:?}\", buf.as_ref());\n    let buf = buf.as_ref();\n    assert_eq!(deserialize_const!([Enum; 2], buf).unwrap().1, data);\n\n    let data = [\n        Enum::A,\n        Enum::B {\n            one: 0x11,\n            two: 0x2233,\n        },\n    ];\n    let mut buf = ConstVec::new();\n    buf = serialize_const(&data, buf);\n    println!(\"{:?}\", buf.as_ref());\n    let buf = buf.as_ref();\n    assert_eq!(deserialize_const!([Enum; 2], buf).unwrap().1, data);\n\n    let data = [\n        Enum::B {\n            one: 0x11,\n            two: 0x2233,\n        },\n        Enum::A,\n    ];\n    let mut buf = ConstVec::new();\n    buf = serialize_const(&data, buf);\n    println!(\"{:?}\", buf.as_ref());\n    let buf = buf.as_ref();\n    assert_eq!(deserialize_const!([Enum; 2], buf).unwrap().1, data);\n}\n\n#[test]\nfn test_serialize_u8_enum() {\n    #[derive(Clone, Copy, Debug, PartialEq, SerializeConst)]\n    #[repr(u8)]\n    enum Enum {\n        A,\n        B,\n    }\n\n    println!(\"{:#?}\", Enum::MEMORY_LAYOUT);\n\n    let data = Enum::A;\n    let mut buf = ConstVec::new();\n    buf = serialize_const(&data, buf);\n    println!(\"{:?}\", buf.as_ref());\n    let buf = buf.as_ref();\n    assert_eq!(deserialize_const!(Enum, buf).unwrap().1, data);\n\n    let data = Enum::B;\n    let mut buf = ConstVec::new();\n    buf = serialize_const(&data, buf);\n    println!(\"{:?}\", buf.as_ref());\n    let buf = buf.as_ref();\n    assert_eq!(deserialize_const!(Enum, buf).unwrap().1, data);\n}\n\n#[test]\nfn test_serialize_corrupted_enum() {\n    #[derive(Clone, Copy, Debug, PartialEq, SerializeConst)]\n    #[repr(C, u8)]\n    enum Enum {\n        A { one: u32, two: u16 },\n    }\n\n    let data = Enum::A {\n        one: 0x11111111,\n        two: 0x22,\n    };\n    let mut buf = ConstVec::new();\n    buf = serialize_const(&data, buf);\n    buf = buf.set(0, 2);\n    println!(\"{:?}\", buf.as_ref());\n    let buf = buf.as_ref();\n    assert_eq!(deserialize_const!(Enum, buf), None);\n}\n\n#[test]\nfn test_serialize_nested_enum() {\n    #[derive(Clone, Copy, Debug, PartialEq, SerializeConst)]\n    #[repr(C, u8)]\n    enum Enum {\n        A { one: u32, two: u16 },\n        B { one: u8, two: InnerEnum } = 15,\n    }\n\n    #[derive(Clone, Copy, Debug, PartialEq, SerializeConst)]\n    #[repr(C, u16)]\n    enum InnerEnum {\n        A(u8),\n        B { one: u64, two: f64 } = 1000,\n        C { one: u32, two: u16 },\n    }\n\n    let data = Enum::A {\n        one: 0x11111111,\n        two: 0x22,\n    };\n    let mut buf = ConstVec::new();\n    buf = serialize_const(&data, buf);\n    println!(\"{:?}\", buf.as_ref());\n    let buf = buf.as_ref();\n    assert_eq!(deserialize_const!(Enum, buf).unwrap().1, data);\n\n    let data = Enum::B {\n        one: 0x11,\n        two: InnerEnum::A(0x22),\n    };\n    let mut buf = ConstVec::new();\n    buf = serialize_const(&data, buf);\n    println!(\"{:?}\", buf.as_ref());\n    let buf = buf.as_ref();\n    assert_eq!(deserialize_const!(Enum, buf).unwrap().1, data);\n\n    let data = Enum::B {\n        one: 0x11,\n        two: InnerEnum::B {\n            one: 0x2233,\n            two: 0.123456789,\n        },\n    };\n    let mut buf = ConstVec::new();\n    buf = serialize_const(&data, buf);\n    println!(\"{:?}\", buf.as_ref());\n    let buf = buf.as_ref();\n    assert_eq!(deserialize_const!(Enum, buf).unwrap().1, data);\n\n    let data = Enum::B {\n        one: 0x11,\n        two: InnerEnum::C {\n            one: 0x2233,\n            two: 56789,\n        },\n    };\n    let mut buf = ConstVec::new();\n    buf = serialize_const(&data, buf);\n    println!(\"{:?}\", buf.as_ref());\n    let buf = buf.as_ref();\n    assert_eq!(deserialize_const!(Enum, buf).unwrap().1, data);\n}\n\n#[test]\nfn test_adding_enum_field_non_breaking() {\n    #[derive(Debug, PartialEq, SerializeConst)]\n    #[repr(C, u8)]\n    enum Initial {\n        A { a: u32, b: u8 },\n    }\n\n    #[derive(Debug, PartialEq, SerializeConst)]\n    #[repr(C, u8)]\n    enum New {\n        A { b: u8, a: u32, c: u32 },\n    }\n\n    let data = New::A {\n        a: 0x11111111,\n        b: 0x22,\n        c: 0x33333333,\n    };\n    let mut buf = ConstVec::new();\n    buf = serialize_const(&data, buf);\n    let buf = buf.as_ref();\n    // The new struct should be able to deserialize into the initial struct\n    let (_, data2) = deserialize_const!(Initial, buf).unwrap();\n    assert_eq!(\n        Initial::A {\n            a: 0x11111111,\n            b: 0x22,\n        },\n        data2\n    );\n}\n\n#[test]\nfn test_adding_enum_variant_non_breaking() {\n    #[derive(Debug, PartialEq, SerializeConst)]\n    #[repr(C, u8)]\n    enum Initial {\n        A { a: u32, b: u8 },\n    }\n\n    #[derive(Debug, PartialEq, SerializeConst)]\n    #[repr(C, u8)]\n    enum New {\n        #[allow(unused)]\n        B {\n            d: u32,\n            e: u8,\n        },\n        A {\n            c: u32,\n            b: u8,\n            a: u32,\n        },\n    }\n\n    let data = New::A {\n        a: 0x11111111,\n        b: 0x22,\n        c: 0x33333333,\n    };\n    let mut buf = ConstVec::new();\n    buf = serialize_const(&data, buf);\n    let buf = buf.as_ref();\n    // The new struct should be able to deserialize into the initial struct\n    let (_, data2) = deserialize_const!(Initial, buf).unwrap();\n    assert_eq!(\n        Initial::A {\n            a: 0x11111111,\n            b: 0x22,\n        },\n        data2\n    );\n}\n"
  },
  {
    "path": "packages/const-serialize/tests/eq.rs",
    "content": "use const_serialize::{serialize_eq, SerializeConst};\n\n#[derive(Clone, Copy, Debug, PartialEq, SerializeConst)]\nstruct Struct {\n    a: u32,\n    b: u8,\n    c: u32,\n    d: Enum,\n}\n\n#[derive(Clone, Copy, Debug, PartialEq, SerializeConst)]\n#[repr(C, u8)]\nenum Enum {\n    A { one: u32, two: u16 },\n    B { one: u8, two: u16 } = 15,\n}\n\n#[test]\nfn const_eq() {\n    const {\n        let data = [\n            Struct {\n                a: 0x11111111,\n                b: 0x22,\n                c: 0x33333333,\n                d: Enum::A {\n                    one: 0x44444444,\n                    two: 0x5555,\n                },\n            },\n            Struct {\n                a: 123,\n                b: 9,\n                c: 38,\n                d: Enum::B {\n                    one: 0x44,\n                    two: 0x555,\n                },\n            },\n            Struct {\n                a: 9,\n                b: 123,\n                c: 39,\n                d: Enum::B {\n                    one: 0x46,\n                    two: 0x555,\n                },\n            },\n        ];\n        let mut other = data;\n        other[2].a += 1;\n        if serialize_eq(&data, &other) {\n            panic!(\"data should be different\");\n        }\n        if !serialize_eq(&data, &data) {\n            panic!(\"data should be the same\");\n        }\n    }\n}\n"
  },
  {
    "path": "packages/const-serialize/tests/lists.rs",
    "content": "use const_serialize::{deserialize_const, serialize_const, ConstVec};\n\n#[test]\nfn test_serialize_const_layout_list() {\n    let mut buf = ConstVec::new();\n    buf = serialize_const(&[1u8, 2, 3] as &[u8; 3], buf);\n    println!(\"{:?}\", buf.as_ref());\n    let buf = buf.as_ref();\n    assert_eq!(deserialize_const!([u8; 3], buf).unwrap().1, [1, 2, 3])\n}\n\n#[test]\nfn test_serialize_const_layout_nested_lists() {\n    let mut buf = ConstVec::new();\n    buf = serialize_const(\n        &[[1u8, 2, 3], [4u8, 5, 6], [7u8, 8, 9]] as &[[u8; 3]; 3],\n        buf,\n    );\n    println!(\"{:?}\", buf.as_ref());\n    let buf = buf.as_ref();\n\n    assert_eq!(\n        deserialize_const!([[u8; 3]; 3], buf).unwrap().1,\n        [[1, 2, 3], [4, 5, 6], [7, 8, 9]]\n    );\n}\n\n#[test]\nfn test_serialize_list_too_little_data() {\n    let mut buf = ConstVec::new();\n    buf = buf.push(1);\n    let buf = buf.as_ref();\n    assert_eq!(deserialize_const!([u64; 10], buf), None);\n}\n"
  },
  {
    "path": "packages/const-serialize/tests/primitive.rs",
    "content": "use const_serialize::{deserialize_const, serialize_const, ConstVec};\n\n#[test]\nfn test_serialize_const_layout_primitive() {\n    let mut buf = ConstVec::new();\n    buf = serialize_const(&1234u32, buf);\n    let buf = buf.as_ref();\n    assert_eq!(deserialize_const!(u32, buf).unwrap().1, 1234u32);\n\n    let mut buf = ConstVec::new();\n    buf = serialize_const(&1234u64, buf);\n    let buf = buf.as_ref();\n    assert_eq!(deserialize_const!(u64, buf).unwrap().1, 1234u64);\n\n    let mut buf = ConstVec::new();\n    buf = serialize_const(&1234i32, buf);\n    let buf = buf.as_ref();\n    assert_eq!(deserialize_const!(i32, buf).unwrap().1, 1234i32);\n\n    let mut buf = ConstVec::new();\n    buf = serialize_const(&1234i64, buf);\n    let buf = buf.as_ref();\n    assert_eq!(deserialize_const!(i64, buf).unwrap().1, 1234i64);\n\n    let mut buf = ConstVec::new();\n    buf = serialize_const(&true, buf);\n    assert_eq!(buf.as_ref(), [1u8]);\n    let buf = buf.as_ref();\n    assert!(deserialize_const!(bool, buf).unwrap().1);\n\n    let mut buf = ConstVec::new();\n    buf = serialize_const(&0.631f32, buf);\n    let buf = buf.as_ref();\n    assert_eq!(deserialize_const!(f32, buf).unwrap().1, 0.631);\n}\n\n#[test]\n\nfn test_serialize_primitive_too_little_data() {\n    let mut buf = ConstVec::new();\n    buf = buf.push(1);\n    buf = buf.push(1);\n    buf = buf.push(1);\n    let buf = buf.as_ref();\n    assert_eq!(deserialize_const!([u64; 10], buf), None);\n}\n"
  },
  {
    "path": "packages/const-serialize/tests/str.rs",
    "content": "use const_serialize::{deserialize_const, serialize_const, ConstStr, ConstVec};\n\n#[test]\nfn test_serialize_const_layout_str() {\n    let mut buf = ConstVec::new();\n    let str = ConstStr::new(\"hello\");\n    buf = serialize_const(&str, buf);\n    println!(\"{:?}\", buf.as_ref());\n    let buf = buf.as_ref();\n    assert!(buf.len() < 10);\n    let str = deserialize_const!(ConstStr, buf).unwrap().1;\n    assert_eq!(str.as_str(), \"hello\");\n}\n\n#[test]\nfn test_serialize_const_layout_nested_str() {\n    let mut buf = ConstVec::new();\n    let str = ConstStr::new(\"hello\");\n    buf = serialize_const(&[str, str, str] as &[ConstStr; 3], buf);\n    println!(\"{:?}\", buf.as_ref());\n    assert!(buf.len() < 30);\n    let buf = buf.as_ref();\n\n    assert_eq!(\n        deserialize_const!([ConstStr; 3], buf).unwrap().1,\n        [\n            ConstStr::new(\"hello\"),\n            ConstStr::new(\"hello\"),\n            ConstStr::new(\"hello\")\n        ]\n    );\n}\n\n#[test]\nfn test_serialize_str_too_little_data() {\n    let mut buf = ConstVec::new();\n    buf = buf.push(1);\n    let buf = buf.as_ref();\n    assert_eq!(deserialize_const!(ConstStr, buf), None);\n}\n"
  },
  {
    "path": "packages/const-serialize/tests/structs.rs",
    "content": "use const_serialize::{deserialize_const, serialize_const, ConstVec, SerializeConst};\nuse std::mem::MaybeUninit;\n\n#[test]\nfn test_transmute_bytes_to_struct() {\n    struct MyStruct {\n        a: u32,\n        b: u8,\n        c: u32,\n        d: u32,\n    }\n    const SIZE: usize = std::mem::size_of::<MyStruct>();\n    let mut out = [MaybeUninit::uninit(); SIZE];\n    let first_align = std::mem::offset_of!(MyStruct, a);\n    let second_align = std::mem::offset_of!(MyStruct, b);\n    let third_align = std::mem::offset_of!(MyStruct, c);\n    let fourth_align = std::mem::offset_of!(MyStruct, d);\n    for (i, byte) in 1234u32.to_le_bytes().iter().enumerate() {\n        out[i + first_align] = MaybeUninit::new(*byte);\n    }\n    for (i, byte) in 12u8.to_le_bytes().iter().enumerate() {\n        out[i + second_align] = MaybeUninit::new(*byte);\n    }\n    for (i, byte) in 13u32.to_le_bytes().iter().enumerate() {\n        out[i + third_align] = MaybeUninit::new(*byte);\n    }\n    for (i, byte) in 14u32.to_le_bytes().iter().enumerate() {\n        out[i + fourth_align] = MaybeUninit::new(*byte);\n    }\n    let out = unsafe { std::mem::transmute_copy::<[MaybeUninit<u8>; SIZE], MyStruct>(&out) };\n    assert_eq!(out.a, 1234);\n    assert_eq!(out.b, 12);\n    assert_eq!(out.c, 13);\n    assert_eq!(out.d, 14);\n}\n\n#[test]\nfn test_serialize_const_layout_struct_list() {\n    #[derive(Clone, Copy, Debug, PartialEq, SerializeConst)]\n    struct Struct {\n        a: u32,\n        b: u8,\n        c: u32,\n        d: u32,\n    }\n\n    impl Struct {\n        #[allow(dead_code)]\n        const fn equal(&self, other: &Struct) -> bool {\n            self.a == other.a && self.b == other.b && self.c == other.c && self.d == other.d\n        }\n    }\n\n    #[derive(Clone, Copy, Debug, PartialEq, SerializeConst)]\n    struct OtherStruct {\n        a: u32,\n        b: u8,\n        c: Struct,\n        d: u32,\n    }\n\n    impl OtherStruct {\n        #[allow(dead_code)]\n        const fn equal(&self, other: &OtherStruct) -> bool {\n            self.a == other.a && self.b == other.b && self.c.equal(&other.c) && self.d == other.d\n        }\n    }\n\n    const INNER_DATA: Struct = Struct {\n        a: 0x11111111,\n        b: 0x22,\n        c: 0x33333333,\n        d: 0x44444444,\n    };\n    const DATA: [OtherStruct; 3] = [\n        OtherStruct {\n            a: 0x11111111,\n            b: 0x22,\n            c: INNER_DATA,\n            d: 0x44444444,\n        },\n        OtherStruct {\n            a: 0x111111,\n            b: 0x23,\n            c: INNER_DATA,\n            d: 0x44444444,\n        },\n        OtherStruct {\n            a: 0x11111111,\n            b: 0x11,\n            c: INNER_DATA,\n            d: 0x44441144,\n        },\n    ];\n\n    const _ASSERT: () = {\n        let mut buf = ConstVec::new();\n        buf = serialize_const(&DATA, buf);\n        let buf = buf.as_ref();\n        let [first, second, third] = match deserialize_const!([OtherStruct; 3], buf) {\n            Some((_, data)) => data,\n            None => panic!(\"data mismatch\"),\n        };\n        if !(first.equal(&DATA[0]) && second.equal(&DATA[1]) && third.equal(&DATA[2])) {\n            panic!(\"data mismatch\");\n        }\n    };\n    const _ASSERT_2: () = {\n        let mut buf = ConstVec::new();\n        const DATA_AGAIN: [[OtherStruct; 3]; 3] = [DATA, DATA, DATA];\n        buf = serialize_const(&DATA_AGAIN, buf);\n        let buf = buf.as_ref();\n        let [first, second, third] = match deserialize_const!([[OtherStruct; 3]; 3], buf) {\n            Some((_, data)) => data,\n            None => panic!(\"data mismatch\"),\n        };\n        if !(first[0].equal(&DATA[0]) && first[1].equal(&DATA[1]) && first[2].equal(&DATA[2])) {\n            panic!(\"data mismatch\");\n        }\n        if !(second[0].equal(&DATA[0]) && second[1].equal(&DATA[1]) && second[2].equal(&DATA[2])) {\n            panic!(\"data mismatch\");\n        }\n        if !(third[0].equal(&DATA[0]) && third[1].equal(&DATA[1]) && third[2].equal(&DATA[2])) {\n            panic!(\"data mismatch\");\n        }\n    };\n\n    let mut buf = ConstVec::new();\n    buf = serialize_const(&DATA, buf);\n    println!(\"{:?}\", buf.as_ref());\n    let buf = buf.as_ref();\n    let (_, data2) = deserialize_const!([OtherStruct; 3], buf).unwrap();\n    assert_eq!(DATA, data2);\n}\n\n#[test]\nfn test_serialize_const_layout_struct() {\n    #[derive(Debug, PartialEq, SerializeConst)]\n    struct Struct {\n        a: u32,\n        b: u8,\n        c: u32,\n        d: u32,\n    }\n\n    #[derive(Debug, PartialEq, SerializeConst)]\n    struct OtherStruct(u32, u8, Struct, u32);\n\n    println!(\"{:?}\", OtherStruct::MEMORY_LAYOUT);\n\n    let data = Struct {\n        a: 0x11111111,\n        b: 0x22,\n        c: 0x33333333,\n        d: 0x44444444,\n    };\n    let data = OtherStruct(0x11111111, 0x22, data, 0x44444444);\n    let mut buf = ConstVec::new();\n    buf = serialize_const(&data, buf);\n    println!(\"{:?}\", buf.as_ref());\n    let buf = buf.as_ref();\n    let (_, data2) = deserialize_const!(OtherStruct, buf).unwrap();\n    assert_eq!(data, data2);\n}\n\n#[test]\nfn test_adding_struct_field_non_breaking() {\n    #[derive(Debug, PartialEq, SerializeConst)]\n    struct Initial {\n        a: u32,\n        b: u8,\n    }\n\n    #[derive(Debug, PartialEq, SerializeConst)]\n    struct New {\n        c: u32,\n        b: u8,\n        a: u32,\n    }\n\n    let data = New {\n        a: 0x11111111,\n        b: 0x22,\n        c: 0x33333333,\n    };\n    let mut buf = ConstVec::new();\n    buf = serialize_const(&data, buf);\n    let buf = buf.as_ref();\n    // The new struct should be able to deserialize into the initial struct\n    let (_, data2) = deserialize_const!(Initial, buf).unwrap();\n    assert_eq!(\n        Initial {\n            a: data.a,\n            b: data.b,\n        },\n        data2\n    );\n}\n"
  },
  {
    "path": "packages/const-serialize/tests/tuples.rs",
    "content": "use const_serialize::{deserialize_const, serialize_const, ConstVec};\n\n#[test]\nfn test_serialize_const_layout_tuple() {\n    let mut buf = ConstVec::new();\n    buf = serialize_const(&(1234u32, 5678u16), buf);\n    let buf = buf.as_ref();\n    assert_eq!(\n        deserialize_const!((u32, u16), buf).unwrap().1,\n        (1234u32, 5678u16)\n    );\n\n    let mut buf = ConstVec::new();\n    buf = serialize_const(&(1234f64, 5678u16, 90u8), buf);\n    let buf = buf.as_ref();\n    assert_eq!(\n        deserialize_const!((f64, u16, u8), buf).unwrap().1,\n        (1234f64, 5678u16, 90u8)\n    );\n\n    let mut buf = ConstVec::new();\n    buf = serialize_const(&(1234u32, 5678u16, 90u8, 1000000f64), buf);\n    let buf = buf.as_ref();\n    assert_eq!(\n        deserialize_const!((u32, u16, u8, f64), buf).unwrap().1,\n        (1234u32, 5678u16, 90u8, 1000000f64)\n    );\n}\n"
  },
  {
    "path": "packages/const-serialize-macro/Cargo.toml",
    "content": "[package]\nname = \"const-serialize-macro\"\nversion = \"0.8.0-alpha.0\"\nauthors = [\"Evan Almloff\"]\nedition = \"2021\"\ndescription = \"A macro to derive const serialize\"\nlicense = \"MIT OR Apache-2.0\"\nrepository = \"https://github.com/dioxuslabs/dioxus\"\nhomepage = \"https://dioxuslabs.com/learn/0.5/getting_started\"\nkeywords = [\"const\", \"serialize\"]\n\n[dependencies]\nsyn = { workspace = true }\nquote = { workspace = true }\nproc-macro2 = { workspace = true }\n\n[lib]\nproc-macro = true\n"
  },
  {
    "path": "packages/const-serialize-macro/src/lib.rs",
    "content": "use proc_macro::TokenStream;\nuse quote::{quote, ToTokens};\nuse syn::{parse_macro_input, DeriveInput, LitInt, Path};\nuse syn::{parse_quote, Generics, WhereClause, WherePredicate};\n\nfn add_bounds(where_clause: &mut Option<WhereClause>, generics: &Generics, krate: &Path) {\n    let bounds = generics.params.iter().filter_map(|param| match param {\n        syn::GenericParam::Type(ty) => {\n            Some::<WherePredicate>(parse_quote! { #ty: #krate::SerializeConst, })\n        }\n        syn::GenericParam::Lifetime(_) => None,\n        syn::GenericParam::Const(_) => None,\n    });\n    if let Some(clause) = where_clause {\n        clause.predicates.extend(bounds);\n    } else {\n        *where_clause = Some(parse_quote! { where #(#bounds)* });\n    }\n}\n\n/// Derive the const serialize trait for a struct\n#[proc_macro_derive(SerializeConst, attributes(const_serialize))]\npub fn derive_parse(raw_input: TokenStream) -> TokenStream {\n    // Parse the input tokens into a syntax tree\n    let input = parse_macro_input!(raw_input as DeriveInput);\n    let krate = input.attrs.iter().find_map(|attr| {\n        attr.path()\n            .is_ident(\"const_serialize\")\n            .then(|| {\n                let mut path = None;\n                if let Err(err) = attr.parse_nested_meta(|meta| {\n                    if meta.path.is_ident(\"crate\") {\n                        let ident: Path = meta.value()?.parse()?;\n                        path = Some(ident);\n                    }\n                    Ok(())\n                }) {\n                    return Some(Err(err));\n                }\n                path.map(Ok)\n            })\n            .flatten()\n    });\n    let krate = match krate {\n        Some(Ok(path)) => path,\n        Some(Err(err)) => return err.into_compile_error().into(),\n        None => parse_quote! { const_serialize },\n    };\n\n    match input.data {\n        syn::Data::Struct(data) => match data.fields {\n            syn::Fields::Unnamed(_) | syn::Fields::Named(_) => {\n                let ty = &input.ident;\n                let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();\n                let mut where_clause = where_clause.cloned();\n                add_bounds(&mut where_clause, &input.generics, &krate);\n                let field_names = data.fields.iter().enumerate().map(|(i, field)| {\n                    field\n                        .ident\n                        .as_ref()\n                        .map(|ident| ident.to_token_stream())\n                        .unwrap_or_else(|| {\n                            LitInt::new(&i.to_string(), proc_macro2::Span::call_site())\n                                .into_token_stream()\n                        })\n                });\n                let field_types = data.fields.iter().map(|field| &field.ty);\n                quote! {\n                    unsafe impl #impl_generics #krate::SerializeConst for #ty #ty_generics #where_clause {\n                        const MEMORY_LAYOUT: #krate::Layout = #krate::Layout::Struct(#krate::StructLayout::new(\n                            std::mem::size_of::<Self>(),\n                            &[#(\n                                #krate::StructFieldLayout::new(\n                                    stringify!(#field_names),\n                                    std::mem::offset_of!(#ty, #field_names),\n                                    <#field_types as #krate::SerializeConst>::MEMORY_LAYOUT,\n                                ),\n                            )*],\n                        ));\n                    }\n                }.into()\n            }\n            syn::Fields::Unit => {\n                let ty = &input.ident;\n                let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();\n                let mut where_clause = where_clause.cloned();\n                add_bounds(&mut where_clause, &input.generics, &krate);\n                quote! {\n                    unsafe impl #impl_generics #krate::SerializeConst for #ty #ty_generics #where_clause {\n                        const MEMORY_LAYOUT: #krate::Layout = #krate::Layout::Struct(#krate::StructLayout::new(\n                            std::mem::size_of::<Self>(),\n                            &[],\n                        ));\n                    }\n                }.into()\n            }\n        },\n        syn::Data::Enum(data) => match data.variants.len() {\n            0 => syn::Error::new(input.ident.span(), \"Enums must have at least one variant\")\n                .to_compile_error()\n                .into(),\n            1.. => {\n                let mut repr_c = false;\n                let mut discriminant_size = None;\n                for attr in &input.attrs {\n                    if attr.path().is_ident(\"repr\") {\n                        if let Err(err) = attr.parse_nested_meta(|meta| {\n                            // #[repr(C)]\n                            if meta.path.is_ident(\"C\") {\n                                repr_c = true;\n                                return Ok(());\n                            }\n\n                            // #[repr(u8)]\n                            if meta.path.is_ident(\"u8\") {\n                                discriminant_size = Some(1);\n                                return Ok(());\n                            }\n\n                            // #[repr(u16)]\n                            if meta.path.is_ident(\"u16\") {\n                                discriminant_size = Some(2);\n                                return Ok(());\n                            }\n\n                            // #[repr(u32)]\n                            if meta.path.is_ident(\"u32\") {\n                                discriminant_size = Some(3);\n                                return Ok(());\n                            }\n\n                            // #[repr(u64)]\n                            if meta.path.is_ident(\"u64\") {\n                                discriminant_size = Some(4);\n                                return Ok(());\n                            }\n\n                            Err(meta.error(\"unrecognized repr\"))\n                        }) {\n                            return err.to_compile_error().into();\n                        }\n                    }\n                }\n\n                let variants_have_fields = data\n                    .variants\n                    .iter()\n                    .any(|variant| !variant.fields.is_empty());\n                if !repr_c && variants_have_fields {\n                    return syn::Error::new(input.ident.span(), \"Enums must be repr(C, u*)\")\n                        .to_compile_error()\n                        .into();\n                }\n\n                if discriminant_size.is_none() {\n                    return syn::Error::new(input.ident.span(), \"Enums must be repr(u*)\")\n                        .to_compile_error()\n                        .into();\n                }\n\n                let ty = &input.ident;\n                let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();\n                let mut where_clause = where_clause.cloned();\n                add_bounds(&mut where_clause, &input.generics, &krate);\n                let mut last_discriminant = None;\n                let variants = data.variants.iter().map(|variant| {\n                    let discriminant = variant\n                        .discriminant\n                        .as_ref()\n                        .map(|(_, discriminant)| discriminant.to_token_stream())\n                        .unwrap_or_else(|| match &last_discriminant {\n                            Some(discriminant) => quote! { #discriminant + 1 },\n                            None => {\n                                quote! { 0 }\n                            }\n                        });\n                    last_discriminant = Some(discriminant.clone());\n                    let variant_name = &variant.ident;\n                    let field_names = variant.fields.iter().enumerate().map(|(i, field)| {\n                        field\n                            .ident\n                            .clone()\n                            .unwrap_or_else(|| quote::format_ident!(\"__field_{}\", i))\n                    });\n                    let field_types = variant.fields.iter().map(|field| &field.ty);\n                    let generics = &input.generics;\n                    quote! {\n                        {\n                            #[allow(unused)]\n                            #[derive(#krate::SerializeConst)]\n                            #[const_serialize(crate = #krate)]\n                            #[repr(C)]\n                            struct VariantStruct #generics {\n                                #(\n                                    #field_names: #field_types,\n                                )*\n                            }\n                            #krate::EnumVariant::new(\n                                stringify!(#variant_name),\n                                #discriminant as u32,\n                                match <VariantStruct #generics as #krate::SerializeConst>::MEMORY_LAYOUT {\n                                    #krate::Layout::Struct(layout) => layout,\n                                    _ => panic!(\"VariantStruct::MEMORY_LAYOUT must be a struct\"),\n                                },\n                                ::std::mem::align_of::<VariantStruct>(),\n                            )\n                        }\n                    }\n                });\n                quote! {\n                    unsafe impl #impl_generics #krate::SerializeConst for #ty #ty_generics #where_clause {\n                        const MEMORY_LAYOUT: #krate::Layout = #krate::Layout::Enum(#krate::EnumLayout::new(\n                            ::std::mem::size_of::<Self>(),\n                            #krate::PrimitiveLayout::new(\n                                #discriminant_size as usize,\n                            ),\n                            {\n                                const DATA: &'static [#krate::EnumVariant] = &[\n                                    #(\n                                        #variants,\n                                    )*\n                                ];\n                                DATA\n                            },\n                        ));\n                    }\n                }.into()\n            }\n        },\n        _ => syn::Error::new(input.ident.span(), \"Only structs and enums are supported\")\n            .to_compile_error()\n            .into(),\n    }\n}\n"
  },
  {
    "path": "packages/core/.vscode/settings.json",
    "content": "{\n    \"rust-analyzer.checkOnSave.allTargets\": false\n}\n"
  },
  {
    "path": "packages/core/.vscode/spellright.dict",
    "content": "Dodrio\nVDoms\ndom\nvirtualdom\nns\nnohasher\nPreact\nvnodes\n"
  },
  {
    "path": "packages/core/Cargo.toml",
    "content": "[package]\nname = \"dioxus-core\"\nversion = { workspace = true }\nauthors = [\"Jonathan Kelley\"]\nedition = \"2021\"\ndescription = \"Build fullstack web, desktop, and mobile apps with a single codebase.\"\nlicense = \"MIT OR Apache-2.0\"\nrepository = \"https://github.com/DioxusLabs/dioxus/\"\nhomepage = \"https://dioxuslabs.com\"\nkeywords = [\"web\", \"desktop\", \"mobile\", \"gui\", \"wasm\"]\nrust-version = \"1.76.0\"\n\n[dependencies]\ndioxus-core-types = { workspace = true }\nconst_format = { workspace = true }\nfutures-channel = { workspace = true }\ngenerational-box = { workspace = true }\nlongest-increasing-subsequence = { workspace = true }\nrustc-hash = { workspace = true }\nrustversion = { workspace = true }\nslab = { workspace = true }\nslotmap = { workspace = true }\ntracing = { workspace = true }\nfutures-util = { workspace = true, default-features = false, features = [\"alloc\", \"std\"] }\nserde = { workspace = true, optional = true, features = [\"derive\"] }\nsubsecond = { workspace = true }\nanyhow = { workspace = true }\n\n[dev-dependencies]\ndioxus = { workspace = true }\ndioxus-ssr = { workspace = true }\ndioxus-html = { workspace = true, features = [\"serialize\"] }\ntokio = { workspace = true, features = [\"full\"] }\nrand = { workspace = true }\nreqwest = { workspace = true }\ntracing-subscriber = { workspace = true, default-features = true }\ntracing-fluent-assertions = \"0.3.0\"\npretty_assertions = { workspace = true }\nsysinfo = \"0.35.2\"\n\n[dev-dependencies.web-sys]\nversion = \"0.3.77\"\nfeatures = [\"Document\", \"HtmlElement\", \"Window\"]\n\n[features]\nserialize = [\"dep:serde\"]\n\n[package.metadata.docs.rs]\ncargo-args = [\"-Zunstable-options\", \"-Zrustdoc-scrape-examples\"]\n"
  },
  {
    "path": "packages/core/README.md",
    "content": "# dioxus-core\n\n`dioxus-core` provides a fast and featureful VirtualDom implementation for Rust.\n\n```rust, no_run\n# tokio::runtime::Runtime::new().unwrap().block_on(async {\nuse dioxus_core::{VirtualDom, Event, Element, Mutations, VNode, ElementId};\n\nlet mut vdom = VirtualDom::new(app);\nlet real_dom = SomeRenderer::new();\n\nloop {\n    tokio::select! {\n        evt = real_dom.event() => {\n            let evt = Event::new(evt, true);\n            vdom.runtime().handle_event(\"onclick\", evt, ElementId(0))\n        },\n        _ = vdom.wait_for_work() => {}\n    }\n    vdom.render_immediate(&mut real_dom.apply())\n}\n\n# fn app() -> Element { VNode::empty() }\n# struct SomeRenderer; impl SomeRenderer { fn new() -> SomeRenderer { SomeRenderer } async fn event(&self) -> std::rc::Rc<dyn std::any::Any> { unimplemented!() } fn apply(&self) -> Mutations { Mutations::default() } }\n# });\n```\n\n## Features\n\nA virtualdom is an efficient and flexible tree data structure that allows you to manage state for a graphical user interface. The Dioxus VirtualDom is perhaps the most fully-featured virtualdom implementation in Rust and powers renderers running across Web, Desktop, Mobile, SSR, TUI, LiveView, and more. When you use the Dioxus VirtualDom, you immediately enable users of your renderer to leverage the wide ecosystem of Dioxus components, hooks, and associated tooling.\n\nSome features of `dioxus-core` include:\n\n- UI components are just functions\n- State is provided by hooks\n- Deep integration with async\n- Strong focus on performance\n- Integrated hotreloading support\n- Extensible system for UI elements and their attributes\n\nIf you are just starting, check out the Guides first.\n\n## Understanding the implementation\n\n`dioxus-core` is designed to be a lightweight crate that. It exposes a number of flexible primitives without being deeply concerned about the intracices of state management itself. We provide a number of useful abstractions built on these primitives in the `dioxus-hooks` crate as well as the `dioxus-signals` crate.\n\nThe important abstractions to understand are:\n\n- The [`VirtualDom`]\n- The [`Component`] and its [`Properties`]\n- Handling events\n- Working with async\n- Suspense\n\n## Usage\n\nThe `dioxus` crate exports the `rsx` macro which transforms a helpful, simpler syntax of Rust.\n\nFirst, start with your app:\n\n```rust\n# use dioxus::dioxus_core::{Mutations, VirtualDom};\nuse dioxus::prelude::*;\n\n// First, declare a root component\nfn app() -> Element {\n    rsx! {\n        div { \"hello world\" }\n    }\n}\n\nfn main() {\n    // Next, create a new VirtualDom using this app as the root component.\n    let mut dom = VirtualDom::new(app);\n\n    // The initial render of the dom will generate a stream of edits for the real dom to apply\n    let mutations = dom.rebuild_to_vec();\n}\n```\n\n## Contributing\n\n- Check out the website [section on contributing](https://dioxuslabs.com/learn/0.7/beyond/contributing).\n- Report issues on our [issue tracker](https://github.com/dioxuslabs/dioxus/issues).\n- [Join](https://discord.gg/XgGxMSkvUM) the discord and ask questions!\n\n<a href=\"https://github.com/dioxuslabs/dioxus/graphs/contributors\">\n  <img src=\"https://contrib.rocks/image?repo=dioxuslabs/dioxus&max=30&columns=10\" />\n</a>\n\n## License\n\nThis project is licensed under the [MIT license].\n\n[mit license]: https://github.com/DioxusLabs/dioxus/blob/master/LICENSE-MIT\n\nUnless you explicitly state otherwise, any contribution intentionally submitted\nfor inclusion in Dioxus by you, shall be licensed as MIT, without any additional\nterms or conditions.\n"
  },
  {
    "path": "packages/core/docs/common_spawn_errors.md",
    "content": "## Compiler errors you may run into while using spawn\n\n<details>\n<summary>async block may outlive the current function, but it borrows `value`, which is owned by the current function</summary>\n\nTasks in Dioxus need only access data that can last for the entire lifetime of the application. That generally means data that is moved into the async block. **If you get this error, you may have forgotten to add `move` to your async block.**\n\nBroken component:\n\n```rust, compile_fail\nuse dioxus::prelude::*;\n\nfn App() -> Element {\n    let signal = use_signal(|| 0);\n\n    use_hook(move || {\n        // ❌ The task may run at any point and reads the value of the signal, but the signal is dropped at the end of the function\n        spawn(async {\n            println!(\"{}\", signal());\n        })\n    });\n\n    todo!()\n}\n```\n\nFixed component:\n\n```rust, no_run\nuse dioxus::prelude::*;\n\nfn App() -> Element {\n    let signal = use_signal(|| 0);\n\n    use_hook(move || {\n        // ✅ The `move` keyword tells rust it can move the `state` signal into the async block. Since the async block owns the signal state, it can read it even after the function returns\n        spawn(async move {\n            println!(\"{}\", signal());\n        })\n    });\n\n    todo!()\n}\n```\n\n</details>\n\n<details>\n<summary>use of moved value: `value`. move occurs because `value` has type `YourType`, which does not implement the `Copy` trait</summary>\n\nData in rust has a single owner. If you run into this error, you have likely tried to move data that isn't `Copy` into two different async tasks. **You can fix this issue by making your data `Copy` or calling `clone` on it before you move it into the async block.**\n\nBroken component:\n\n```rust, compile_fail\n# use dioxus::prelude::*;\n// `MyComponent` accepts a string which cannot be copied implicitly\n#[component]\nfn MyComponent(string: String) -> Element {\n    use_hook(move || {\n        // ❌ We are moving the string into the async task which means we can't access it elsewhere\n        spawn(async move {\n            println!(\"{}\", string);\n        });\n        // ❌ Since we already moved the string, we can't move it into our new task. This will cause a compiler error\n        spawn(async move {\n            println!(\"{}\", string);\n        })\n    });\n\n    todo!()\n}\n```\n\nYou can fix this issue by either:\n\n- Making your data `Copy` with `ReadSignal`:\n\n```rust, no_run\n# use dioxus::prelude::*;\n// `MyComponent` accepts `ReadSignal<String>` which implements `Copy`\n#[component]\nfn MyComponent(string: ReadSignal<String>) -> Element {\n    use_hook(move || {\n        // ✅ Because the `string` signal is `Copy`, we can copy it into the async task while still having access to it elsewhere\n        spawn(async move {\n            println!(\"{}\", string);\n        });\n        // ✅ Since `string` is `Copy`, we can copy it into another async task\n        spawn(async move {\n            println!(\"{}\", string);\n        })\n    });\n\n    todo!()\n}\n```\n\n- Calling `clone` on your data before you move it into the closure:\n\n```rust, no_run\n# use dioxus::prelude::*;\n// `MyComponent` accepts a string which doesn't implement `Copy`\n#[component]\nfn MyComponent(string: String) -> Element {\n    use_hook(move || {\n        // ✅ The string only has one owner. We could move it into this closure, but since we want to use the string in other closures later, we will clone it instead\n        spawn({\n            // Clone the string in a new block\n            let string = string.clone();\n            // Then move the cloned string into the async block\n            async move {\n                println!(\"{}\", string);\n            }\n        });\n        // ✅ We don't use the string after this closure, so we can just move it into the closure directly\n        spawn(async move {\n            println!(\"{}\", string);\n        })\n    });\n\n    todo!()\n}\n```\n\n</details>\n"
  },
  {
    "path": "packages/core/docs/reactivity.md",
    "content": "# Reactivity\n\nThe core of dioxus relies on the concept of reactivity. Reactivity is the system that updates your app when state changes.\n\nThere are two parts to reactivity: **Reactive Contexts** and **Tracked Values**.\n\n## Reactive Contexts\n\nReactive Contexts keep track of what state different parts of your app rely on. Reactive Context show up throughout dioxus: Component, use_effect, use_memo and use_resource all have their own reactive contexts:\n\n```rust, no_run\n# use dioxus::prelude::*;\nlet count = use_signal(|| 0);\n// The reactive context in the memo knows that the memo depends on the count signal\nuse_memo(move || count() * 2);\n```\n\n## Tracked Values\n\nTracked values are values that reactive contexts know about. When you read a tracked value, the reactive context will rerun when the value changes. Signals, Memos, and Resources are all tracked values:\n\n```rust, no_run\n# use dioxus::prelude::*;\n// The count signal is tracked\nlet count = use_signal(|| 0);\n// When you read the count signal, the reactive context subscribes to the count signal\nlet double_count = use_memo(move || count() * 2);\n```\n\n## Reactivity\n\nReactivity is the system that combines reactive context and tracked values to update your app when state changes.\n\nYou can use reactivity to create derived state and update your app when state changes.\n\nYou can derive state from other state with [`use_memo`](https://docs.rs/dioxus/latest/dioxus/prelude/fn.use_memo.html).\n\n```rust, no_run\nuse dioxus::prelude::*;\n\nlet mut count = use_signal(|| 0);\nlet double_count = use_memo(move || count() * 2);\n\n// Now whenever we read double_count, we know it is always twice the value of count\nprintln!(\"{}\", double_count); // Prints \"2\"\n\n// After we write to count, the reactive context will rerun and double_count will be updated automatically\ncount += 1;\n\nprintln!(\"{}\", double_count); // Prints \"4\"\n```\n\nYou can also use reactivity to create derive state asynchronously. For example, you can use [`use_resource`](https://docs.rs/dioxus/latest/dioxus/prelude/fn.use_resource.html) to load data from a server:\n\n```rust, no_run\nuse dioxus::prelude::*;\n\nlet count = use_signal(|| 0);\nlet double_count = use_resource(move || async move {\n    // Start a request to the server. We are reading the value of count to format it into the url\n    // Since we are reading count, this resource will \"subscribe\" to changes to count (when count changes, the resource will rerun)\n    let response = reqwest::get(format!(\"https://myserver.com/doubleme?count={count}\")).await.unwrap();\n    response.text().await.unwrap()\n});\n```\n\n## Non Reactive State\n\nYou can use plain Rust types in Dioxus, but you should be aware that they are not reactive. If you read the non-reactive state, reactive scopes will not subscribe to the state.\n\nYou can make non-reactive state reactive by using the `Signal` type instead of a plain Rust type or by using the `use_reactive` hook.\n\n```rust, no_run\nuse dioxus::prelude::*;\n\n// ❌ Don't create non-reactive state\nlet state = use_hook(|| std::cell::RefCell::new(0));\n\n// Computed values will get out of date if the state they depend on is not reactive\nlet doubled = use_memo(move || *state.borrow() * 2);\n\n// ✅ Create reactive state\nlet state = use_signal(|| 0);\n\n// Computed values will automatically keep up to date with the latest reactive state\nlet doubled = use_memo(move || state() * 2);\n\n// ❌ Don't depend on non-reactive prop state in memos/resources\n#[component]\nfn MyComponent(state: i32) -> Element {\n    let doubled = use_memo(move || state * 2);\n    todo!()\n}\n\n// ✅ Wrap your props in ReadSignal to make them reactive\n#[component]\nfn MyReactiveComponent(state: ReadSignal<i32>) -> Element {\n    let doubled = use_memo(move || state() * 2);\n    todo!()\n}\n```\n\nIf your state can't be reactive, you can use the `use_reactive` hook to make it reactive.\n\n```rust, no_run\nuse dioxus::prelude::*;\n\nlet state = rand::random::<i32>();\n\n// You can make the state reactive by wrapping it in use_reactive\nlet doubled = use_memo(use_reactive!(|state| state * 2));\n```\n"
  },
  {
    "path": "packages/core/src/any_props.rs",
    "content": "use crate::{innerlude::CapturedPanic, ComponentFunction, Element};\nuse std::{any::Any, panic::AssertUnwindSafe};\n\npub(crate) type BoxedAnyProps = Box<dyn AnyProps>;\n\n/// A trait for a component that can be rendered.\npub(crate) trait AnyProps: 'static {\n    /// Render the component with the internal props.\n    fn render(&self) -> Element;\n    /// Make the old props equal to the new type erased props. Return if the props were equal and should be memoized.\n    fn memoize(&mut self, other: &dyn Any) -> bool;\n    /// Get the props as a type erased `dyn Any`.\n    fn props(&self) -> &dyn Any;\n    /// Get the props as a type erased `dyn Any`.\n    fn props_mut(&mut self) -> &mut dyn Any;\n    /// Duplicate this component into a new boxed component.\n    fn duplicate(&self) -> BoxedAnyProps;\n}\n\n/// A component along with the props the component uses to render.\npub(crate) struct VProps<F: ComponentFunction<P, M>, P, M> {\n    render_fn: F,\n    memo: fn(&mut P, &P) -> bool,\n    props: P,\n    name: &'static str,\n    phantom: std::marker::PhantomData<M>,\n}\n\nimpl<F: ComponentFunction<P, M>, P: Clone, M> Clone for VProps<F, P, M> {\n    fn clone(&self) -> Self {\n        Self {\n            render_fn: self.render_fn.clone(),\n            memo: self.memo,\n            props: self.props.clone(),\n            name: self.name,\n            phantom: std::marker::PhantomData,\n        }\n    }\n}\n\nimpl<F: ComponentFunction<P, M> + Clone, P: Clone + 'static, M: 'static> VProps<F, P, M> {\n    /// Create a [`VProps`] object.\n    pub fn new(\n        render_fn: F,\n        memo: fn(&mut P, &P) -> bool,\n        props: P,\n        name: &'static str,\n    ) -> VProps<F, P, M> {\n        VProps {\n            render_fn,\n            memo,\n            props,\n            name,\n            phantom: std::marker::PhantomData,\n        }\n    }\n}\n\nimpl<F: ComponentFunction<P, M> + Clone, P: Clone + 'static, M: 'static> AnyProps\n    for VProps<F, P, M>\n{\n    fn memoize(&mut self, other: &dyn Any) -> bool {\n        match other.downcast_ref::<P>() {\n            Some(other) => (self.memo)(&mut self.props, other),\n            None => false,\n        }\n    }\n\n    fn props(&self) -> &dyn Any {\n        &self.props\n    }\n\n    fn props_mut(&mut self) -> &mut dyn Any {\n        &mut self.props\n    }\n\n    fn render(&self) -> Element {\n        fn render_inner(_name: &str, res: Result<Element, Box<dyn Any + Send>>) -> Element {\n            match res {\n                Ok(node) => node,\n                Err(err) => {\n                    // on wasm this massively bloats binary sizes and we can't even capture the panic\n                    // so do nothing\n                    #[cfg(not(target_arch = \"wasm32\"))]\n                    {\n                        tracing::error!(\"Panic while rendering component `{_name}`: {err:?}\");\n                    }\n                    Element::Err(CapturedPanic(err).into())\n                }\n            }\n        }\n\n        render_inner(\n            self.name,\n            std::panic::catch_unwind(AssertUnwindSafe(move || {\n                self.render_fn.rebuild(self.props.clone())\n            })),\n        )\n    }\n\n    fn duplicate(&self) -> BoxedAnyProps {\n        Box::new(Self {\n            render_fn: self.render_fn.clone(),\n            memo: self.memo,\n            props: self.props.clone(),\n            name: self.name,\n            phantom: std::marker::PhantomData,\n        })\n    }\n}\n"
  },
  {
    "path": "packages/core/src/arena.rs",
    "content": "use crate::innerlude::ScopeOrder;\nuse crate::{virtual_dom::VirtualDom, ScopeId};\n\n/// An Element's unique identifier.\n///\n/// `ElementId` is a `usize` that is unique across the entire VirtualDOM - but not unique across time. If a component is\n/// unmounted, then the `ElementId` will be reused for a new component.\n#[cfg_attr(feature = \"serialize\", derive(serde::Serialize, serde::Deserialize))]\n#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]\npub struct ElementId(pub usize);\n\n/// An Element that can be bubbled to's unique identifier.\n///\n/// `BubbleId` is a `usize` that is unique across the entire VirtualDOM - but not unique across time. If a component is\n/// unmounted, then the `BubbleId` will be reused for a new component.\n#[cfg_attr(feature = \"serialize\", derive(serde::Serialize, serde::Deserialize))]\n#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]\npub(crate) struct MountId(pub(crate) usize);\n\nimpl Default for MountId {\n    fn default() -> Self {\n        Self::PLACEHOLDER\n    }\n}\n\nimpl MountId {\n    pub(crate) const PLACEHOLDER: Self = Self(usize::MAX);\n\n    pub(crate) fn as_usize(self) -> Option<usize> {\n        if self.mounted() {\n            Some(self.0)\n        } else {\n            None\n        }\n    }\n\n    #[allow(unused)]\n    pub(crate) fn mounted(self) -> bool {\n        self != Self::PLACEHOLDER\n    }\n}\n\n#[derive(Debug, Clone, Copy)]\npub struct ElementRef {\n    // the pathway of the real element inside the template\n    pub(crate) path: ElementPath,\n\n    // The actual element\n    pub(crate) mount: MountId,\n}\n\n#[derive(Clone, Copy, Debug)]\npub struct ElementPath {\n    pub(crate) path: &'static [u8],\n}\n\nimpl VirtualDom {\n    pub(crate) fn next_element(&mut self) -> ElementId {\n        let mut elements = self.runtime.elements.borrow_mut();\n        ElementId(elements.insert(None))\n    }\n\n    pub(crate) fn reclaim(&mut self, el: ElementId) {\n        if !self.try_reclaim(el) {\n            tracing::error!(\"cannot reclaim {:?}\", el);\n        }\n    }\n\n    pub(crate) fn try_reclaim(&mut self, el: ElementId) -> bool {\n        // We never reclaim the unmounted elements or the root element\n        if el.0 == 0 || el.0 == usize::MAX {\n            return true;\n        }\n\n        let mut elements = self.runtime.elements.borrow_mut();\n        elements.try_remove(el.0).is_some()\n    }\n\n    // Drop a scope without dropping its children\n    //\n    // Note: This will not remove any ids from the arena\n    pub(crate) fn drop_scope(&mut self, id: ScopeId) {\n        let height = {\n            let scope = self.scopes.remove(id.0);\n            let context = scope.state();\n            context.height\n        };\n\n        self.dirty_scopes.remove(&ScopeOrder::new(height, id));\n\n        // If this scope was a suspense boundary, remove it from the resolved scopes\n        self.resolved_scopes.retain(|s| s != &id);\n    }\n}\n\nimpl ElementPath {\n    pub(crate) fn is_descendant(&self, small: &[u8]) -> bool {\n        small.len() <= self.path.len() && small == &self.path[..small.len()]\n    }\n}\n\n#[test]\nfn is_descendant() {\n    let event_path = ElementPath {\n        path: &[1, 2, 3, 4, 5],\n    };\n\n    assert!(event_path.is_descendant(&[1, 2, 3, 4, 5]));\n    assert!(event_path.is_descendant(&[1, 2, 3, 4]));\n    assert!(event_path.is_descendant(&[1, 2, 3]));\n    assert!(event_path.is_descendant(&[1, 2]));\n    assert!(event_path.is_descendant(&[1]));\n\n    assert!(!event_path.is_descendant(&[1, 2, 3, 4, 5, 6]));\n    assert!(!event_path.is_descendant(&[2, 3, 4]));\n}\n\nimpl PartialEq<&[u8]> for ElementPath {\n    fn eq(&self, other: &&[u8]) -> bool {\n        self.path.eq(*other)\n    }\n}\n"
  },
  {
    "path": "packages/core/src/diff/component.rs",
    "content": "use std::{\n    any::TypeId,\n    ops::{Deref, DerefMut},\n};\n\nuse crate::{\n    any_props::AnyProps,\n    innerlude::{\n        ElementRef, MountId, ScopeOrder, SuspenseBoundaryProps, SuspenseBoundaryPropsWithOwner,\n        VComponent, WriteMutations,\n    },\n    nodes::VNode,\n    scopes::{LastRenderedNode, ScopeId},\n    virtual_dom::VirtualDom,\n    Element, SuspenseContext,\n};\n\nimpl VirtualDom {\n    pub(crate) fn run_and_diff_scope<M: WriteMutations>(\n        &mut self,\n        to: Option<&mut M>,\n        scope_id: ScopeId,\n    ) {\n        let scope = &mut self.scopes[scope_id.0];\n        if SuspenseBoundaryProps::downcast_from_props(&mut *scope.props).is_some() {\n            SuspenseBoundaryProps::diff(scope_id, self, to)\n        } else {\n            let new_nodes = self.run_scope(scope_id);\n            self.diff_scope(to, scope_id, new_nodes);\n        }\n    }\n\n    #[tracing::instrument(skip(self, to), level = \"trace\", name = \"VirtualDom::diff_scope\")]\n    fn diff_scope<M: WriteMutations>(\n        &mut self,\n        to: Option<&mut M>,\n        scope: ScopeId,\n        new_nodes: Element,\n    ) {\n        self.runtime.clone().with_scope_on_stack(scope, || {\n            // We don't diff the nodes if the scope is suspended or has an error\n            let Ok(new_real_nodes) = &new_nodes else {\n                return;\n            };\n            let scope_state = &mut self.scopes[scope.0];\n            // Load the old and new rendered nodes\n            let old = scope_state.last_rendered_node.take().unwrap();\n\n            // If there are suspended scopes, we need to check if the scope is suspended before we diff it\n            // If it is suspended, we need to diff it but write the mutations nothing\n            // Note: It is important that we still diff the scope even if it is suspended, because the scope may render other child components which may change between renders\n            let mut render_to = to.filter(|_| self.runtime.scope_should_render(scope));\n            old.diff_node(new_real_nodes, self, render_to.as_deref_mut());\n\n            self.scopes[scope.0].last_rendered_node = Some(LastRenderedNode::new(new_nodes));\n\n            if render_to.is_some() {\n                self.runtime.get_state(scope).mount(&self.runtime);\n            }\n        })\n    }\n\n    /// Create a new [`Scope`](crate::scope_context::Scope) for a component.\n    ///\n    /// Returns the number of nodes created on the stack\n    #[tracing::instrument(skip(self, to), level = \"trace\", name = \"VirtualDom::create_scope\")]\n    pub(crate) fn create_scope<M: WriteMutations>(\n        &mut self,\n        to: Option<&mut M>,\n        scope: ScopeId,\n        new_nodes: LastRenderedNode,\n        parent: Option<ElementRef>,\n    ) -> usize {\n        self.runtime.clone().with_scope_on_stack(scope, || {\n            // If there are suspended scopes, we need to check if the scope is suspended before we diff it\n            // If it is suspended, we need to diff it but write the mutations nothing\n            // Note: It is important that we still diff the scope even if it is suspended, because the scope may render other child components which may change between renders\n            let mut render_to = to.filter(|_| self.runtime.scope_should_render(scope));\n\n            // Create the node\n            let nodes = new_nodes.create(self, parent, render_to.as_deref_mut());\n\n            // Then set the new node as the last rendered node\n            self.scopes[scope.0].last_rendered_node = Some(new_nodes);\n\n            if render_to.is_some() {\n                self.runtime.get_state(scope).mount(&self.runtime);\n            }\n\n            nodes\n        })\n    }\n\n    pub(crate) fn remove_component_node<M: WriteMutations>(\n        &mut self,\n        to: Option<&mut M>,\n        destroy_component_state: bool,\n        scope_id: ScopeId,\n        replace_with: Option<usize>,\n    ) {\n        // If this is a suspense boundary, remove the suspended nodes as well\n        SuspenseContext::remove_suspended_nodes::<M>(self, scope_id, destroy_component_state);\n\n        // Remove the component from the dom\n        if let Some(node) = self.scopes[scope_id.0].last_rendered_node.clone() {\n            node.remove_node_inner(self, to, destroy_component_state, replace_with)\n        };\n\n        if destroy_component_state {\n            // Now drop all the resources\n            self.drop_scope(scope_id);\n        }\n    }\n}\n\nimpl VNode {\n    pub(crate) fn diff_vcomponent(\n        &self,\n        mount: MountId,\n        idx: usize,\n        new: &VComponent,\n        old: &VComponent,\n        scope_id: ScopeId,\n        parent: Option<ElementRef>,\n        dom: &mut VirtualDom,\n        to: Option<&mut impl WriteMutations>,\n    ) {\n        // Replace components that have different render fns\n        if old.render_fn != new.render_fn {\n            return self.replace_vcomponent(mount, idx, new, parent, dom, to);\n        }\n\n        // copy out the box for both\n        let old_scope = &mut dom.scopes[scope_id.0];\n        let old_props: &mut dyn AnyProps = old_scope.props.deref_mut();\n        let new_props: &dyn AnyProps = new.props.deref();\n\n        // If the props are static, then we try to memoize by setting the new with the old\n        // The target ScopeState still has the reference to the old props, so there's no need to update anything\n        // This also implicitly drops the new props since they're not used\n        if old_props.memoize(new_props.props()) {\n            tracing::trace!(\"Memoized props for component {:#?}\", scope_id,);\n            return;\n        }\n\n        // Now diff the scope\n        dom.run_and_diff_scope(to, scope_id);\n\n        let height = dom.runtime.get_state(scope_id).height;\n        dom.dirty_scopes.remove(&ScopeOrder::new(height, scope_id));\n    }\n\n    fn replace_vcomponent(\n        &self,\n        mount: MountId,\n        idx: usize,\n        new: &VComponent,\n        parent: Option<ElementRef>,\n        dom: &mut VirtualDom,\n        mut to: Option<&mut impl WriteMutations>,\n    ) {\n        let scope = ScopeId(dom.get_mounted_dyn_node(mount, idx));\n\n        // Remove the scope id from the mount\n        dom.set_mounted_dyn_node(mount, idx, ScopeId::PLACEHOLDER.0);\n        let m = self.create_component_node(mount, idx, new, parent, dom, to.as_deref_mut());\n\n        // Instead of *just* removing it, we can use the replace mutation\n        dom.remove_component_node(to, true, scope, Some(m));\n    }\n\n    /// Create a new component (if it doesn't already exist) node and then mount the [`crate::ScopeState`] for a component\n    ///\n    /// Returns the number of nodes created on the stack\n    pub(super) fn create_component_node(\n        &self,\n        mount: MountId,\n        idx: usize,\n        component: &VComponent,\n        parent: Option<ElementRef>,\n        dom: &mut VirtualDom,\n        to: Option<&mut impl WriteMutations>,\n    ) -> usize {\n        // If this is a suspense boundary, run our suspense creation logic instead of running the component\n        if component.props.props().type_id() == TypeId::of::<SuspenseBoundaryPropsWithOwner>() {\n            return SuspenseBoundaryProps::create(mount, idx, component, parent, dom, to);\n        }\n\n        let mut scope_id = ScopeId(dom.get_mounted_dyn_node(mount, idx));\n\n        // If the scopeid is a placeholder, we need to load up a new scope for this vcomponent. If it's already mounted, then we can just use that\n        if scope_id.is_placeholder() {\n            scope_id = dom\n                .new_scope(component.props.duplicate(), component.name)\n                .state()\n                .id;\n\n            // Store the scope id for the next render\n            dom.set_mounted_dyn_node(mount, idx, scope_id.0);\n\n            // If this is a new scope, we also need to run it once to get the initial state\n            let new = dom.run_scope(scope_id);\n\n            // Then set the new node as the last rendered node\n            dom.scopes[scope_id.0].last_rendered_node = Some(LastRenderedNode::new(new));\n        }\n\n        let scope = ScopeId(dom.get_mounted_dyn_node(mount, idx));\n\n        let new_node = dom.scopes[scope.0]\n            .last_rendered_node\n            .clone()\n            .expect(\"Component to be mounted\");\n\n        dom.create_scope(to, scope, new_node, parent)\n    }\n}\n"
  },
  {
    "path": "packages/core/src/diff/iterator.rs",
    "content": "use crate::{\n    innerlude::{ElementRef, WriteMutations},\n    nodes::VNode,\n    DynamicNode, ScopeId, VirtualDom,\n};\n\nuse rustc_hash::{FxHashMap, FxHashSet};\n\nimpl VirtualDom {\n    pub(crate) fn diff_non_empty_fragment(\n        &mut self,\n        to: Option<&mut impl WriteMutations>,\n        old: &[VNode],\n        new: &[VNode],\n        parent: Option<ElementRef>,\n    ) {\n        let new_is_keyed = new[0].key.is_some();\n        let old_is_keyed = old[0].key.is_some();\n        debug_assert!(\n            new.iter().all(|n| n.key.is_some() == new_is_keyed),\n            \"all siblings must be keyed or all siblings must be non-keyed\"\n        );\n        debug_assert!(\n            old.iter().all(|o| o.key.is_some() == old_is_keyed),\n            \"all siblings must be keyed or all siblings must be non-keyed\"\n        );\n\n        if new_is_keyed && old_is_keyed {\n            self.diff_keyed_children(to, old, new, parent);\n        } else {\n            self.diff_non_keyed_children(to, old, new, parent);\n        }\n    }\n\n    // Diff children that are not keyed.\n    //\n    // The parent must be on the top of the change list stack when entering this\n    // function:\n    //\n    //     [... parent]\n    //\n    // the change list stack is in the same state when this function returns.\n    fn diff_non_keyed_children(\n        &mut self,\n        mut to: Option<&mut impl WriteMutations>,\n        old: &[VNode],\n        new: &[VNode],\n        parent: Option<ElementRef>,\n    ) {\n        use std::cmp::Ordering;\n\n        // Handled these cases in `diff_children` before calling this function.\n        debug_assert!(!new.is_empty());\n        debug_assert!(!old.is_empty());\n\n        match old.len().cmp(&new.len()) {\n            Ordering::Greater => self.remove_nodes(to.as_deref_mut(), &old[new.len()..], None),\n            Ordering::Less => self.create_and_insert_after(\n                to.as_deref_mut(),\n                &new[old.len()..],\n                old.last().unwrap(),\n                parent,\n            ),\n            Ordering::Equal => {}\n        }\n\n        for (new, old) in new.iter().zip(old.iter()) {\n            old.diff_node(new, self, to.as_deref_mut());\n        }\n    }\n\n    // Diffing \"keyed\" children.\n    //\n    // With keyed children, we care about whether we delete, move, or create nodes\n    // versus mutate existing nodes in place. Presumably there is some sort of CSS\n    // transition animation that makes the virtual DOM diffing algorithm\n    // observable. By specifying keys for nodes, we know which virtual DOM nodes\n    // must reuse (or not reuse) the same physical DOM nodes.\n    //\n    // This is loosely based on Inferno's keyed patching implementation. However, we\n    // have to modify the algorithm since we are compiling the diff down into change\n    // list instructions that will be executed later, rather than applying the\n    // changes to the DOM directly as we compare virtual DOMs.\n    //\n    // https://github.com/infernojs/inferno/blob/36fd96/packages/inferno/src/DOM/patching.ts#L530-L739\n    //\n    // The stack is empty upon entry.\n    fn diff_keyed_children(\n        &mut self,\n        mut to: Option<&mut impl WriteMutations>,\n        old: &[VNode],\n        new: &[VNode],\n        parent: Option<ElementRef>,\n    ) {\n        if cfg!(debug_assertions) {\n            let mut keys = rustc_hash::FxHashSet::default();\n            let mut assert_unique_keys = |children: &[VNode]| {\n                keys.clear();\n                for child in children {\n                    let key = child.key.clone();\n                    debug_assert!(\n                        key.is_some(),\n                        \"if any sibling is keyed, all siblings must be keyed\"\n                    );\n                    keys.insert(key);\n                }\n                debug_assert_eq!(\n                    children.len(),\n                    keys.len(),\n                    \"keyed siblings must each have a unique key\"\n                );\n            };\n            assert_unique_keys(old);\n            assert_unique_keys(new);\n        }\n\n        // First up, we diff all the nodes with the same key at the beginning of the\n        // children.\n        //\n        // `shared_prefix_count` is the count of how many nodes at the start of\n        // `new` and `old` share the same keys.\n        let (left_offset, right_offset) =\n            match self.diff_keyed_ends(to.as_deref_mut(), old, new, parent) {\n                Some(count) => count,\n                None => return,\n            };\n\n        // Ok, we now hopefully have a smaller range of children in the middle\n        // within which to re-order nodes with the same keys, remove old nodes with\n        // now-unused keys, and create new nodes with fresh keys.\n\n        let old_middle = &old[left_offset..(old.len() - right_offset)];\n        let new_middle = &new[left_offset..(new.len() - right_offset)];\n\n        debug_assert!(\n            !old_middle.is_empty(),\n            \"Old middle returned from `diff_keyed_ends` should not be empty\"\n        );\n        debug_assert!(\n            !new_middle.is_empty(),\n            \"New middle returned from `diff_keyed_ends` should not be empty\"\n        );\n\n        // A few nodes in the middle were removed, just remove the old nodes\n        if new_middle.is_empty() {\n            self.remove_nodes(to, old_middle, None);\n        } else {\n            self.diff_keyed_middle(to, old_middle, new_middle, parent);\n        }\n    }\n\n    /// Diff both ends of the children that share keys.\n    ///\n    /// Returns a left offset and right offset of that indicates a smaller section to pass onto the middle diffing.\n    ///\n    /// If there is no offset, then this function returns None and the diffing is complete.\n    fn diff_keyed_ends(\n        &mut self,\n        mut to: Option<&mut impl WriteMutations>,\n        old: &[VNode],\n        new: &[VNode],\n        parent: Option<ElementRef>,\n    ) -> Option<(usize, usize)> {\n        let mut left_offset = 0;\n\n        for (old, new) in old.iter().zip(new.iter()) {\n            // abort early if we finally run into nodes with different keys\n            if old.key != new.key {\n                break;\n            }\n            old.diff_node(new, self, to.as_deref_mut());\n            left_offset += 1;\n        }\n\n        // If that was all of the old children, then create and append the remaining\n        // new children and we're finished.\n        if left_offset == old.len() {\n            self.create_and_insert_after(to, &new[left_offset..], &new[left_offset - 1], parent);\n            return None;\n        }\n\n        // if the shared prefix is less than either length, then we need to walk backwards\n        let mut right_offset = 0;\n        for (old, new) in old.iter().rev().zip(new.iter().rev()) {\n            // abort early if we finally run into nodes with different keys\n            if old.key != new.key {\n                break;\n            }\n            old.diff_node(new, self, to.as_deref_mut());\n            right_offset += 1;\n        }\n\n        // If that was all of the old children, then create and prepend the remaining\n        // new children and we're finished.\n        if right_offset == old.len() {\n            self.create_and_insert_before(\n                to,\n                &new[..new.len() - right_offset],\n                &new[new.len() - right_offset],\n                parent,\n            );\n            return None;\n        }\n\n        // If the right offset + the left offset is the same as the new length, then we just need to remove the old nodes\n        if right_offset + left_offset == new.len() {\n            self.remove_nodes(to, &old[left_offset..old.len() - right_offset], None);\n            return None;\n        }\n\n        // If the right offset + the left offset is the same as the old length, then we just need to add the new nodes\n        if right_offset + left_offset == old.len() {\n            self.create_and_insert_before(\n                to,\n                &new[left_offset..new.len() - right_offset],\n                &new[new.len() - right_offset],\n                parent,\n            );\n            return None;\n        }\n\n        Some((left_offset, right_offset))\n    }\n\n    // The most-general, expensive code path for keyed children diffing.\n    //\n    // We find the longest subsequence within `old` of children that are relatively\n    // ordered the same way in `new` (via finding a longest-increasing-subsequence\n    // of the old child's index within `new`). The children that are elements of\n    // this subsequence will remain in place, minimizing the number of DOM moves we\n    // will have to do.\n    //\n    // Upon entry to this function, the change list stack must be empty.\n    //\n    // This function will load the appropriate nodes onto the stack and do diffing in place.\n    //\n    // Upon exit from this function, it will be restored to that same self.\n    #[allow(clippy::too_many_lines)]\n    fn diff_keyed_middle(\n        &mut self,\n        mut to: Option<&mut impl WriteMutations>,\n        old: &[VNode],\n        new: &[VNode],\n        parent: Option<ElementRef>,\n    ) {\n        /*\n        1. Map the old keys into a numerical ordering based on indices.\n        2. Create a map of old key to its index\n        3. Map each new key to the old key, carrying over the old index.\n            - IE if we have ABCD becomes BACD, our sequence would be 1,0,2,3\n            - if we have ABCD to ABDE, our sequence would be 0,1,3,MAX because E doesn't exist\n\n        now, we should have a list of integers that indicates where in the old list the new items map to.\n\n        4. Compute the LIS of this list\n            - this indicates the longest list of new children that won't need to be moved.\n\n        5. Identify which nodes need to be removed\n        6. Identify which nodes will need to be diffed\n\n        7. Going along each item in the new list, create it and insert it before the next closest item in the LIS.\n            - if the item already existed, just move it to the right place.\n\n        8. Finally, generate instructions to remove any old children.\n        9. Generate instructions to finally diff children that are the same between both\n        */\n        // 0. Debug sanity checks\n        // Should have already diffed the shared-key prefixes and suffixes.\n        debug_assert_ne!(new.first().map(|i| &i.key), old.first().map(|i| &i.key));\n        debug_assert_ne!(new.last().map(|i| &i.key), old.last().map(|i| &i.key));\n\n        // 1. Map the old keys into a numerical ordering based on indices.\n        // 2. Create a map of old key to its index\n        // IE if the keys were A B C, then we would have (A, 0) (B, 1) (C, 2).\n        let old_key_to_old_index = old\n            .iter()\n            .enumerate()\n            .map(|(i, o)| (o.key.as_ref().unwrap().as_str(), i))\n            .collect::<FxHashMap<_, _>>();\n\n        let mut shared_keys = FxHashSet::default();\n\n        // 3. Map each new key to the old key, carrying over the old index.\n        let new_index_to_old_index = new\n            .iter()\n            .map(|node| {\n                let key = node.key.as_ref().unwrap();\n                if let Some(&index) = old_key_to_old_index.get(key.as_str()) {\n                    shared_keys.insert(key);\n                    index\n                } else {\n                    usize::MAX\n                }\n            })\n            .collect::<Box<[_]>>();\n\n        // If none of the old keys are reused by the new children, then we remove all the remaining old children and\n        // create the new children afresh.\n        if shared_keys.is_empty() {\n            debug_assert!(\n                !old.is_empty(),\n                \"we should never be appending - just creating N\"\n            );\n\n            let m = self.create_children(to.as_deref_mut(), new, parent);\n            self.remove_nodes(to, old, Some(m));\n\n            return;\n        }\n\n        // remove any old children that are not shared\n        for child_to_remove in old\n            .iter()\n            .filter(|child| !shared_keys.contains(child.key.as_ref().unwrap()))\n        {\n            child_to_remove.remove_node(self, to.as_deref_mut(), None);\n        }\n\n        // 4. Compute the LIS of this list\n        let mut lis_sequence = Vec::with_capacity(new_index_to_old_index.len());\n\n        let mut allocation = vec![0; new_index_to_old_index.len() * 2];\n        let (predecessors, starts) = allocation.split_at_mut(new_index_to_old_index.len());\n\n        longest_increasing_subsequence::lis_with(\n            &new_index_to_old_index,\n            &mut lis_sequence,\n            |a, b| a < b,\n            predecessors,\n            starts,\n        );\n\n        // if a new node gets u32 max and is at the end, then it might be part of our LIS (because u32 max is a valid LIS)\n        if lis_sequence.first().map(|f| new_index_to_old_index[*f]) == Some(usize::MAX) {\n            lis_sequence.remove(0);\n        }\n\n        // Diff each nod in the LIS\n        for idx in &lis_sequence {\n            old[new_index_to_old_index[*idx]].diff_node(&new[*idx], self, to.as_deref_mut());\n        }\n\n        /// Create or diff each node in a range depending on whether it is in the LIS or not\n        /// Returns the number of nodes created on the stack\n        fn create_or_diff(\n            vdom: &mut VirtualDom,\n            new: &[VNode],\n            old: &[VNode],\n            mut to: Option<&mut impl WriteMutations>,\n            parent: Option<ElementRef>,\n            new_index_to_old_index: &[usize],\n            range: std::ops::Range<usize>,\n        ) -> usize {\n            let range_start = range.start;\n            new[range]\n                .iter()\n                .enumerate()\n                .map(|(idx, new_node)| {\n                    let new_idx = range_start + idx;\n                    let old_index = new_index_to_old_index[new_idx];\n                    // If the node existed in the old list, diff it\n                    if let Some(old_node) = old.get(old_index) {\n                        old_node.diff_node(new_node, vdom, to.as_deref_mut());\n                        if let Some(to) = to.as_deref_mut() {\n                            new_node.push_all_root_nodes(vdom, to)\n                        } else {\n                            0\n                        }\n                    } else {\n                        // Otherwise, just add it to the stack\n                        new_node.create(vdom, parent, to.as_deref_mut())\n                    }\n                })\n                .sum()\n        }\n\n        // add mount instruction for the items before the LIS\n        let last = *lis_sequence.first().unwrap();\n        if last < (new.len() - 1) {\n            let nodes_created = create_or_diff(\n                self,\n                new,\n                old,\n                to.as_deref_mut(),\n                parent,\n                &new_index_to_old_index,\n                (last + 1)..new.len(),\n            );\n\n            // Insert all the nodes that we just created after the last node in the LIS\n            self.insert_after(to.as_deref_mut(), nodes_created, &new[last]);\n        }\n\n        // For each node inside of the LIS, but not included in the LIS, generate a mount instruction\n        // We loop over the LIS in reverse order and insert any nodes we find in the gaps between indexes\n        let mut lis_iter = lis_sequence.iter();\n        let mut last = *lis_iter.next().unwrap();\n        for next in lis_iter {\n            if last - next > 1 {\n                let nodes_created = create_or_diff(\n                    self,\n                    new,\n                    old,\n                    to.as_deref_mut(),\n                    parent,\n                    &new_index_to_old_index,\n                    (next + 1)..last,\n                );\n\n                self.insert_before(to.as_deref_mut(), nodes_created, &new[last]);\n            }\n            last = *next;\n        }\n\n        // add mount instruction for the items after the LIS\n        let first_lis = *lis_sequence.last().unwrap();\n        if first_lis > 0 {\n            let nodes_created = create_or_diff(\n                self,\n                new,\n                old,\n                to.as_deref_mut(),\n                parent,\n                &new_index_to_old_index,\n                0..first_lis,\n            );\n\n            self.insert_before(to, nodes_created, &new[first_lis]);\n        }\n    }\n\n    fn create_and_insert_before(\n        &mut self,\n        mut to: Option<&mut impl WriteMutations>,\n        new: &[VNode],\n        before: &VNode,\n        parent: Option<ElementRef>,\n    ) {\n        let m = self.create_children(to.as_deref_mut(), new, parent);\n        self.insert_before(to, m, before);\n    }\n\n    fn insert_before(&mut self, to: Option<&mut impl WriteMutations>, new: usize, before: &VNode) {\n        if let Some(to) = to {\n            if new > 0 {\n                let id = before.find_first_element(self);\n                to.insert_nodes_before(id, new);\n            }\n        }\n    }\n\n    fn create_and_insert_after(\n        &mut self,\n        mut to: Option<&mut impl WriteMutations>,\n        new: &[VNode],\n        after: &VNode,\n        parent: Option<ElementRef>,\n    ) {\n        let m = self.create_children(to.as_deref_mut(), new, parent);\n        self.insert_after(to, m, after);\n    }\n\n    fn insert_after(&mut self, to: Option<&mut impl WriteMutations>, new: usize, after: &VNode) {\n        if let Some(to) = to {\n            if new > 0 {\n                let id = after.find_last_element(self);\n                to.insert_nodes_after(id, new);\n            }\n        }\n    }\n}\n\nimpl VNode {\n    /// Push all the root nodes on the stack\n    pub(crate) fn push_all_root_nodes(\n        &self,\n        dom: &VirtualDom,\n        to: &mut impl WriteMutations,\n    ) -> usize {\n        let template = self.template;\n\n        let mounts = dom.runtime.mounts.borrow();\n        let mount = mounts.get(self.mount.get().0).unwrap();\n\n        template\n            .roots\n            .iter()\n            .enumerate()\n            .map(\n                |(root_idx, _)| match self.get_dynamic_root_node_and_id(root_idx) {\n                    Some((_, DynamicNode::Fragment(nodes))) => {\n                        let mut accumulated = 0;\n                        for node in nodes {\n                            accumulated += node.push_all_root_nodes(dom, to);\n                        }\n                        accumulated\n                    }\n                    Some((idx, DynamicNode::Component(_))) => {\n                        let scope = ScopeId(mount.mounted_dynamic_nodes[idx]);\n                        let node = dom.get_scope(scope).unwrap().root_node();\n                        node.push_all_root_nodes(dom, to)\n                    }\n                    // This is a static root node or a single dynamic node, just push it\n                    None | Some((_, DynamicNode::Placeholder(_) | DynamicNode::Text(_))) => {\n                        to.push_root(mount.root_ids[root_idx]);\n                        1\n                    }\n                },\n            )\n            .sum()\n    }\n}\n"
  },
  {
    "path": "packages/core/src/diff/mod.rs",
    "content": "//! This module contains all the code for creating and diffing nodes.\n//!\n//! For suspense there are three different cases we need to handle:\n//! - Creating nodes/scopes without mounting them\n//! - Diffing nodes that are not mounted\n//! - Mounted nodes that have already been created\n//!\n//! To support those cases, we lazily create components and only optionally write to the real dom while diffing with Option<&mut impl WriteMutations>\n\n#![allow(clippy::too_many_arguments)]\n\nuse crate::{\n    arena::MountId,\n    innerlude::{ElementRef, WriteMutations},\n    nodes::VNode,\n    virtual_dom::VirtualDom,\n    ElementId, TemplateNode,\n};\n\nmod component;\nmod iterator;\nmod node;\n\nimpl VirtualDom {\n    pub(crate) fn create_children(\n        &mut self,\n        mut to: Option<&mut impl WriteMutations>,\n        nodes: &[VNode],\n        parent: Option<ElementRef>,\n    ) -> usize {\n        nodes\n            .iter()\n            .map(|child| child.create(self, parent, to.as_deref_mut()))\n            .sum()\n    }\n\n    pub(crate) fn get_mounted_parent(&self, mount: MountId) -> Option<ElementRef> {\n        let mounts = self.runtime.mounts.borrow();\n        mounts[mount.0].parent\n    }\n\n    pub(crate) fn get_mounted_dyn_node(&self, mount: MountId, dyn_node_idx: usize) -> usize {\n        let mounts = self.runtime.mounts.borrow();\n        mounts[mount.0].mounted_dynamic_nodes[dyn_node_idx]\n    }\n\n    pub(crate) fn set_mounted_dyn_node(&self, mount: MountId, dyn_node_idx: usize, value: usize) {\n        let mut mounts = self.runtime.mounts.borrow_mut();\n        mounts[mount.0].mounted_dynamic_nodes[dyn_node_idx] = value;\n    }\n\n    pub(crate) fn get_mounted_dyn_attr(&self, mount: MountId, dyn_attr_idx: usize) -> ElementId {\n        let mounts = self.runtime.mounts.borrow();\n        mounts[mount.0].mounted_attributes[dyn_attr_idx]\n    }\n\n    pub(crate) fn set_mounted_dyn_attr(\n        &self,\n        mount: MountId,\n        dyn_attr_idx: usize,\n        value: ElementId,\n    ) {\n        let mut mounts = self.runtime.mounts.borrow_mut();\n        mounts[mount.0].mounted_attributes[dyn_attr_idx] = value;\n    }\n\n    pub(crate) fn get_mounted_root_node(&self, mount: MountId, root_idx: usize) -> ElementId {\n        let mounts = self.runtime.mounts.borrow();\n        mounts[mount.0].root_ids[root_idx]\n    }\n\n    pub(crate) fn set_mounted_root_node(&self, mount: MountId, root_idx: usize, value: ElementId) {\n        let mut mounts = self.runtime.mounts.borrow_mut();\n        mounts[mount.0].root_ids[root_idx] = value;\n    }\n\n    /// Remove these nodes from the dom\n    /// Wont generate mutations for the inner nodes\n    fn remove_nodes(\n        &mut self,\n        mut to: Option<&mut impl WriteMutations>,\n        nodes: &[VNode],\n        replace_with: Option<usize>,\n    ) {\n        for (i, node) in nodes.iter().rev().enumerate() {\n            let last_node = i == nodes.len() - 1;\n            node.remove_node(self, to.as_deref_mut(), replace_with.filter(|_| last_node));\n        }\n    }\n}\n\n/// We can apply various optimizations to dynamic nodes that are the single child of their parent.\n///\n/// IE\n///  - for text - we can use SetTextContent\n///  - for clearing children we can use RemoveChildren\n///  - for appending children we can use AppendChildren\n#[allow(dead_code)]\nfn is_dyn_node_only_child(node: &VNode, idx: usize) -> bool {\n    let template = node.template;\n    let path = template.node_paths[idx];\n\n    // use a loop to index every static node's children until the path has run out\n    // only break if the last path index is a dynamic node\n    let mut static_node = &template.roots[path[0] as usize];\n\n    for i in 1..path.len() - 1 {\n        match static_node {\n            TemplateNode::Element { children, .. } => static_node = &children[path[i] as usize],\n            _ => return false,\n        }\n    }\n\n    match static_node {\n        TemplateNode::Element { children, .. } => children.len() == 1,\n        _ => false,\n    }\n}\n"
  },
  {
    "path": "packages/core/src/diff/node.rs",
    "content": "use crate::innerlude::MountId;\nuse crate::{Attribute, AttributeValue, DynamicNode::*};\nuse crate::{VNode, VirtualDom, WriteMutations};\nuse core::iter::Peekable;\n\nuse crate::{\n    arena::ElementId,\n    innerlude::{ElementPath, ElementRef, VNodeMount, VText},\n    nodes::DynamicNode,\n    scopes::ScopeId,\n    TemplateNode,\n};\n\nimpl VNode {\n    pub(crate) fn diff_node(\n        &self,\n        new: &VNode,\n        dom: &mut VirtualDom,\n        mut to: Option<&mut impl WriteMutations>,\n    ) {\n        // The node we are diffing from should always be mounted\n        debug_assert!(\n            dom.runtime\n                .mounts\n                .borrow()\n                .get(self.mount.get().0)\n                .is_some()\n                || to.is_none()\n        );\n\n        // If the templates are different, we need to replace the entire template\n        if self.template != new.template {\n            let mount_id = self.mount.get();\n            let parent = dom.get_mounted_parent(mount_id);\n            return self.replace(std::slice::from_ref(new), parent, dom, to);\n        }\n\n        self.move_mount_to(new, dom);\n\n        // If the templates are the same, we don't need to do anything, except copy over the mount information\n        if self == new {\n            return;\n        }\n\n        // If the templates are the same, we can diff the attributes and children\n        // Start with the attributes\n        // Since the attributes are only side effects, we can skip diffing them entirely if the node is suspended and we aren't outputting mutations\n        if let Some(to) = to.as_deref_mut() {\n            self.diff_attributes(new, dom, to);\n        }\n\n        // Now diff the dynamic nodes\n        let mount_id = new.mount.get();\n        for (dyn_node_idx, (old, new)) in self\n            .dynamic_nodes\n            .iter()\n            .zip(new.dynamic_nodes.iter())\n            .enumerate()\n        {\n            self.diff_dynamic_node(mount_id, dyn_node_idx, old, new, dom, to.as_deref_mut())\n        }\n    }\n\n    fn move_mount_to(&self, new: &VNode, dom: &mut VirtualDom) {\n        // Copy over the mount information\n        let mount_id = self.mount.take();\n        new.mount.set(mount_id);\n\n        if mount_id.mounted() {\n            let mut mounts = dom.runtime.mounts.borrow_mut();\n            let mount = &mut mounts[mount_id.0];\n\n            // Update the reference to the node for bubbling events\n            mount.node = new.clone();\n        }\n    }\n\n    fn diff_dynamic_node(\n        &self,\n        mount: MountId,\n        idx: usize,\n        old_node: &DynamicNode,\n        new_node: &DynamicNode,\n        dom: &mut VirtualDom,\n        mut to: Option<&mut impl WriteMutations>,\n    ) {\n        tracing::trace!(\"diffing dynamic node from {old_node:?} to {new_node:?}\");\n        match (old_node, new_node) {\n            (Text(old), Text(new)) => {\n                // Diffing text is just a side effect, if we are diffing suspended nodes and are not outputting mutations, we can skip it\n                if let Some(to) = to {\n                    let id = ElementId(dom.get_mounted_dyn_node(mount, idx));\n                    self.diff_vtext(to, id, old, new)\n                }\n            }\n            (Placeholder(_), Placeholder(_)) => {}\n            (Fragment(old), Fragment(new)) => dom.diff_non_empty_fragment(\n                to,\n                old,\n                new,\n                Some(self.reference_to_dynamic_node(mount, idx)),\n            ),\n            (Component(old), Component(new)) => {\n                let scope_id = ScopeId(dom.get_mounted_dyn_node(mount, idx));\n                self.diff_vcomponent(\n                    mount,\n                    idx,\n                    new,\n                    old,\n                    scope_id,\n                    Some(self.reference_to_dynamic_node(mount, idx)),\n                    dom,\n                    to,\n                )\n            }\n            (old, new) => {\n                // TODO: we should pass around the mount instead of the mount id\n                // that would make moving the mount around here much easier\n\n                // Mark the mount as unused. When a scope is created, it reads the mount and\n                // if it is the placeholder value, it will create the scope, otherwise it will\n                // reuse the scope\n                let old_mount = dom.get_mounted_dyn_node(mount, idx);\n                dom.set_mounted_dyn_node(mount, idx, usize::MAX);\n\n                let new_nodes_on_stack =\n                    self.create_dynamic_node(new, mount, idx, dom, to.as_deref_mut());\n\n                // Restore the mount for the scope we are removing\n                let new_mount = dom.get_mounted_dyn_node(mount, idx);\n                dom.set_mounted_dyn_node(mount, idx, old_mount);\n\n                self.remove_dynamic_node(mount, dom, to, true, idx, old, Some(new_nodes_on_stack));\n\n                // Restore the mount for the node we created\n                dom.set_mounted_dyn_node(mount, idx, new_mount);\n            }\n        };\n    }\n\n    /// Try to get the dynamic node and its index for a root node\n    pub(crate) fn get_dynamic_root_node_and_id(\n        &self,\n        root_idx: usize,\n    ) -> Option<(usize, &DynamicNode)> {\n        self.template.roots[root_idx]\n            .dynamic_id()\n            .map(|id| (id, &self.dynamic_nodes[id]))\n    }\n\n    pub(crate) fn find_first_element(&self, dom: &VirtualDom) -> ElementId {\n        let mount_id = self.mount.get();\n        let first = match self.get_dynamic_root_node_and_id(0) {\n            // This node is static, just get the root id\n            None => dom.get_mounted_root_node(mount_id, 0),\n            // If it is dynamic and shallow, grab the id from the mounted dynamic nodes\n            Some((idx, Placeholder(_) | Text(_))) => {\n                ElementId(dom.get_mounted_dyn_node(mount_id, idx))\n            }\n            // The node is a fragment, so we need to find the first element in the fragment\n            Some((_, Fragment(children))) => {\n                let child = children.first().unwrap();\n                child.find_first_element(dom)\n            }\n            // The node is a component, so we need to find the first element in the component\n            Some((id, Component(_))) => {\n                let scope = ScopeId(dom.get_mounted_dyn_node(mount_id, id));\n                dom.get_scope(scope)\n                    .unwrap()\n                    .root_node()\n                    .find_first_element(dom)\n            }\n        };\n\n        // The first element should never be the default element id (the root element)\n        debug_assert_ne!(first, ElementId::default());\n\n        first\n    }\n\n    pub(crate) fn find_last_element(&self, dom: &VirtualDom) -> ElementId {\n        let mount_id = self.mount.get();\n        let last_root_index = self.template.roots.len() - 1;\n        let last = match self.get_dynamic_root_node_and_id(last_root_index) {\n            // This node is static, just get the root id\n            None => dom.get_mounted_root_node(mount_id, last_root_index),\n            // If it is dynamic and shallow, grab the id from the mounted dynamic nodes\n            Some((idx, Placeholder(_) | Text(_))) => {\n                ElementId(dom.get_mounted_dyn_node(mount_id, idx))\n            }\n            // The node is a fragment, so we need to find the last element in the fragment\n            Some((_, Fragment(children))) => {\n                let child = children.last().unwrap();\n                child.find_last_element(dom)\n            }\n            // The node is a component, so we need to find the first element in the component\n            Some((id, Component(_))) => {\n                let scope = ScopeId(dom.get_mounted_dyn_node(mount_id, id));\n                dom.get_scope(scope)\n                    .unwrap()\n                    .root_node()\n                    .find_last_element(dom)\n            }\n        };\n\n        // The last element should never be the default element id (the root element)\n        debug_assert_ne!(last, ElementId::default());\n\n        last\n    }\n\n    /// Diff the two text nodes\n    ///\n    /// This just sets the text of the node if it's different.\n    fn diff_vtext(&self, to: &mut impl WriteMutations, id: ElementId, left: &VText, right: &VText) {\n        if left.value != right.value {\n            to.set_node_text(&right.value, id);\n        }\n    }\n\n    pub(crate) fn replace(\n        &self,\n        right: &[VNode],\n        parent: Option<ElementRef>,\n        dom: &mut VirtualDom,\n        to: Option<&mut impl WriteMutations>,\n    ) {\n        self.replace_inner(right, parent, dom, to, true)\n    }\n\n    /// Replace this node with new children, but *don't destroy* the old node's component state\n    ///\n    /// This is useful for moving a node from the rendered nodes into a suspended node\n    pub(crate) fn move_node_to_background(\n        &self,\n        right: &[VNode],\n        parent: Option<ElementRef>,\n        dom: &mut VirtualDom,\n        to: Option<&mut impl WriteMutations>,\n    ) {\n        self.replace_inner(right, parent, dom, to, false)\n    }\n\n    pub(crate) fn replace_inner(\n        &self,\n        right: &[VNode],\n        parent: Option<ElementRef>,\n        dom: &mut VirtualDom,\n        mut to: Option<&mut impl WriteMutations>,\n        destroy_component_state: bool,\n    ) {\n        let m = dom.create_children(to.as_deref_mut(), right, parent);\n\n        // Instead of *just* removing it, we can use the replace mutation\n        self.remove_node_inner(dom, to, destroy_component_state, Some(m))\n    }\n\n    /// Remove a node from the dom and potentially replace it with the top m nodes from the stack\n    pub(crate) fn remove_node<M: WriteMutations>(\n        &self,\n        dom: &mut VirtualDom,\n        to: Option<&mut M>,\n        replace_with: Option<usize>,\n    ) {\n        self.remove_node_inner(dom, to, true, replace_with)\n    }\n\n    /// Remove a node, but only maybe destroy the component state of that node. During suspense, we need to remove a node from the real dom without wiping the component state\n    pub(crate) fn remove_node_inner<M: WriteMutations>(\n        &self,\n        dom: &mut VirtualDom,\n        to: Option<&mut M>,\n        destroy_component_state: bool,\n        replace_with: Option<usize>,\n    ) {\n        let mount = self.mount.get();\n        if !mount.mounted() {\n            return;\n        }\n\n        // Clean up any attributes that have claimed a static node as dynamic for mount/unmounts\n        // Will not generate mutations!\n        self.reclaim_attributes(mount, dom);\n\n        // Remove the nested dynamic nodes\n        // We don't generate mutations for these, as they will be removed by the parent (in the next line)\n        // But we still need to make sure to reclaim them from the arena and drop their hooks, etc\n        self.remove_nested_dyn_nodes::<M>(mount, dom, destroy_component_state);\n\n        // Clean up the roots, assuming we need to generate mutations for these\n        // This is done last in order to preserve Node ID reclaim order (reclaim in reverse order of claim)\n        self.reclaim_roots(mount, dom, to, destroy_component_state, replace_with);\n\n        if destroy_component_state {\n            let mount = self.mount.take();\n            // Remove the mount information\n            dom.runtime.mounts.borrow_mut().remove(mount.0);\n        }\n    }\n\n    fn reclaim_roots(\n        &self,\n        mount: MountId,\n        dom: &mut VirtualDom,\n        mut to: Option<&mut impl WriteMutations>,\n        destroy_component_state: bool,\n        replace_with: Option<usize>,\n    ) {\n        let roots = self.template.roots;\n        for (idx, node) in roots.iter().enumerate() {\n            let last_node = idx == roots.len() - 1;\n            if let Some(id) = node.dynamic_id() {\n                let dynamic_node = &self.dynamic_nodes[id];\n                self.remove_dynamic_node(\n                    mount,\n                    dom,\n                    to.as_deref_mut(),\n                    destroy_component_state,\n                    id,\n                    dynamic_node,\n                    replace_with.filter(|_| last_node),\n                );\n            } else if let Some(to) = to.as_deref_mut() {\n                let id = dom.get_mounted_root_node(mount, idx);\n                if let (true, Some(replace_with)) = (last_node, replace_with) {\n                    to.replace_node_with(id, replace_with);\n                } else {\n                    to.remove_node(id);\n                }\n                dom.reclaim(id);\n            } else {\n                let id = dom.get_mounted_root_node(mount, idx);\n                dom.reclaim(id);\n            }\n        }\n    }\n\n    fn remove_nested_dyn_nodes<M: WriteMutations>(\n        &self,\n        mount: MountId,\n        dom: &mut VirtualDom,\n        destroy_component_state: bool,\n    ) {\n        let template = self.template;\n        for (idx, dyn_node) in self.dynamic_nodes.iter().enumerate() {\n            let path_len = template.node_paths.get(idx).map(|path| path.len());\n            // Roots are cleaned up automatically above and nodes with a empty path are placeholders\n            if let Some(2..) = path_len {\n                self.remove_dynamic_node(\n                    mount,\n                    dom,\n                    Option::<&mut M>::None,\n                    destroy_component_state,\n                    idx,\n                    dyn_node,\n                    None,\n                )\n            }\n        }\n    }\n\n    fn remove_dynamic_node(\n        &self,\n        mount: MountId,\n        dom: &mut VirtualDom,\n        mut to: Option<&mut impl WriteMutations>,\n        destroy_component_state: bool,\n        idx: usize,\n        node: &DynamicNode,\n        replace_with: Option<usize>,\n    ) {\n        match node {\n            Component(_comp) => {\n                let scope_id = ScopeId(dom.get_mounted_dyn_node(mount, idx));\n                dom.remove_component_node(to, destroy_component_state, scope_id, replace_with);\n            }\n            Text(_) | Placeholder(_) => {\n                let id = ElementId(dom.get_mounted_dyn_node(mount, idx));\n                if let Some(to) = to {\n                    if let Some(replace_with) = replace_with {\n                        to.replace_node_with(id, replace_with);\n                    } else {\n                        to.remove_node(id);\n                    }\n                }\n                dom.reclaim(id)\n            }\n            Fragment(nodes) => {\n                for node in &nodes[..nodes.len() - 1] {\n                    node.remove_node_inner(dom, to.as_deref_mut(), destroy_component_state, None)\n                }\n                if let Some(last_node) = nodes.last() {\n                    last_node.remove_node_inner(dom, to, destroy_component_state, replace_with)\n                }\n            }\n        };\n    }\n\n    pub(super) fn reclaim_attributes(&self, mount: MountId, dom: &mut VirtualDom) {\n        let mut next_id = None;\n        for (idx, path) in self.template.attr_paths.iter().enumerate() {\n            // We clean up the roots in the next step, so don't worry about them here\n            if path.len() <= 1 {\n                continue;\n            }\n\n            // only reclaim the new element if it's different from the previous one\n            let new_id = dom.get_mounted_dyn_attr(mount, idx);\n            if Some(new_id) != next_id {\n                dom.reclaim(new_id);\n                next_id = Some(new_id);\n            }\n        }\n    }\n\n    pub(super) fn diff_attributes(\n        &self,\n        new: &VNode,\n        dom: &mut VirtualDom,\n        to: &mut impl WriteMutations,\n    ) {\n        let mount_id = new.mount.get();\n        for (idx, (old_attrs, new_attrs)) in self\n            .dynamic_attrs\n            .iter()\n            .zip(new.dynamic_attrs.iter())\n            .enumerate()\n        {\n            let mut old_attributes_iter = old_attrs.iter().peekable();\n            let mut new_attributes_iter = new_attrs.iter().peekable();\n            let attribute_id = dom.get_mounted_dyn_attr(mount_id, idx);\n            let path = self.template.attr_paths[idx];\n\n            loop {\n                match (old_attributes_iter.peek(), new_attributes_iter.peek()) {\n                    (Some(old_attribute), Some(new_attribute)) => {\n                        // check which name is greater\n                        match old_attribute.name.cmp(new_attribute.name) {\n                            // The two attributes are the same, so diff them\n                            std::cmp::Ordering::Equal => {\n                                let old = old_attributes_iter.next().unwrap();\n                                let new = new_attributes_iter.next().unwrap();\n                                // Volatile attributes are attributes that the browser may override so we always update them\n                                let volatile = old.volatile;\n                                // We only need to write the attribute if the attribute is volatile or the value has changed\n                                // and this is not an event listener.\n                                // Interpreters reference event listeners by name and element id, so we don't need to write them\n                                // even if the closure has changed.\n                                let attribute_changed = match (&old.value, &new.value) {\n                                    (AttributeValue::Text(l), AttributeValue::Text(r)) => l != r,\n                                    (AttributeValue::Float(l), AttributeValue::Float(r)) => l != r,\n                                    (AttributeValue::Int(l), AttributeValue::Int(r)) => l != r,\n                                    (AttributeValue::Bool(l), AttributeValue::Bool(r)) => l != r,\n                                    (AttributeValue::Any(l), AttributeValue::Any(r)) => {\n                                        !l.as_ref().any_cmp(r.as_ref())\n                                    }\n                                    (AttributeValue::None, AttributeValue::None) => false,\n                                    (AttributeValue::Listener(_), AttributeValue::Listener(_)) => {\n                                        false\n                                    }\n                                    _ => true,\n                                };\n                                if volatile || attribute_changed {\n                                    self.write_attribute(\n                                        path,\n                                        new,\n                                        attribute_id,\n                                        mount_id,\n                                        dom,\n                                        to,\n                                    );\n                                }\n                            }\n                            // In a sorted list, if the old attribute name is first, then the new attribute is missing\n                            std::cmp::Ordering::Less => {\n                                let old = old_attributes_iter.next().unwrap();\n                                self.remove_attribute(old, attribute_id, to)\n                            }\n                            // In a sorted list, if the new attribute name is first, then the old attribute is missing\n                            std::cmp::Ordering::Greater => {\n                                let new = new_attributes_iter.next().unwrap();\n                                self.write_attribute(path, new, attribute_id, mount_id, dom, to);\n                            }\n                        }\n                    }\n                    (Some(_), None) => {\n                        let left = old_attributes_iter.next().unwrap();\n                        self.remove_attribute(left, attribute_id, to)\n                    }\n                    (None, Some(_)) => {\n                        let right = new_attributes_iter.next().unwrap();\n                        self.write_attribute(path, right, attribute_id, mount_id, dom, to)\n                    }\n                    (None, None) => break,\n                }\n            }\n        }\n    }\n\n    fn remove_attribute(&self, attribute: &Attribute, id: ElementId, to: &mut impl WriteMutations) {\n        match &attribute.value {\n            AttributeValue::Listener(_) => {\n                to.remove_event_listener(&attribute.name[2..], id);\n            }\n            _ => {\n                to.set_attribute(\n                    attribute.name,\n                    attribute.namespace,\n                    &AttributeValue::None,\n                    id,\n                );\n            }\n        }\n    }\n\n    fn write_attribute(\n        &self,\n        path: &'static [u8],\n        attribute: &Attribute,\n        id: ElementId,\n        mount: MountId,\n        dom: &mut VirtualDom,\n        to: &mut impl WriteMutations,\n    ) {\n        match &attribute.value {\n            AttributeValue::Listener(_) => {\n                let element_ref = ElementRef {\n                    path: ElementPath { path },\n                    mount,\n                };\n                let mut elements = dom.runtime.elements.borrow_mut();\n                elements[id.0] = Some(element_ref);\n                to.create_event_listener(&attribute.name[2..], id);\n            }\n            _ => {\n                to.set_attribute(attribute.name, attribute.namespace, &attribute.value, id);\n            }\n        }\n    }\n\n    /// Create this rsx block. This will create scopes from components that this rsx block contains, but it will not write anything to the DOM.\n    pub(crate) fn create(\n        &self,\n        dom: &mut VirtualDom,\n        parent: Option<ElementRef>,\n        mut to: Option<&mut impl WriteMutations>,\n    ) -> usize {\n        // Get the most up to date template\n        let template = self.template;\n\n        // Initialize the mount information for this vnode if it isn't already mounted\n        if !self.mount.get().mounted() {\n            let mut mounts = dom.runtime.mounts.borrow_mut();\n            let entry = mounts.vacant_entry();\n            let mount = MountId(entry.key());\n            self.mount.set(mount);\n            tracing::trace!(?self, ?mount, \"creating template\");\n            entry.insert(VNodeMount {\n                node: self.clone(),\n                parent,\n                root_ids: vec![ElementId(0); template.roots.len()].into_boxed_slice(),\n                mounted_attributes: vec![ElementId(0); template.attr_paths.len()]\n                    .into_boxed_slice(),\n                mounted_dynamic_nodes: vec![usize::MAX; template.node_paths.len()]\n                    .into_boxed_slice(),\n            });\n        }\n\n        // Walk the roots, creating nodes and assigning IDs\n        // nodes in an iterator of (dynamic_node_index, path) and attrs in an iterator of (attr_index, path)\n        let mut nodes = template.node_paths.iter().copied().enumerate().peekable();\n        let mut attrs = template.attr_paths.iter().copied().enumerate().peekable();\n\n        // Get the mounted id of this block\n        // At this point, we should have already mounted the block\n        debug_assert!(\n            dom.runtime.mounts.borrow().contains(\n                self.mount\n                    .get()\n                    .as_usize()\n                    .expect(\"node should already be mounted\"),\n            ),\n            \"Tried to find mount {:?} in dom.mounts, but it wasn't there\",\n            self.mount.get()\n        );\n        let mount = self.mount.get();\n\n        // Go through each root node and create the node, adding it to the stack.\n        // Each node already exists in the template, so we can just clone it from the template\n        let nodes_created = template\n            .roots\n            .iter()\n            .enumerate()\n            .map(|(root_idx, root)| {\n                match root {\n                    TemplateNode::Dynamic { id } => {\n                        // Take a dynamic node off the depth first iterator\n                        nodes.next().unwrap();\n                        // Then mount the node\n                        self.create_dynamic_node(\n                            &self.dynamic_nodes[*id],\n                            mount,\n                            *id,\n                            dom,\n                            to.as_deref_mut(),\n                        )\n                    }\n                    // For static text and element nodes, just load the template root. This may be a placeholder or just a static node. We now know that each root node has a unique id\n                    TemplateNode::Text { .. } | TemplateNode::Element { .. } => {\n                        if let Some(to) = to.as_deref_mut() {\n                            self.load_template_root(mount, root_idx, dom, to);\n                        }\n\n                        // If this is an element, load in all of the placeholder or dynamic content under this root element too\n                        if matches!(root, TemplateNode::Element { .. }) {\n                            // !!VERY IMPORTANT!!\n                            // Write out all attributes before we load the children. Loading the children will change paths we rely on\n                            // to assign ids to elements with dynamic attributes\n                            if let Some(to) = to.as_deref_mut() {\n                                self.write_attrs(mount, &mut attrs, root_idx as u8, dom, to);\n                            }\n                            // This operation relies on the fact that the root node is the top node on the stack so we need to do it here\n                            self.load_placeholders(\n                                mount,\n                                &mut nodes,\n                                root_idx as u8,\n                                dom,\n                                to.as_deref_mut(),\n                            );\n                        }\n\n                        // This creates one node on the stack\n                        1\n                    }\n                }\n            })\n            .sum();\n\n        // And return the number of nodes we created on the stack\n        nodes_created\n    }\n}\n\nimpl VNode {\n    /// Get a reference back into a dynamic node\n    fn reference_to_dynamic_node(&self, mount: MountId, dynamic_node_id: usize) -> ElementRef {\n        ElementRef {\n            path: ElementPath {\n                path: self.template.node_paths[dynamic_node_id],\n            },\n            mount,\n        }\n    }\n\n    pub(crate) fn create_dynamic_node(\n        &self,\n        node: &DynamicNode,\n        mount: MountId,\n        dynamic_node_id: usize,\n        dom: &mut VirtualDom,\n        to: Option<&mut impl WriteMutations>,\n    ) -> usize {\n        use DynamicNode::*;\n        match node {\n            Component(component) => {\n                let parent = Some(self.reference_to_dynamic_node(mount, dynamic_node_id));\n                self.create_component_node(mount, dynamic_node_id, component, parent, dom, to)\n            }\n            Fragment(frag) => {\n                let parent = Some(self.reference_to_dynamic_node(mount, dynamic_node_id));\n                dom.create_children(to, frag, parent)\n            }\n            Text(text) => {\n                // If we are diffing suspended nodes and are not outputting mutations, we can skip it\n                if let Some(to) = to {\n                    self.create_dynamic_text(mount, dynamic_node_id, text, dom, to)\n                } else {\n                    0\n                }\n            }\n            Placeholder(_) => {\n                // If we are diffing suspended nodes and are not outputting mutations, we can skip it\n                if let Some(to) = to {\n                    tracing::trace!(\"creating placeholder\");\n                    self.create_placeholder(mount, dynamic_node_id, dom, to)\n                } else {\n                    tracing::trace!(\"skipping creating placeholder\");\n                    0\n                }\n            }\n        }\n    }\n\n    /// Load all of the placeholder nodes for descendent of this root node\n    ///\n    /// ```rust, no_run\n    /// # use dioxus::prelude::*;\n    /// # let some_text = \"hello world\";\n    /// # let some_value = \"123\";\n    /// rsx! {\n    ///     div { // We just wrote this node\n    ///         // This is a placeholder\n    ///         {some_value}\n    ///\n    ///         // Load this too\n    ///         \"{some_text}\"\n    ///     }\n    /// };\n    /// ```\n    ///\n    /// IMPORTANT: This function assumes that root node is the top node on the stack\n    fn load_placeholders(\n        &self,\n        mount: MountId,\n        dynamic_nodes_iter: &mut Peekable<impl Iterator<Item = (usize, &'static [u8])>>,\n        root_idx: u8,\n        dom: &mut VirtualDom,\n        mut to: Option<&mut impl WriteMutations>,\n    ) {\n        fn collect_dyn_node_range(\n            dynamic_nodes: &mut Peekable<impl Iterator<Item = (usize, &'static [u8])>>,\n            root_idx: u8,\n        ) -> Option<(usize, usize)> {\n            let start = match dynamic_nodes.peek() {\n                Some((idx, [first, ..])) if *first == root_idx => *idx,\n                _ => return None,\n            };\n\n            let mut end = start;\n\n            while let Some((idx, p)) =\n                dynamic_nodes.next_if(|(_, p)| matches!(p, [idx, ..] if *idx == root_idx))\n            {\n                if p.len() == 1 {\n                    continue;\n                }\n\n                end = idx;\n            }\n\n            Some((start, end))\n        }\n\n        let (start, end) = match collect_dyn_node_range(dynamic_nodes_iter, root_idx) {\n            Some((a, b)) => (a, b),\n            None => return,\n        };\n\n        // !!VERY IMPORTANT!!\n        //\n        // We need to walk the dynamic nodes in reverse order because we are going to replace the\n        // placeholder with the new nodes, which will invalidate our paths into the template.\n        // If we go in reverse, we leave a \"wake of destruction\" in our path, but our next iteration\n        // will still be \"clean\" since we only invalidated downstream nodes.\n        //\n        // Forgetting to do this will cause weird bugs like:\n        //  https://github.com/DioxusLabs/dioxus/issues/2809\n        //\n        // Which are quite serious.\n        // There might be more places in this codebase where we need to do `.rev()`\n        let reversed_iter = (start..=end).rev();\n\n        for dynamic_node_id in reversed_iter {\n            let m = self.create_dynamic_node(\n                &self.dynamic_nodes[dynamic_node_id],\n                mount,\n                dynamic_node_id,\n                dom,\n                to.as_deref_mut(),\n            );\n            if let Some(to) = to.as_deref_mut() {\n                // If we actually created real new nodes, we need to replace the placeholder for this dynamic node with the new dynamic nodes\n                if m > 0 {\n                    // The path is one shorter because the top node is the root\n                    let path = &self.template.node_paths[dynamic_node_id][1..];\n                    to.replace_placeholder_with_nodes(path, m);\n                }\n            }\n        }\n    }\n\n    /// After we have written a root element, we need to write all the attributes that are on the root node\n    ///\n    /// ```rust, ignore\n    /// rsx! {\n    ///     div { // We just wrote this node\n    ///         class: \"{class}\", // We need to set these attributes\n    ///         id: \"{id}\",\n    ///         style: \"{style}\",\n    ///     }\n    /// }\n    /// ```\n    ///\n    /// IMPORTANT: This function assumes that root node is the top node on the stack\n    fn write_attrs(\n        &self,\n        mount: MountId,\n        dynamic_attributes_iter: &mut Peekable<impl Iterator<Item = (usize, &'static [u8])>>,\n        root_idx: u8,\n        dom: &mut VirtualDom,\n        to: &mut impl WriteMutations,\n    ) {\n        let mut last_path = None;\n        // Only take nodes that are under this root node\n        let from_root_node = |(_, path): &(usize, &[u8])| path.first() == Some(&root_idx);\n        while let Some((attribute_idx, attribute_path)) =\n            dynamic_attributes_iter.next_if(from_root_node)\n        {\n            let attribute = &self.dynamic_attrs[attribute_idx];\n\n            let id = match last_path {\n                // If the last path was exactly the same, we can reuse the id\n                Some((path, id)) if path == attribute_path => id,\n                // Otherwise, we need to create a new id\n                _ => {\n                    let id = self.assign_static_node_as_dynamic(mount, attribute_path, dom, to);\n                    last_path = Some((attribute_path, id));\n                    id\n                }\n            };\n\n            // Write the value for each attribute in the group\n            for attr in &**attribute {\n                self.write_attribute(attribute_path, attr, id, mount, dom, to);\n            }\n            // Set the mounted dynamic attribute once. This must be set even if no actual\n            // attributes are present so it is present for renderers like fullstack to look\n            // up the position where attributes may be inserted in the future\n            dom.set_mounted_dyn_attr(mount, attribute_idx, id);\n        }\n    }\n\n    fn load_template_root(\n        &self,\n        mount: MountId,\n        root_idx: usize,\n        dom: &mut VirtualDom,\n        to: &mut impl WriteMutations,\n    ) -> ElementId {\n        // Get an ID for this root since it's a real root\n        let this_id = dom.next_element();\n        dom.set_mounted_root_node(mount, root_idx, this_id);\n\n        to.load_template(self.template, root_idx, this_id);\n\n        this_id\n    }\n\n    /// We have some dynamic attributes attached to a some node\n    ///\n    /// That node needs to be loaded at runtime, so we need to give it an ID\n    ///\n    /// If the node in question is the root node, we just return the ID\n    ///\n    /// If the node is not on the stack, we create a new ID for it and assign it\n    fn assign_static_node_as_dynamic(\n        &self,\n        mount: MountId,\n        path: &'static [u8],\n        dom: &mut VirtualDom,\n        to: &mut impl WriteMutations,\n    ) -> ElementId {\n        // This is just the root node. We already know it's id\n        if let [root_idx] = path {\n            return dom.get_mounted_root_node(mount, *root_idx as usize);\n        }\n\n        // The node is deeper in the template and we should create a new id for it\n        let id = dom.next_element();\n\n        to.assign_node_id(&path[1..], id);\n\n        id\n    }\n\n    fn create_dynamic_text(\n        &self,\n        mount: MountId,\n        idx: usize,\n        text: &VText,\n        dom: &mut VirtualDom,\n        to: &mut impl WriteMutations,\n    ) -> usize {\n        let new_id = mount.mount_node(idx, dom);\n\n        // If this is a root node, the path is empty and we need to create a new text node\n        to.create_text_node(&text.value, new_id);\n        // We create one node on the stack\n        1\n    }\n\n    pub(crate) fn create_placeholder(\n        &self,\n        mount: MountId,\n        idx: usize,\n        dom: &mut VirtualDom,\n        to: &mut impl WriteMutations,\n    ) -> usize {\n        let new_id = mount.mount_node(idx, dom);\n\n        // If this is a root node, the path is empty and we need to create a new placeholder node\n        to.create_placeholder(new_id);\n        // We create one node on the stack\n        1\n    }\n}\n\nimpl MountId {\n    fn mount_node(self, node_index: usize, dom: &mut VirtualDom) -> ElementId {\n        let id = dom.next_element();\n        dom.set_mounted_dyn_node(self, node_index, id.0);\n        id\n    }\n}\n"
  },
  {
    "path": "packages/core/src/effect.rs",
    "content": "use crate::innerlude::ScopeOrder;\nuse std::borrow::Borrow;\nuse std::cell::RefCell;\nuse std::collections::VecDeque;\n\n/// Effects will always run after all changes to the DOM have been applied.\n///\n/// Effects are the lowest priority task in the scheduler.\n/// They are run after all other dirty scopes and futures have been resolved. Other dirty scopes and futures may cause the component this effect is attached to to rerun, which would update the DOM.\npub(crate) struct Effect {\n    // The scope that the effect is attached to\n    pub(crate) order: ScopeOrder,\n    // The callbacks that will be run when effects are rerun\n    effect: RefCell<VecDeque<Box<dyn FnOnce() + 'static>>>,\n}\n\nimpl Effect {\n    pub(crate) fn new(order: ScopeOrder, f: Box<dyn FnOnce() + 'static>) -> Self {\n        let mut effect = VecDeque::new();\n        effect.push_back(f);\n        Self {\n            order,\n            effect: RefCell::new(effect),\n        }\n    }\n\n    pub(crate) fn push_back(&self, f: impl FnOnce() + 'static) {\n        self.effect.borrow_mut().push_back(Box::new(f));\n    }\n\n    pub(crate) fn run(&self) {\n        let mut effect = self.effect.borrow_mut();\n        while let Some(f) = effect.pop_front() {\n            f();\n        }\n    }\n}\n\nimpl Ord for Effect {\n    fn cmp(&self, other: &Self) -> std::cmp::Ordering {\n        self.order.cmp(&other.order)\n    }\n}\n\nimpl PartialOrd for Effect {\n    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {\n        Some(self.cmp(other))\n    }\n}\n\nimpl PartialEq for Effect {\n    fn eq(&self, other: &Self) -> bool {\n        self.order == other.order\n    }\n}\n\nimpl Eq for Effect {}\n\nimpl Borrow<ScopeOrder> for Effect {\n    fn borrow(&self) -> &ScopeOrder {\n        &self.order\n    }\n}\n"
  },
  {
    "path": "packages/core/src/error_boundary.rs",
    "content": "use crate::{\n    innerlude::{provide_context, CapturedError},\n    try_consume_context, use_hook, Element, IntoDynNode, Properties, ReactiveContext, Subscribers,\n    Template, TemplateAttribute, TemplateNode, VNode,\n};\nuse std::{\n    any::Any,\n    cell::RefCell,\n    fmt::{Debug, Display},\n    rc::Rc,\n};\n\n/// Return early with an error.\n#[macro_export]\nmacro_rules! bail {\n    ($msg:literal $(,)?) => {\n        return $crate::internal::Err($crate::internal::__anyhow!($msg).into())\n    };\n    ($err:expr $(,)?) => {\n        return $crate::internal::Err($crate::internal::__anyhow!($err).into())\n    };\n    ($fmt:expr, $($arg:tt)*) => {\n        return $crate::internal::Err($crate::internal::__anyhow!($fmt, $($arg)*).into())\n    };\n}\n\n/// A panic in a component that was caught by an error boundary.\n///\n/// <div class=\"warning\">\n///\n/// WASM currently does not support caching unwinds, so this struct will not be created in WASM.\n///\n/// </div>\npub(crate) struct CapturedPanic(pub(crate) Box<dyn Any + Send + 'static>);\nunsafe impl Sync for CapturedPanic {}\nimpl std::error::Error for CapturedPanic {}\nimpl Debug for CapturedPanic {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_struct(\"CapturedPanic\").finish()\n    }\n}\n\nimpl Display for CapturedPanic {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.write_fmt(format_args!(\"Encountered panic: {:?}\", self.0))\n    }\n}\n\n/// A context supplied by fullstack to create hydration compatible error boundaries. Generally, this\n/// is not present and the default in memory error boundary is used. If fullstack is enabled, it will\n/// provide its own factory that handles syncing errors to the hydration context\n#[derive(Clone, Copy)]\nstruct CreateErrorBoundary(fn() -> ErrorContext);\n\nimpl Default for CreateErrorBoundary {\n    fn default() -> Self {\n        Self(|| ErrorContext::new(None))\n    }\n}\n\n/// Provides a method that is used to create error boundaries in `use_error_boundary_provider`.\n/// This is only called from fullstack to create a hydration compatible error boundary\n#[doc(hidden)]\npub fn provide_create_error_boundary(create_error_boundary: fn() -> ErrorContext) {\n    provide_context(CreateErrorBoundary(create_error_boundary));\n}\n\n/// Create an error boundary with the current error boundary factory (either hydration compatible or default)\nfn create_error_boundary() -> ErrorContext {\n    let create_error_boundary = try_consume_context::<CreateErrorBoundary>().unwrap_or_default();\n    (create_error_boundary.0)()\n}\n\n/// Provide an error boundary to catch errors from child components. This needs to called in a hydration comptable\n/// order if fullstack is enabled\npub fn use_error_boundary_provider() -> ErrorContext {\n    use_hook(|| provide_context(create_error_boundary()))\n}\n\n/// A context with information about suspended components\n#[derive(Clone)]\npub struct ErrorContext {\n    error: Rc<RefCell<Option<CapturedError>>>,\n    subscribers: Subscribers,\n}\n\nimpl Debug for ErrorContext {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_struct(\"ErrorContext\")\n            .field(\"error\", &self.error)\n            .finish()\n    }\n}\n\nimpl PartialEq for ErrorContext {\n    fn eq(&self, other: &Self) -> bool {\n        Rc::ptr_eq(&self.error, &other.error)\n    }\n}\n\nimpl ErrorContext {\n    /// Create a new suspense boundary in a specific scope\n    pub fn new(error: Option<CapturedError>) -> Self {\n        Self {\n            error: Rc::new(RefCell::new(error)),\n            subscribers: Subscribers::new(),\n        }\n    }\n\n    /// Get the current error, if any. If multiple components have errored, this will return the first\n    /// error that made it to this boundary.\n    pub fn error(&self) -> Option<CapturedError> {\n        // Subscribe to the current reactive context if one exists. This is usually\n        // the error boundary component that is rendering the errors\n        if let Some(rc) = ReactiveContext::current() {\n            self.subscribers.add(rc);\n        }\n\n        self.error.borrow().clone()\n    }\n\n    /// Push an error into this Error Boundary\n    pub fn insert_error(&self, error: CapturedError) {\n        self.error.borrow_mut().replace(error);\n        self.mark_dirty()\n    }\n\n    /// Clear all errors from this Error Boundary\n    pub fn clear_errors(&self) {\n        self.error.borrow_mut().take();\n        self.mark_dirty();\n    }\n\n    /// Mark the error context as dirty and notify all subscribers\n    fn mark_dirty(&self) {\n        let mut this_subscribers_vec = Vec::new();\n        self.subscribers\n            .visit(|subscriber| this_subscribers_vec.push(*subscriber));\n        for subscriber in this_subscribers_vec {\n            self.subscribers.remove(&subscriber);\n            subscriber.mark_dirty();\n        }\n    }\n}\n\n#[allow(clippy::type_complexity)]\n#[derive(Clone)]\npub struct ErrorHandler(Rc<dyn Fn(ErrorContext) -> Element>);\nimpl<F: Fn(ErrorContext) -> Element + 'static> From<F> for ErrorHandler {\n    fn from(value: F) -> Self {\n        Self(Rc::new(value))\n    }\n}\n\nfn default_handler(errors: ErrorContext) -> Element {\n    static TEMPLATE: Template = Template {\n        roots: &[TemplateNode::Element {\n            tag: \"div\",\n            namespace: None,\n            attrs: &[TemplateAttribute::Static {\n                name: \"color\",\n                namespace: Some(\"style\"),\n                value: \"red\",\n            }],\n            children: &[TemplateNode::Dynamic { id: 0usize }],\n        }],\n        node_paths: &[&[0u8, 0u8]],\n        attr_paths: &[],\n    };\n    std::result::Result::Ok(VNode::new(\n        None,\n        TEMPLATE,\n        Box::new([errors\n            .error()\n            .iter()\n            .map(|e| {\n                static TEMPLATE: Template = Template {\n                    roots: &[TemplateNode::Element {\n                        tag: \"pre\",\n                        namespace: None,\n                        attrs: &[],\n                        children: &[TemplateNode::Dynamic { id: 0usize }],\n                    }],\n                    node_paths: &[&[0u8, 0u8]],\n                    attr_paths: &[],\n                };\n                VNode::new(\n                    None,\n                    TEMPLATE,\n                    Box::new([e.to_string().into_dyn_node()]),\n                    Default::default(),\n                )\n            })\n            .into_dyn_node()]),\n        Default::default(),\n    ))\n}\n\n#[derive(Clone)]\npub struct ErrorBoundaryProps {\n    children: Element,\n    handle_error: ErrorHandler,\n}\n\n/// Create a new error boundary component that catches any errors thrown from child components\n///\n/// ## Details\n///\n/// Error boundaries handle errors within a specific part of your application. They are similar to `try/catch` in JavaScript, but they only catch errors in the tree below them.\n/// Any errors passed up from a child will be caught by the nearest error boundary. Error boundaries are quick to implement, but it can be useful to individually handle errors\n/// in your components to provide a better user experience when you know that an error is likely to occur.\n///\n/// ## Example\n///\n/// ```rust, no_run\n/// use dioxus::prelude::*;\n///\n/// fn App() -> Element {\n///     let mut multiplier = use_signal(|| String::from(\"2\"));\n///     rsx! {\n///         input {\n///             r#type: \"text\",\n///             value: multiplier,\n///             oninput: move |e| multiplier.set(e.value())\n///         }\n///         ErrorBoundary {\n///             handle_error: |errors: ErrorContext| {\n///                 rsx! {\n///                     div {\n///                         \"Oops, we encountered an error. Please report {errors:?} to the developer of this application\"\n///                     }\n///                 }\n///             },\n///             Counter {\n///                 multiplier\n///             }\n///         }\n///     }\n/// }\n///\n/// #[component]\n/// fn Counter(multiplier: ReadSignal<String>) -> Element {\n///     let multiplier_parsed = multiplier().parse::<usize>()?;\n///     let mut count = use_signal(|| multiplier_parsed);\n///     rsx! {\n///         button {\n///             onclick: move |_| {\n///                 let multiplier_parsed = multiplier().parse::<usize>()?;\n///                 *count.write() *= multiplier_parsed;\n///                 Ok(())\n///             },\n///             \"{count}x{multiplier}\"\n///         }\n///     }\n/// }\n/// ```\n///\n/// ## Resetting the error boundary\n///\n/// Once the error boundary catches an error, it will render the rsx returned from the handle_error function instead of the children. To reset the error boundary,\n/// you can call the [`ErrorContext::clear_errors`] method. This will clear all errors and re-render the children.\n///\n/// ```rust, no_run\n/// # use dioxus::prelude::*;\n/// fn App() -> Element {\n///     let mut multiplier = use_signal(|| String::new());\n///     rsx! {\n///         input {\n///             r#type: \"text\",\n///             value: multiplier,\n///             oninput: move |e| multiplier.set(e.value())\n///         }\n///         ErrorBoundary {\n///             handle_error: |errors: ErrorContext| {\n///                 rsx! {\n///                     div {\n///                         \"Oops, we encountered an error. Please report {errors:?} to the developer of this application\"\n///                     }\n///                     button {\n///                         onclick: move |_| {\n///                             errors.clear_errors();\n///                         },\n///                         \"try again\"\n///                     }\n///                 }\n///             },\n///             Counter {\n///                 multiplier\n///             }\n///         }\n///     }\n/// }\n///\n/// #[component]\n/// fn Counter(multiplier: ReadSignal<String>) -> Element {\n///     let multiplier_parsed = multiplier().parse::<usize>()?;\n///     let mut count = use_signal(|| multiplier_parsed);\n///     rsx! {\n///         button {\n///             onclick: move |_| {\n///                 let multiplier_parsed = multiplier().parse::<usize>()?;\n///                 *count.write() *= multiplier_parsed;\n///                 Ok(())\n///             },\n///             \"{count}x{multiplier}\"\n///         }\n///     }\n/// }\n/// ```\n#[allow(non_upper_case_globals, non_snake_case)]\npub fn ErrorBoundary(props: ErrorBoundaryProps) -> Element {\n    let error_boundary = use_error_boundary_provider();\n    let errors = error_boundary.error();\n    let has_errors = errors.is_some();\n\n    // Drop errors before running user code that might borrow the error lock\n    drop(errors);\n\n    if has_errors {\n        (props.handle_error.0)(error_boundary.clone())\n    } else {\n        std::result::Result::Ok({\n            static TEMPLATE: Template = Template {\n                roots: &[TemplateNode::Dynamic { id: 0usize }],\n                node_paths: &[&[0u8]],\n                attr_paths: &[],\n            };\n            VNode::new(\n                None,\n                TEMPLATE,\n                Box::new([(props.children).into_dyn_node()]),\n                Default::default(),\n            )\n        })\n    }\n}\n\nimpl ErrorBoundaryProps {\n    /**\n    Create a builder for building `ErrorBoundaryProps`.\n    On the builder, call `.children(...)`(optional), `.handle_error(...)`(optional) to set the values of the fields.\n    Finally, call `.build()` to create the instance of `ErrorBoundaryProps`.\n                        */\n    #[allow(dead_code)]\n    pub fn builder() -> ErrorBoundaryPropsBuilder<((), ())> {\n        ErrorBoundaryPropsBuilder { fields: ((), ()) }\n    }\n}\n\n#[must_use]\n#[doc(hidden)]\n#[allow(dead_code, non_camel_case_types, non_snake_case)]\npub struct ErrorBoundaryPropsBuilder<TypedBuilderFields> {\n    fields: TypedBuilderFields,\n}\nimpl<TypedBuilderFields> Clone for ErrorBoundaryPropsBuilder<TypedBuilderFields>\nwhere\n    TypedBuilderFields: Clone,\n{\n    fn clone(&self) -> Self {\n        Self {\n            fields: self.fields.clone(),\n        }\n    }\n}\nimpl Properties for ErrorBoundaryProps {\n    type Builder = ErrorBoundaryPropsBuilder<((), ())>;\n    fn builder() -> Self::Builder {\n        ErrorBoundaryProps::builder()\n    }\n    fn memoize(&mut self, other: &Self) -> bool {\n        *self = other.clone();\n        false\n    }\n}\n#[doc(hidden)]\n#[allow(dead_code, non_camel_case_types, non_snake_case)]\npub trait ErrorBoundaryPropsBuilder_Optional<T> {\n    fn into_value<F: FnOnce() -> T>(self, default: F) -> T;\n}\nimpl<T> ErrorBoundaryPropsBuilder_Optional<T> for () {\n    fn into_value<F: FnOnce() -> T>(self, default: F) -> T {\n        default()\n    }\n}\nimpl<T> ErrorBoundaryPropsBuilder_Optional<T> for (T,) {\n    fn into_value<F: FnOnce() -> T>(self, _: F) -> T {\n        self.0\n    }\n}\n#[allow(dead_code, non_camel_case_types, missing_docs)]\nimpl<__handle_error> ErrorBoundaryPropsBuilder<((), __handle_error)> {\n    pub fn children(\n        self,\n        children: Element,\n    ) -> ErrorBoundaryPropsBuilder<((Element,), __handle_error)> {\n        let children = (children,);\n        let (_, handle_error) = self.fields;\n        ErrorBoundaryPropsBuilder {\n            fields: (children, handle_error),\n        }\n    }\n}\n#[doc(hidden)]\n#[allow(dead_code, non_camel_case_types, non_snake_case)]\npub enum ErrorBoundaryPropsBuilder_Error_Repeated_field_children {}\n#[doc(hidden)]\n#[allow(dead_code, non_camel_case_types, missing_docs)]\nimpl<__handle_error> ErrorBoundaryPropsBuilder<((Element,), __handle_error)> {\n    #[deprecated(note = \"Repeated field children\")]\n    pub fn children(\n        self,\n        _: ErrorBoundaryPropsBuilder_Error_Repeated_field_children,\n    ) -> ErrorBoundaryPropsBuilder<((Element,), __handle_error)> {\n        self\n    }\n}\n#[allow(dead_code, non_camel_case_types, missing_docs)]\nimpl<__children> ErrorBoundaryPropsBuilder<(__children, ())> {\n    pub fn handle_error(\n        self,\n        handle_error: impl ::core::convert::Into<ErrorHandler>,\n    ) -> ErrorBoundaryPropsBuilder<(__children, (ErrorHandler,))> {\n        let handle_error = (handle_error.into(),);\n        let (children, _) = self.fields;\n        ErrorBoundaryPropsBuilder {\n            fields: (children, handle_error),\n        }\n    }\n}\n#[doc(hidden)]\n#[allow(dead_code, non_camel_case_types, non_snake_case)]\npub enum ErrorBoundaryPropsBuilder_Error_Repeated_field_handle_error {}\n#[doc(hidden)]\n#[allow(dead_code, non_camel_case_types, missing_docs)]\nimpl<__children> ErrorBoundaryPropsBuilder<(__children, (ErrorHandler,))> {\n    #[deprecated(note = \"Repeated field handle_error\")]\n    pub fn handle_error(\n        self,\n        _: ErrorBoundaryPropsBuilder_Error_Repeated_field_handle_error,\n    ) -> ErrorBoundaryPropsBuilder<(__children, (ErrorHandler,))> {\n        self\n    }\n}\n#[allow(dead_code, non_camel_case_types, missing_docs)]\nimpl<\n        __handle_error: ErrorBoundaryPropsBuilder_Optional<ErrorHandler>,\n        __children: ErrorBoundaryPropsBuilder_Optional<Element>,\n    > ErrorBoundaryPropsBuilder<(__children, __handle_error)>\n{\n    pub fn build(self) -> ErrorBoundaryProps {\n        let (children, handle_error) = self.fields;\n        let children = ErrorBoundaryPropsBuilder_Optional::into_value(children, VNode::empty);\n        let handle_error = ErrorBoundaryPropsBuilder_Optional::into_value(handle_error, || {\n            ErrorHandler(Rc::new(default_handler))\n        });\n        ErrorBoundaryProps {\n            children,\n            handle_error,\n        }\n    }\n}\n"
  },
  {
    "path": "packages/core/src/events.rs",
    "content": "use crate::{current_scope_id, properties::SuperFrom, runtime::RuntimeGuard, Runtime, ScopeId};\nuse futures_util::FutureExt;\nuse generational_box::GenerationalBox;\nuse std::{any::Any, cell::RefCell, marker::PhantomData, panic::Location, rc::Rc};\n\n/// A wrapper around some generic data that handles the event's state\n///\n///\n/// Prevent this event from continuing to bubble up the tree to parent elements.\n///\n/// # Example\n///\n/// ```rust, no_run\n/// # use dioxus::prelude::*;\n/// rsx! {\n///     button {\n///         onclick: move |evt: Event<MouseData>| {\n///             evt.stop_propagation();\n///         }\n///     }\n/// };\n/// ```\npub struct Event<T: 'static + ?Sized> {\n    /// The data associated with this event\n    pub data: Rc<T>,\n    pub(crate) metadata: Rc<RefCell<EventMetadata>>,\n}\n\n#[derive(Clone, Copy)]\npub(crate) struct EventMetadata {\n    pub(crate) propagates: bool,\n    pub(crate) prevent_default: bool,\n}\n\nimpl<T: ?Sized + 'static> Event<T> {\n    /// Create a new event from the inner data\n    pub fn new(data: Rc<T>, propagates: bool) -> Self {\n        Self {\n            data,\n            metadata: Rc::new(RefCell::new(EventMetadata {\n                propagates,\n                prevent_default: false,\n            })),\n        }\n    }\n}\n\nimpl<T: ?Sized> Event<T> {\n    /// Map the event data to a new type\n    ///\n    /// # Example\n    ///\n    /// ```rust, no_run\n    /// # use dioxus::prelude::*;\n    /// rsx! {\n    ///    button {\n    ///       onclick: move |evt: MouseEvent| {\n    ///          let data = evt.map(|data| data.client_coordinates());\n    ///          println!(\"{:?}\", data.data());\n    ///       }\n    ///    }\n    /// };\n    /// ```\n    pub fn map<U: 'static, F: FnOnce(&T) -> U>(&self, f: F) -> Event<U> {\n        Event {\n            data: Rc::new(f(&self.data)),\n            metadata: self.metadata.clone(),\n        }\n    }\n\n    /// Convert this event into a boxed event with a dynamic type\n    pub fn into_any(self) -> Event<dyn Any>\n    where\n        T: Sized,\n    {\n        Event {\n            data: self.data as Rc<dyn Any>,\n            metadata: self.metadata,\n        }\n    }\n\n    /// Prevent this event from continuing to bubble up the tree to parent elements.\n    ///\n    /// # Example\n    ///\n    /// ```rust, no_run\n    /// # use dioxus::prelude::*;\n    /// rsx! {\n    ///     button {\n    ///         onclick: move |evt: Event<MouseData>| {\n    ///             # #[allow(deprecated)]\n    ///             evt.cancel_bubble();\n    ///         }\n    ///     }\n    /// };\n    /// ```\n    #[deprecated = \"use stop_propagation instead\"]\n    pub fn cancel_bubble(&self) {\n        self.metadata.borrow_mut().propagates = false;\n    }\n\n    /// Check if the event propagates up the tree to parent elements\n    pub fn propagates(&self) -> bool {\n        self.metadata.borrow().propagates\n    }\n\n    /// Prevent this event from continuing to bubble up the tree to parent elements.\n    ///\n    /// # Example\n    ///\n    /// ```rust, no_run\n    /// # use dioxus::prelude::*;\n    /// rsx! {\n    ///     button {\n    ///         onclick: move |evt: Event<MouseData>| {\n    ///             evt.stop_propagation();\n    ///         }\n    ///     }\n    /// };\n    /// ```\n    pub fn stop_propagation(&self) {\n        self.metadata.borrow_mut().propagates = false;\n    }\n\n    /// Get a reference to the inner data from this event\n    ///\n    /// ```rust, no_run\n    /// # use dioxus::prelude::*;\n    /// rsx! {\n    ///     button {\n    ///         onclick: move |evt: Event<MouseData>| {\n    ///             let data = evt.data();\n    ///             async move {\n    ///                 println!(\"{:?}\", data);\n    ///             }\n    ///         }\n    ///     }\n    /// };\n    /// ```\n    pub fn data(&self) -> Rc<T> {\n        self.data.clone()\n    }\n\n    /// Prevent the default action of the event.\n    ///\n    /// # Example\n    ///\n    /// ```rust\n    /// # use dioxus::prelude::*;\n    /// fn App() -> Element {\n    ///     rsx! {\n    ///         a {\n    ///             // You can prevent the default action of the event with `prevent_default`\n    ///             onclick: move |event| {\n    ///                 event.prevent_default();\n    ///             },\n    ///             href: \"https://dioxuslabs.com\",\n    ///             \"don't go to the link\"\n    ///         }\n    ///     }\n    /// }\n    /// ```\n    ///\n    /// Note: This must be called synchronously when handling the event. Calling it after the event has been handled will have no effect.\n    ///\n    /// <div class=\"warning\">\n    ///\n    /// This method is not available on the LiveView renderer because LiveView handles all events over a websocket which cannot block.\n    ///\n    /// </div>\n    #[track_caller]\n    pub fn prevent_default(&self) {\n        self.metadata.borrow_mut().prevent_default = true;\n    }\n\n    /// Check if the default action of the event is enabled.\n    pub fn default_action_enabled(&self) -> bool {\n        !self.metadata.borrow().prevent_default\n    }\n}\n\nimpl<T: ?Sized> Clone for Event<T> {\n    fn clone(&self) -> Self {\n        Self {\n            metadata: self.metadata.clone(),\n            data: self.data.clone(),\n        }\n    }\n}\n\nimpl<T> std::ops::Deref for Event<T> {\n    type Target = Rc<T>;\n    fn deref(&self) -> &Self::Target {\n        &self.data\n    }\n}\n\nimpl<T: std::fmt::Debug> std::fmt::Debug for Event<T> {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_struct(\"UiEvent\")\n            .field(\"bubble_state\", &self.propagates())\n            .field(\"prevent_default\", &!self.default_action_enabled())\n            .field(\"data\", &self.data)\n            .finish()\n    }\n}\n\n/// The callback type generated by the `rsx!` macro when an `on` field is specified for components.\n///\n/// This makes it possible to pass `move |evt| {}` style closures into components as property fields.\n///\n/// # Example\n///\n/// ```rust, no_run\n/// # use dioxus::prelude::*;\n/// rsx! {\n///     MyComponent { onclick: move |evt| tracing::debug!(\"clicked\") }\n/// };\n///\n/// #[derive(Props, Clone, PartialEq)]\n/// struct MyProps {\n///     onclick: EventHandler<MouseEvent>,\n/// }\n///\n/// fn MyComponent(cx: MyProps) -> Element {\n///     rsx! {\n///         button {\n///             onclick: move |evt| cx.onclick.call(evt),\n///         }\n///     }\n/// }\n/// ```\npub type EventHandler<T = ()> = Callback<T>;\n\n/// The callback type generated by the `rsx!` macro when an `on` field is specified for components.\n///\n/// This makes it possible to pass `move |evt| {}` style closures into components as property fields.\n///\n///\n/// # Example\n///\n/// ```rust, ignore\n/// rsx! {\n///     MyComponent { onclick: move |evt| {\n///         tracing::debug!(\"clicked\");\n///         42\n///     } }\n/// }\n///\n/// #[derive(Props)]\n/// struct MyProps {\n///     onclick: Callback<MouseEvent, i32>,\n/// }\n///\n/// fn MyComponent(cx: MyProps) -> Element {\n///     rsx! {\n///         button {\n///             onclick: move |evt| println!(\"number: {}\", cx.onclick.call(evt)),\n///         }\n///     }\n/// }\n/// ```\npub struct Callback<Args = (), Ret = ()> {\n    pub(crate) origin: ScopeId,\n    /// During diffing components with EventHandler, we move the EventHandler over in place instead of rerunning the child component.\n    ///\n    /// ```rust\n    /// # use dioxus::prelude::*;\n    /// #[component]\n    /// fn Child(onclick: EventHandler<MouseEvent>) -> Element {\n    ///     rsx! {\n    ///         button {\n    ///             // Diffing Child will not rerun this component, it will just update the callback in place so that if this callback is called, it will run the latest version of the callback\n    ///             onclick: move |evt| onclick(evt),\n    ///         }\n    ///     }\n    /// }\n    /// ```\n    ///\n    /// This is both more efficient and allows us to avoid out of date EventHandlers.\n    ///\n    /// We double box here because we want the data to be copy (GenerationalBox) and still update in place (ExternalListenerCallback)\n    /// This isn't an ideal solution for performance, but it is non-breaking and fixes the issues described in <https://github.com/DioxusLabs/dioxus/pull/2298>\n    pub(super) callback: GenerationalBox<Option<ExternalListenerCallback<Args, Ret>>>,\n}\n\nimpl<Args, Ret> std::fmt::Debug for Callback<Args, Ret> {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_struct(\"Callback\")\n            .field(\"origin\", &self.origin)\n            .field(\"callback\", &self.callback)\n            .finish()\n    }\n}\n\nimpl<T: 'static, Ret: Default + 'static> Default for Callback<T, Ret> {\n    fn default() -> Self {\n        Callback::new(|_| Ret::default())\n    }\n}\n\n/// A helper trait for [`Callback`]s that allows functions to accept a [`Callback`] that may return an async block which will automatically be spawned.\n///\n/// ```rust, no_run\n/// use dioxus::prelude::*;\n/// fn accepts_fn<Ret: dioxus_core::SpawnIfAsync<Marker>, Marker>(callback: impl FnMut(u32) -> Ret + 'static) {\n///     let callback = Callback::new(callback);\n/// }\n/// // You can accept both async and non-async functions\n/// accepts_fn(|x| async move { println!(\"{}\", x) });\n/// accepts_fn(|x| println!(\"{}\", x));\n/// ```\n#[rustversion::attr(\n    since(1.78.0),\n    diagnostic::on_unimplemented(\n        message = \"`SpawnIfAsync` is not implemented for `{Self}`\",\n        label = \"Return Value\",\n        note = \"Closures (or event handlers) in dioxus need to return either: nothing (the unit type `()`), or an async block that dioxus will automatically spawn\",\n        note = \"You likely need to add a semicolon to the end of the event handler to make it return nothing\",\n    )\n)]\npub trait SpawnIfAsync<Marker, Ret = ()>: Sized {\n    /// Spawn the value into the dioxus runtime if it is an async block\n    fn spawn(self) -> Ret;\n}\n\n// Support for FnMut -> Ret for any return type\nimpl<Ret> SpawnIfAsync<(), Ret> for Ret {\n    fn spawn(self) -> Ret {\n        self\n    }\n}\n\n// Support for FnMut -> async { unit } for the unit return type\n#[doc(hidden)]\npub struct AsyncMarker;\nimpl<F: std::future::Future<Output = ()> + 'static> SpawnIfAsync<AsyncMarker> for F {\n    fn spawn(self) {\n        // Quick poll once to deal with things like prevent_default in the same tick\n        let mut fut = Box::pin(self);\n        let res = fut.as_mut().now_or_never();\n\n        if res.is_none() {\n            crate::spawn(async move {\n                fut.await;\n            });\n        }\n    }\n}\n\n// Support for FnMut -> async { Result(()) } for the unit return type\n#[doc(hidden)]\npub struct AsyncResultMarker;\n\nimpl<T> SpawnIfAsync<AsyncResultMarker> for T\nwhere\n    T: std::future::Future<Output = crate::Result<()>> + 'static,\n{\n    #[inline]\n    fn spawn(self) {\n        // Quick poll once to deal with things like prevent_default in the same tick\n        let mut fut = Box::pin(self);\n        let res = fut.as_mut().now_or_never();\n\n        if res.is_none() {\n            crate::spawn(async move {\n                if let Err(err) = fut.await {\n                    crate::throw_error(err)\n                }\n            });\n        }\n    }\n}\n\n// Support for FnMut -> Result(()) for the unit return type\nimpl SpawnIfAsync<()> for crate::Result<()> {\n    #[inline]\n    fn spawn(self) {\n        if let Err(err) = self {\n            crate::throw_error(err)\n        }\n    }\n}\n\n// We can't directly forward the marker because it would overlap with a bunch of other impls, so we wrap it in another type instead\n#[doc(hidden)]\npub struct MarkerWrapper<T>(PhantomData<T>);\n\n// Closure can be created from FnMut -> async { anything } or FnMut -> Ret\nimpl<\n        Function: FnMut(Args) -> Spawn + 'static,\n        Args: 'static,\n        Spawn: SpawnIfAsync<Marker, Ret> + 'static,\n        Ret: 'static,\n        Marker,\n    > SuperFrom<Function, MarkerWrapper<Marker>> for Callback<Args, Ret>\n{\n    fn super_from(input: Function) -> Self {\n        Callback::new(input)\n    }\n}\n\nimpl<\n        Function: FnMut(Event<T>) -> Spawn + 'static,\n        T: 'static,\n        Spawn: SpawnIfAsync<Marker> + 'static,\n        Marker,\n    > SuperFrom<Function, MarkerWrapper<Marker>> for ListenerCallback<T>\n{\n    fn super_from(input: Function) -> Self {\n        ListenerCallback::new(input)\n    }\n}\n\n// ListenerCallback<T> can be created from Callback<Event<T>>\nimpl<T: 'static> SuperFrom<Callback<Event<T>>> for ListenerCallback<T> {\n    fn super_from(input: Callback<Event<T>>) -> Self {\n        // https://github.com/rust-lang/rust-clippy/issues/15072\n        #[allow(clippy::redundant_closure)]\n        ListenerCallback::new(move |event| input(event))\n    }\n}\n\n#[doc(hidden)]\npub struct UnitClosure<Marker>(PhantomData<Marker>);\n\n// Closure can be created from FnMut -> async { () } or FnMut -> Ret\nimpl<\n        Function: FnMut() -> Spawn + 'static,\n        Spawn: SpawnIfAsync<Marker, Ret> + 'static,\n        Ret: 'static,\n        Marker,\n    > SuperFrom<Function, UnitClosure<Marker>> for Callback<(), Ret>\n{\n    fn super_from(mut input: Function) -> Self {\n        Callback::new(move |()| input())\n    }\n}\n\n#[test]\nfn closure_types_infer() {\n    #[allow(unused)]\n    fn compile_checks() {\n        // You should be able to use a closure as a callback\n        let callback: Callback<(), ()> = Callback::new(|_| {});\n        // Or an async closure\n        let callback: Callback<(), ()> = Callback::new(|_| async {});\n\n        // You can also pass in a closure that returns a value\n        let callback: Callback<(), u32> = Callback::new(|_| 123);\n\n        // Or pass in a value\n        let callback: Callback<u32, ()> = Callback::new(|value: u32| async move {\n            println!(\"{}\", value);\n        });\n\n        // Unit closures shouldn't require an argument\n        let callback: Callback<(), ()> = Callback::super_from(|| async move {\n            println!(\"hello world\");\n        });\n    }\n}\n\nimpl<Args, Ret> Copy for Callback<Args, Ret> {}\n\nimpl<Args, Ret> Clone for Callback<Args, Ret> {\n    fn clone(&self) -> Self {\n        *self\n    }\n}\n\nimpl<Args: 'static, Ret: 'static> PartialEq for Callback<Args, Ret> {\n    fn eq(&self, other: &Self) -> bool {\n        self.callback.ptr_eq(&other.callback) && self.origin == other.origin\n    }\n}\n\npub(super) struct ExternalListenerCallback<Args, Ret> {\n    callback: Box<dyn FnMut(Args) -> Ret>,\n    runtime: std::rc::Weak<Runtime>,\n}\n\nimpl<Args: 'static, Ret: 'static> Callback<Args, Ret> {\n    /// Create a new [`Callback`] from an [`FnMut`]. The callback is owned by the current scope and will be dropped when the scope is dropped.\n    /// This should not be called directly in the body of a component because it will not be dropped until the component is dropped.\n    #[track_caller]\n    pub fn new<MaybeAsync: SpawnIfAsync<Marker, Ret>, Marker>(\n        mut f: impl FnMut(Args) -> MaybeAsync + 'static,\n    ) -> Self {\n        let runtime = Runtime::current();\n        let origin = runtime.current_scope_id();\n        let owner = crate::innerlude::current_owner::<generational_box::UnsyncStorage>();\n        let callback = owner.insert_rc(Some(ExternalListenerCallback {\n            callback: Box::new(move |event: Args| f(event).spawn()),\n            runtime: Rc::downgrade(&runtime),\n        }));\n        Self { callback, origin }\n    }\n\n    /// Leak a new [`Callback`] that will not be dropped unless it is manually dropped.\n    #[track_caller]\n    pub fn leak(mut f: impl FnMut(Args) -> Ret + 'static) -> Self {\n        let runtime = Runtime::current();\n        let origin = runtime.current_scope_id();\n        let callback = GenerationalBox::leak_rc(\n            Some(ExternalListenerCallback {\n                callback: Box::new(move |event: Args| f(event).spawn()),\n                runtime: Rc::downgrade(&runtime),\n            }),\n            Location::caller(),\n        );\n        Self { callback, origin }\n    }\n\n    /// Call this callback with the appropriate argument type\n    ///\n    /// This borrows the callback using a RefCell. Recursively calling a callback will cause a panic.\n    #[track_caller]\n    pub fn call(&self, arguments: Args) -> Ret {\n        if let Some(callback) = self.callback.write().as_mut() {\n            let runtime = callback\n                .runtime\n                .upgrade()\n                .expect(\"Callback was called after the runtime was dropped\");\n            let _guard = RuntimeGuard::new(runtime.clone());\n            runtime.with_scope_on_stack(self.origin, || (callback.callback)(arguments))\n        } else {\n            panic!(\"Callback was manually dropped\")\n        }\n    }\n\n    /// Create a `impl FnMut + Copy` closure from the Closure type\n    pub fn into_closure(self) -> impl FnMut(Args) -> Ret + Copy + 'static {\n        move |args| self.call(args)\n    }\n\n    /// Forcibly drop the internal handler callback, releasing memory\n    ///\n    /// This will force any future calls to \"call\" to not doing anything\n    pub fn release(&self) {\n        self.callback.set(None);\n    }\n\n    /// Replace the function in the callback with a new one\n    pub fn replace(&mut self, callback: Box<dyn FnMut(Args) -> Ret>) {\n        let runtime = Runtime::current();\n        self.callback.set(Some(ExternalListenerCallback {\n            callback,\n            runtime: Rc::downgrade(&runtime),\n        }));\n    }\n\n    #[doc(hidden)]\n    /// This should only be used by the `rsx!` macro.\n    pub fn __point_to(&mut self, other: &Self) {\n        self.callback.point_to(other.callback).unwrap();\n    }\n}\n\nimpl<Args: 'static, Ret: 'static> std::ops::Deref for Callback<Args, Ret> {\n    type Target = dyn Fn(Args) -> Ret + 'static;\n\n    fn deref(&self) -> &Self::Target {\n        // https://github.com/dtolnay/case-studies/tree/master/callable-types\n\n        // First we create a closure that captures something with the Same in memory layout as Self (MaybeUninit<Self>).\n        let uninit_callable = std::mem::MaybeUninit::<Self>::uninit();\n        // Then move that value into the closure. We assume that the closure now has a in memory layout of Self.\n        let uninit_closure = move |t| Self::call(unsafe { &*uninit_callable.as_ptr() }, t);\n\n        // Check that the size of the closure is the same as the size of Self in case the compiler changed the layout of the closure.\n        let size_of_closure = std::mem::size_of_val(&uninit_closure);\n        assert_eq!(size_of_closure, std::mem::size_of::<Self>());\n\n        // Then cast the lifetime of the closure to the lifetime of &self.\n        fn cast_lifetime<'a, T>(_a: &T, b: &'a T) -> &'a T {\n            b\n        }\n        let reference_to_closure = cast_lifetime(\n            {\n                // The real closure that we will never use.\n                &uninit_closure\n            },\n            #[allow(clippy::missing_transmute_annotations)]\n            // We transmute self into a reference to the closure. This is safe because we know that the closure has the same memory layout as Self so &Closure == &Self.\n            unsafe {\n                std::mem::transmute(self)\n            },\n        );\n\n        // Cast the closure to a trait object.\n        reference_to_closure as &_\n    }\n}\n\ntype AnyEventHandler = Rc<RefCell<dyn FnMut(Event<dyn Any>)>>;\n\n/// An owned callback type used in [`AttributeValue::Listener`](crate::AttributeValue::Listener).\n///\n/// This is the type that powers the `on` attributes in the `rsx!` macro, allowing you to pass event\n/// handlers to elements.\n///\n/// ```rust, ignore\n/// rsx! {\n///     button {\n///         onclick: AttributeValue::Listener(ListenerCallback::new(move |evt: Event<MouseData>| {\n///             // ...\n///         }))\n///     }\n/// }\n/// ```\npub struct ListenerCallback<T = ()> {\n    pub(crate) origin: ScopeId,\n    callback: AnyEventHandler,\n    _marker: PhantomData<T>,\n}\n\nimpl<T> Clone for ListenerCallback<T> {\n    fn clone(&self) -> Self {\n        Self {\n            origin: self.origin,\n            callback: self.callback.clone(),\n            _marker: PhantomData,\n        }\n    }\n}\n\nimpl<T> PartialEq for ListenerCallback<T> {\n    fn eq(&self, other: &Self) -> bool {\n        // We compare the pointers of the callbacks, since they are unique\n        Rc::ptr_eq(&self.callback, &other.callback) && self.origin == other.origin\n    }\n}\n\nimpl<T> ListenerCallback<T> {\n    /// Create a new [`ListenerCallback`] from a callback\n    ///\n    /// This is expected to be called within a runtime scope. Make sure a runtime is current before\n    /// calling this method.\n    pub fn new<MaybeAsync, Marker>(mut f: impl FnMut(Event<T>) -> MaybeAsync + 'static) -> Self\n    where\n        T: 'static,\n        MaybeAsync: SpawnIfAsync<Marker>,\n    {\n        Self {\n            origin: current_scope_id(),\n            callback: Rc::new(RefCell::new(move |event: Event<dyn Any>| {\n                let data = event.data.downcast::<T>().unwrap();\n                f(Event {\n                    metadata: event.metadata.clone(),\n                    data,\n                })\n                .spawn();\n            })),\n            _marker: PhantomData,\n        }\n    }\n\n    /// Call the callback with an event\n    ///\n    /// This is expected to be called within a runtime scope. Make sure a runtime is current before\n    /// calling this method.\n    pub fn call(&self, event: Event<dyn Any>) {\n        Runtime::current().with_scope_on_stack(self.origin, || {\n            (self.callback.borrow_mut())(event);\n        });\n    }\n\n    /// Erase the type of the callback, allowing it to be used with any type of event\n    pub fn erase(self) -> ListenerCallback {\n        ListenerCallback {\n            origin: self.origin,\n            callback: self.callback,\n            _marker: PhantomData,\n        }\n    }\n}\n"
  },
  {
    "path": "packages/core/src/fragment.rs",
    "content": "use crate::innerlude::*;\n\n/// Create inline fragments using Component syntax.\n///\n/// ## Details\n///\n/// Fragments capture a series of children without rendering extra nodes.\n///\n/// Creating fragments explicitly with the Fragment component is particularly useful when rendering lists or tables and\n/// a key is needed to identify each item.\n///\n/// ## Example\n///\n/// ```rust\n/// # use dioxus::prelude::*;\n/// let value = 1;\n/// rsx! {\n///     Fragment { key: \"{value}\" }\n/// };\n/// ```\n///\n/// ## Usage\n///\n/// Fragments are incredibly useful when necessary, but *do* add cost in the diffing phase.\n/// Try to avoid highly nested fragments if you can. Unlike React, there is no protection against infinitely nested fragments.\n///\n/// This function defines a dedicated `Fragment` component that can be used to create inline fragments in the RSX macro.\n///\n/// You want to use this free-function when your fragment needs a key and simply returning multiple nodes from rsx! won't cut it.\n#[allow(non_upper_case_globals, non_snake_case)]\npub fn Fragment(cx: FragmentProps) -> Element {\n    cx.0\n}\n\n#[derive(Clone, PartialEq)]\npub struct FragmentProps(pub(crate) Element);\n\npub struct FragmentBuilder<const BUILT: bool>(Element);\nimpl FragmentBuilder<false> {\n    pub fn children(self, children: Element) -> FragmentBuilder<true> {\n        FragmentBuilder(children)\n    }\n}\nimpl<const A: bool> FragmentBuilder<A> {\n    pub fn build(self) -> FragmentProps {\n        FragmentProps(self.0)\n    }\n}\n\n/// Access the children elements passed into the component\n///\n/// This enables patterns where a component is passed children from its parent.\n///\n/// ## Details\n///\n/// Unlike React, Dioxus allows *only* lists of children to be passed from parent to child - not arbitrary functions\n/// or classes. If you want to generate nodes instead of accepting them as a list, consider declaring a closure\n/// on the props that takes Context.\n///\n/// If a parent passes children into a component, the child will always re-render when the parent re-renders. In other\n/// words, a component cannot be automatically memoized if it borrows nodes from its parent, even if the component's\n/// props are valid for the static lifetime.\n///\n/// ## Example\n///\n/// ```rust\n/// # use dioxus::prelude::*;\n/// fn app() -> Element {\n///     rsx! {\n///         CustomCard {\n///             h1 {}\n///             p {}\n///         }\n///     }\n/// }\n///\n/// #[component]\n/// fn CustomCard(children: Element) -> Element {\n///     rsx! {\n///         div {\n///             h1 {\"Title card\"}\n///             {children}\n///         }\n///     }\n/// }\n/// ```\nimpl Properties for FragmentProps {\n    type Builder = FragmentBuilder<false>;\n    fn builder() -> Self::Builder {\n        FragmentBuilder(VNode::empty())\n    }\n    fn memoize(&mut self, new: &Self) -> bool {\n        let equal = self == new;\n        if !equal {\n            let new_clone = new.clone();\n            self.0 = new_clone.0;\n        }\n        equal\n    }\n}\n"
  },
  {
    "path": "packages/core/src/generational_box.rs",
    "content": "//! Integration with the generational-box crate for copy state management.\n//!\n//! Each scope in dioxus has a single [Owner]\n\nuse generational_box::{AnyStorage, Owner, SyncStorage, UnsyncStorage};\nuse std::{\n    any::{Any, TypeId},\n    cell::RefCell,\n};\n\n/// Run a closure with the given owner.\n///\n/// This will override the default owner for the current component.\npub fn with_owner<S: AnyStorage, F: FnOnce() -> R, R>(owner: Owner<S>, f: F) -> R {\n    let old_owner = set_owner(Some(owner));\n    let result = f();\n    set_owner(old_owner);\n    result\n}\n\n/// Set the owner for the current thread.\nfn set_owner<S: AnyStorage>(owner: Option<Owner<S>>) -> Option<Owner<S>> {\n    let id = TypeId::of::<S>();\n    if id == TypeId::of::<SyncStorage>() {\n        SYNC_OWNER.with(|cell| {\n            std::mem::replace(\n                &mut *cell.borrow_mut(),\n                owner.map(|owner| {\n                    *(Box::new(owner) as Box<dyn Any>)\n                        .downcast::<Owner<SyncStorage>>()\n                        .unwrap()\n                }),\n            )\n            .map(|owner| *(Box::new(owner) as Box<dyn Any>).downcast().unwrap())\n        })\n    } else {\n        UNSYNC_OWNER.with(|cell| {\n            std::mem::replace(\n                &mut *cell.borrow_mut(),\n                owner.map(|owner| {\n                    *(Box::new(owner) as Box<dyn Any>)\n                        .downcast::<Owner<UnsyncStorage>>()\n                        .unwrap()\n                }),\n            )\n            .map(|owner| *(Box::new(owner) as Box<dyn Any>).downcast().unwrap())\n        })\n    }\n}\n\nthread_local! {\n    static SYNC_OWNER: RefCell<Option<Owner<SyncStorage>>> = const { RefCell::new(None) };\n    static UNSYNC_OWNER: RefCell<Option<Owner<UnsyncStorage>>> = const { RefCell::new(None) };\n}\n\n/// Returns the current owner. This owner will be used to drop any `Copy` state that is created by the `generational-box` crate.\n///\n/// If an owner has been set with `with_owner`, that owner will be returned. Otherwise, the owner from the current scope will be returned.\npub fn current_owner<S: AnyStorage>() -> Owner<S> {\n    let id = TypeId::of::<S>();\n    let override_owner = if id == TypeId::of::<SyncStorage>() {\n        SYNC_OWNER.with(|cell| {\n            let owner = cell.borrow();\n\n            owner.clone().map(|owner| {\n                *(Box::new(owner) as Box<dyn Any>)\n                    .downcast::<Owner<S>>()\n                    .unwrap()\n            })\n        })\n    } else {\n        UNSYNC_OWNER.with(|cell| {\n            cell.borrow().clone().map(|owner| {\n                *(Box::new(owner) as Box<dyn Any>)\n                    .downcast::<Owner<S>>()\n                    .unwrap()\n            })\n        })\n    };\n    if let Some(owner) = override_owner {\n        return owner;\n    }\n\n    crate::Runtime::current().current_owner()\n}\n"
  },
  {
    "path": "packages/core/src/global_context.rs",
    "content": "use crate::innerlude::CapturedError;\nuse crate::{innerlude::SuspendedFuture, runtime::Runtime, Element, ScopeId, Task};\nuse std::future::Future;\nuse std::rc::Rc;\nuse std::sync::Arc;\n\n/// Get the current scope id\npub fn current_scope_id() -> ScopeId {\n    Runtime::with(|rt| rt.current_scope_id())\n}\n\n/// Throw a [`CapturedError`] into the current scope. The error will bubble up to the nearest [`crate::ErrorBoundary()`] or the root of the app.\n///\n/// # Examples\n/// ```rust, no_run\n/// # use dioxus::prelude::*;\n/// fn Component() -> Element {\n///     let request = spawn(async move {\n///         match reqwest::get(\"https://api.example.com\").await {\n///             Ok(_) => unimplemented!(),\n///             // You can explicitly throw an error into a scope with throw_error\n///             Err(err) => dioxus::core::throw_error(err),\n///         }\n///     });\n///\n///     unimplemented!()\n/// }\n/// ```\npub fn throw_error(error: impl Into<CapturedError> + 'static) {\n    Runtime::with(|rt| rt.throw_error(rt.current_scope_id(), error))\n}\n\n/// Consume context from the current scope\npub fn try_consume_context<T: 'static + Clone>() -> Option<T> {\n    Runtime::with_current_scope(|cx| cx.consume_context::<T>())\n}\n\n/// Consume context from the current scope\npub fn consume_context<T: 'static + Clone>() -> T {\n    Runtime::with_current_scope(|cx| cx.consume_context::<T>())\n        .unwrap_or_else(|| panic!(\"Could not find context {}\", std::any::type_name::<T>()))\n}\n\n/// Consume context from the current scope\npub fn consume_context_from_scope<T: 'static + Clone>(scope_id: ScopeId) -> Option<T> {\n    Runtime::current()\n        .try_get_state(scope_id)\n        .and_then(|cx| cx.consume_context::<T>())\n}\n\n/// Check if the current scope has a context\npub fn has_context<T: 'static + Clone>() -> Option<T> {\n    Runtime::with_current_scope(|cx| cx.has_context::<T>())\n}\n\n/// Provide context to the current scope\npub fn provide_context<T: 'static + Clone>(value: T) -> T {\n    Runtime::with_current_scope(|cx| cx.provide_context(value))\n}\n\n/// Provide a context to the root scope\npub fn provide_root_context<T: 'static + Clone>(value: T) -> T {\n    Runtime::with_current_scope(|cx| cx.provide_root_context(value))\n}\n\n/// Suspended the current component on a specific task and then return None\npub fn suspend(task: Task) -> Element {\n    Err(crate::innerlude::RenderError::Suspended(\n        SuspendedFuture::new(task),\n    ))\n}\n\n/// Start a new future on the same thread as the rest of the VirtualDom.\n///\n/// **You should generally use `spawn` instead of this method unless you specifically need to run a task during suspense**\n///\n/// This future will not contribute to suspense resolving but it will run during suspense.\n///\n/// Because this future runs during suspense, you need to be careful to work with hydration. It is not recommended to do any async IO work in this future, as it can easily cause hydration issues. However, you can use isomorphic tasks to do work that can be consistently replicated on the server and client like logging or responding to state changes.\n///\n/// ```rust, no_run\n/// # use dioxus::prelude::*;\n/// # use dioxus_core::spawn_isomorphic;\n/// // ❌ Do not do requests in isomorphic tasks. It may resolve at a different time on the server and client, causing hydration issues.\n/// let mut state = use_signal(|| None);\n/// spawn_isomorphic(async move {\n///     state.set(Some(reqwest::get(\"https://api.example.com\").await));\n/// });\n///\n/// // ✅ You may wait for a signal to change and then log it\n/// let mut state = use_signal(|| 0);\n/// spawn_isomorphic(async move {\n///     loop {\n///         tokio::time::sleep(std::time::Duration::from_secs(1)).await;\n///         println!(\"State is {state}\");\n///     }\n/// });\n/// ```\n///\n#[doc = include_str!(\"../docs/common_spawn_errors.md\")]\npub fn spawn_isomorphic(fut: impl Future<Output = ()> + 'static) -> Task {\n    Runtime::with_current_scope(|cx| cx.spawn_isomorphic(fut))\n}\n\n/// Spawns the future and returns the [`Task`]. This task will automatically be canceled when the component is dropped.\n///\n/// # Example\n/// ```rust\n/// use dioxus::prelude::*;\n///\n/// fn App() -> Element {\n///     rsx! {\n///         button {\n///             onclick: move |_| {\n///                 spawn(async move {\n///                     tokio::time::sleep(std::time::Duration::from_secs(1)).await;\n///                     println!(\"Hello World\");\n///                 });\n///             },\n///             \"Print hello in one second\"\n///         }\n///     }\n/// }\n/// ```\n///\n#[doc = include_str!(\"../docs/common_spawn_errors.md\")]\npub fn spawn(fut: impl Future<Output = ()> + 'static) -> Task {\n    Runtime::with_current_scope(|cx| cx.spawn(fut))\n}\n\n/// Queue an effect to run after the next render. You generally shouldn't need to interact with this function directly. [use_effect](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_effect.html) will call this function for you.\npub fn queue_effect(f: impl FnOnce() + 'static) {\n    Runtime::with_current_scope(|cx| cx.queue_effect(f))\n}\n\n/// Spawn a future that Dioxus won't clean up when this component is unmounted\n///\n/// This is good for tasks that need to be run after the component has been dropped.\n///\n/// **This will run the task in the root scope. Any calls to global methods inside the future (including `context`) will be run in the root scope.**\n///\n/// # Example\n///\n/// ```rust\n/// use dioxus::prelude::*;\n/// use dioxus_core::spawn_forever;\n///\n/// // The parent component can create and destroy children dynamically\n/// fn App() -> Element {\n///     let mut count = use_signal(|| 0);\n///\n///     rsx! {\n///         button {\n///             onclick: move |_| count += 1,\n///             \"Increment\"\n///         }\n///         button {\n///             onclick: move |_| count -= 1,\n///             \"Decrement\"\n///         }\n///\n///         for id in 0..10 {\n///             Child { id }\n///         }\n///     }\n/// }\n///\n/// #[component]\n/// fn Child(id: i32) -> Element {\n///     rsx! {\n///         button {\n///             onclick: move |_| {\n///                 // This will spawn a task in the root scope that will run forever\n///                 // It will keep running even if you drop the child component by decreasing the count\n///                 spawn_forever(async move {\n///                     loop {\n///                         tokio::time::sleep(std::time::Duration::from_secs(1)).await;\n///                         println!(\"Running task spawned in child component {id}\");\n///                     }\n///                 });\n///             },\n///             \"Spawn background task\"\n///         }\n///     }\n/// }\n/// ```\n///\n#[doc = include_str!(\"../docs/common_spawn_errors.md\")]\npub fn spawn_forever(fut: impl Future<Output = ()> + 'static) -> Task {\n    Runtime::with_scope(ScopeId::ROOT, |cx| cx.spawn(fut))\n}\n\n/// Informs the scheduler that this task is no longer needed and should be removed.\n///\n/// This drops the task immediately.\npub fn remove_future(id: Task) {\n    Runtime::with(|rt| rt.remove_task(id));\n}\n\n/// Store a value between renders. The foundational hook for all other hooks.\n///\n/// Accepts an `initializer` closure, which is run on the first use of the hook (typically the initial render).\n/// `use_hook` will return a clone of the value on every render.\n///\n/// In order to clean up resources you would need to implement the [`Drop`] trait for an inner value stored in a RC or similar (Signals for instance),\n/// as these only drop their inner value once all references have been dropped, which only happens when the component is dropped.\n///\n/// <div class=\"warning\">\n///\n/// `use_hook` is not reactive. It just returns the value on every render. If you need state that will track changes, use [`use_signal`](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_signal.html) instead.\n///\n/// ❌ Don't use `use_hook` with `Rc<RefCell<T>>` for state. It will not update the UI and other hooks when the state changes.\n/// ```rust\n/// use dioxus::prelude::*;\n/// use std::rc::Rc;\n/// use std::cell::RefCell;\n///\n/// pub fn Comp() -> Element {\n///     let count = use_hook(|| Rc::new(RefCell::new(0)));\n///\n///     rsx! {\n///         button {\n///             onclick: move |_| *count.borrow_mut() += 1,\n///             \"{count.borrow()}\"\n///         }\n///     }\n/// }\n/// ```\n///\n/// ✅ Use `use_signal` instead.\n/// ```rust\n/// use dioxus::prelude::*;\n///\n/// pub fn Comp() -> Element {\n///     let mut count = use_signal(|| 0);\n///\n///     rsx! {\n///         button {\n///             onclick: move |_| count += 1,\n///             \"{count}\"\n///         }\n///     }\n/// }\n/// ```\n///\n/// </div>\n///\n/// # Example\n///\n/// ```rust, no_run\n/// use dioxus::prelude::*;\n///\n/// // prints a greeting on the initial render\n/// pub fn use_hello_world() {\n///     use_hook(|| println!(\"Hello, world!\"));\n/// }\n/// ```\n///\n/// # Custom Hook Example\n///\n/// ```rust, no_run\n/// use dioxus::prelude::*;\n///\n/// pub struct InnerCustomState(usize);\n///\n/// impl Drop for InnerCustomState {\n///     fn drop(&mut self){\n///         println!(\"Component has been dropped.\");\n///     }\n/// }\n///\n/// #[derive(Clone, Copy)]\n/// pub struct CustomState {\n///     inner: Signal<InnerCustomState>\n/// }\n///\n/// pub fn use_custom_state() -> CustomState {\n///     use_hook(|| CustomState {\n///         inner: Signal::new(InnerCustomState(0))\n///     })\n/// }\n/// ```\n#[track_caller]\npub fn use_hook<State: Clone + 'static>(initializer: impl FnOnce() -> State) -> State {\n    Runtime::with_current_scope(|cx| cx.use_hook(initializer))\n}\n\n/// Get the current render since the inception of this component.\n///\n/// This can be used as a helpful diagnostic when debugging hooks/renders, etc.\npub fn generation() -> usize {\n    Runtime::with_current_scope(|cx| cx.generation())\n}\n\n/// Get the parent of the current scope if it exists.\npub fn parent_scope() -> Option<ScopeId> {\n    Runtime::with_current_scope(|cx| cx.parent_id())\n}\n\n/// Mark the current scope as dirty, causing it to re-render.\npub fn needs_update() {\n    Runtime::with_current_scope(|cx| cx.needs_update());\n}\n\n/// Mark the current scope as dirty, causing it to re-render.\npub fn needs_update_any(id: ScopeId) {\n    Runtime::with_current_scope(|cx| cx.needs_update_any(id));\n}\n\n/// Schedule an update for the current component.\n///\n/// Note: Unlike [`needs_update`], the function returned by this method will work outside of the dioxus runtime.\n///\n/// Note: The function returned by this method will schedule an update for the current component even if it has already updated between when `schedule_update` was called and when the returned function is called.\n/// If the desired behavior is to invalidate the current rendering of the current component (and no-op if already invalidated)\n/// [`subscribe`](crate::reactive_context::ReactiveContext::subscribe) to the [`current`](crate::reactive_context::ReactiveContext::current) [`ReactiveContext`](crate::reactive_context::ReactiveContext) instead.\n///\n/// You should prefer [`schedule_update_any`] if you need to update multiple components.\n#[track_caller]\npub fn schedule_update() -> Arc<dyn Fn() + Send + Sync> {\n    Runtime::with_current_scope(|cx| cx.schedule_update())\n}\n\n/// Schedule an update for any component given its [`ScopeId`].\n///\n/// A component's [`ScopeId`] can be obtained from the [`current_scope_id`] method.\n///\n/// Note: Unlike [`needs_update`], the function returned by this method will work outside of the dioxus runtime.\n///\n/// Note: It does not matter when `schedule_update_any` is called: the returned function will invalidate what ever generation of the specified component is current when returned function is called.\n/// If the desired behavior is to schedule invalidation of the current rendering of a component, use [`ReactiveContext`](crate::reactive_context::ReactiveContext) instead.\n#[track_caller]\npub fn schedule_update_any() -> Arc<dyn Fn(ScopeId) + Send + Sync> {\n    Runtime::with_current_scope(|cx| cx.schedule_update_any())\n}\n\n/// Creates a callback that will be run before the component is removed.\n/// This can be used to clean up side effects from the component\n/// (created with [`use_effect`](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_effect.html)).\n///\n/// Note:\n/// Effects do not run on the server, but use_drop **DOES**. It runs any time the component is dropped including during SSR rendering on the server. If your clean up logic targets web, the logic has to be gated by a feature, see the below example for details.\n///\n/// Example:\n/// ```rust\n/// use dioxus::prelude::*;\n/// use dioxus_core::use_drop;\n///\n/// fn app() -> Element {\n///     let mut state = use_signal(|| true);\n///     rsx! {\n///         for _ in 0..100 {\n///             h1 {\n///                 \"spacer\"\n///             }\n///         }\n///         if state() {\n///             child_component {}\n///         }\n///         button {\n///             onclick: move |_| {\n///                 state.toggle()\n///             },\n///             \"Unmount element\"\n///         }\n///     }\n/// }\n///\n/// fn child_component() -> Element {\n///     let mut original_scroll_position = use_signal(|| 0.0);\n///\n///     use_effect(move || {\n///         let window = web_sys::window().unwrap();\n///         let document = window.document().unwrap();\n///         let element = document.get_element_by_id(\"my_element\").unwrap();\n///         element.scroll_into_view();\n///         original_scroll_position.set(window.scroll_y().unwrap());\n///     });\n///\n///     use_drop(move || {\n///         // This only make sense to web and hence the `web!` macro\n///         web! {\n///             /// restore scroll to the top of the page\n///             let window = web_sys::window().unwrap();\n///             window.scroll_with_x_and_y(original_scroll_position(), 0.0);\n///         }\n///     });\n///\n///     rsx! {\n///         div {\n///             id: \"my_element\",\n///             \"hello\"\n///         }\n///     }\n/// }\n/// ```\n#[doc(alias = \"use_on_unmount\")]\npub fn use_drop<D: FnOnce() + 'static>(destroy: D) {\n    struct LifeCycle<D: FnOnce()> {\n        /// Wrap the closure in an option so that we can take it out on drop.\n        ondestroy: Option<D>,\n    }\n\n    /// On drop, we want to run the closure.\n    impl<D: FnOnce()> Drop for LifeCycle<D> {\n        fn drop(&mut self) {\n            if let Some(f) = self.ondestroy.take() {\n                f();\n            }\n        }\n    }\n\n    use_hook(|| {\n        Rc::new(LifeCycle {\n            ondestroy: Some(destroy),\n        })\n    });\n}\n\n/// A hook that allows you to insert a \"before render\" function.\n///\n/// This function will always be called before dioxus tries to render your component. This should be used for safely handling\n/// early returns\npub fn use_before_render(f: impl FnMut() + 'static) {\n    use_hook(|| Runtime::with_current_scope(|cx| cx.push_before_render(f)));\n}\n\n/// Push this function to be run after the next render\n///\n/// This function will always be called before dioxus tries to render your component. This should be used for safely handling\n/// early returns\npub fn use_after_render(f: impl FnMut() + 'static) {\n    use_hook(|| Runtime::with_current_scope(|cx| cx.push_after_render(f)));\n}\n\n/// Use a hook with a cleanup function\npub fn use_hook_with_cleanup<T: Clone + 'static>(\n    hook: impl FnOnce() -> T,\n    cleanup: impl FnOnce(T) + 'static,\n) -> T {\n    let value = use_hook(hook);\n    let _value = value.clone();\n    use_drop(move || cleanup(_value));\n    value\n}\n"
  },
  {
    "path": "packages/core/src/hotreload_utils.rs",
    "content": "use std::{\n    any::{Any, TypeId},\n    hash::{Hash, Hasher},\n};\n\n#[cfg(feature = \"serialize\")]\nuse crate::nodes::deserialize_string_leaky;\nuse crate::{\n    Attribute, AttributeValue, DynamicNode, Template, TemplateAttribute, TemplateNode, VNode, VText,\n};\n\n#[cfg_attr(feature = \"serialize\", derive(serde::Serialize, serde::Deserialize))]\n#[doc(hidden)]\n#[derive(Debug, PartialEq, Clone)]\npub struct HotreloadedLiteral {\n    pub name: String,\n    pub value: HotReloadLiteral,\n}\n\n#[cfg_attr(feature = \"serialize\", derive(serde::Serialize, serde::Deserialize))]\n#[doc(hidden)]\n#[derive(Debug, PartialEq, Clone)]\npub enum HotReloadLiteral {\n    Fmted(FmtedSegments),\n    Float(f64),\n    Int(i64),\n    Bool(bool),\n}\n\nimpl HotReloadLiteral {\n    pub fn as_fmted(&self) -> Option<&FmtedSegments> {\n        match self {\n            Self::Fmted(segments) => Some(segments),\n            _ => None,\n        }\n    }\n\n    pub fn as_float(&self) -> Option<f64> {\n        match self {\n            Self::Float(f) => Some(*f),\n            _ => None,\n        }\n    }\n\n    pub fn as_int(&self) -> Option<i64> {\n        match self {\n            Self::Int(i) => Some(*i),\n            _ => None,\n        }\n    }\n\n    pub fn as_bool(&self) -> Option<bool> {\n        match self {\n            Self::Bool(b) => Some(*b),\n            _ => None,\n        }\n    }\n}\n\nimpl Hash for HotReloadLiteral {\n    fn hash<H: Hasher>(&self, state: &mut H) {\n        match self {\n            Self::Fmted(segments) => segments.hash(state),\n            Self::Float(f) => f.to_bits().hash(state),\n            Self::Int(i) => i.hash(state),\n            Self::Bool(b) => b.hash(state),\n        }\n    }\n}\n\n#[cfg_attr(feature = \"serialize\", derive(serde::Serialize, serde::Deserialize))]\n#[doc(hidden)]\n#[derive(Debug, PartialEq, Eq, Clone, Hash)]\npub struct FmtedSegments {\n    pub(crate) segments: Vec<FmtSegment>,\n}\n\nimpl FmtedSegments {\n    pub fn new(segments: Vec<FmtSegment>) -> Self {\n        Self { segments }\n    }\n\n    /// Render the formatted string by stitching together the segments\n    pub(crate) fn render_with(&self, dynamic_text: &[String]) -> String {\n        let mut out = String::new();\n\n        for segment in &self.segments {\n            match segment {\n                FmtSegment::Literal { value } => out.push_str(value),\n                FmtSegment::Dynamic { id } => out.push_str(&dynamic_text[*id]),\n            }\n        }\n\n        out\n    }\n}\n\ntype StaticStr = &'static str;\n\n#[cfg_attr(feature = \"serialize\", derive(serde::Serialize, serde::Deserialize))]\n#[doc(hidden)]\n#[derive(Debug, PartialEq, Eq, Clone, Hash)]\npub enum FmtSegment {\n    Literal {\n        #[cfg_attr(\n            feature = \"serialize\",\n            serde(deserialize_with = \"deserialize_string_leaky\")\n        )]\n        value: StaticStr,\n    },\n    Dynamic {\n        id: usize,\n    },\n}\n\n// let __pool = DynamicValuePool::new(\n//     vec![...],\n//     vec![...],\n//     vec![...],\n// );\n// VNode::new(\n//     None,\n//     Template {\n//         name: \"...\",\n//         roots: &[...],\n//         node_paths: &[..],\n//         attr_paths: &[...],\n//     },\n//     Box::new([...]),\n//     Box::new([...]),\n// )\n\n// Open questions:\n// - How do we handle type coercion for different sized component property integers?\n// - Should non-string hot literals go through the centralized pool?\n// - Should formatted strings be a runtime concept?\n\n#[doc(hidden)]\npub struct DynamicLiteralPool {\n    dynamic_text: Box<[String]>,\n}\n\nimpl DynamicLiteralPool {\n    pub fn new(dynamic_text: Vec<String>) -> Self {\n        Self {\n            dynamic_text: dynamic_text.into_boxed_slice(),\n        }\n    }\n\n    // TODO: This should be marked as private in the next major release\n    pub fn get_component_property<'a, T>(\n        &self,\n        id: usize,\n        hot_reload: &'a HotReloadedTemplate,\n        f: impl FnOnce(&'a HotReloadLiteral) -> Option<T>,\n    ) -> Option<T> {\n        let value = hot_reload.component_values.get(id)?;\n        f(value)\n    }\n\n    fn get_component_property_or_default<'a, T: Default>(\n        &self,\n        id: usize,\n        hot_reload: &'a HotReloadedTemplate,\n        f: impl FnOnce(&'a HotReloadLiteral) -> Option<T>,\n    ) -> Option<T> {\n        // If the component was removed since the last hot reload, the hot reload template may not\n        // have the property. If that is the case, just use a default value since the component is\n        // never rendered.\n        if id >= hot_reload.component_values.len() {\n            return Some(T::default());\n        }\n        self.get_component_property(id, hot_reload, f)\n    }\n\n    /// Get a component property of a specific type at the component property index\n    pub fn component_property<T: 'static>(\n        &mut self,\n        id: usize,\n        hot_reload: &HotReloadedTemplate,\n        // We pass in the original value for better type inference\n        // For example, if the original literal is `0i128`, we know the output must be the type `i128`\n        _coherse_type: T,\n    ) -> T {\n        fn assert_type<T: 'static, T2: 'static>(t: T) -> T2 {\n            *(Box::new(t) as Box<dyn Any>).downcast::<T2>().unwrap()\n        }\n        let grab_float = || {\n            self.get_component_property_or_default(id, hot_reload, HotReloadLiteral::as_float).unwrap_or_else(|| {\n                tracing::error!(\"Expected a float component property, because the type was {}. The CLI gave the hot reloading engine a type of {:?}. This is probably caused by a bug in dioxus hot reloading. Please report this issue.\", std::any::type_name::<T>(), hot_reload.component_values.get(id));\n                Default::default()\n            })\n        };\n        let grab_int = || {\n            self.get_component_property_or_default(id, hot_reload, HotReloadLiteral::as_int).unwrap_or_else(|| {\n                tracing::error!(\"Expected a integer component property, because the type was {}. The CLI gave the hot reloading engine a type of {:?}. This is probably caused by a bug in dioxus hot reloading. Please report this issue.\", std::any::type_name::<T>(), hot_reload.component_values.get(id));\n                Default::default()\n            })\n        };\n        let grab_bool = || {\n            self.get_component_property_or_default(id, hot_reload, HotReloadLiteral::as_bool).unwrap_or_else(|| {\n                tracing::error!(\"Expected a bool component property, because the type was {}. The CLI gave the hot reloading engine a type of {:?}. This is probably caused by a bug in dioxus hot reloading. Please report this issue.\", std::any::type_name::<T>(), hot_reload.component_values.get(id));\n                Default::default()\n            })\n        };\n        let grab_fmted = || {\n            self.get_component_property_or_default(id, hot_reload, |fmted| HotReloadLiteral::as_fmted(fmted).map(|segments| self.render_formatted(segments))).unwrap_or_else(|| {\n                tracing::error!(\"Expected a string component property, because the type was {}. The CLI gave the hot reloading engine a type of {:?}. This is probably caused by a bug in dioxus hot reloading. Please report this issue.\", std::any::type_name::<T>(), hot_reload.component_values.get(id));\n                Default::default()\n            })\n        };\n        match TypeId::of::<T>() {\n            // Any string types that accept a literal\n            _ if TypeId::of::<String>() == TypeId::of::<T>() => assert_type(grab_fmted()),\n            _ if TypeId::of::<&str>() == TypeId::of::<T>() => {\n                assert_type(Box::leak(grab_fmted().into_boxed_str()) as &'static str)\n            }\n            // Any integer types that accept a literal\n            _ if TypeId::of::<i128>() == TypeId::of::<T>() => assert_type(grab_int() as i128),\n            _ if TypeId::of::<i64>() == TypeId::of::<T>() => assert_type(grab_int()),\n            _ if TypeId::of::<i32>() == TypeId::of::<T>() => assert_type(grab_int() as i32),\n            _ if TypeId::of::<i16>() == TypeId::of::<T>() => assert_type(grab_int() as i16),\n            _ if TypeId::of::<i8>() == TypeId::of::<T>() => assert_type(grab_int() as i8),\n            _ if TypeId::of::<isize>() == TypeId::of::<T>() => assert_type(grab_int() as isize),\n            _ if TypeId::of::<u128>() == TypeId::of::<T>() => assert_type(grab_int() as u128),\n            _ if TypeId::of::<u64>() == TypeId::of::<T>() => assert_type(grab_int() as u64),\n            _ if TypeId::of::<u32>() == TypeId::of::<T>() => assert_type(grab_int() as u32),\n            _ if TypeId::of::<u16>() == TypeId::of::<T>() => assert_type(grab_int() as u16),\n            _ if TypeId::of::<u8>() == TypeId::of::<T>() => assert_type(grab_int() as u8),\n            _ if TypeId::of::<usize>() == TypeId::of::<T>() => assert_type(grab_int() as usize),\n            // Any float types that accept a literal\n            _ if TypeId::of::<f64>() == TypeId::of::<T>() => assert_type(grab_float()),\n            _ if TypeId::of::<f32>() == TypeId::of::<T>() => assert_type(grab_float() as f32),\n            // Any bool types that accept a literal\n            _ if TypeId::of::<bool>() == TypeId::of::<T>() => assert_type(grab_bool()),\n            _ => panic!(\"Unsupported component property type\"),\n        }\n    }\n\n    pub fn render_formatted(&self, segments: &FmtedSegments) -> String {\n        segments.render_with(&self.dynamic_text)\n    }\n}\n#[doc(hidden)]\npub struct DynamicValuePool {\n    dynamic_attributes: Box<[Box<[Attribute]>]>,\n    dynamic_nodes: Box<[DynamicNode]>,\n    literal_pool: DynamicLiteralPool,\n}\n\nimpl DynamicValuePool {\n    pub fn new(\n        dynamic_nodes: Vec<DynamicNode>,\n        dynamic_attributes: Vec<Box<[Attribute]>>,\n        literal_pool: DynamicLiteralPool,\n    ) -> Self {\n        Self {\n            dynamic_attributes: dynamic_attributes.into_boxed_slice(),\n            dynamic_nodes: dynamic_nodes.into_boxed_slice(),\n            literal_pool,\n        }\n    }\n\n    pub fn render_with(&mut self, hot_reload: &HotReloadedTemplate) -> VNode {\n        // Get the node_paths from a depth first traversal of the template\n        let key = hot_reload\n            .key\n            .as_ref()\n            .map(|key| self.literal_pool.render_formatted(key));\n        let dynamic_nodes = hot_reload\n            .dynamic_nodes\n            .iter()\n            .map(|node| self.render_dynamic_node(node))\n            .collect();\n        let dynamic_attrs = hot_reload\n            .dynamic_attributes\n            .iter()\n            .map(|attr| self.render_attribute(attr))\n            .collect();\n\n        VNode::new(key, hot_reload.template, dynamic_nodes, dynamic_attrs)\n    }\n\n    fn render_dynamic_node(&mut self, node: &HotReloadDynamicNode) -> DynamicNode {\n        match node {\n            // If the node is dynamic, take it from the pool and return it\n            HotReloadDynamicNode::Dynamic(id) => self.dynamic_nodes[*id].clone(),\n            // Otherwise, format the text node and return it\n            HotReloadDynamicNode::Formatted(segments) => DynamicNode::Text(VText {\n                value: self.literal_pool.render_formatted(segments),\n            }),\n        }\n    }\n\n    fn render_attribute(&mut self, attr: &HotReloadDynamicAttribute) -> Box<[Attribute]> {\n        match attr {\n            HotReloadDynamicAttribute::Dynamic(id) => self.dynamic_attributes[*id].clone(),\n            HotReloadDynamicAttribute::Named(NamedAttribute {\n                name,\n                namespace,\n                value,\n            }) => Box::new([Attribute {\n                name,\n                namespace: *namespace,\n                value: match value {\n                    HotReloadAttributeValue::Literal(HotReloadLiteral::Fmted(segments)) => {\n                        AttributeValue::Text(self.literal_pool.render_formatted(segments))\n                    }\n                    HotReloadAttributeValue::Literal(HotReloadLiteral::Float(f)) => {\n                        AttributeValue::Float(*f)\n                    }\n                    HotReloadAttributeValue::Literal(HotReloadLiteral::Int(i)) => {\n                        AttributeValue::Int(*i)\n                    }\n                    HotReloadAttributeValue::Literal(HotReloadLiteral::Bool(b)) => {\n                        AttributeValue::Bool(*b)\n                    }\n                    HotReloadAttributeValue::Dynamic(id) => {\n                        self.dynamic_attributes[*id][0].value.clone()\n                    }\n                },\n                volatile: false,\n            }]),\n        }\n    }\n}\n\n#[doc(hidden)]\n#[derive(Debug, Clone, PartialEq)]\n#[cfg_attr(feature = \"serialize\", derive(serde::Serialize, serde::Deserialize))]\npub struct HotReloadTemplateWithLocation {\n    pub key: TemplateGlobalKey,\n    pub template: HotReloadedTemplate,\n}\n\n#[doc(hidden)]\n#[derive(Debug, Clone, PartialEq, Hash, PartialOrd, Eq, Ord)]\n#[cfg_attr(feature = \"serialize\", derive(serde::Serialize, serde::Deserialize))]\npub struct TemplateGlobalKey {\n    pub file: String,\n    pub line: usize,\n    pub column: usize,\n    pub index: usize,\n}\n\ntype StaticTemplateArray = &'static [TemplateNode];\n\n#[doc(hidden)]\n#[derive(Debug, PartialEq, Clone)]\n#[cfg_attr(feature = \"serialize\", derive(serde::Serialize, serde::Deserialize))]\npub struct HotReloadedTemplate {\n    pub key: Option<FmtedSegments>,\n    pub dynamic_nodes: Vec<HotReloadDynamicNode>,\n    pub dynamic_attributes: Vec<HotReloadDynamicAttribute>,\n    pub component_values: Vec<HotReloadLiteral>,\n    #[cfg_attr(\n        feature = \"serialize\",\n        serde(deserialize_with = \"crate::nodes::deserialize_leaky\")\n    )]\n    pub roots: StaticTemplateArray,\n    /// The template that is computed from the hot reload roots\n    template: Template,\n}\n\nimpl HotReloadedTemplate {\n    pub fn new(\n        key: Option<FmtedSegments>,\n        dynamic_nodes: Vec<HotReloadDynamicNode>,\n        dynamic_attributes: Vec<HotReloadDynamicAttribute>,\n        component_values: Vec<HotReloadLiteral>,\n        roots: &'static [TemplateNode],\n    ) -> Self {\n        let node_paths = Self::node_paths(roots);\n        let attr_paths = Self::attr_paths(roots);\n\n        let template = Template {\n            roots,\n            node_paths,\n            attr_paths,\n        };\n        Self {\n            key,\n            dynamic_nodes,\n            dynamic_attributes,\n            component_values,\n            roots,\n            template,\n        }\n    }\n\n    fn node_paths(roots: &'static [TemplateNode]) -> &'static [&'static [u8]] {\n        fn add_node_paths(\n            roots: &[TemplateNode],\n            node_paths: &mut Vec<&'static [u8]>,\n            current_path: Vec<u8>,\n        ) {\n            for (idx, node) in roots.iter().enumerate() {\n                let mut path = current_path.clone();\n                path.push(idx as u8);\n                match node {\n                    TemplateNode::Element { children, .. } => {\n                        add_node_paths(children, node_paths, path);\n                    }\n                    TemplateNode::Text { .. } => {}\n                    TemplateNode::Dynamic { id } => {\n                        debug_assert_eq!(node_paths.len(), *id);\n                        node_paths.push(Box::leak(path.into_boxed_slice()));\n                    }\n                }\n            }\n        }\n\n        let mut node_paths = Vec::new();\n        add_node_paths(roots, &mut node_paths, Vec::new());\n        let leaked: &'static [&'static [u8]] = Box::leak(node_paths.into_boxed_slice());\n        leaked\n    }\n\n    fn attr_paths(roots: &'static [TemplateNode]) -> &'static [&'static [u8]] {\n        fn add_attr_paths(\n            roots: &[TemplateNode],\n            attr_paths: &mut Vec<&'static [u8]>,\n            current_path: Vec<u8>,\n        ) {\n            for (idx, node) in roots.iter().enumerate() {\n                let mut path = current_path.clone();\n                path.push(idx as u8);\n                if let TemplateNode::Element {\n                    children, attrs, ..\n                } = node\n                {\n                    for attr in *attrs {\n                        if let TemplateAttribute::Dynamic { id } = attr {\n                            debug_assert_eq!(attr_paths.len(), *id);\n                            attr_paths.push(Box::leak(path.clone().into_boxed_slice()));\n                        }\n                    }\n                    add_attr_paths(children, attr_paths, path);\n                }\n            }\n        }\n\n        let mut attr_paths = Vec::new();\n        add_attr_paths(roots, &mut attr_paths, Vec::new());\n        let leaked: &'static [&'static [u8]] = Box::leak(attr_paths.into_boxed_slice());\n        leaked\n    }\n}\n\n#[doc(hidden)]\n#[derive(Debug, PartialEq, Clone, Hash)]\n#[cfg_attr(feature = \"serialize\", derive(serde::Serialize, serde::Deserialize))]\npub enum HotReloadDynamicNode {\n    Dynamic(usize),\n    Formatted(FmtedSegments),\n}\n\n#[doc(hidden)]\n#[derive(Debug, PartialEq, Clone, Hash)]\n#[cfg_attr(feature = \"serialize\", derive(serde::Serialize, serde::Deserialize))]\npub enum HotReloadDynamicAttribute {\n    Dynamic(usize),\n    Named(NamedAttribute),\n}\n\n#[doc(hidden)]\n#[derive(Debug, PartialEq, Clone, Hash)]\n#[cfg_attr(feature = \"serialize\", derive(serde::Serialize, serde::Deserialize))]\npub struct NamedAttribute {\n    /// The name of this attribute.\n    #[cfg_attr(\n        feature = \"serialize\",\n        serde(deserialize_with = \"crate::nodes::deserialize_string_leaky\")\n    )]\n    name: StaticStr,\n    /// The namespace of this attribute. Does not exist in the HTML spec\n    #[cfg_attr(\n        feature = \"serialize\",\n        serde(deserialize_with = \"crate::nodes::deserialize_option_leaky\")\n    )]\n    namespace: Option<StaticStr>,\n\n    value: HotReloadAttributeValue,\n}\n\nimpl NamedAttribute {\n    pub fn new(\n        name: &'static str,\n        namespace: Option<&'static str>,\n        value: HotReloadAttributeValue,\n    ) -> Self {\n        Self {\n            name,\n            namespace,\n            value,\n        }\n    }\n}\n\n#[doc(hidden)]\n#[derive(Debug, PartialEq, Clone, Hash)]\n#[cfg_attr(feature = \"serialize\", derive(serde::Serialize, serde::Deserialize))]\npub enum HotReloadAttributeValue {\n    Literal(HotReloadLiteral),\n    Dynamic(usize),\n}\n"
  },
  {
    "path": "packages/core/src/launch.rs",
    "content": "//! This module contains utilities renderers use to integrate with the launch function.\n\n/// A marker trait for platform configs. We use this marker to\n/// make sure that the user doesn't accidentally pass in a config\n/// builder instead of the config\npub trait LaunchConfig: 'static {}\n\nimpl LaunchConfig for () {}\n"
  },
  {
    "path": "packages/core/src/lib.rs",
    "content": "#![doc = include_str!(\"../README.md\")]\n#![doc(html_logo_url = \"https://avatars.githubusercontent.com/u/79236386\")]\n#![doc(html_favicon_url = \"https://avatars.githubusercontent.com/u/79236386\")]\n#![warn(missing_docs)]\n\nmod any_props;\nmod arena;\nmod diff;\nmod effect;\nmod error_boundary;\nmod events;\nmod fragment;\nmod generational_box;\nmod global_context;\nmod launch;\nmod mutations;\nmod nodes;\nmod properties;\nmod reactive_context;\nmod render_error;\nmod root_wrapper;\nmod runtime;\nmod scheduler;\nmod scope_arena;\nmod scope_context;\nmod scopes;\nmod suspense;\nmod tasks;\nmod virtual_dom;\n\nmod hotreload_utils;\n\n/// Items exported from this module are used in macros and should not be used directly.\n#[doc(hidden)]\npub mod internal {\n    #[doc(hidden)]\n    pub use crate::hotreload_utils::{\n        DynamicLiteralPool, DynamicValuePool, FmtSegment, FmtedSegments, HotReloadAttributeValue,\n        HotReloadDynamicAttribute, HotReloadDynamicNode, HotReloadLiteral,\n        HotReloadTemplateWithLocation, HotReloadedTemplate, HotreloadedLiteral, NamedAttribute,\n        TemplateGlobalKey,\n    };\n\n    #[allow(non_snake_case)]\n    #[doc(hidden)]\n    pub fn Err<T, E>(e: E) -> Result<T, E> {\n        std::result::Result::Err(e)\n    }\n\n    pub use anyhow::__anyhow;\n\n    #[doc(hidden)]\n    pub use generational_box;\n}\n\npub(crate) mod innerlude {\n    pub(crate) use crate::any_props::*;\n    pub use crate::arena::*;\n    pub(crate) use crate::effect::*;\n    pub use crate::error_boundary::*;\n    pub use crate::events::*;\n    pub use crate::fragment::*;\n    pub use crate::generational_box::*;\n    pub use crate::global_context::*;\n    pub use crate::launch::*;\n    pub use crate::mutations::*;\n    pub use crate::nodes::*;\n    pub use crate::properties::*;\n    pub use crate::reactive_context::*;\n    pub use crate::render_error::*;\n    pub use crate::runtime::{Runtime, RuntimeGuard};\n    pub use crate::scheduler::*;\n    pub use crate::scopes::*;\n    pub use crate::suspense::*;\n    pub use crate::tasks::*;\n    pub use crate::virtual_dom::*;\n\n    pub use anyhow::anyhow;\n    pub use anyhow::Context as AnyhowContext;\n    // pub use anyhow::Error as AnyhowError;\n    // pub type Error = CapturedError;\n\n    /// A result type with a default error of [`CapturedError`].\n    pub type Result<T, E = CapturedError> = std::result::Result<T, E>;\n\n    /// An [`Element`] is a possibly-none [`VNode`] created by calling `render` on [`ScopeId`] or [`ScopeState`].\n    ///\n    /// An Errored [`Element`] will propagate the error to the nearest error boundary.\n    pub type Element = std::result::Result<VNode, RenderError>;\n\n    /// A [`Component`] is a function that takes [`Properties`] and returns an [`Element`].\n    pub type Component<P = ()> = fn(P) -> Element;\n}\n\npub use crate::innerlude::{\n    anyhow, consume_context, consume_context_from_scope, current_owner, current_scope_id,\n    fc_to_builder, generation, has_context, needs_update, needs_update_any, parent_scope,\n    provide_context, provide_create_error_boundary, provide_root_context, queue_effect,\n    remove_future, schedule_update, schedule_update_any, spawn, spawn_forever, spawn_isomorphic,\n    suspend, throw_error, try_consume_context, use_after_render, use_before_render, use_drop,\n    use_hook, use_hook_with_cleanup, with_owner, AnyValue, AnyhowContext, Attribute,\n    AttributeValue, Callback, CapturedError, Component, ComponentFunction, DynamicNode, Element,\n    ElementId, ErrorBoundary, ErrorContext, Event, EventHandler, Fragment, HasAttributes,\n    IntoAttributeValue, IntoDynNode, LaunchConfig, ListenerCallback, MarkerWrapper, Mutation,\n    Mutations, NoOpMutations, OptionStringFromMarker, Properties, ReactiveContext, RenderError,\n    Result, Runtime, RuntimeGuard, ScopeId, ScopeState, SpawnIfAsync, SubscriberList, Subscribers,\n    SuperFrom, SuperInto, SuspendedFuture, SuspenseBoundary, SuspenseBoundaryProps,\n    SuspenseContext, Task, Template, TemplateAttribute, TemplateNode, VComponent, VNode,\n    VNodeInner, VPlaceholder, VText, VirtualDom, WriteMutations,\n};\n\n/// Equivalent to `Ok::<_, dioxus::CapturedError>(value)`.\n///\n/// This simplifies creation of an `dioxus::Result` in places where type\n/// inference cannot deduce the `E` type of the result &mdash; without needing\n/// to write`Ok::<_, dioxus::CapturedError>(value)`.\n///\n/// One might think that `dioxus::Result::Ok(value)` would work in such cases\n/// but it does not.\n///\n/// ```console\n/// error[E0282]: type annotations needed for `std::result::Result<i32, E>`\n///   --> src/main.rs:11:13\n///    |\n/// 11 |     let _ = dioxus::Result::Ok(1);\n///    |         -   ^^^^^^^^^^^^^^^^^^ cannot infer type for type parameter `E` declared on the enum `Result`\n///    |         |\n///    |         consider giving this pattern the explicit type `std::result::Result<i32, E>`, where the type parameter `E` is specified\n/// ```\n#[allow(non_snake_case)]\npub fn Ok<T>(value: T) -> Result<T, CapturedError> {\n    Result::Ok(value)\n}\n\npub use const_format;\n"
  },
  {
    "path": "packages/core/src/mutations.rs",
    "content": "use crate::{arena::ElementId, AttributeValue, Template};\n\n/// Something that can handle the mutations that are generated by the diffing process and apply them to the Real DOM\n///\n/// This object provides a bunch of important information for a renderer to use patch the Real Dom with the state of the\n/// VirtualDom. This includes the scopes that were modified, the templates that were discovered, and a list of changes\n/// in the form of a [`Mutation`].\n///\n/// These changes are specific to one subtree, so to patch multiple subtrees, you'd need to handle each set separately.\n///\n/// Templates, however, apply to all subtrees, not just target subtree.\n///\n/// Mutations are the only link between the RealDOM and the VirtualDOM.\npub trait WriteMutations {\n    /// Add these m children to the target element\n    ///\n    /// Id: The ID of the element being mounted to\n    /// M: The number of nodes on the stack to append to the target element\n    fn append_children(&mut self, id: ElementId, m: usize);\n\n    /// Assign the element at the given path the target ElementId.\n    ///\n    /// The path is in the form of a list of indices based on children. Templates cannot have more than 255 children per\n    /// element, hence the use of a single byte.\n    ///\n    /// Path: The path of the child of the topmost node on the stack. A path of `[]` represents the topmost node. A path of `[0]` represents the first child. `[0,1,2]` represents 1st child's 2nd child's 3rd child.\n    /// Id: The ID we're assigning to this element/placeholder. This will be used later to modify the element or replace it with another element.\n    fn assign_node_id(&mut self, path: &'static [u8], id: ElementId);\n\n    /// Create a placeholder in the DOM that we will use later.\n    ///\n    /// Dioxus currently requires the use of placeholders to maintain a re-entrance point for things like list diffing\n    ///\n    /// Id: The ID we're assigning to this element/placeholder. This will be used later to modify the element or replace it with another element.\n    fn create_placeholder(&mut self, id: ElementId);\n\n    /// Create a node specifically for text with the given value\n    ///\n    /// Value: The text content of this text node\n    /// Id: The ID we're assigning to this specific text nodes. This will be used later to modify the element or replace it with another element.\n    fn create_text_node(&mut self, value: &str, id: ElementId);\n\n    /// Load and clone an existing node from a template saved under that specific name\n    ///\n    /// Dioxus guarantees that the renderer will have already been provided the template.\n    /// When the template is picked up in the template list, it should be saved under its \"name\" - here, the name\n    ///\n    /// Name: The unique \"name\" of the template based on the template location. When paired with `rsx!`, this is autogenerated\n    /// Index: The index root we loading from the template. The template is stored as a list of nodes. This index represents the position of that root\n    /// Id: The ID we're assigning to this element being loaded from the template (This will be used later to move the element around in lists)\n    fn load_template(&mut self, template: Template, index: usize, id: ElementId);\n\n    /// Replace the target element (given by its ID) with the topmost m nodes on the stack\n    ///\n    /// id: The ID of the node we're going to replace with new nodes\n    /// m: The number of nodes on the stack to replace the target element with\n    fn replace_node_with(&mut self, id: ElementId, m: usize);\n\n    /// Replace an existing element in the template at the given path with the m nodes on the stack\n    ///\n    /// Path: The path of the child of the topmost node on the stack. A path of `[]` represents the topmost node. A path of `[0]` represents the first child. `[0,1,2]` represents 1st child's 2nd child's 3rd child.\n    /// M: The number of nodes on the stack to replace the target element with\n    fn replace_placeholder_with_nodes(&mut self, path: &'static [u8], m: usize);\n\n    /// Insert a number of nodes after a given node.\n    ///\n    /// Id: The ID of the node to insert after.\n    /// M: The number of nodes on the stack to insert after the target node.\n    fn insert_nodes_after(&mut self, id: ElementId, m: usize);\n\n    /// Insert a number of nodes before a given node.\n    ///\n    /// Id: The ID of the node to insert before.\n    /// M: The number of nodes on the stack to insert before the target node.\n    fn insert_nodes_before(&mut self, id: ElementId, m: usize);\n\n    /// Set the value of a node's attribute.\n    ///\n    /// Name: The name of the attribute to set.\n    /// NS: The (optional) namespace of the attribute. For instance, \"style\" is in the \"style\" namespace.\n    /// Value: The value of the attribute.\n    /// Id: The ID of the node to set the attribute of.\n    fn set_attribute(\n        &mut self,\n        name: &'static str,\n        ns: Option<&'static str>,\n        value: &AttributeValue,\n        id: ElementId,\n    );\n\n    /// Set the text content of a node.\n    ///\n    /// Value: The textcontent of the node\n    /// Id: The ID of the node to set the textcontent of.\n    fn set_node_text(&mut self, value: &str, id: ElementId);\n\n    /// Create a new Event Listener.\n    ///\n    /// Name: The name of the event to listen for.\n    /// Id: The ID of the node to attach the listener to.\n    fn create_event_listener(&mut self, name: &'static str, id: ElementId);\n\n    /// Remove an existing Event Listener.\n    ///\n    /// Name: The name of the event to remove.\n    /// Id: The ID of the node to remove.\n    fn remove_event_listener(&mut self, name: &'static str, id: ElementId);\n\n    /// Remove a particular node from the DOM\n    ///\n    /// Id: The ID of the node to remove.\n    fn remove_node(&mut self, id: ElementId);\n\n    /// Push the given root node onto our stack.\n    ///\n    /// Id: The ID of the root node to push.\n    fn push_root(&mut self, id: ElementId);\n}\n\n/// A `Mutation` represents a single instruction for the renderer to use to modify the UI tree to match the state\n/// of the Dioxus VirtualDom.\n///\n/// These edits can be serialized and sent over the network or through any interface\n#[derive(Debug, PartialEq)]\npub enum Mutation {\n    /// Add these m children to the target element\n    AppendChildren {\n        /// The ID of the element being mounted to\n        id: ElementId,\n\n        /// The number of nodes on the stack to append to the target element\n        m: usize,\n    },\n\n    /// Assign the element at the given path the target ElementId.\n    ///\n    /// The path is in the form of a list of indices based on children. Templates cannot have more than 255 children per\n    /// element, hence the use of a single byte.\n    AssignId {\n        /// The path of the child of the topmost node on the stack\n        ///\n        /// A path of `[]` represents the topmost node. A path of `[0]` represents the first child.\n        /// `[0,1,2]` represents 1st child's 2nd child's 3rd child.\n        path: &'static [u8],\n\n        /// The ID we're assigning to this element/placeholder.\n        ///\n        /// This will be used later to modify the element or replace it with another element.\n        id: ElementId,\n    },\n\n    /// Create a placeholder in the DOM that we will use later.\n    ///\n    /// Dioxus currently requires the use of placeholders to maintain a re-entrance point for things like list diffing\n    CreatePlaceholder {\n        /// The ID we're assigning to this element/placeholder.\n        ///\n        /// This will be used later to modify the element or replace it with another element.\n        id: ElementId,\n    },\n\n    /// Create a node specifically for text with the given value\n    CreateTextNode {\n        /// The text content of this text node\n        value: String,\n\n        /// The ID we're assigning to this specific text nodes\n        ///\n        /// This will be used later to modify the element or replace it with another element.\n        id: ElementId,\n    },\n\n    /// Load and clone an existing node from a template with a given ID\n    ///\n    /// Dioxus guarantees that the renderer will have already been provided the template.\n    /// When the template is picked up in the template list, it should be saved under its \"name\" - here, the name\n    LoadTemplate {\n        /// Which root are we loading from the template?\n        ///\n        /// The template is stored as a list of nodes. This index represents the position of that root\n        index: usize,\n\n        /// The ID we're assigning to this element being loaded from the template\n        ///\n        /// This will be used later to move the element around in lists\n        id: ElementId,\n    },\n\n    /// Replace the target element (given by its ID) with the topmost m nodes on the stack\n    ReplaceWith {\n        /// The ID of the node we're going to replace with\n        id: ElementId,\n\n        /// The number of nodes on the stack to replace the target element with\n        m: usize,\n    },\n\n    /// Replace an existing element in the template at the given path with the m nodes on the stack\n    ReplacePlaceholder {\n        /// The path of the child of the topmost node on the stack\n        ///\n        /// A path of `[]` represents the topmost node. A path of `[0]` represents the first child.\n        /// `[0,1,2]` represents 1st child's 2nd child's 3rd child.\n        path: &'static [u8],\n\n        /// The number of nodes on the stack to replace the target element with\n        m: usize,\n    },\n\n    /// Insert a number of nodes after a given node.\n    InsertAfter {\n        /// The ID of the node to insert after.\n        id: ElementId,\n\n        /// The number of nodes on the stack to insert after the target node.\n        m: usize,\n    },\n\n    /// Insert a number of nodes before a given node.\n    InsertBefore {\n        /// The ID of the node to insert before.\n        id: ElementId,\n\n        /// The number of nodes on the stack to insert before the target node.\n        m: usize,\n    },\n\n    /// Set the value of a node's attribute.\n    SetAttribute {\n        /// The name of the attribute to set.\n        name: &'static str,\n\n        /// The (optional) namespace of the attribute.\n        /// For instance, \"style\" is in the \"style\" namespace.\n        ns: Option<&'static str>,\n\n        /// The value of the attribute.\n        value: AttributeValue,\n\n        /// The ID of the node to set the attribute of.\n        id: ElementId,\n    },\n\n    /// Set the textcontent of a node.\n    SetText {\n        /// The textcontent of the node\n        value: String,\n\n        /// The ID of the node to set the textcontent of.\n        id: ElementId,\n    },\n\n    /// Create a new Event Listener.\n    NewEventListener {\n        /// The name of the event to listen for.\n        name: String,\n\n        /// The ID of the node to attach the listener to.\n        id: ElementId,\n    },\n\n    /// Remove an existing Event Listener.\n    RemoveEventListener {\n        /// The name of the event to remove.\n        name: String,\n\n        /// The ID of the node to remove.\n        id: ElementId,\n    },\n\n    /// Remove a particular node from the DOM\n    Remove {\n        /// The ID of the node to remove.\n        id: ElementId,\n    },\n\n    /// Push the given root node onto our stack.\n    PushRoot {\n        /// The ID of the root node to push.\n        id: ElementId,\n    },\n}\n\n/// A static list of mutations that can be applied to the DOM. Note: this list does not contain any `Any` attribute values\n#[derive(Debug, PartialEq, Default)]\npub struct Mutations {\n    /// Any mutations required to patch the renderer to match the layout of the VirtualDom\n    pub edits: Vec<Mutation>,\n}\n\nimpl WriteMutations for Mutations {\n    fn append_children(&mut self, id: ElementId, m: usize) {\n        self.edits.push(Mutation::AppendChildren { id, m })\n    }\n\n    fn assign_node_id(&mut self, path: &'static [u8], id: ElementId) {\n        self.edits.push(Mutation::AssignId { path, id })\n    }\n\n    fn create_placeholder(&mut self, id: ElementId) {\n        self.edits.push(Mutation::CreatePlaceholder { id })\n    }\n\n    fn create_text_node(&mut self, value: &str, id: ElementId) {\n        self.edits.push(Mutation::CreateTextNode {\n            value: value.into(),\n            id,\n        })\n    }\n\n    fn load_template(&mut self, _template: Template, index: usize, id: ElementId) {\n        self.edits.push(Mutation::LoadTemplate { index, id })\n    }\n\n    fn replace_node_with(&mut self, id: ElementId, m: usize) {\n        self.edits.push(Mutation::ReplaceWith { id, m })\n    }\n\n    fn replace_placeholder_with_nodes(&mut self, path: &'static [u8], m: usize) {\n        self.edits.push(Mutation::ReplacePlaceholder { path, m })\n    }\n\n    fn insert_nodes_after(&mut self, id: ElementId, m: usize) {\n        self.edits.push(Mutation::InsertAfter { id, m })\n    }\n\n    fn insert_nodes_before(&mut self, id: ElementId, m: usize) {\n        self.edits.push(Mutation::InsertBefore { id, m })\n    }\n\n    fn set_attribute(\n        &mut self,\n        name: &'static str,\n        ns: Option<&'static str>,\n        value: &AttributeValue,\n        id: ElementId,\n    ) {\n        self.edits.push(Mutation::SetAttribute {\n            name,\n            ns,\n            value: match value {\n                AttributeValue::Text(s) => AttributeValue::Text(s.clone()),\n                AttributeValue::Bool(b) => AttributeValue::Bool(*b),\n                AttributeValue::Float(n) => AttributeValue::Float(*n),\n                AttributeValue::Int(n) => AttributeValue::Int(*n),\n                AttributeValue::None => AttributeValue::None,\n                _ => panic!(\"Cannot serialize attribute value\"),\n            },\n            id,\n        })\n    }\n\n    fn set_node_text(&mut self, value: &str, id: ElementId) {\n        self.edits.push(Mutation::SetText {\n            value: value.into(),\n            id,\n        })\n    }\n\n    fn create_event_listener(&mut self, name: &'static str, id: ElementId) {\n        self.edits.push(Mutation::NewEventListener {\n            name: name.into(),\n            id,\n        })\n    }\n\n    fn remove_event_listener(&mut self, name: &'static str, id: ElementId) {\n        self.edits.push(Mutation::RemoveEventListener {\n            name: name.into(),\n            id,\n        })\n    }\n\n    fn remove_node(&mut self, id: ElementId) {\n        self.edits.push(Mutation::Remove { id })\n    }\n\n    fn push_root(&mut self, id: ElementId) {\n        self.edits.push(Mutation::PushRoot { id })\n    }\n}\n\n/// A struct that ignores all mutations\npub struct NoOpMutations;\n\nimpl WriteMutations for NoOpMutations {\n    fn append_children(&mut self, _: ElementId, _: usize) {}\n\n    fn assign_node_id(&mut self, _: &'static [u8], _: ElementId) {}\n\n    fn create_placeholder(&mut self, _: ElementId) {}\n\n    fn create_text_node(&mut self, _: &str, _: ElementId) {}\n\n    fn load_template(&mut self, _: Template, _: usize, _: ElementId) {}\n\n    fn replace_node_with(&mut self, _: ElementId, _: usize) {}\n\n    fn replace_placeholder_with_nodes(&mut self, _: &'static [u8], _: usize) {}\n\n    fn insert_nodes_after(&mut self, _: ElementId, _: usize) {}\n\n    fn insert_nodes_before(&mut self, _: ElementId, _: usize) {}\n\n    fn set_attribute(\n        &mut self,\n        _: &'static str,\n        _: Option<&'static str>,\n        _: &AttributeValue,\n        _: ElementId,\n    ) {\n    }\n\n    fn set_node_text(&mut self, _: &str, _: ElementId) {}\n\n    fn create_event_listener(&mut self, _: &'static str, _: ElementId) {}\n\n    fn remove_event_listener(&mut self, _: &'static str, _: ElementId) {}\n\n    fn remove_node(&mut self, _: ElementId) {}\n\n    fn push_root(&mut self, _: ElementId) {}\n}\n"
  },
  {
    "path": "packages/core/src/nodes.rs",
    "content": "use crate::{\n    any_props::BoxedAnyProps,\n    arena::ElementId,\n    events::ListenerCallback,\n    innerlude::{ElementRef, MountId, ScopeState, VProps},\n    properties::ComponentFunction,\n    Element, Event, Properties, ScopeId, VirtualDom,\n};\nuse dioxus_core_types::DioxusFormattable;\nuse std::ops::Deref;\nuse std::rc::Rc;\nuse std::vec;\nuse std::{\n    any::{Any, TypeId},\n    cell::Cell,\n    fmt::{Arguments, Debug},\n};\n\n/// The information about the\n#[derive(Debug)]\npub(crate) struct VNodeMount {\n    /// The parent of this node\n    pub parent: Option<ElementRef>,\n\n    /// A back link to the original node\n    pub node: VNode,\n\n    /// The IDs for the roots of this template - to be used when moving the template around and removing it from\n    /// the actual Dom\n    pub root_ids: Box<[ElementId]>,\n\n    /// The element in the DOM that each attribute is mounted to\n    pub(crate) mounted_attributes: Box<[ElementId]>,\n\n    /// For components: This is the ScopeId the component is mounted to\n    /// For other dynamic nodes: This is element in the DOM that each dynamic node is mounted to\n    pub(crate) mounted_dynamic_nodes: Box<[usize]>,\n}\n\n/// A reference to a template along with any context needed to hydrate it\n///\n/// The dynamic parts of the template are stored separately from the static parts. This allows faster diffing by skipping\n/// static parts of the template.\n#[derive(Debug)]\npub struct VNodeInner {\n    /// The key given to the root of this template.\n    ///\n    /// In fragments, this is the key of the first child. In other cases, it is the key of the root.\n    pub key: Option<String>,\n\n    /// The static nodes and static descriptor of the template\n    pub template: Template,\n\n    /// The dynamic nodes in the template\n    pub dynamic_nodes: Box<[DynamicNode]>,\n\n    /// The dynamic attribute slots in the template\n    ///\n    /// This is a list of positions in the template where dynamic attributes can be inserted.\n    ///\n    /// The inner list *must* be in the format [static named attributes, remaining dynamically named attributes].\n    ///\n    /// For example:\n    /// ```rust\n    /// # use dioxus::prelude::*;\n    /// let class = \"my-class\";\n    /// let attrs = vec![];\n    /// let color = \"red\";\n    ///\n    /// rsx! {\n    ///     div {\n    ///         class: \"{class}\",\n    ///         ..attrs,\n    ///         p {\n    ///             color: \"{color}\",\n    ///         }\n    ///     }\n    /// };\n    /// ```\n    ///\n    /// Would be represented as:\n    /// ```text\n    /// [\n    ///     [class, every attribute in attrs sorted by name], // Slot 0 in the template\n    ///     [color], // Slot 1 in the template\n    /// ]\n    /// ```\n    pub dynamic_attrs: Box<[Box<[Attribute]>]>,\n}\n\n/// A reference to a template along with any context needed to hydrate it\n///\n/// The dynamic parts of the template are stored separately from the static parts. This allows faster diffing by skipping\n/// static parts of the template.\n#[derive(Debug, Clone)]\npub struct VNode {\n    vnode: Rc<VNodeInner>,\n\n    /// The mount information for this template\n    pub(crate) mount: Cell<MountId>,\n}\n\nimpl Default for VNode {\n    fn default() -> Self {\n        Self::placeholder()\n    }\n}\n\nimpl PartialEq for VNode {\n    fn eq(&self, other: &Self) -> bool {\n        Rc::ptr_eq(&self.vnode, &other.vnode)\n    }\n}\n\nimpl Deref for VNode {\n    type Target = VNodeInner;\n\n    fn deref(&self) -> &Self::Target {\n        &self.vnode\n    }\n}\n\nimpl VNode {\n    /// Create a template with no nodes that will be skipped over during diffing\n    pub fn empty() -> Element {\n        Ok(Self::default())\n    }\n\n    /// Create a template with a single placeholder node\n    pub fn placeholder() -> Self {\n        use std::cell::OnceCell;\n        // We can reuse all placeholders across the same thread to save memory\n        thread_local! {\n            static PLACEHOLDER_VNODE: OnceCell<Rc<VNodeInner>> = const { OnceCell::new() };\n        }\n        let vnode = PLACEHOLDER_VNODE.with(|cell| {\n            cell.get_or_init(move || {\n                Rc::new(VNodeInner {\n                    key: None,\n                    dynamic_nodes: Box::new([DynamicNode::Placeholder(Default::default())]),\n                    dynamic_attrs: Box::new([]),\n                    template: Template {\n                        roots: &[TemplateNode::Dynamic { id: 0 }],\n                        node_paths: &[&[0]],\n                        attr_paths: &[],\n                    },\n                })\n            })\n            .clone()\n        });\n        Self {\n            vnode,\n            mount: Default::default(),\n        }\n    }\n\n    /// Create a new VNode\n    pub fn new(\n        key: Option<String>,\n        template: Template,\n        dynamic_nodes: Box<[DynamicNode]>,\n        dynamic_attrs: Box<[Box<[Attribute]>]>,\n    ) -> Self {\n        Self {\n            vnode: Rc::new(VNodeInner {\n                key,\n                template,\n                dynamic_nodes,\n                dynamic_attrs,\n            }),\n            mount: Default::default(),\n        }\n    }\n\n    /// Load a dynamic root at the given index\n    ///\n    /// Returns [`None`] if the root is actually a static node (Element/Text)\n    pub fn dynamic_root(&self, idx: usize) -> Option<&DynamicNode> {\n        self.template.roots[idx]\n            .dynamic_id()\n            .map(|id| &self.dynamic_nodes[id])\n    }\n\n    /// Get the mounted id for a dynamic node index\n    pub fn mounted_dynamic_node(\n        &self,\n        dynamic_node_idx: usize,\n        dom: &VirtualDom,\n    ) -> Option<ElementId> {\n        let mount = self.mount.get().as_usize()?;\n\n        match &self.dynamic_nodes[dynamic_node_idx] {\n            DynamicNode::Text(_) | DynamicNode::Placeholder(_) => {\n                let mounts = dom.runtime.mounts.borrow();\n                mounts\n                    .get(mount)?\n                    .mounted_dynamic_nodes\n                    .get(dynamic_node_idx)\n                    .map(|id| ElementId(*id))\n            }\n            _ => None,\n        }\n    }\n\n    /// Get the mounted id for a root node index\n    pub fn mounted_root(&self, root_idx: usize, dom: &VirtualDom) -> Option<ElementId> {\n        let mount = self.mount.get().as_usize()?;\n\n        let mounts = dom.runtime.mounts.borrow();\n        mounts.get(mount)?.root_ids.get(root_idx).copied()\n    }\n\n    /// Get the mounted id for a dynamic attribute index\n    pub fn mounted_dynamic_attribute(\n        &self,\n        dynamic_attribute_idx: usize,\n        dom: &VirtualDom,\n    ) -> Option<ElementId> {\n        let mount = self.mount.get().as_usize()?;\n\n        let mounts = dom.runtime.mounts.borrow();\n        mounts\n            .get(mount)?\n            .mounted_attributes\n            .get(dynamic_attribute_idx)\n            .copied()\n    }\n\n    /// Create a deep clone of this VNode\n    pub(crate) fn deep_clone(&self) -> Self {\n        Self {\n            vnode: Rc::new(VNodeInner {\n                key: self.vnode.key.clone(),\n                template: self.vnode.template,\n                dynamic_nodes: self\n                    .vnode\n                    .dynamic_nodes\n                    .iter()\n                    .map(|node| match node {\n                        DynamicNode::Fragment(nodes) => DynamicNode::Fragment(\n                            nodes.iter().map(|node| node.deep_clone()).collect(),\n                        ),\n                        other => other.clone(),\n                    })\n                    .collect(),\n                dynamic_attrs: self\n                    .vnode\n                    .dynamic_attrs\n                    .iter()\n                    .map(|attr| {\n                        attr.iter()\n                            .map(|attribute| attribute.deep_clone())\n                            .collect()\n                    })\n                    .collect(),\n            }),\n            mount: Default::default(),\n        }\n    }\n}\n\ntype StaticStr = &'static str;\ntype StaticPathArray = &'static [&'static [u8]];\ntype StaticTemplateArray = &'static [TemplateNode];\ntype StaticTemplateAttributeArray = &'static [TemplateAttribute];\n\n/// A static layout of a UI tree that describes a set of dynamic and static nodes.\n///\n/// This is the core innovation in Dioxus. Most UIs are made of static nodes, yet participate in diffing like any\n/// dynamic node. This struct can be created at compile time. It promises that its pointer is unique, allow Dioxus to use\n/// its static description of the UI to skip immediately to the dynamic nodes during diffing.\n#[cfg_attr(feature = \"serialize\", derive(serde::Serialize, serde::Deserialize))]\n#[derive(Debug, Clone, Copy, Eq, PartialOrd, Ord)]\npub struct Template {\n    /// The list of template nodes that make up the template\n    ///\n    /// Unlike react, calls to `rsx!` can have multiple roots. This list supports that paradigm.\n    #[cfg_attr(feature = \"serialize\", serde(deserialize_with = \"deserialize_leaky\"))]\n    pub roots: StaticTemplateArray,\n\n    /// The paths of each node relative to the root of the template.\n    ///\n    /// These will be one segment shorter than the path sent to the renderer since those paths are relative to the\n    /// topmost element, not the `roots` field.\n    #[cfg_attr(\n        feature = \"serialize\",\n        serde(deserialize_with = \"deserialize_bytes_leaky\")\n    )]\n    pub node_paths: StaticPathArray,\n\n    /// The paths of each dynamic attribute relative to the root of the template\n    ///\n    /// These will be one segment shorter than the path sent to the renderer since those paths are relative to the\n    /// topmost element, not the `roots` field.\n    #[cfg_attr(\n        feature = \"serialize\",\n        serde(deserialize_with = \"deserialize_bytes_leaky\", bound = \"\")\n    )]\n    pub attr_paths: StaticPathArray,\n}\n\n// Are identical static items merged in the current build. Rust doesn't have a cfg(merge_statics) attribute\n// so we have to check this manually\n#[allow(unpredictable_function_pointer_comparisons)] // This attribute should be removed once MSRV is 1.85 or greater and the below change is made\nfn static_items_merged() -> bool {\n    fn a() {}\n    fn b() {}\n    a as fn() == b as fn()\n    // std::ptr::fn_addr_eq(a as fn(), b as fn()) <<<<---- This should replace the a as fn() === b as fn() once the MSRV is 1.85 or greater\n}\n\nimpl std::hash::Hash for Template {\n    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {\n        // If identical static items are merged, we can compare templates by pointer\n        if static_items_merged() {\n            std::ptr::hash(self.roots as *const _, state);\n            std::ptr::hash(self.node_paths as *const _, state);\n            std::ptr::hash(self.attr_paths as *const _, state);\n        }\n        // Otherwise, we hash by value\n        else {\n            self.roots.hash(state);\n            self.node_paths.hash(state);\n            self.attr_paths.hash(state);\n        }\n    }\n}\n\nimpl PartialEq for Template {\n    fn eq(&self, other: &Self) -> bool {\n        // If identical static items are merged, we can compare templates by pointer\n        if static_items_merged() {\n            std::ptr::eq(self.roots as *const _, other.roots as *const _)\n                && std::ptr::eq(self.node_paths as *const _, other.node_paths as *const _)\n                && std::ptr::eq(self.attr_paths as *const _, other.attr_paths as *const _)\n        }\n        // Otherwise, we compare by value\n        else {\n            self.roots == other.roots\n                && self.node_paths == other.node_paths\n                && self.attr_paths == other.attr_paths\n        }\n    }\n}\n\n#[cfg(feature = \"serialize\")]\npub(crate) fn deserialize_string_leaky<'a, 'de, D>(\n    deserializer: D,\n) -> Result<&'static str, D::Error>\nwhere\n    D: serde::Deserializer<'de>,\n{\n    use serde::Deserialize;\n\n    let deserialized = String::deserialize(deserializer)?;\n    Ok(&*Box::leak(deserialized.into_boxed_str()))\n}\n\n#[cfg(feature = \"serialize\")]\nfn deserialize_bytes_leaky<'a, 'de, D>(\n    deserializer: D,\n) -> Result<&'static [&'static [u8]], D::Error>\nwhere\n    D: serde::Deserializer<'de>,\n{\n    use serde::Deserialize;\n\n    let deserialized = Vec::<Vec<u8>>::deserialize(deserializer)?;\n    let deserialized = deserialized\n        .into_iter()\n        .map(|v| &*Box::leak(v.into_boxed_slice()))\n        .collect::<Vec<_>>();\n    Ok(&*Box::leak(deserialized.into_boxed_slice()))\n}\n\n#[cfg(feature = \"serialize\")]\npub(crate) fn deserialize_leaky<'a, 'de, T, D>(deserializer: D) -> Result<&'static [T], D::Error>\nwhere\n    T: serde::Deserialize<'de>,\n    D: serde::Deserializer<'de>,\n{\n    use serde::Deserialize;\n\n    let deserialized = Box::<[T]>::deserialize(deserializer)?;\n    Ok(&*Box::leak(deserialized))\n}\n\n#[cfg(feature = \"serialize\")]\npub(crate) fn deserialize_option_leaky<'a, 'de, D>(\n    deserializer: D,\n) -> Result<Option<&'static str>, D::Error>\nwhere\n    D: serde::Deserializer<'de>,\n{\n    use serde::Deserialize;\n\n    let deserialized = Option::<String>::deserialize(deserializer)?;\n    Ok(deserialized.map(|deserialized| &*Box::leak(deserialized.into_boxed_str())))\n}\n\nimpl Template {\n    /// Is this template worth caching at all, since it's completely runtime?\n    ///\n    /// There's no point in saving templates that are completely dynamic, since they'll be recreated every time anyway.\n    pub fn is_completely_dynamic(&self) -> bool {\n        use TemplateNode::*;\n        self.roots.iter().all(|root| matches!(root, Dynamic { .. }))\n    }\n}\n\n/// A statically known node in a layout.\n///\n/// This can be created at compile time, saving the VirtualDom time when diffing the tree\n#[derive(Debug, Clone, Copy, PartialEq, Hash, Eq, PartialOrd, Ord)]\n#[cfg_attr(\n    feature = \"serialize\",\n    derive(serde::Serialize, serde::Deserialize),\n    serde(tag = \"type\")\n)]\npub enum TemplateNode {\n    /// An statically known element in the dom.\n    ///\n    /// In HTML this would be something like `<div id=\"123\"> </div>`\n    Element {\n        /// The name of the element\n        ///\n        /// IE for a div, it would be the string \"div\"\n        #[cfg_attr(\n            feature = \"serialize\",\n            serde(deserialize_with = \"deserialize_string_leaky\")\n        )]\n        tag: StaticStr,\n\n        /// The namespace of the element\n        ///\n        /// In HTML, this would be a valid URI that defines a namespace for all elements below it\n        /// SVG is an example of this namespace\n        #[cfg_attr(\n            feature = \"serialize\",\n            serde(deserialize_with = \"deserialize_option_leaky\")\n        )]\n        namespace: Option<StaticStr>,\n\n        /// A list of possibly dynamic attributes for this element\n        ///\n        /// An attribute on a DOM node, such as `id=\"my-thing\"` or `href=\"https://example.com\"`.\n        #[cfg_attr(\n            feature = \"serialize\",\n            serde(deserialize_with = \"deserialize_leaky\", bound = \"\")\n        )]\n        attrs: StaticTemplateAttributeArray,\n\n        /// A list of template nodes that define another set of template nodes\n        #[cfg_attr(feature = \"serialize\", serde(deserialize_with = \"deserialize_leaky\"))]\n        children: StaticTemplateArray,\n    },\n\n    /// This template node is just a piece of static text\n    Text {\n        /// The actual text\n        #[cfg_attr(\n            feature = \"serialize\",\n            serde(deserialize_with = \"deserialize_string_leaky\", bound = \"\")\n        )]\n        text: StaticStr,\n    },\n\n    /// This template node is unknown, and needs to be created at runtime.\n    Dynamic {\n        /// The index of the dynamic node in the VNode's dynamic_nodes list\n        id: usize,\n    },\n}\n\nimpl TemplateNode {\n    /// Try to load the dynamic node at the given index\n    pub fn dynamic_id(&self) -> Option<usize> {\n        use TemplateNode::*;\n        match self {\n            Dynamic { id } => Some(*id),\n            _ => None,\n        }\n    }\n}\n\n/// A node created at runtime\n///\n/// This node's index in the DynamicNode list on VNode should match its respective `Dynamic` index\n#[derive(Debug, Clone)]\npub enum DynamicNode {\n    /// A component node\n    ///\n    /// Most of the time, Dioxus will actually know which component this is as compile time, but the props and\n    /// assigned scope are dynamic.\n    ///\n    /// The actual VComponent can be dynamic between two VNodes, though, allowing implementations to swap\n    /// the render function at runtime\n    Component(VComponent),\n\n    /// A text node\n    Text(VText),\n\n    /// A placeholder\n    ///\n    /// Used by suspense when a node isn't ready and by fragments that don't render anything\n    ///\n    /// In code, this is just an ElementId whose initial value is set to 0 upon creation\n    Placeholder(VPlaceholder),\n\n    /// A list of VNodes.\n    ///\n    /// Note that this is not a list of dynamic nodes. These must be VNodes and created through conditional rendering\n    /// or iterators.\n    Fragment(Vec<VNode>),\n}\n\nimpl DynamicNode {\n    /// Convert any item that implements [`IntoDynNode`] into a [`DynamicNode`]\n    pub fn make_node<'c, I>(into: impl IntoDynNode<I> + 'c) -> DynamicNode {\n        into.into_dyn_node()\n    }\n}\n\nimpl Default for DynamicNode {\n    fn default() -> Self {\n        Self::Placeholder(Default::default())\n    }\n}\n\n/// An instance of a child component\npub struct VComponent {\n    /// The name of this component\n    pub name: &'static str,\n\n    /// The raw pointer to the render function\n    pub(crate) render_fn: usize,\n\n    /// The props for this component\n    pub(crate) props: BoxedAnyProps,\n}\n\nimpl Clone for VComponent {\n    fn clone(&self) -> Self {\n        Self {\n            name: self.name,\n            props: self.props.duplicate(),\n            render_fn: self.render_fn,\n        }\n    }\n}\n\nimpl VComponent {\n    /// Create a new [`VComponent`] variant\n    pub fn new<P, M: 'static>(\n        component: impl ComponentFunction<P, M>,\n        props: P,\n        fn_name: &'static str,\n    ) -> Self\n    where\n        P: Properties + 'static,\n    {\n        let render_fn = component.fn_ptr();\n        let props = Box::new(VProps::new(\n            component,\n            <P as Properties>::memoize,\n            props,\n            fn_name,\n        ));\n\n        VComponent {\n            render_fn,\n            name: fn_name,\n            props,\n        }\n    }\n\n    /// Get the [`ScopeId`] this node is mounted to if it's mounted\n    ///\n    /// This is useful for rendering nodes outside of the VirtualDom, such as in SSR\n    ///\n    /// Returns [`None`] if the node is not mounted\n    pub fn mounted_scope_id(\n        &self,\n        dynamic_node_index: usize,\n        vnode: &VNode,\n        dom: &VirtualDom,\n    ) -> Option<ScopeId> {\n        let mount = vnode.mount.get().as_usize()?;\n\n        let mounts = dom.runtime.mounts.borrow();\n        let scope_id = mounts.get(mount)?.mounted_dynamic_nodes[dynamic_node_index];\n\n        Some(ScopeId(scope_id))\n    }\n\n    /// Get the scope this node is mounted to if it's mounted\n    ///\n    /// This is useful for rendering nodes outside of the VirtualDom, such as in SSR\n    ///\n    /// Returns [`None`] if the node is not mounted\n    pub fn mounted_scope<'a>(\n        &self,\n        dynamic_node_index: usize,\n        vnode: &VNode,\n        dom: &'a VirtualDom,\n    ) -> Option<&'a ScopeState> {\n        let mount = vnode.mount.get().as_usize()?;\n\n        let mounts = dom.runtime.mounts.borrow();\n        let scope_id = mounts.get(mount)?.mounted_dynamic_nodes[dynamic_node_index];\n\n        dom.scopes.get(scope_id)\n    }\n}\n\nimpl std::fmt::Debug for VComponent {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_struct(\"VComponent\")\n            .field(\"name\", &self.name)\n            .finish()\n    }\n}\n\n/// A text node\n#[derive(Clone, Debug)]\npub struct VText {\n    /// The actual text itself\n    pub value: String,\n}\n\nimpl VText {\n    /// Create a new VText\n    pub fn new(value: impl ToString) -> Self {\n        Self {\n            value: value.to_string(),\n        }\n    }\n}\n\nimpl From<Arguments<'_>> for VText {\n    fn from(args: Arguments) -> Self {\n        Self::new(args.to_string())\n    }\n}\n\n/// A placeholder node, used by suspense and fragments\n#[derive(Clone, Debug, Default)]\n#[non_exhaustive]\npub struct VPlaceholder {}\n\n/// An attribute of the TemplateNode, created at compile time\n#[derive(Debug, PartialEq, Hash, Eq, PartialOrd, Ord)]\n#[cfg_attr(\n    feature = \"serialize\",\n    derive(serde::Serialize, serde::Deserialize),\n    serde(tag = \"type\")\n)]\npub enum TemplateAttribute {\n    /// This attribute is entirely known at compile time, enabling\n    Static {\n        /// The name of this attribute.\n        ///\n        /// For example, the `href` attribute in `href=\"https://example.com\"`, would have the name \"href\"\n        #[cfg_attr(\n            feature = \"serialize\",\n            serde(deserialize_with = \"deserialize_string_leaky\", bound = \"\")\n        )]\n        name: StaticStr,\n\n        /// The value of this attribute, known at compile time\n        ///\n        /// Currently this only accepts &str, so values, even if they're known at compile time, are not known\n        #[cfg_attr(\n            feature = \"serialize\",\n            serde(deserialize_with = \"deserialize_string_leaky\", bound = \"\")\n        )]\n        value: StaticStr,\n\n        /// The namespace of this attribute. Does not exist in the HTML spec\n        #[cfg_attr(\n            feature = \"serialize\",\n            serde(deserialize_with = \"deserialize_option_leaky\", bound = \"\")\n        )]\n        namespace: Option<StaticStr>,\n    },\n\n    /// The attribute in this position is actually determined dynamically at runtime\n    ///\n    /// This is the index into the dynamic_attributes field on the container VNode\n    Dynamic {\n        /// The index\n        id: usize,\n    },\n}\n\n/// An attribute on a DOM node, such as `id=\"my-thing\"` or `href=\"https://example.com\"`\n#[derive(Debug, Clone, PartialEq)]\npub struct Attribute {\n    /// The name of the attribute.\n    pub name: &'static str,\n\n    /// The value of the attribute\n    pub value: AttributeValue,\n\n    /// The namespace of the attribute.\n    ///\n    /// Doesn’t exist in the html spec. Used in Dioxus to denote “style” tags and other attribute groups.\n    pub namespace: Option<&'static str>,\n\n    /// An indication of we should always try and set the attribute. Used in controlled components to ensure changes are propagated\n    pub volatile: bool,\n}\n\nimpl Attribute {\n    /// Create a new [`Attribute`] from a name, value, namespace, and volatile bool\n    ///\n    /// \"Volatile\" refers to whether or not Dioxus should always override the value. This helps prevent the UI in\n    /// some renderers stay in sync with the VirtualDom's understanding of the world\n    pub fn new<T>(\n        name: &'static str,\n        value: impl IntoAttributeValue<T>,\n        namespace: Option<&'static str>,\n        volatile: bool,\n    ) -> Attribute {\n        Attribute {\n            name,\n            namespace,\n            volatile,\n            value: value.into_value(),\n        }\n    }\n\n    /// Create a new deep clone of this attribute\n    pub(crate) fn deep_clone(&self) -> Self {\n        Attribute {\n            name: self.name,\n            namespace: self.namespace,\n            volatile: self.volatile,\n            value: self.value.clone(),\n        }\n    }\n}\n\n/// Any of the built-in values that the Dioxus VirtualDom supports as dynamic attributes on elements\n///\n/// These are built-in to be faster during the diffing process. To use a custom value, use the [`AttributeValue::Any`]\n/// variant.\n#[derive(Clone)]\npub enum AttributeValue {\n    /// Text attribute\n    Text(String),\n\n    /// A float\n    Float(f64),\n\n    /// Signed integer\n    Int(i64),\n\n    /// Boolean\n    Bool(bool),\n\n    /// A listener, like \"onclick\"\n    Listener(ListenerCallback),\n\n    /// An arbitrary value that implements PartialEq and is static\n    Any(Rc<dyn AnyValue>),\n\n    /// A \"none\" value, resulting in the removal of an attribute from the dom\n    None,\n}\n\nimpl AttributeValue {\n    /// Create a new [`AttributeValue`] with the listener variant from a callback\n    ///\n    /// The callback must be confined to the lifetime of the ScopeState\n    pub fn listener<T: 'static>(callback: impl FnMut(Event<T>) + 'static) -> AttributeValue {\n        AttributeValue::Listener(ListenerCallback::new(callback).erase())\n    }\n\n    /// Create a new [`AttributeValue`] with a value that implements [`AnyValue`]\n    pub fn any_value<T: AnyValue>(value: T) -> AttributeValue {\n        AttributeValue::Any(Rc::new(value))\n    }\n}\n\nimpl std::fmt::Debug for AttributeValue {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        match self {\n            Self::Text(arg0) => f.debug_tuple(\"Text\").field(arg0).finish(),\n            Self::Float(arg0) => f.debug_tuple(\"Float\").field(arg0).finish(),\n            Self::Int(arg0) => f.debug_tuple(\"Int\").field(arg0).finish(),\n            Self::Bool(arg0) => f.debug_tuple(\"Bool\").field(arg0).finish(),\n            Self::Listener(_) => f.debug_tuple(\"Listener\").finish(),\n            Self::Any(_) => f.debug_tuple(\"Any\").finish(),\n            Self::None => write!(f, \"None\"),\n        }\n    }\n}\n\nimpl PartialEq for AttributeValue {\n    fn eq(&self, other: &Self) -> bool {\n        match (self, other) {\n            (Self::Text(l0), Self::Text(r0)) => l0 == r0,\n            (Self::Float(l0), Self::Float(r0)) => l0 == r0,\n            (Self::Int(l0), Self::Int(r0)) => l0 == r0,\n            (Self::Bool(l0), Self::Bool(r0)) => l0 == r0,\n            (Self::Listener(l0), Self::Listener(r0)) => l0 == r0,\n            (Self::Any(l0), Self::Any(r0)) => l0.as_ref().any_cmp(r0.as_ref()),\n            (Self::None, Self::None) => true,\n            _ => false,\n        }\n    }\n}\n\n#[doc(hidden)]\npub trait AnyValue: 'static {\n    fn any_cmp(&self, other: &dyn AnyValue) -> bool;\n    fn as_any(&self) -> &dyn Any;\n    fn type_id(&self) -> TypeId {\n        self.as_any().type_id()\n    }\n}\n\nimpl<T: Any + PartialEq + 'static> AnyValue for T {\n    fn any_cmp(&self, other: &dyn AnyValue) -> bool {\n        if let Some(other) = other.as_any().downcast_ref() {\n            self == other\n        } else {\n            false\n        }\n    }\n\n    fn as_any(&self) -> &dyn Any {\n        self\n    }\n}\n\n/// A trait that allows various items to be converted into a dynamic node for the rsx macro\npub trait IntoDynNode<A = ()> {\n    /// Consume this item and produce a DynamicNode\n    fn into_dyn_node(self) -> DynamicNode;\n}\n\nimpl IntoDynNode for () {\n    fn into_dyn_node(self) -> DynamicNode {\n        DynamicNode::default()\n    }\n}\nimpl IntoDynNode for VNode {\n    fn into_dyn_node(self) -> DynamicNode {\n        DynamicNode::Fragment(vec![self])\n    }\n}\nimpl IntoDynNode for DynamicNode {\n    fn into_dyn_node(self) -> DynamicNode {\n        self\n    }\n}\nimpl<T: IntoDynNode> IntoDynNode for Option<T> {\n    fn into_dyn_node(self) -> DynamicNode {\n        match self {\n            Some(val) => val.into_dyn_node(),\n            None => DynamicNode::default(),\n        }\n    }\n}\nimpl IntoDynNode for &Element {\n    fn into_dyn_node(self) -> DynamicNode {\n        match self.as_ref() {\n            Ok(val) => val.into_dyn_node(),\n            _ => DynamicNode::default(),\n        }\n    }\n}\nimpl IntoDynNode for Element {\n    fn into_dyn_node(self) -> DynamicNode {\n        match self {\n            Ok(val) => val.into_dyn_node(),\n            _ => DynamicNode::default(),\n        }\n    }\n}\nimpl IntoDynNode for &Option<VNode> {\n    fn into_dyn_node(self) -> DynamicNode {\n        match self.as_ref() {\n            Some(val) => val.clone().into_dyn_node(),\n            _ => DynamicNode::default(),\n        }\n    }\n}\nimpl IntoDynNode for &str {\n    fn into_dyn_node(self) -> DynamicNode {\n        DynamicNode::Text(VText {\n            value: self.to_string(),\n        })\n    }\n}\nimpl IntoDynNode for String {\n    fn into_dyn_node(self) -> DynamicNode {\n        DynamicNode::Text(VText { value: self })\n    }\n}\nimpl IntoDynNode for Arguments<'_> {\n    fn into_dyn_node(self) -> DynamicNode {\n        DynamicNode::Text(VText {\n            value: self.to_string(),\n        })\n    }\n}\nimpl IntoDynNode for &VNode {\n    fn into_dyn_node(self) -> DynamicNode {\n        DynamicNode::Fragment(vec![self.clone()])\n    }\n}\n\npub trait IntoVNode {\n    fn into_vnode(self) -> VNode;\n}\nimpl IntoVNode for VNode {\n    fn into_vnode(self) -> VNode {\n        self\n    }\n}\nimpl IntoVNode for &VNode {\n    fn into_vnode(self) -> VNode {\n        self.clone()\n    }\n}\nimpl IntoVNode for Element {\n    fn into_vnode(self) -> VNode {\n        match self {\n            Ok(val) => val.into_vnode(),\n            _ => VNode::default(),\n        }\n    }\n}\nimpl IntoVNode for &Element {\n    fn into_vnode(self) -> VNode {\n        match self {\n            Ok(val) => val.into_vnode(),\n            _ => VNode::default(),\n        }\n    }\n}\nimpl IntoVNode for Option<VNode> {\n    fn into_vnode(self) -> VNode {\n        match self {\n            Some(val) => val.into_vnode(),\n            _ => VNode::default(),\n        }\n    }\n}\nimpl IntoVNode for &Option<VNode> {\n    fn into_vnode(self) -> VNode {\n        match self.as_ref() {\n            Some(val) => val.clone().into_vnode(),\n            _ => VNode::default(),\n        }\n    }\n}\nimpl IntoVNode for Option<Element> {\n    fn into_vnode(self) -> VNode {\n        match self {\n            Some(val) => val.into_vnode(),\n            _ => VNode::default(),\n        }\n    }\n}\nimpl IntoVNode for &Option<Element> {\n    fn into_vnode(self) -> VNode {\n        match self.as_ref() {\n            Some(val) => val.clone().into_vnode(),\n            _ => VNode::default(),\n        }\n    }\n}\n\n// Note that we're using the E as a generic but this is never crafted anyways.\npub struct FromNodeIterator;\nimpl<T, I> IntoDynNode<FromNodeIterator> for T\nwhere\n    T: Iterator<Item = I>,\n    I: IntoVNode,\n{\n    fn into_dyn_node(self) -> DynamicNode {\n        let children: Vec<_> = self.into_iter().map(|node| node.into_vnode()).collect();\n\n        if children.is_empty() {\n            DynamicNode::default()\n        } else {\n            DynamicNode::Fragment(children)\n        }\n    }\n}\n\n/// A value that can be converted into an attribute value\npub trait IntoAttributeValue<T = ()> {\n    /// Convert into an attribute value\n    fn into_value(self) -> AttributeValue;\n}\n\nimpl IntoAttributeValue for AttributeValue {\n    fn into_value(self) -> AttributeValue {\n        self\n    }\n}\n\nimpl IntoAttributeValue for &str {\n    fn into_value(self) -> AttributeValue {\n        AttributeValue::Text(self.to_string())\n    }\n}\n\nimpl IntoAttributeValue for String {\n    fn into_value(self) -> AttributeValue {\n        AttributeValue::Text(self)\n    }\n}\n\nimpl IntoAttributeValue for f32 {\n    fn into_value(self) -> AttributeValue {\n        AttributeValue::Float(self as _)\n    }\n}\nimpl IntoAttributeValue for f64 {\n    fn into_value(self) -> AttributeValue {\n        AttributeValue::Float(self)\n    }\n}\n\nimpl IntoAttributeValue for i8 {\n    fn into_value(self) -> AttributeValue {\n        AttributeValue::Int(self as _)\n    }\n}\nimpl IntoAttributeValue for i16 {\n    fn into_value(self) -> AttributeValue {\n        AttributeValue::Int(self as _)\n    }\n}\nimpl IntoAttributeValue for i32 {\n    fn into_value(self) -> AttributeValue {\n        AttributeValue::Int(self as _)\n    }\n}\nimpl IntoAttributeValue for i64 {\n    fn into_value(self) -> AttributeValue {\n        AttributeValue::Int(self)\n    }\n}\nimpl IntoAttributeValue for isize {\n    fn into_value(self) -> AttributeValue {\n        AttributeValue::Int(self as _)\n    }\n}\nimpl IntoAttributeValue for i128 {\n    fn into_value(self) -> AttributeValue {\n        AttributeValue::Int(self as _)\n    }\n}\n\nimpl IntoAttributeValue for u8 {\n    fn into_value(self) -> AttributeValue {\n        AttributeValue::Int(self as _)\n    }\n}\nimpl IntoAttributeValue for u16 {\n    fn into_value(self) -> AttributeValue {\n        AttributeValue::Int(self as _)\n    }\n}\nimpl IntoAttributeValue for u32 {\n    fn into_value(self) -> AttributeValue {\n        AttributeValue::Int(self as _)\n    }\n}\nimpl IntoAttributeValue for u64 {\n    fn into_value(self) -> AttributeValue {\n        AttributeValue::Int(self as _)\n    }\n}\nimpl IntoAttributeValue for usize {\n    fn into_value(self) -> AttributeValue {\n        AttributeValue::Int(self as _)\n    }\n}\nimpl IntoAttributeValue for u128 {\n    fn into_value(self) -> AttributeValue {\n        AttributeValue::Int(self as _)\n    }\n}\n\nimpl IntoAttributeValue for bool {\n    fn into_value(self) -> AttributeValue {\n        AttributeValue::Bool(self)\n    }\n}\n\nimpl IntoAttributeValue for Arguments<'_> {\n    fn into_value(self) -> AttributeValue {\n        AttributeValue::Text(self.to_string())\n    }\n}\n\nimpl IntoAttributeValue for Rc<dyn AnyValue> {\n    fn into_value(self) -> AttributeValue {\n        AttributeValue::Any(self)\n    }\n}\n\nimpl<T> IntoAttributeValue for ListenerCallback<T> {\n    fn into_value(self) -> AttributeValue {\n        AttributeValue::Listener(self.erase())\n    }\n}\n\nimpl<T: IntoAttributeValue> IntoAttributeValue for Option<T> {\n    fn into_value(self) -> AttributeValue {\n        match self {\n            Some(val) => val.into_value(),\n            None => AttributeValue::None,\n        }\n    }\n}\n\npub struct AnyFmtMarker;\nimpl<T> IntoAttributeValue<AnyFmtMarker> for T\nwhere\n    T: DioxusFormattable,\n{\n    fn into_value(self) -> AttributeValue {\n        AttributeValue::Text(self.format().to_string())\n    }\n}\n\n/// A trait for anything that has a dynamic list of attributes\npub trait HasAttributes {\n    /// Push an attribute onto the list of attributes\n    fn push_attribute<T>(\n        self,\n        name: &'static str,\n        ns: Option<&'static str>,\n        attr: impl IntoAttributeValue<T>,\n        volatile: bool,\n    ) -> Self;\n}\n"
  },
  {
    "path": "packages/core/src/properties.rs",
    "content": "use std::fmt::Arguments;\n\nuse crate::innerlude::*;\n\n/// Every \"Props\" used for a component must implement the `Properties` trait. This trait gives some hints to Dioxus\n/// on how to memoize the props and some additional optimizations that can be made. We strongly encourage using the\n/// derive macro to implement the `Properties` trait automatically.\n///\n/// Dioxus requires your props to be 'static, `Clone`, and `PartialEq`. We use the `PartialEq` trait to determine if\n/// the props have changed when we diff the component.\n///\n/// ## Example\n///\n/// ```rust\n/// # use dioxus::prelude::*;\n/// #[derive(Props, PartialEq, Clone)]\n/// struct MyComponentProps {\n///     data: String\n/// }\n///\n/// fn MyComponent(props: MyComponentProps) -> Element {\n///     rsx! {\n///         div { \"Hello {props.data}\" }\n///     }\n/// }\n/// ```\n///\n/// Or even better, derive your entire props struct with the [`#[crate::component]`] macro:\n///\n/// ```rust\n/// # use dioxus::prelude::*;\n/// #[component]\n/// fn MyComponent(data: String) -> Element {\n///     rsx! {\n///         div { \"Hello {data}\" }\n///     }\n/// }\n/// ```\n#[rustversion::attr(\n    since(1.78.0),\n    diagnostic::on_unimplemented(\n        message = \"`Props` is not implemented for `{Self}`\",\n        label = \"Props\",\n        note = \"Props is a trait that is automatically implemented for all structs that can be used as props for a component\",\n        note = \"If you manually created a new properties struct, you may have forgotten to add `#[derive(Props, PartialEq, Clone)]` to your struct\",\n    )\n)]\npub trait Properties: Clone + Sized + 'static {\n    /// The type of the builder for this component.\n    /// Used to create \"in-progress\" versions of the props.\n    type Builder;\n\n    /// Create a builder for this component.\n    fn builder() -> Self::Builder;\n\n    /// Make the old props equal to the new props. Return if the props were equal and should be memoized.\n    fn memoize(&mut self, other: &Self) -> bool;\n\n    /// Create a component from the props.\n    fn into_vcomponent<M: 'static>(self, render_fn: impl ComponentFunction<Self, M>) -> VComponent {\n        let type_name = std::any::type_name_of_val(&render_fn);\n        VComponent::new(render_fn, self, type_name)\n    }\n}\n\nimpl Properties for () {\n    type Builder = EmptyBuilder;\n    fn builder() -> Self::Builder {\n        EmptyBuilder {}\n    }\n    fn memoize(&mut self, _other: &Self) -> bool {\n        true\n    }\n}\n\n/// Root properties never need to be memoized, so we can use a dummy implementation.\npub(crate) struct RootProps<P>(pub P);\n\nimpl<P> Clone for RootProps<P>\nwhere\n    P: Clone,\n{\n    fn clone(&self) -> Self {\n        Self(self.0.clone())\n    }\n}\n\nimpl<P> Properties for RootProps<P>\nwhere\n    P: Clone + 'static,\n{\n    type Builder = P;\n    fn builder() -> Self::Builder {\n        unreachable!(\"Root props technically are never built\")\n    }\n    fn memoize(&mut self, _other: &Self) -> bool {\n        true\n    }\n}\n\n// We allow components to use the () generic parameter if they have no props. This impl enables the \"build\" method\n// that the macros use to anonymously complete prop construction.\npub struct EmptyBuilder;\nimpl EmptyBuilder {\n    pub fn build(self) {}\n}\n\n/// This utility function launches the builder method so that the rsx! macro can use the typed-builder pattern\n/// to initialize a component's props.\npub fn fc_to_builder<P, M>(_: impl ComponentFunction<P, M>) -> <P as Properties>::Builder\nwhere\n    P: Properties,\n{\n    P::builder()\n}\n\n/// Any component that implements the `ComponentFn` trait can be used as a component.\n///\n/// This trait is automatically implemented for functions that are in one of the following forms:\n/// - `fn() -> Element`\n/// - `fn(props: Properties) -> Element`\n///\n/// You can derive it automatically for any function with arguments that implement PartialEq with the `#[component]` attribute:\n/// ```rust\n/// # use dioxus::prelude::*;\n/// #[component]\n/// fn MyComponent(a: u32, b: u32) -> Element {\n///     rsx! { \"a: {a}, b: {b}\" }\n/// }\n/// ```\n#[rustversion::attr(\n    since(1.78.0),\n    diagnostic::on_unimplemented(\n        message = \"`Component<{Props}>` is not implemented for `{Self}`\",\n        label = \"Component\",\n        note = \"Components are functions in the form `fn() -> Element`, `fn(props: Properties) -> Element`, or `#[component] fn(partial_eq1: u32, partial_eq2: u32) -> Element`.\",\n        note = \"You may have forgotten to add `#[component]` to your function to automatically implement the `ComponentFunction` trait.\"\n    )\n)]\npub trait ComponentFunction<Props, Marker = ()>: Clone + 'static {\n    /// Get the raw address of the component render function.\n    fn fn_ptr(&self) -> usize;\n\n    /// Convert the component to a function that takes props and returns an element.\n    fn rebuild(&self, props: Props) -> Element;\n}\n\n/// Accept any callbacks that take props\nimpl<F, P> ComponentFunction<P> for F\nwhere\n    F: Fn(P) -> Element + Clone + 'static,\n{\n    fn rebuild(&self, props: P) -> Element {\n        subsecond::HotFn::current(self.clone()).call((props,))\n    }\n\n    fn fn_ptr(&self) -> usize {\n        subsecond::HotFn::current(self.clone()).ptr_address().0 as usize\n    }\n}\n\n/// Accept any callbacks that take no props\npub struct EmptyMarker;\nimpl<F> ComponentFunction<(), EmptyMarker> for F\nwhere\n    F: Fn() -> Element + Clone + 'static,\n{\n    fn rebuild(&self, props: ()) -> Element {\n        subsecond::HotFn::current(self.clone()).call(props)\n    }\n\n    fn fn_ptr(&self) -> usize {\n        subsecond::HotFn::current(self.clone()).ptr_address().0 as usize\n    }\n}\n\n/// A enhanced version of the `Into` trait that allows with more flexibility.\npub trait SuperInto<O, M = ()> {\n    /// Convert from a type to another type.\n    fn super_into(self) -> O;\n}\n\nimpl<T, O, M> SuperInto<O, M> for T\nwhere\n    O: SuperFrom<T, M>,\n{\n    fn super_into(self) -> O {\n        O::super_from(self)\n    }\n}\n\n/// A enhanced version of the `From` trait that allows with more flexibility.\npub trait SuperFrom<T, M = ()> {\n    /// Convert from a type to another type.\n    fn super_from(_: T) -> Self;\n}\n\n// first implement for all types that are that implement the From trait\nimpl<T, O> SuperFrom<T, ()> for O\nwhere\n    O: From<T>,\n{\n    fn super_from(input: T) -> Self {\n        Self::from(input)\n    }\n}\n\n#[doc(hidden)]\npub struct OptionStringFromMarker;\n\nimpl<'a> SuperFrom<&'a str, OptionStringFromMarker> for Option<String> {\n    fn super_from(input: &'a str) -> Self {\n        Some(String::from(input))\n    }\n}\n\n#[doc(hidden)]\npub struct OptionArgumentsFromMarker;\n\nimpl<'a> SuperFrom<Arguments<'a>, OptionArgumentsFromMarker> for Option<String> {\n    fn super_from(input: Arguments<'a>) -> Self {\n        Some(input.to_string())\n    }\n}\n\n#[doc(hidden)]\npub struct OptionCallbackMarker<T>(std::marker::PhantomData<T>);\n\n// Closure can be created from FnMut -> async { anything } or FnMut -> Ret\nimpl<\n        Function: FnMut(Args) -> Spawn + 'static,\n        Args: 'static,\n        Spawn: SpawnIfAsync<Marker, Ret> + 'static,\n        Ret: 'static,\n        Marker,\n    > SuperFrom<Function, OptionCallbackMarker<Marker>> for Option<Callback<Args, Ret>>\n{\n    fn super_from(input: Function) -> Self {\n        Some(Callback::new(input))\n    }\n}\n\n#[test]\n#[allow(unused)]\nfn optional_callback_compiles() {\n    fn compiles() {\n        // Converting from closures (without type hints in the closure works)\n        let callback: Callback<i32, i32> = (|num| num * num).super_into();\n        let callback: Callback<i32, ()> = (|num| async move { println!(\"{num}\") }).super_into();\n\n        // Converting from closures to optional callbacks works\n        let optional: Option<Callback<i32, i32>> = (|num| num * num).super_into();\n        let optional: Option<Callback<i32, ()>> =\n            (|num| async move { println!(\"{num}\") }).super_into();\n    }\n}\n\n#[test]\n#[allow(unused)]\nfn from_props_compiles() {\n    // T -> T works\n    let option: i32 = 0i32.super_into();\n    let option: i32 = 0.super_into(); // Note we don't need type hints on all inputs\n    let option: i128 = 0.super_into();\n    let option: &'static str = \"hello world\".super_into();\n\n    // // T -> From<T> works\n    let option: i64 = 0i32.super_into();\n    let option: String = \"hello world\".super_into();\n\n    // T -> Option works\n    let option: Option<i32> = 0i32.super_into();\n    let option: Option<i32> = 0.super_into();\n    let option: Option<i128> = 0.super_into();\n    fn takes_option_string<M>(_: impl SuperInto<Option<String>, M>) {}\n    takes_option_string(\"hello world\");\n    takes_option_string(\"hello world\".to_string());\n}\n"
  },
  {
    "path": "packages/core/src/reactive_context.rs",
    "content": "use crate::{current_scope_id, scope_context::Scope, tasks::SchedulerMsg, Runtime, ScopeId};\nuse futures_channel::mpsc::UnboundedReceiver;\nuse generational_box::{BorrowMutError, GenerationalBox, SyncStorage};\nuse std::{\n    cell::RefCell,\n    collections::HashSet,\n    hash::Hash,\n    sync::{Arc, Mutex},\n};\n\n#[doc = include_str!(\"../docs/reactivity.md\")]\n#[derive(Clone, Copy)]\npub struct ReactiveContext {\n    scope: ScopeId,\n    inner: GenerationalBox<Inner, SyncStorage>,\n}\n\nimpl PartialEq for ReactiveContext {\n    fn eq(&self, other: &Self) -> bool {\n        self.inner.ptr_eq(&other.inner)\n    }\n}\n\nimpl Eq for ReactiveContext {}\n\nthread_local! {\n    static CURRENT: RefCell<Vec<ReactiveContext>> = const { RefCell::new(vec![]) };\n}\n\nimpl std::fmt::Display for ReactiveContext {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        #[cfg(debug_assertions)]\n        {\n            if let Ok(read) = self.inner.try_read() {\n                if let Some(scope) = read.scope {\n                    return write!(f, \"ReactiveContext(for scope: {:?})\", scope);\n                }\n                return write!(f, \"ReactiveContext created at {}\", read.origin);\n            }\n        }\n        write!(f, \"ReactiveContext\")\n    }\n}\n\nimpl ReactiveContext {\n    /// Create a new reactive context\n    #[track_caller]\n    pub fn new() -> (Self, UnboundedReceiver<()>) {\n        Self::new_with_origin(std::panic::Location::caller())\n    }\n\n    /// Create a new reactive context with a location for debugging purposes\n    /// This is useful for reactive contexts created within closures\n    pub fn new_with_origin(\n        origin: &'static std::panic::Location<'static>,\n    ) -> (Self, UnboundedReceiver<()>) {\n        let (tx, rx) = futures_channel::mpsc::unbounded();\n        let callback = move || {\n            // If there is already an update queued, we don't need to queue another\n            if !tx.is_empty() {\n                return;\n            }\n            let _ = tx.unbounded_send(());\n        };\n        let _self = Self::new_with_callback(callback, current_scope_id(), origin);\n        (_self, rx)\n    }\n\n    /// Create a new reactive context that may update a scope. When any signal that this context subscribes to changes, the callback will be run\n    pub fn new_with_callback(\n        callback: impl FnMut() + Send + Sync + 'static,\n        scope: ScopeId,\n        #[allow(unused)] origin: &'static std::panic::Location<'static>,\n    ) -> Self {\n        let inner = Inner {\n            self_: None,\n            update: Box::new(callback),\n            subscribers: Default::default(),\n            #[cfg(debug_assertions)]\n            origin,\n            #[cfg(debug_assertions)]\n            scope: None,\n        };\n\n        let owner = Runtime::current().scope_owner(scope);\n\n        let self_ = Self {\n            scope,\n            inner: owner.insert(inner),\n        };\n\n        self_.inner.write().self_ = Some(self_);\n\n        self_\n    }\n\n    /// Get the current reactive context from the nearest reactive hook or scope\n    pub fn current() -> Option<Self> {\n        CURRENT.with(|current| current.borrow().last().cloned())\n    }\n\n    /// Create a reactive context for a scope id\n    pub(crate) fn new_for_scope(scope: &Scope, runtime: &Runtime) -> Self {\n        let id = scope.id;\n        let sender = runtime.sender.clone();\n        let update_scope = move || {\n            _ = sender.unbounded_send(SchedulerMsg::Immediate(id));\n        };\n\n        // Otherwise, create a new context at the current scope\n        let inner = Inner {\n            self_: None,\n            update: Box::new(update_scope),\n            subscribers: Default::default(),\n            #[cfg(debug_assertions)]\n            origin: std::panic::Location::caller(),\n            #[cfg(debug_assertions)]\n            scope: Some(id),\n        };\n\n        let owner = scope.owner();\n\n        let self_ = Self {\n            scope: id,\n            inner: owner.insert(inner),\n        };\n\n        self_.inner.write().self_ = Some(self_);\n\n        self_\n    }\n\n    /// Clear all subscribers to this context\n    pub fn clear_subscribers(&self) {\n        // The key type is mutable, but the hash is stable through mutations because we hash by pointer\n        #[allow(clippy::mutable_key_type)]\n        let old_subscribers = std::mem::take(&mut self.inner.write().subscribers);\n        for subscriber in old_subscribers {\n            subscriber.0.remove(self);\n        }\n    }\n\n    /// Update the subscribers\n    pub(crate) fn update_subscribers(&self) {\n        #[allow(clippy::mutable_key_type)]\n        let subscribers = &self.inner.read().subscribers;\n        for subscriber in subscribers.iter() {\n            subscriber.0.add(*self);\n        }\n    }\n\n    /// Reset the reactive context and then run the callback in the context. This can be used to create custom reactive hooks like `use_memo`.\n    ///\n    /// ```rust, no_run\n    /// # use dioxus::prelude::*;\n    /// # use dioxus_core::ReactiveContext;\n    /// # use futures_util::StreamExt;\n    /// fn use_simplified_memo(mut closure: impl FnMut() -> i32 + 'static) -> Signal<i32> {\n    ///     use_hook(|| {\n    ///         // Create a new reactive context and channel that will receive a value every time a value the reactive context subscribes to changes\n    ///         let (reactive_context, mut changed) = ReactiveContext::new();\n    ///         // Compute the value of the memo inside the reactive context. This will subscribe the reactive context to any values you read inside the closure\n    ///         let value = reactive_context.reset_and_run_in(&mut closure);\n    ///         // Create a new signal with the value of the memo\n    ///         let mut signal = Signal::new(value);\n    ///         // Create a task that reruns the closure when the reactive context changes\n    ///         spawn(async move {\n    ///             while changed.next().await.is_some() {\n    ///                 // Since we reset the reactive context as we run the closure, our memo will only subscribe to the new values that are read in the closure\n    ///                 let new_value = reactive_context.run_in(&mut closure);\n    ///                 if new_value != value {\n    ///                     signal.set(new_value);\n    ///                 }\n    ///             }\n    ///         });\n    ///         signal\n    ///     })\n    /// }\n    ///\n    /// let mut boolean = use_signal(|| false);\n    /// let mut count = use_signal(|| 0);\n    /// // Because we use `reset_and_run_in` instead of just `run_in`, our memo will only subscribe to the signals that are read this run of the closure (initially just the boolean)\n    /// let memo = use_simplified_memo(move || if boolean() { count() } else { 0 });\n    /// println!(\"{memo}\");\n    /// // Because the count signal is not read in this run of the closure, the memo will not rerun\n    /// count += 1;\n    /// println!(\"{memo}\");\n    /// // Because the boolean signal is read in this run of the closure, the memo will rerun\n    /// boolean.toggle();\n    /// println!(\"{memo}\");\n    /// // If we toggle the boolean again, and the memo unsubscribes from the count signal\n    /// boolean.toggle();\n    /// println!(\"{memo}\");\n    /// ```\n    pub fn reset_and_run_in<O>(&self, f: impl FnOnce() -> O) -> O {\n        self.clear_subscribers();\n        self.run_in(f)\n    }\n\n    /// Run this function in the context of this reactive context\n    ///\n    /// This will set the current reactive context to this context for the duration of the function.\n    /// You can then get information about the current subscriptions.\n    pub fn run_in<O>(&self, f: impl FnOnce() -> O) -> O {\n        CURRENT.with(|current| current.borrow_mut().push(*self));\n        let out = f();\n        CURRENT.with(|current| current.borrow_mut().pop());\n        self.update_subscribers();\n        out\n    }\n\n    /// Marks this reactive context as dirty\n    ///\n    /// If there's a scope associated with this context, then it will be marked as dirty too\n    ///\n    /// Returns true if the context was marked as dirty, or false if the context has been dropped\n    pub fn mark_dirty(&self) -> bool {\n        if let Ok(mut self_write) = self.inner.try_write() {\n            #[cfg(debug_assertions)]\n            {\n                tracing::trace!(\n                    \"Marking reactive context created at {} as dirty\",\n                    self_write.origin\n                );\n            }\n\n            (self_write.update)();\n\n            true\n        } else {\n            false\n        }\n    }\n\n    /// Subscribe to this context. The reactive context will automatically remove itself from the subscriptions when it is reset.\n    pub fn subscribe(&self, subscriptions: impl Into<Subscribers>) {\n        match self.inner.try_write() {\n            Ok(mut inner) => {\n                let subscriptions = subscriptions.into();\n                subscriptions.add(*self);\n                inner\n                    .subscribers\n                    .insert(PointerHash(subscriptions.inner.clone()));\n            }\n            // If the context was dropped, we don't need to subscribe to it anymore\n            Err(BorrowMutError::Dropped(_)) => {}\n            Err(expect) => {\n                panic!(\n                    \"Expected to be able to write to reactive context to subscribe, but it failed with: {expect:?}\"\n                );\n            }\n        }\n    }\n\n    /// Get the scope that inner CopyValue is associated with\n    pub fn origin_scope(&self) -> ScopeId {\n        self.scope\n    }\n}\n\nimpl Hash for ReactiveContext {\n    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {\n        self.inner.id().hash(state);\n    }\n}\n\nstruct PointerHash<T: ?Sized>(Arc<T>);\n\nimpl<T: ?Sized> Hash for PointerHash<T> {\n    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {\n        std::sync::Arc::<T>::as_ptr(&self.0).hash(state);\n    }\n}\n\nimpl<T: ?Sized> PartialEq for PointerHash<T> {\n    fn eq(&self, other: &Self) -> bool {\n        std::sync::Arc::ptr_eq(&self.0, &other.0)\n    }\n}\n\nimpl<T: ?Sized> Eq for PointerHash<T> {}\n\nimpl<T: ?Sized> Clone for PointerHash<T> {\n    fn clone(&self) -> Self {\n        Self(self.0.clone())\n    }\n}\n\nstruct Inner {\n    self_: Option<ReactiveContext>,\n\n    // Futures will call .changed().await\n    update: Box<dyn FnMut() + Send + Sync>,\n\n    // Subscribers to this context\n    subscribers: HashSet<PointerHash<dyn SubscriberList + Send + Sync>>,\n\n    // Debug information for signal subscriptions\n    #[cfg(debug_assertions)]\n    origin: &'static std::panic::Location<'static>,\n\n    #[cfg(debug_assertions)]\n    // The scope that this reactive context is associated with\n    scope: Option<ScopeId>,\n}\n\nimpl Drop for Inner {\n    fn drop(&mut self) {\n        let Some(self_) = self.self_.take() else {\n            return;\n        };\n\n        for subscriber in std::mem::take(&mut self.subscribers) {\n            subscriber.0.remove(&self_);\n        }\n    }\n}\n\n/// A list of [ReactiveContext]s that are subscribed. This is used to notify subscribers when the value changes.\n#[derive(Clone)]\npub struct Subscribers {\n    /// The list of subscribers.\n    pub(crate) inner: Arc<dyn SubscriberList + Send + Sync>,\n}\n\nimpl Default for Subscribers {\n    fn default() -> Self {\n        Self::new()\n    }\n}\n\nimpl Subscribers {\n    /// Create a new no-op list of subscribers.\n    pub fn new_noop() -> Self {\n        struct NoopSubscribers;\n        impl SubscriberList for NoopSubscribers {\n            fn add(&self, _subscriber: ReactiveContext) {}\n\n            fn remove(&self, _subscriber: &ReactiveContext) {}\n\n            fn visit(&self, _f: &mut dyn FnMut(&ReactiveContext)) {}\n        }\n        Subscribers {\n            inner: Arc::new(NoopSubscribers),\n        }\n    }\n\n    /// Create a new list of subscribers.\n    pub fn new() -> Self {\n        Subscribers {\n            inner: Arc::new(Mutex::new(HashSet::new())),\n        }\n    }\n\n    /// Add a subscriber to the list.\n    pub fn add(&self, subscriber: ReactiveContext) {\n        self.inner.add(subscriber);\n    }\n\n    /// Remove a subscriber from the list.\n    pub fn remove(&self, subscriber: &ReactiveContext) {\n        self.inner.remove(subscriber);\n    }\n\n    /// Visit all subscribers in the list.\n    pub fn visit(&self, mut f: impl FnMut(&ReactiveContext)) {\n        self.inner.visit(&mut f);\n    }\n}\n\nimpl<S: SubscriberList + Send + Sync + 'static> From<Arc<S>> for Subscribers {\n    fn from(inner: Arc<S>) -> Self {\n        Subscribers { inner }\n    }\n}\n\n/// A list of subscribers that can be notified when the value changes. This is used to track when the value changes and notify subscribers.\npub trait SubscriberList: Send + Sync {\n    /// Add a subscriber to the list.\n    fn add(&self, subscriber: ReactiveContext);\n\n    /// Remove a subscriber from the list.\n    fn remove(&self, subscriber: &ReactiveContext);\n\n    /// Visit all subscribers in the list.\n    fn visit(&self, f: &mut dyn FnMut(&ReactiveContext));\n}\n\nimpl SubscriberList for Mutex<HashSet<ReactiveContext>> {\n    fn add(&self, subscriber: ReactiveContext) {\n        if let Ok(mut lock) = self.lock() {\n            lock.insert(subscriber);\n        } else {\n            tracing::warn!(\"Failed to lock subscriber list to add subscriber: {subscriber}\");\n        }\n    }\n\n    fn remove(&self, subscriber: &ReactiveContext) {\n        if let Ok(mut lock) = self.lock() {\n            lock.remove(subscriber);\n        } else {\n            tracing::warn!(\"Failed to lock subscriber list to remove subscriber: {subscriber}\");\n        }\n    }\n\n    fn visit(&self, f: &mut dyn FnMut(&ReactiveContext)) {\n        if let Ok(lock) = self.lock() {\n            lock.iter().for_each(f);\n        } else {\n            tracing::warn!(\"Failed to lock subscriber list to visit subscribers\");\n        }\n    }\n}\n"
  },
  {
    "path": "packages/core/src/render_error.rs",
    "content": "use std::{\n    fmt::{Debug, Display},\n    sync::Arc,\n};\n\nuse crate::innerlude::*;\n\n/// An error that can occur while rendering a component\n#[derive(Debug, Clone, PartialEq)]\npub enum RenderError {\n    /// The render function returned early due to an error.\n    ///\n    /// We captured the error, wrapped it in an Arc, and stored it here. You can no longer modify the error,\n    /// but you can cheaply pass it around.\n    Error(CapturedError),\n\n    /// The component was suspended\n    Suspended(SuspendedFuture),\n}\n\nimpl Default for RenderError {\n    fn default() -> Self {\n        struct RenderAbortedEarly;\n        impl Debug for RenderAbortedEarly {\n            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n                f.write_str(\"Render aborted early\")\n            }\n        }\n        impl Display for RenderAbortedEarly {\n            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n                f.write_str(\"Render aborted early\")\n            }\n        }\n        impl std::error::Error for RenderAbortedEarly {}\n        Self::Error(CapturedError(Arc::new(RenderAbortedEarly.into())))\n    }\n}\n\nimpl Display for RenderError {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        match self {\n            Self::Error(e) => write!(f, \"Render aborted: {e}\"),\n            Self::Suspended(e) => write!(f, \"Component suspended: {e:?}\"),\n        }\n    }\n}\n\nimpl From<CapturedError> for RenderError {\n    fn from(e: CapturedError) -> Self {\n        Self::Error(e)\n    }\n}\n\nimpl<E: Into<anyhow::Error>> From<E> for RenderError {\n    fn from(e: E) -> Self {\n        let anyhow_err = e.into();\n\n        if let Some(suspended) = anyhow_err.downcast_ref::<SuspendedFuture>() {\n            return Self::Suspended(suspended.clone());\n        }\n\n        if let Some(render_error) = anyhow_err.downcast_ref::<RenderError>() {\n            return render_error.clone();\n        }\n\n        Self::Error(CapturedError(Arc::new(anyhow_err)))\n    }\n}\n\n/// An `anyhow::Error` wrapped in an `Arc` so it can be cheaply cloned and passed around.\n#[derive(Debug, Clone)]\npub struct CapturedError(pub Arc<anyhow::Error>);\n\nimpl CapturedError {\n    /// Create a `CapturedError` from anything that implements `Display`.\n    pub fn from_display(t: impl Display) -> Self {\n        Self(Arc::new(anyhow::anyhow!(t.to_string())))\n    }\n\n    /// Create a `CapturedError` from anything that implements `std::error::Error`.\n    pub fn new<E>(error: E) -> Self\n    where\n        E: std::error::Error + Send + Sync + 'static,\n    {\n        anyhow::Error::new(error).into()\n    }\n\n    /// Create a `CapturedError` from anything that implements `Display` and `Debug`.\n    pub fn msg<M>(t: M) -> Self\n    where\n        M: Display + Debug + Send + Sync + 'static,\n    {\n        anyhow::Error::msg(t).into()\n    }\n\n    /// Create a `CapturedError` from a boxed `std::error::Error`.\n    pub fn from_boxed(boxed_error: Box<dyn std::error::Error + Send + Sync + 'static>) -> Self {\n        anyhow::Error::from_boxed(boxed_error).into()\n    }\n\n    /// Returns the strong count of the underlying error.\n    pub fn _strong_count(&self) -> usize {\n        std::sync::Arc::strong_count(&self.0)\n    }\n\n    /// Try to unwrap the underlying error if this is the only reference to it.\n    pub fn into_inner(self) -> Option<anyhow::Error> {\n        Arc::try_unwrap(self.0).ok()\n    }\n}\n\n#[cfg(feature = \"serialize\")]\nimpl serde::Serialize for CapturedError {\n    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>\n    where\n        S: serde::Serializer,\n    {\n        serializer.serialize_str(&self.0.to_string())\n    }\n}\n\n#[cfg(feature = \"serialize\")]\nimpl<'de> serde::Deserialize<'de> for CapturedError {\n    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>\n    where\n        D: serde::Deserializer<'de>,\n    {\n        let s = String::deserialize(deserializer)?;\n        Ok(Self::from_display(s))\n    }\n}\n\nimpl std::ops::Deref for CapturedError {\n    type Target = anyhow::Error;\n\n    fn deref(&self) -> &Self::Target {\n        &self.0\n    }\n}\n\nimpl<E: Into<anyhow::Error>> From<E> for CapturedError {\n    fn from(e: E) -> Self {\n        Self(Arc::new(e.into()))\n    }\n}\n\nimpl std::fmt::Display for CapturedError {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        std::fmt::Display::fmt(&*self.0, f)\n    }\n}\n\nimpl PartialEq for CapturedError {\n    fn eq(&self, other: &Self) -> bool {\n        Arc::ptr_eq(&self.0, &other.0)\n    }\n}\n\n#[test]\nfn assert_errs_can_downcast() {\n    fn assert_is_stderr_like<T: Send + Sync + Display + Debug>() {}\n\n    assert_is_stderr_like::<RenderError>();\n    assert_is_stderr_like::<CapturedError>();\n}\n"
  },
  {
    "path": "packages/core/src/root_wrapper.rs",
    "content": "use crate::{\n    fc_to_builder, properties::RootProps, DynamicNode, Element, ErrorBoundary, Properties,\n    SuspenseBoundary, Template, TemplateNode, VComponent, VNode,\n};\n\n// We wrap the root scope in a component that renders it inside a default ErrorBoundary and SuspenseBoundary\n#[allow(non_snake_case)]\n#[allow(clippy::let_and_return)]\npub(crate) fn RootScopeWrapper(props: RootProps<VComponent>) -> Element {\n    static TEMPLATE: Template = Template {\n        roots: &[TemplateNode::Dynamic { id: 0usize }],\n        node_paths: &[&[0u8]],\n        attr_paths: &[],\n    };\n    Element::Ok(VNode::new(\n        None,\n        TEMPLATE,\n        Box::new([DynamicNode::Component(\n            fc_to_builder(SuspenseBoundary)\n                .fallback(|_| Element::Ok(VNode::placeholder()))\n                .children(Ok(VNode::new(\n                    None,\n                    TEMPLATE,\n                    Box::new([DynamicNode::Component({\n                        fc_to_builder(ErrorBoundary)\n                            .children(Element::Ok(VNode::new(\n                                None,\n                                TEMPLATE,\n                                Box::new([DynamicNode::Component(props.0)]),\n                                Box::new([]),\n                            )))\n                            .build()\n                            .into_vcomponent(ErrorBoundary)\n                    })]),\n                    Box::new([]),\n                )))\n                .build()\n                .into_vcomponent(SuspenseBoundary),\n        )]),\n        Box::new([]),\n    ))\n}\n"
  },
  {
    "path": "packages/core/src/runtime.rs",
    "content": "use crate::nodes::VNodeMount;\nuse crate::scheduler::ScopeOrder;\nuse crate::scope_context::SuspenseLocation;\nuse crate::{arena::ElementRef, CapturedError};\nuse crate::{\n    innerlude::{DirtyTasks, Effect},\n    SuspenseContext,\n};\nuse crate::{\n    innerlude::{LocalTask, SchedulerMsg},\n    scope_context::Scope,\n    scopes::ScopeId,\n    Task,\n};\nuse crate::{AttributeValue, ElementId, Event};\nuse generational_box::{AnyStorage, Owner};\nuse slab::Slab;\nuse slotmap::DefaultKey;\nuse std::any::Any;\nuse std::collections::BTreeSet;\nuse std::{\n    cell::{Cell, Ref, RefCell},\n    rc::Rc,\n};\nuse tracing::instrument;\n\nthread_local! {\n    static RUNTIMES: RefCell<Vec<Rc<Runtime>>> = const { RefCell::new(vec![]) };\n}\n\n/// A global runtime that is shared across all scopes that provides the async runtime and context API\npub struct Runtime {\n    // We use this to track the current scope\n    // This stack should only be modified through [`Runtime::with_scope_on_stack`] to ensure that the stack is correctly restored\n    scope_stack: RefCell<Vec<ScopeId>>,\n\n    // We use this to track the current suspense location. Generally this lines up with the scope stack, but it may be different for children of a suspense boundary\n    // This stack should only be modified through [`Runtime::with_suspense_location`] to ensure that the stack is correctly restored\n    suspense_stack: RefCell<Vec<SuspenseLocation>>,\n\n    // A hand-rolled slab of scope states\n    pub(crate) scope_states: RefCell<Vec<Option<Scope>>>,\n\n    // We use this to track the current task\n    pub(crate) current_task: Cell<Option<Task>>,\n\n    /// Tasks created with cx.spawn\n    pub(crate) tasks: RefCell<slotmap::SlotMap<DefaultKey, Rc<LocalTask>>>,\n\n    // Currently suspended tasks\n    pub(crate) suspended_tasks: Cell<usize>,\n\n    pub(crate) rendering: Cell<bool>,\n\n    pub(crate) sender: futures_channel::mpsc::UnboundedSender<SchedulerMsg>,\n\n    // The effects that need to be run after the next render\n    pub(crate) pending_effects: RefCell<BTreeSet<Effect>>,\n\n    // Tasks that are waiting to be polled\n    pub(crate) dirty_tasks: RefCell<BTreeSet<DirtyTasks>>,\n\n    // The element ids that are used in the renderer\n    // These mark a specific place in a whole rsx block\n    pub(crate) elements: RefCell<Slab<Option<ElementRef>>>,\n\n    // Once nodes are mounted, the information about where they are mounted is stored here\n    // We need to store this information on the virtual dom so that we know what nodes are mounted where when we bubble events\n    // Each mount is associated with a whole rsx block. [`VirtualDom::elements`] link to a specific node in the block\n    pub(crate) mounts: RefCell<Slab<VNodeMount>>,\n}\n\nimpl Runtime {\n    pub(crate) fn new(sender: futures_channel::mpsc::UnboundedSender<SchedulerMsg>) -> Rc<Self> {\n        let mut elements = Slab::default();\n        // the root element is always given element ID 0 since it's the container for the entire tree\n        elements.insert(None);\n\n        Rc::new(Self {\n            sender,\n            rendering: Cell::new(false),\n            scope_states: Default::default(),\n            scope_stack: Default::default(),\n            suspense_stack: Default::default(),\n            current_task: Default::default(),\n            tasks: Default::default(),\n            suspended_tasks: Default::default(),\n            pending_effects: Default::default(),\n            dirty_tasks: Default::default(),\n            elements: RefCell::new(elements),\n            mounts: Default::default(),\n        })\n    }\n\n    /// Get the current runtime\n    pub fn current() -> Rc<Self> {\n        RUNTIMES\n            .with(|stack| stack.borrow().last().cloned())\n            .unwrap_or_else(|| {\n                panic!(\n                    \"Must be called from inside a Dioxus runtime.\n\nHelp: Some APIs in dioxus require a global runtime to be present.\nIf you are calling one of these APIs from outside of a dioxus runtime\n(typically in a web-sys closure or dynamic library), you will need to\ngrab the runtime from a scope that has it and then move it into your\nnew scope with a runtime guard.\n\nFor example, if you are trying to use dioxus apis from a web-sys\nclosure, you can grab the runtime from the scope it is created in:\n\n```rust\nuse dioxus::prelude::*;\nstatic COUNT: GlobalSignal<i32> = Signal::global(|| 0);\n\n#[component]\nfn MyComponent() -> Element {{\n    use_effect(|| {{\n        // Grab the runtime from the MyComponent scope\n        let runtime = Runtime::current().expect(\\\"Components run in the Dioxus runtime\\\");\n        // Move the runtime into the web-sys closure scope\n        let web_sys_closure = Closure::new(|| {{\n            // Then create a guard to provide the runtime to the closure\n            let _guard = RuntimeGuard::new(runtime);\n            // and run whatever code needs the runtime\n            tracing::info!(\\\"The count is: {{COUNT}}\\\");\n        }});\n    }})\n}}\n```\"\n                )\n            })\n    }\n\n    /// Try to get the current runtime, returning None if it doesn't exist (outside the context of a dioxus app)\n    pub fn try_current() -> Option<Rc<Self>> {\n        RUNTIMES.with(|stack| stack.borrow().last().cloned())\n    }\n\n    /// Wrap a closure so that it always runs in the runtime that is currently active\n    pub fn wrap_closure<'a, I, O>(f: impl Fn(I) -> O + 'a) -> impl Fn(I) -> O + 'a {\n        let current_runtime = Self::current();\n        move |input| match current_runtime.try_current_scope_id() {\n            Some(scope) => current_runtime.in_scope(scope, || f(input)),\n            None => {\n                let _runtime_guard = RuntimeGuard::new(current_runtime.clone());\n                f(input)\n            }\n        }\n    }\n\n    /// Run a closure with the rendering flag set to true\n    pub(crate) fn while_rendering<T>(&self, f: impl FnOnce() -> T) -> T {\n        self.rendering.set(true);\n        let result = f();\n        self.rendering.set(false);\n        result\n    }\n\n    /// Run a closure with the rendering flag set to false\n    pub(crate) fn while_not_rendering<T>(&self, f: impl FnOnce() -> T) -> T {\n        let previous = self.rendering.get();\n        self.rendering.set(false);\n        let result = f();\n        self.rendering.set(previous);\n        result\n    }\n\n    /// Create a scope context. This slab is synchronized with the scope slab.\n    pub(crate) fn create_scope(&self, context: Scope) {\n        let id = context.id;\n        let mut scopes = self.scope_states.borrow_mut();\n        if scopes.len() <= id.0 {\n            scopes.resize_with(id.0 + 1, Default::default);\n        }\n        scopes[id.0] = Some(context);\n    }\n\n    pub(crate) fn remove_scope(self: &Rc<Self>, id: ScopeId) {\n        {\n            let borrow = self.scope_states.borrow();\n            if let Some(scope) = &borrow[id.0] {\n                // Manually drop tasks, hooks, and contexts inside of the runtime\n                self.in_scope(id, || {\n                    // Drop all spawned tasks - order doesn't matter since tasks don't rely on eachother\n                    // In theory nested tasks might not like this\n                    for id in scope.spawned_tasks.take() {\n                        self.remove_task(id);\n                    }\n\n                    // Drop all queued effects\n                    self.pending_effects\n                        .borrow_mut()\n                        .remove(&ScopeOrder::new(scope.height, scope.id));\n\n                    // Drop all hooks in reverse order in case a hook depends on another hook.\n                    for hook in scope.hooks.take().drain(..).rev() {\n                        drop(hook);\n                    }\n\n                    // Drop all contexts\n                    scope.shared_contexts.take();\n                });\n            }\n        }\n        self.scope_states.borrow_mut()[id.0].take();\n    }\n\n    /// Get the owner for the current scope.\n    #[track_caller]\n    pub fn current_owner<S: AnyStorage>(&self) -> Owner<S> {\n        self.get_state(self.current_scope_id()).owner()\n    }\n\n    /// Get the owner for the current scope.\n    #[track_caller]\n    pub fn scope_owner<S: AnyStorage>(&self, scope: ScopeId) -> Owner<S> {\n        self.get_state(scope).owner()\n    }\n\n    /// Get the current scope id\n    pub fn current_scope_id(&self) -> ScopeId {\n        self.scope_stack.borrow().last().copied().unwrap()\n    }\n\n    /// Try to get the current scope id, returning None if it we aren't actively inside a scope\n    pub fn try_current_scope_id(&self) -> Option<ScopeId> {\n        self.scope_stack.borrow().last().copied()\n    }\n\n    /// Call this function with the current scope set to the given scope\n    #[track_caller]\n    pub fn in_scope<O>(self: &Rc<Self>, id: ScopeId, f: impl FnOnce() -> O) -> O {\n        let _runtime_guard = RuntimeGuard::new(self.clone());\n        {\n            self.push_scope(id);\n        }\n        let o = f();\n        {\n            self.pop_scope();\n        }\n        o\n    }\n\n    /// Get the current suspense location\n    pub(crate) fn current_suspense_location(&self) -> Option<SuspenseLocation> {\n        self.suspense_stack.borrow().last().cloned()\n    }\n\n    /// Run a callback a [`SuspenseLocation`] at the top of the stack\n    pub(crate) fn with_suspense_location<O>(\n        &self,\n        suspense_location: SuspenseLocation,\n        f: impl FnOnce() -> O,\n    ) -> O {\n        self.suspense_stack.borrow_mut().push(suspense_location);\n        let o = f();\n        self.suspense_stack.borrow_mut().pop();\n        o\n    }\n\n    /// Run a callback with the current scope at the top of the stack\n    pub(crate) fn with_scope_on_stack<O>(&self, scope: ScopeId, f: impl FnOnce() -> O) -> O {\n        self.push_scope(scope);\n        let o = f();\n        self.pop_scope();\n        o\n    }\n\n    /// Push a scope onto the stack\n    fn push_scope(&self, scope: ScopeId) {\n        let suspense_location = self\n            .scope_states\n            .borrow()\n            .get(scope.0)\n            .and_then(|s| s.as_ref())\n            .map(|s| s.suspense_location())\n            .unwrap_or_default();\n        self.suspense_stack.borrow_mut().push(suspense_location);\n        self.scope_stack.borrow_mut().push(scope);\n    }\n\n    /// Pop a scope off the stack\n    fn pop_scope(&self) {\n        self.scope_stack.borrow_mut().pop();\n        self.suspense_stack.borrow_mut().pop();\n    }\n\n    /// Get the state for any scope given its ID\n    ///\n    /// This is useful for inserting or removing contexts from a scope, or rendering out its root node\n    pub(crate) fn get_state(&self, id: ScopeId) -> Ref<'_, Scope> {\n        Ref::filter_map(self.scope_states.borrow(), |scopes| {\n            scopes.get(id.0).and_then(|f| f.as_ref())\n        })\n        .ok()\n        .unwrap()\n    }\n\n    /// Get the state for any scope given its ID\n    ///\n    /// This is useful for inserting or removing contexts from a scope, or rendering out its root node\n    pub(crate) fn try_get_state(&self, id: ScopeId) -> Option<Ref<'_, Scope>> {\n        Ref::filter_map(self.scope_states.borrow(), |contexts| {\n            contexts.get(id.0).and_then(|f| f.as_ref())\n        })\n        .ok()\n    }\n\n    /// Pushes a new scope onto the stack\n    pub(crate) fn push(runtime: Rc<Runtime>) {\n        RUNTIMES.with(|stack| stack.borrow_mut().push(runtime));\n    }\n\n    /// Pops a scope off the stack\n    pub(crate) fn pop() {\n        RUNTIMES.with(|stack| stack.borrow_mut().pop().unwrap());\n    }\n\n    /// Runs a function with the current runtime\n    pub(crate) fn with<R>(callback: impl FnOnce(&Runtime) -> R) -> R {\n        callback(&Self::current())\n    }\n\n    /// Runs a function with the current scope\n    pub(crate) fn with_current_scope<R>(callback: impl FnOnce(&Scope) -> R) -> R {\n        Self::with(|rt| Self::with_scope(rt.current_scope_id(), callback))\n    }\n\n    /// Runs a function with the current scope\n    pub(crate) fn with_scope<R>(scope: ScopeId, callback: impl FnOnce(&Scope) -> R) -> R {\n        let rt = Runtime::current();\n        Self::in_scope(&rt, scope, || callback(&rt.get_state(scope)))\n    }\n\n    /// Finish a render. This will mark all effects as ready to run and send the render signal.\n    pub(crate) fn finish_render(&self) {\n        // If there are new effects we can run, send a message to the scheduler to run them (after the renderer has applied the mutations)\n        if !self.pending_effects.borrow().is_empty() {\n            self.sender\n                .unbounded_send(SchedulerMsg::EffectQueued)\n                .expect(\"Scheduler should exist\");\n        }\n    }\n\n    /// Check if we should render a scope\n    pub(crate) fn scope_should_render(&self, scope_id: ScopeId) -> bool {\n        // If there are no suspended futures, we know the scope is not  and we can skip context checks\n        if self.suspended_tasks.get() == 0 {\n            return true;\n        }\n\n        // If this is not a suspended scope, and we are under a frozen context, then we should\n        let scopes = self.scope_states.borrow();\n        let scope = &scopes[scope_id.0].as_ref().unwrap();\n        !matches!(scope.suspense_location(), SuspenseLocation::UnderSuspense(suspense) if suspense.is_suspended())\n    }\n\n    /// Call a listener inside the VirtualDom with data from outside the VirtualDom. **The ElementId passed in must be the id of an element with a listener, not a static node or a text node.**\n    ///\n    /// This method will identify the appropriate element. The data must match up with the listener declared. Note that\n    /// this method does not give any indication as to the success of the listener call. If the listener is not found,\n    /// nothing will happen.\n    ///\n    /// It is up to the listeners themselves to mark nodes as dirty.\n    ///\n    /// If you have multiple events, you can call this method multiple times before calling \"render_with_deadline\"\n    #[instrument(skip(self, event), level = \"trace\", name = \"Runtime::handle_event\")]\n    pub fn handle_event(self: &Rc<Self>, name: &str, event: Event<dyn Any>, element: ElementId) {\n        let _runtime = RuntimeGuard::new(self.clone());\n        let elements = self.elements.borrow();\n\n        if let Some(Some(parent_path)) = elements.get(element.0).copied() {\n            if event.propagates() {\n                self.handle_bubbling_event(parent_path, name, event);\n            } else {\n                self.handle_non_bubbling_event(parent_path, name, event);\n            }\n        }\n    }\n\n    /*\n    ------------------------\n    The algorithm works by walking through the list of dynamic attributes, checking their paths, and breaking when\n    we find the target path.\n\n    With the target path, we try and move up to the parent until there is no parent.\n    Due to how bubbling works, we call the listeners before walking to the parent.\n\n    If we wanted to do capturing, then we would accumulate all the listeners and call them in reverse order.\n    ----------------------\n\n    For a visual demonstration, here we present a tree on the left and whether or not a listener is collected on the\n    right.\n\n    |           <-- yes (is ascendant)\n    | | |       <-- no  (is not direct ascendant)\n    | |         <-- yes (is ascendant)\n    | | | | |   <--- target element, break early, don't check other listeners\n    | | |       <-- no, broke early\n    |           <-- no, broke early\n    */\n    #[instrument(\n        skip(self, uievent),\n        level = \"trace\",\n        name = \"VirtualDom::handle_bubbling_event\"\n    )]\n    fn handle_bubbling_event(&self, parent: ElementRef, name: &str, uievent: Event<dyn Any>) {\n        let mounts = self.mounts.borrow();\n\n        // If the event bubbles, we traverse through the tree until we find the target element.\n        // Loop through each dynamic attribute (in a depth first order) in this template before moving up to the template's parent.\n        let mut parent = Some(parent);\n        while let Some(path) = parent {\n            let mut listeners = vec![];\n\n            let Some(mount) = mounts.get(path.mount.0) else {\n                // If the node is suspended and not mounted, we can just ignore the event\n                return;\n            };\n            let el_ref = &mount.node;\n            let node_template = el_ref.template;\n            let target_path = path.path;\n\n            // Accumulate listeners into the listener list bottom to top\n            for (idx, this_path) in node_template.attr_paths.iter().enumerate() {\n                let attrs = &*el_ref.dynamic_attrs[idx];\n\n                for attr in attrs.iter() {\n                    // Remove the \"on\" prefix if it exists, TODO, we should remove this and settle on one\n                    if attr.name.get(2..) == Some(name) && target_path.is_descendant(this_path) {\n                        listeners.push(&attr.value);\n\n                        // Break if this is the exact target element.\n                        // This means we won't call two listeners with the same name on the same element. This should be\n                        // documented, or be rejected from the rsx! macro outright\n                        if target_path == this_path {\n                            break;\n                        }\n                    }\n                }\n            }\n\n            // Now that we've accumulated all the parent attributes for the target element, call them in reverse order\n            // We check the bubble state between each call to see if the event has been stopped from bubbling\n            tracing::event!(\n                tracing::Level::TRACE,\n                \"Calling {} listeners\",\n                listeners.len()\n            );\n            for listener in listeners.into_iter().rev() {\n                if let AttributeValue::Listener(listener) = listener {\n                    listener.call(uievent.clone());\n                    let metadata = uievent.metadata.borrow();\n\n                    if !metadata.propagates {\n                        return;\n                    }\n                }\n            }\n\n            let mount = el_ref.mount.get().as_usize();\n            parent = mount.and_then(|id| mounts.get(id).and_then(|el| el.parent));\n        }\n    }\n\n    /// Call an event listener in the simplest way possible without bubbling upwards\n    #[instrument(\n        skip(self, uievent),\n        level = \"trace\",\n        name = \"VirtualDom::handle_non_bubbling_event\"\n    )]\n    fn handle_non_bubbling_event(&self, node: ElementRef, name: &str, uievent: Event<dyn Any>) {\n        let mounts = self.mounts.borrow();\n        let Some(mount) = mounts.get(node.mount.0) else {\n            // If the node is suspended and not mounted, we can just ignore the event\n            return;\n        };\n        let el_ref = &mount.node;\n        let node_template = el_ref.template;\n        let target_path = node.path;\n\n        for (idx, this_path) in node_template.attr_paths.iter().enumerate() {\n            let attrs = &*el_ref.dynamic_attrs[idx];\n\n            for attr in attrs.iter() {\n                // Remove the \"on\" prefix if it exists, TODO, we should remove this and settle on one\n                // Only call the listener if this is the exact target element.\n                if attr.name.get(2..) == Some(name) && target_path == this_path {\n                    if let AttributeValue::Listener(listener) = &attr.value {\n                        listener.call(uievent.clone());\n                        break;\n                    }\n                }\n            }\n        }\n    }\n\n    /// Consume context from the current scope\n    pub fn consume_context<T: 'static + Clone>(&self, id: ScopeId) -> Option<T> {\n        self.get_state(id).consume_context::<T>()\n    }\n\n    /// Consume context from the current scope\n    pub fn consume_context_from_scope<T: 'static + Clone>(&self, scope_id: ScopeId) -> Option<T> {\n        self.get_state(scope_id).consume_context::<T>()\n    }\n\n    /// Check if the current scope has a context\n    pub fn has_context<T: 'static + Clone>(&self, id: ScopeId) -> Option<T> {\n        self.get_state(id).has_context::<T>()\n    }\n\n    /// Provide context to the current scope\n    pub fn provide_context<T: 'static + Clone>(&self, id: ScopeId, value: T) -> T {\n        self.get_state(id).provide_context(value)\n    }\n\n    /// Get the parent of the current scope if it exists\n    pub fn parent_scope(&self, scope: ScopeId) -> Option<ScopeId> {\n        self.get_state(scope).parent_id()\n    }\n\n    /// Check if the current scope is a descendant of the given scope\n    pub fn is_descendant_of(&self, us: ScopeId, other: ScopeId) -> bool {\n        let mut current = us;\n        while let Some(parent) = self.parent_scope(current) {\n            if parent == other {\n                return true;\n            }\n            current = parent;\n        }\n        false\n    }\n\n    /// Mark the current scope as dirty, causing it to re-render\n    pub fn needs_update(&self, scope: ScopeId) {\n        self.get_state(scope).needs_update();\n    }\n\n    /// Get the height of the current scope\n    pub fn height(&self, id: ScopeId) -> u32 {\n        self.get_state(id).height\n    }\n\n    /// Throw a [`CapturedError`] into a scope. The error will bubble up to the nearest [`ErrorBoundary`](crate::ErrorBoundary) or the root of the app.\n    ///\n    /// # Examples\n    /// ```rust, no_run\n    /// # use dioxus::prelude::*;\n    /// fn Component() -> Element {\n    ///     let request = spawn(async move {\n    ///         match reqwest::get(\"https://api.example.com\").await {\n    ///             Ok(_) => unimplemented!(),\n    ///             // You can explicitly throw an error into a scope with throw_error\n    ///             Err(err) => dioxus::core::Runtime::current().throw_error(ScopeId::APP, err),\n    ///         }\n    ///     });\n    ///\n    ///     unimplemented!()\n    /// }\n    /// ```\n    pub fn throw_error(&self, id: ScopeId, error: impl Into<CapturedError> + 'static) {\n        let error = error.into();\n        if let Some(cx) = self.consume_context::<crate::ErrorContext>(id) {\n            cx.insert_error(error)\n        } else {\n            tracing::error!(\n                    \"Tried to throw an error into an error boundary, but failed to locate a boundary: {:?}\",\n                    error\n                )\n        }\n    }\n\n    /// Get the suspense context the current scope is in\n    pub fn suspense_context(&self) -> Option<SuspenseContext> {\n        self.get_state(self.current_scope_id())\n            .suspense_location()\n            .suspense_context()\n            .cloned()\n    }\n\n    /// Force every component to be dirty and require a re-render. Used by hot-reloading.\n    ///\n    /// This might need to change to a different flag in the event hooks order changes within components.\n    /// What we really need is a way to mark components as needing a complete rebuild if they were hit by changes.\n    pub fn force_all_dirty(&self) {\n        self.scope_states.borrow_mut().iter().for_each(|state| {\n            if let Some(scope) = state.as_ref() {\n                scope.needs_update();\n            }\n        });\n    }\n\n    /// Check if the virtual dom is currently rendering\n    pub fn vdom_is_rendering(&self) -> bool {\n        self.rendering.get()\n    }\n}\n\n/// A guard for a new runtime. This must be used to override the current runtime when importing components from a dynamic library that has it's own runtime.\n///\n/// ```rust\n/// use dioxus::prelude::*;\n/// use dioxus_core::{Runtime, RuntimeGuard};\n///\n/// fn main() {\n///     let virtual_dom = VirtualDom::new(app);\n/// }\n///\n/// fn app() -> Element {\n///     rsx! { Component { runtime: Runtime::current() } }\n/// }\n///\n/// // In a dynamic library\n/// #[derive(Props, Clone)]\n/// struct ComponentProps {\n///    runtime: std::rc::Rc<Runtime>,\n/// }\n///\n/// impl PartialEq for ComponentProps {\n///     fn eq(&self, _other: &Self) -> bool {\n///         true\n///     }\n/// }\n///\n/// fn Component(cx: ComponentProps) -> Element {\n///     use_hook(|| {\n///         let _guard = RuntimeGuard::new(cx.runtime.clone());\n///     });\n///\n///     rsx! { div {} }\n/// }\n/// ```\npub struct RuntimeGuard(());\n\nimpl RuntimeGuard {\n    /// Create a new runtime guard that sets the current Dioxus runtime. The runtime will be reset when the guard is dropped\n    pub fn new(runtime: Rc<Runtime>) -> Self {\n        Runtime::push(runtime);\n        Self(())\n    }\n}\n\nimpl Drop for RuntimeGuard {\n    fn drop(&mut self) {\n        Runtime::pop();\n    }\n}\n"
  },
  {
    "path": "packages/core/src/scheduler.rs",
    "content": "//! # Dioxus uses a scheduler to run queued work in the correct order.\n//!\n//! ## Goals\n//! We try to prevent three different situations:\n//! 1. Running queued work after it could be dropped. Related issues (<https://github.com/DioxusLabs/dioxus/pull/1993>)\n//!\n//! User code often assumes that this property is true. For example, if this code reruns the child component after signal is changed to None, it will panic\n//! ```rust, ignore\n//! fn ParentComponent() -> Element {\n//!     let signal: Signal<Option<i32>> = use_signal(None);\n//!\n//!     rsx! {\n//!         if signal.read().is_some() {\n//!             ChildComponent { signal }\n//!         }\n//!     }\n//! }\n//!\n//! #[component]\n//! fn ChildComponent(signal: WriteSignal<Option<i32>>) -> Element {\n//!     // It feels safe to assume that signal is some because the parent component checked that it was some\n//!     rsx! { \"{signal.read().unwrap()}\" }\n//! }\n//! ```\n//!\n//! 2. Running effects before the dom is updated. Related issues (<https://github.com/DioxusLabs/dioxus/issues/2307>)\n//!\n//! Effects can be used to run code that accesses the DOM directly. They should only run when the DOM is in an updated state. If they are run with an out of date version of the DOM, unexpected behavior can occur.\n//! ```rust, ignore\n//! fn EffectComponent() -> Element {\n//!     let id = use_signal(0);\n//!     use_effect(move || {\n//!         let id = id.read();\n//!         // This will panic if the id is not written to the DOM before the effect is run\n//!         document::eval(format!(r#\"document.getElementById(\"{id}\").innerHTML = \"Hello World\";\"#));\n//!     });\n//!\n//!     rsx! {\n//!         div { id: \"{id}\" }\n//!     }\n//! }\n//! ```\n//!\n//! 3. Observing out of date state. Related issues (<https://github.com/DioxusLabs/dioxus/issues/1935>)\n//!\n//! Where ever possible, updates should happen in an order that makes it impossible to observe an out of date state.\n//! ```rust, ignore\n//! fn OutOfDateComponent() -> Element {\n//!     let id = use_signal(0);\n//!     // When you read memo, it should **always** be two times the value of id\n//!     let memo = use_memo(move || id() * 2);\n//!     assert_eq!(memo(), id() * 2);\n//!\n//!     // This should be true even if you update the value of id in the middle of the component\n//!     id += 1;\n//!     assert_eq!(memo(), id() * 2);\n//!\n//!     rsx! {\n//!         div { id: \"{id}\" }\n//!     }\n//! }\n//! ```\n//!\n//! ## Implementation\n//!\n//! There are three different types of queued work that can be run by the virtualdom:\n//! 1. Dirty Scopes:\n//!    Description: When a scope is marked dirty, a rerun of the scope will be scheduled. This will cause the scope to rerun and update the DOM if any changes are detected during the diffing phase.\n//!    Priority: These are the highest priority tasks. Dirty scopes will be rerun in order from the scope closest to the root to the scope furthest from the root. We follow this order to ensure that if a higher component reruns and drops a lower component, the lower component will not be run after it should be dropped.\n//!\n//! 2. Tasks:\n//!    Description: Futures spawned in the dioxus runtime each have an unique task id. When the waker for that future is called, the task is rerun.\n//!    Priority: These are the second highest priority tasks. They are run after all other dirty scopes have been resolved because those dirty scopes may cause children (and the tasks those children own) to drop which should cancel the futures.\n//!\n//! 3. Effects:\n//!    Description: Effects should always run after all changes to the DOM have been applied.\n//!    Priority: These are the lowest priority tasks in the scheduler. They are run after all other dirty scopes and futures have been resolved. Other tasks may cause components to rerun, which would update the DOM. These effects should only run after the DOM has been updated.\n\nuse crate::innerlude::Effect;\nuse crate::ScopeId;\nuse crate::Task;\nuse crate::VirtualDom;\nuse std::borrow::Borrow;\nuse std::cell::RefCell;\nuse std::collections::VecDeque;\nuse std::hash::Hash;\n\n#[derive(Debug, Clone, Copy, Eq)]\npub struct ScopeOrder {\n    pub(crate) height: u32,\n    pub(crate) id: ScopeId,\n}\n\nimpl ScopeOrder {\n    pub fn new(height: u32, id: ScopeId) -> Self {\n        Self { height, id }\n    }\n}\n\nimpl PartialEq for ScopeOrder {\n    fn eq(&self, other: &Self) -> bool {\n        self.id == other.id\n    }\n}\n\nimpl PartialOrd for ScopeOrder {\n    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {\n        Some(self.cmp(other))\n    }\n}\n\nimpl Ord for ScopeOrder {\n    fn cmp(&self, other: &Self) -> std::cmp::Ordering {\n        self.height.cmp(&other.height).then(self.id.cmp(&other.id))\n    }\n}\n\nimpl Hash for ScopeOrder {\n    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {\n        self.id.hash(state);\n    }\n}\n\nimpl VirtualDom {\n    /// Queue a task to be polled\n    pub(crate) fn queue_task(&mut self, task: Task, order: ScopeOrder) {\n        let mut dirty_tasks = self.runtime.dirty_tasks.borrow_mut();\n        match dirty_tasks.get(&order) {\n            Some(scope) => scope.queue_task(task),\n            None => {\n                let scope = DirtyTasks::from(order);\n                scope.queue_task(task);\n                dirty_tasks.insert(scope);\n            }\n        }\n    }\n\n    /// Queue a scope to be rerendered\n    pub(crate) fn queue_scope(&mut self, order: ScopeOrder) {\n        self.dirty_scopes.insert(order);\n    }\n\n    /// Check if there are any dirty scopes\n    pub(crate) fn has_dirty_scopes(&self) -> bool {\n        !self.dirty_scopes.is_empty()\n    }\n\n    /// Take the top task from the highest scope\n    pub(crate) fn pop_task(&mut self) -> Option<Task> {\n        let mut dirty_tasks = self.runtime.dirty_tasks.borrow_mut();\n        let tasks = dirty_tasks.first()?;\n\n        // The scope that owns the effect should still exist. We can't just ignore the task if the scope doesn't exist\n        // because the scope id may have been reallocated\n        debug_assert!(self.scopes.contains(tasks.order.id.0));\n\n        let mut tasks = tasks.tasks_queued.borrow_mut();\n        let task = tasks.pop_front()?;\n        if tasks.is_empty() {\n            drop(tasks);\n            dirty_tasks.pop_first();\n        }\n        Some(task)\n    }\n\n    /// Take any effects from the highest scope. This should only be called if there is no pending scope reruns or tasks\n    pub(crate) fn pop_effect(&mut self) -> Option<Effect> {\n        let mut pending_effects = self.runtime.pending_effects.borrow_mut();\n        let effect = pending_effects.pop_first()?;\n\n        // The scope that owns the effect should still exist. We can't just ignore the effect if the scope doesn't exist\n        // because the scope id may have been reallocated\n        debug_assert!(self.scopes.contains(effect.order.id.0));\n\n        Some(effect)\n    }\n\n    /// Take any work from the highest scope. This may include rerunning the scope and/or running tasks\n    pub(crate) fn pop_work(&mut self) -> Option<Work> {\n        let dirty_scope = self.dirty_scopes.first();\n        // Make sure the top dirty scope is valid\n        #[cfg(debug_assertions)]\n        if let Some(scope) = dirty_scope {\n            assert!(self.scopes.contains(scope.id.0));\n        }\n\n        // Find the height of the highest dirty scope\n        let dirty_task = {\n            let mut dirty_tasks = self.runtime.dirty_tasks.borrow_mut();\n            let mut dirty_task = dirty_tasks.first();\n            // Pop any invalid tasks off of each dirty scope;\n            while let Some(task) = dirty_task {\n                if task.tasks_queued.borrow().is_empty() {\n                    dirty_tasks.pop_first();\n                    dirty_task = dirty_tasks.first()\n                } else {\n                    break;\n                }\n            }\n            dirty_task.map(|task| task.order)\n        };\n\n        match (dirty_scope, dirty_task) {\n            (Some(scope), Some(task)) => {\n                let tasks_order = task.borrow();\n                match scope.cmp(tasks_order) {\n                    std::cmp::Ordering::Less => {\n                        let scope = self.dirty_scopes.pop_first().unwrap();\n                        Some(Work::RerunScope(scope))\n                    }\n                    std::cmp::Ordering::Equal | std::cmp::Ordering::Greater => {\n                        Some(Work::PollTask(self.pop_task().unwrap()))\n                    }\n                }\n            }\n            (Some(_), None) => {\n                let scope = self.dirty_scopes.pop_first().unwrap();\n                Some(Work::RerunScope(scope))\n            }\n            (None, Some(_)) => Some(Work::PollTask(self.pop_task().unwrap())),\n            (None, None) => None,\n        }\n    }\n}\n\n#[derive(Debug)]\npub enum Work {\n    RerunScope(ScopeOrder),\n    PollTask(Task),\n}\n\n#[derive(Debug, Clone, Eq)]\npub(crate) struct DirtyTasks {\n    pub order: ScopeOrder,\n    pub tasks_queued: RefCell<VecDeque<Task>>,\n}\n\nimpl From<ScopeOrder> for DirtyTasks {\n    fn from(order: ScopeOrder) -> Self {\n        Self {\n            order,\n            tasks_queued: VecDeque::new().into(),\n        }\n    }\n}\n\nimpl DirtyTasks {\n    pub fn queue_task(&self, task: Task) {\n        let mut borrow_mut = self.tasks_queued.borrow_mut();\n        // If the task is already queued, we don't need to do anything\n        if borrow_mut.contains(&task) {\n            return;\n        }\n        borrow_mut.push_back(task);\n    }\n\n    pub(crate) fn remove(&self, id: Task) {\n        self.tasks_queued.borrow_mut().retain(|task| *task != id);\n    }\n}\n\nimpl Borrow<ScopeOrder> for DirtyTasks {\n    fn borrow(&self) -> &ScopeOrder {\n        &self.order\n    }\n}\n\nimpl Ord for DirtyTasks {\n    fn cmp(&self, other: &Self) -> std::cmp::Ordering {\n        self.order.cmp(&other.order)\n    }\n}\nimpl PartialOrd for DirtyTasks {\n    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {\n        Some(self.cmp(other))\n    }\n}\n\nimpl PartialEq for DirtyTasks {\n    fn eq(&self, other: &Self) -> bool {\n        self.order == other.order\n    }\n}\n\nimpl Hash for DirtyTasks {\n    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {\n        self.order.hash(state);\n    }\n}\n"
  },
  {
    "path": "packages/core/src/scope_arena.rs",
    "content": "use crate::{\n    any_props::{AnyProps, BoxedAnyProps},\n    innerlude::{RenderError, ScopeOrder, ScopeState},\n    scope_context::{Scope, SuspenseLocation},\n    scopes::ScopeId,\n    virtual_dom::VirtualDom,\n    Element, ReactiveContext,\n};\n\nimpl VirtualDom {\n    pub(super) fn new_scope(\n        &mut self,\n        props: BoxedAnyProps,\n        name: &'static str,\n    ) -> &mut ScopeState {\n        let parent_id = self.runtime.try_current_scope_id();\n        let height = match parent_id.and_then(|id| self.runtime.try_get_state(id)) {\n            Some(parent) => parent.height() + 1,\n            None => 0,\n        };\n        let suspense_boundary = self\n            .runtime\n            .current_suspense_location()\n            .unwrap_or(SuspenseLocation::NotSuspended);\n        let entry = self.scopes.vacant_entry();\n        let id = ScopeId(entry.key());\n\n        let scope_runtime = Scope::new(name, id, parent_id, height, suspense_boundary);\n        let reactive_context = ReactiveContext::new_for_scope(&scope_runtime, &self.runtime);\n\n        let scope = entry.insert(ScopeState {\n            runtime: self.runtime.clone(),\n            context_id: id,\n            props,\n            last_rendered_node: Default::default(),\n            reactive_context,\n        });\n\n        self.runtime.create_scope(scope_runtime);\n\n        scope\n    }\n\n    /// Run a scope and return the rendered nodes. This will not modify the DOM or update the last rendered node of the scope.\n    #[tracing::instrument(skip(self), level = \"trace\", name = \"VirtualDom::run_scope\")]\n    #[track_caller]\n    pub(crate) fn run_scope(&mut self, scope_id: ScopeId) -> Element {\n        // Ensure we are currently inside a `Runtime`.\n        crate::Runtime::current();\n\n        self.runtime.clone().with_scope_on_stack(scope_id, || {\n            let scope = &self.scopes[scope_id.0];\n            let output = {\n                let scope_state = scope.state();\n\n                scope_state.hook_index.set(0);\n\n                // Run all pre-render hooks\n                for pre_run in scope_state.before_render.borrow_mut().iter_mut() {\n                    pre_run();\n                }\n\n                let props: &dyn AnyProps = &*scope.props;\n\n                let span = tracing::trace_span!(\"render\", scope = %scope.state().name);\n                span.in_scope(|| {\n                    scope.reactive_context.reset_and_run_in(|| {\n                        let render_return = props.render();\n                        // After the component is run, we need to do a deep clone of the VNode. This\n                        // breaks any references to mounted parts of the VNode from the component.\n                        // Without this, the component could store a mounted version of the VNode\n                        // which causes a lot of issues for diffing because we expect only the old\n                        // or new node to be mounted.\n                        //\n                        // For example, the dog app example returns rsx from a resource. Every time\n                        // the component runs, it returns a clone of the last rsx that was returned from\n                        // that resource. If we don't deep clone the VNode and the resource changes, then\n                        // we could end up diffing two different versions of the same mounted node\n                        let mut render_return = match render_return {\n                            Ok(node) => Ok(node.deep_clone()),\n                            Err(RenderError::Error(err)) => Err(RenderError::Error(err.clone())),\n                            Err(RenderError::Suspended(fut)) => {\n                                Err(RenderError::Suspended(fut.deep_clone()))\n                            }\n                        };\n\n                        self.handle_element_return(&mut render_return, &scope.state());\n                        render_return\n                    })\n                })\n            };\n\n            let scope_state = scope.state();\n\n            // Run all post-render hooks\n            for post_run in scope_state.after_render.borrow_mut().iter_mut() {\n                post_run();\n            }\n\n            // remove this scope from dirty scopes\n            self.dirty_scopes\n                .remove(&ScopeOrder::new(scope_state.height, scope_id));\n            output\n        })\n    }\n\n    /// Insert any errors, or suspended tasks from an element return into the runtime\n    fn handle_element_return(&self, node: &mut Element, scope: &Scope) {\n        match node {\n            Err(RenderError::Error(e)) => {\n                tracing::error!(\"Error while rendering component `{}`: {e}\", scope.name);\n                self.runtime.throw_error(scope.id, e.clone());\n            }\n            Err(RenderError::Suspended(e)) => {\n                let task = e.task();\n                // Insert the task into the nearest suspense boundary if it exists\n                let boundary = scope.suspense_location();\n                let already_suspended = self\n                    .runtime\n                    .tasks\n                    .borrow()\n                    .get(task.id)\n                    .expect(\"Suspended on a task that no longer exists\")\n                    .suspend(boundary.clone());\n                if !already_suspended {\n                    tracing::trace!(\"Suspending {:?} on {:?}\", scope.id, task);\n                    // Add this task to the suspended tasks list of the boundary\n                    if let SuspenseLocation::UnderSuspense(boundary) = &boundary {\n                        boundary.add_suspended_task(e.clone());\n                    }\n                    self.runtime\n                        .suspended_tasks\n                        .set(self.runtime.suspended_tasks.get() + 1);\n                }\n            }\n            Ok(_) => {\n                // If the render was successful, we can move the render generation forward by one\n                scope.render_count.set(scope.render_count.get() + 1);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "packages/core/src/scope_context.rs",
    "content": "use crate::{\n    innerlude::{SchedulerMsg, SuspenseContext},\n    Runtime, ScopeId, Task,\n};\nuse generational_box::{AnyStorage, Owner};\nuse rustc_hash::FxHashSet;\nuse std::{\n    any::Any,\n    cell::{Cell, RefCell},\n    future::Future,\n    sync::Arc,\n};\n\npub(crate) enum ScopeStatus {\n    Mounted,\n    Unmounted {\n        // Before the component is mounted, we need to keep track of effects that need to be run once the scope is mounted\n        effects_queued: Vec<Box<dyn FnOnce() + 'static>>,\n    },\n}\n\n#[derive(Debug, Clone, Default)]\npub(crate) enum SuspenseLocation {\n    #[default]\n    NotSuspended,\n    SuspenseBoundary(SuspenseContext),\n    UnderSuspense(SuspenseContext),\n    InSuspensePlaceholder(SuspenseContext),\n}\n\nimpl SuspenseLocation {\n    pub(crate) fn suspense_context(&self) -> Option<&SuspenseContext> {\n        match self {\n            SuspenseLocation::InSuspensePlaceholder(context) => Some(context),\n            SuspenseLocation::UnderSuspense(context) => Some(context),\n            SuspenseLocation::SuspenseBoundary(context) => Some(context),\n            _ => None,\n        }\n    }\n}\n\n/// A component's state separate from its props.\n///\n/// This struct exists to provide a common interface for all scopes without relying on generics.\npub(crate) struct Scope {\n    pub(crate) name: &'static str,\n    pub(crate) id: ScopeId,\n    pub(crate) parent_id: Option<ScopeId>,\n    pub(crate) height: u32,\n    pub(crate) render_count: Cell<usize>,\n\n    // Note: the order of the hook and context fields is important. The hooks field must be dropped before the contexts field in case a hook drop implementation tries to access a context.\n    pub(crate) hooks: RefCell<Vec<Box<dyn Any>>>,\n    pub(crate) hook_index: Cell<usize>,\n    pub(crate) shared_contexts: RefCell<Vec<Box<dyn Any>>>,\n    pub(crate) spawned_tasks: RefCell<FxHashSet<Task>>,\n    pub(crate) before_render: RefCell<Vec<Box<dyn FnMut()>>>,\n    pub(crate) after_render: RefCell<Vec<Box<dyn FnMut()>>>,\n\n    /// The suspense boundary that this scope is currently in (if any)\n    suspense_boundary: SuspenseLocation,\n\n    pub(crate) status: RefCell<ScopeStatus>,\n}\n\nimpl Scope {\n    pub(crate) fn new(\n        name: &'static str,\n        id: ScopeId,\n        parent_id: Option<ScopeId>,\n        height: u32,\n        suspense_boundary: SuspenseLocation,\n    ) -> Self {\n        Self {\n            name,\n            id,\n            parent_id,\n            height,\n            render_count: Cell::new(0),\n            shared_contexts: RefCell::new(vec![]),\n            spawned_tasks: RefCell::new(FxHashSet::default()),\n            hooks: RefCell::new(vec![]),\n            hook_index: Cell::new(0),\n            before_render: RefCell::new(vec![]),\n            after_render: RefCell::new(vec![]),\n            status: RefCell::new(ScopeStatus::Unmounted {\n                effects_queued: Vec::new(),\n            }),\n            suspense_boundary,\n        }\n    }\n\n    pub fn parent_id(&self) -> Option<ScopeId> {\n        self.parent_id\n    }\n\n    fn sender(&self) -> futures_channel::mpsc::UnboundedSender<SchedulerMsg> {\n        Runtime::current().sender.clone()\n    }\n\n    /// Mount the scope and queue any pending effects if it is not already mounted\n    pub(crate) fn mount(&self, runtime: &Runtime) {\n        let mut status = self.status.borrow_mut();\n        if let ScopeStatus::Unmounted { effects_queued } = &mut *status {\n            for f in effects_queued.drain(..) {\n                runtime.queue_effect_on_mounted_scope(self.id, f);\n            }\n            *status = ScopeStatus::Mounted;\n        }\n    }\n\n    /// Get the suspense location of this scope\n    pub(crate) fn suspense_location(&self) -> SuspenseLocation {\n        self.suspense_boundary.clone()\n    }\n\n    /// If this scope is a suspense boundary, return the suspense context\n    pub(crate) fn suspense_boundary(&self) -> Option<SuspenseContext> {\n        match self.suspense_location() {\n            SuspenseLocation::SuspenseBoundary(context) => Some(context),\n            _ => None,\n        }\n    }\n\n    /// Check if a node should run during suspense\n    pub(crate) fn should_run_during_suspense(&self) -> bool {\n        let Some(context) = self.suspense_boundary.suspense_context() else {\n            return false;\n        };\n\n        !context.frozen()\n    }\n\n    /// Mark this scope as dirty, and schedule a render for it.\n    pub(crate) fn needs_update(&self) {\n        self.needs_update_any(self.id)\n    }\n\n    /// Mark this scope as dirty, and schedule a render for it.\n    pub(crate) fn needs_update_any(&self, id: ScopeId) {\n        self.sender()\n            .unbounded_send(SchedulerMsg::Immediate(id))\n            .expect(\"Scheduler to exist if scope exists\");\n    }\n\n    /// Create a subscription that schedules a future render for the referenced component.\n    ///\n    /// Note: you should prefer using [`Self::schedule_update_any`] and [`Self::id`].\n    ///\n    /// Note: The function returned by this method will schedule an update for the current component even if it has already updated between when `schedule_update` was called and when the returned function is called.\n    /// If the desired behavior is to invalidate the current rendering of the current component (and no-op if already invalidated)\n    /// [`subscribe`](crate::reactive_context::ReactiveContext::subscribe) to the [`current`](crate::reactive_context::ReactiveContext::current) [`ReactiveContext`](crate::reactive_context::ReactiveContext) instead.\n    pub(crate) fn schedule_update(&self) -> Arc<dyn Fn() + Send + Sync + 'static> {\n        let (chan, id) = (self.sender(), self.id);\n        Arc::new(move || drop(chan.unbounded_send(SchedulerMsg::Immediate(id))))\n    }\n\n    /// Schedule an update for any component given its [`ScopeId`].\n    ///\n    /// A component's [`ScopeId`] can be obtained from `use_hook` or the [`current_scope_id`](crate::current_scope_id) method.\n    ///\n    /// This method should be used when you want to schedule an update for a component.\n    ///\n    /// Note: It does not matter when `schedule_update_any` is called: the returned function will invalidate what ever generation of the specified component is current when returned function is called.\n    /// If the desired behavior is to schedule invalidation of the current rendering of a component, use [`ReactiveContext`](crate::reactive_context::ReactiveContext) instead.\n    pub(crate) fn schedule_update_any(&self) -> Arc<dyn Fn(ScopeId) + Send + Sync> {\n        let chan = self.sender();\n        Arc::new(move |id| {\n            _ = chan.unbounded_send(SchedulerMsg::Immediate(id));\n        })\n    }\n\n    /// Get the owner for the current scope.\n    pub(crate) fn owner<S: AnyStorage>(&self) -> Owner<S> {\n        match self.has_context() {\n            Some(rt) => rt,\n            None => {\n                let owner = S::owner();\n                self.provide_context(owner)\n            }\n        }\n    }\n\n    /// Return any context of type T if it exists on this scope\n    pub(crate) fn has_context<T: 'static + Clone>(&self) -> Option<T> {\n        self.shared_contexts\n            .borrow()\n            .iter()\n            .find_map(|any| any.downcast_ref::<T>())\n            .cloned()\n    }\n\n    /// Try to retrieve a shared state with type `T` from any parent scope.\n    ///\n    /// Clones the state if it exists.\n    pub(crate) fn consume_context<T: 'static + Clone>(&self) -> Option<T> {\n        if let Some(this_ctx) = self.has_context::<T>() {\n            return Some(this_ctx);\n        }\n\n        let mut search_parent = self.parent_id;\n\n        Runtime::with(|runtime| {\n            while let Some(parent_id) = search_parent {\n                let parent = runtime.try_get_state(parent_id)?;\n                if let Some(shared) = parent.has_context::<T>() {\n                    return Some(shared);\n                }\n                search_parent = parent.parent_id;\n            }\n            None\n        })\n    }\n\n    /// Inject a `Box<dyn Any>` into the context of this scope\n    pub(crate) fn provide_any_context(&self, mut value: Box<dyn Any>) {\n        let mut contexts = self.shared_contexts.borrow_mut();\n\n        // If the context exists, swap it out for the new value\n        for ctx in contexts.iter_mut() {\n            // Swap the ptr directly\n            if ctx.as_ref().type_id() == value.as_ref().type_id() {\n                std::mem::swap(ctx, &mut value);\n                return;\n            }\n        }\n\n        // Else, just push it\n        contexts.push(value);\n    }\n\n    /// Expose state to children further down the [`crate::VirtualDom`] Tree. Requires `Clone` on the context to allow getting values down the tree.\n    ///\n    /// This is a \"fundamental\" operation and should only be called during initialization of a hook.\n    ///\n    /// For a hook that provides the same functionality, use `use_provide_context` and `use_context` instead.\n    ///\n    /// # Example\n    ///\n    /// ```rust\n    /// # use dioxus::prelude::*;\n    /// #[derive(Clone)]\n    /// struct SharedState(&'static str);\n    ///\n    /// // The parent provides context that is available in all children\n    /// fn app() -> Element {\n    ///     use_hook(|| provide_context(SharedState(\"world\")));\n    ///     rsx!(Child {})\n    /// }\n    ///\n    /// // Any child elements can access the context with the `consume_context` function\n    /// fn Child() -> Element {\n    ///     let state = use_context::<SharedState>();\n    ///     rsx!(div { \"hello {state.0}\" })\n    /// }\n    /// ```\n    pub(crate) fn provide_context<T: 'static + Clone>(&self, value: T) -> T {\n        let mut contexts = self.shared_contexts.borrow_mut();\n\n        // If the context exists, swap it out for the new value\n        for ctx in contexts.iter_mut() {\n            // Swap the ptr directly\n            if let Some(ctx) = ctx.downcast_mut::<T>() {\n                *ctx = value.clone();\n                return value;\n            }\n        }\n\n        // Else, just push it\n        contexts.push(Box::new(value.clone()));\n\n        value\n    }\n\n    /// Provide a context to the root and then consume it\n    ///\n    /// This is intended for \"global\" state management solutions that would rather be implicit for the entire app.\n    /// Things like signal runtimes and routers are examples of \"singletons\" that would benefit from lazy initialization.\n    ///\n    /// Note that you should be checking if the context existed before trying to provide a new one. Providing a context\n    /// when a context already exists will swap the context out for the new one, which may not be what you want.\n    pub(crate) fn provide_root_context<T: 'static + Clone>(&self, context: T) -> T {\n        Runtime::with(|runtime| runtime.get_state(ScopeId::ROOT).provide_context(context))\n    }\n\n    /// Start a new future on the same thread as the rest of the VirtualDom.\n    ///\n    /// **You should generally use `spawn` instead of this method unless you specifically need to need to run a task during suspense**\n    ///\n    /// This future will not contribute to suspense resolving but it will run during suspense.\n    ///\n    /// Because this future runs during suspense, you need to be careful to work with hydration. It is not recommended to do any async IO work in this future, as it can easily cause hydration issues. However, you can use isomorphic tasks to do work that can be consistently replicated on the server and client like logging or responding to state changes.\n    ///\n    /// ```rust, no_run\n    /// # use dioxus::prelude::*;\n    /// # use dioxus_core::spawn_isomorphic;\n    /// // ❌ Do not do requests in isomorphic tasks. It may resolve at a different time on the server and client, causing hydration issues.\n    /// let mut state = use_signal(|| None);\n    /// spawn_isomorphic(async move {\n    ///     state.set(Some(reqwest::get(\"https://api.example.com\").await));\n    /// });\n    ///\n    /// // ✅ You may wait for a signal to change and then log it\n    /// let mut state = use_signal(|| 0);\n    /// spawn_isomorphic(async move {\n    ///     loop {\n    ///         tokio::time::sleep(std::time::Duration::from_secs(1)).await;\n    ///         println!(\"State is {state}\");\n    ///     }\n    /// });\n    /// ```\n    pub(crate) fn spawn_isomorphic(&self, fut: impl Future<Output = ()> + 'static) -> Task {\n        let id = Runtime::with(|rt| rt.spawn_isomorphic(self.id, fut));\n        self.spawned_tasks.borrow_mut().insert(id);\n        id\n    }\n\n    /// Spawns the future and returns the [`Task`]\n    pub(crate) fn spawn(&self, fut: impl Future<Output = ()> + 'static) -> Task {\n        let id = Runtime::with(|rt| rt.spawn(self.id, fut));\n        self.spawned_tasks.borrow_mut().insert(id);\n        id\n    }\n\n    /// Queue an effect to run after the next render\n    pub(crate) fn queue_effect(&self, f: impl FnOnce() + 'static) {\n        Runtime::with(|rt| rt.queue_effect(self.id, f));\n    }\n\n    /// Store a value in the hook list, returning the value.\n    pub(crate) fn use_hook<State: Clone + 'static>(\n        &self,\n        initializer: impl FnOnce() -> State,\n    ) -> State {\n        let cur_hook = self.hook_index.get();\n\n        // The hook list works by keeping track of the current hook index and pushing the index forward\n        // while retrieving the hook value.\n        self.hook_index.set(cur_hook + 1);\n\n        let mut hooks = self.hooks\n            .try_borrow_mut()\n            .expect(\"The hook list is already borrowed: This error is likely caused by trying to use  hook inside a hook which violates the rules of hooks.\");\n\n        // Try and retrieve the hook value if it exists\n        if let Some(existing) = self.use_hook_inner::<State>(&mut hooks, cur_hook) {\n            return existing;\n        }\n\n        // Otherwise, initialize the hook value. In debug mode, we allow hook types to change after a hot patch\n        self.push_hook_value(&mut hooks, cur_hook, initializer())\n    }\n\n    // The interior version that gets monomorphized by the `State` type but not the `initializer` type.\n    // This helps trim down binary sizes\n    fn use_hook_inner<State: Clone + 'static>(\n        &self,\n        hooks: &mut Vec<Box<dyn std::any::Any>>,\n        cur_hook: usize,\n    ) -> Option<State> {\n        hooks.get(cur_hook).and_then(|inn| {\n            let raw_ref: &dyn Any = inn.as_ref();\n            raw_ref.downcast_ref::<State>().cloned()\n        })\n    }\n\n    /// Push a new hook value or insert the value into the existing slot, warning if this is not after a hot patch\n    fn push_hook_value<State: Clone + 'static>(\n        &self,\n        hooks: &mut Vec<Box<dyn std::any::Any>>,\n        cur_hook: usize,\n        value: State,\n    ) -> State {\n        // If this is a new hook, push it\n        if cur_hook >= hooks.len() {\n            hooks.push(Box::new(value.clone()));\n            return value;\n        }\n\n        // If we're in dev mode, we allow swapping hook values if the hook was initialized at this index\n        if cfg!(debug_assertions) && unsafe { subsecond::get_jump_table().is_some() } {\n            hooks[cur_hook] = Box::new(value.clone());\n            return value;\n        }\n\n        // Otherwise, panic\n        panic!(\n            r#\"Unable to retrieve the hook that was initialized at this index.\n                    Consult the `rules of hooks` to understand how to use hooks properly.\n\n                    You likely used the hook in a conditional. Hooks rely on consistent ordering between renders.\n                    Functions prefixed with \"use\" should never be called conditionally.\n\n                    Help: Run `dx check` to look for check for some common hook errors.\"#\n        );\n    }\n\n    pub(crate) fn push_before_render(&self, f: impl FnMut() + 'static) {\n        self.before_render.borrow_mut().push(Box::new(f));\n    }\n\n    pub(crate) fn push_after_render(&self, f: impl FnMut() + 'static) {\n        self.after_render.borrow_mut().push(Box::new(f));\n    }\n\n    /// Get the current render since the inception of this component\n    ///\n    /// This can be used as a helpful diagnostic when debugging hooks/renders, etc\n    pub(crate) fn generation(&self) -> usize {\n        self.render_count.get()\n    }\n\n    /// Get the height of this scope\n    pub(crate) fn height(&self) -> u32 {\n        self.height\n    }\n}\n"
  },
  {
    "path": "packages/core/src/scopes.rs",
    "content": "use crate::{\n    any_props::BoxedAnyProps, reactive_context::ReactiveContext, scope_context::Scope, Element,\n    RenderError, Runtime, VNode,\n};\nuse std::{cell::Ref, rc::Rc};\n\n/// A component's unique identifier.\n///\n/// `ScopeId` is a `usize` that acts a key for the internal slab of Scopes. This means that the key is not unique across\n/// time. We do try and guarantee that between calls to `wait_for_work`, no ScopeIds will be recycled in order to give\n/// time for any logic that relies on these IDs to properly update.\n#[cfg_attr(feature = \"serialize\", derive(serde::Serialize, serde::Deserialize))]\n#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]\npub struct ScopeId(pub usize);\n\nimpl std::fmt::Debug for ScopeId {\n    #[allow(unused_mut)]\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        let mut builder = f.debug_tuple(\"ScopeId\");\n        let mut builder = builder.field(&self.0);\n        #[cfg(debug_assertions)]\n        {\n            if let Some(scope) = Runtime::try_current()\n                .as_ref()\n                .and_then(|r| r.try_get_state(*self))\n            {\n                builder = builder.field(&scope.name);\n            }\n        }\n        builder.finish()\n    }\n}\n\nimpl ScopeId {\n    /// The ScopeId of the main scope passed into [`crate::VirtualDom::new`].\n    ///\n    /// This scope will last for the entire duration of your app, making it convenient for long-lived state\n    /// that is created dynamically somewhere down the component tree.\n    ///\n    /// # Example\n    ///\n    /// ```rust, no_run\n    /// use dioxus::prelude::*;\n    /// let my_persistent_state = Signal::new_in_scope(String::new(), ScopeId::APP);\n    /// ```\n    // ScopeId(0) is the root scope wrapper\n    // ScopeId(1) is the default suspense boundary\n    // ScopeId(2) is the default error boundary\n    // ScopeId(3) is the users root scope\n    pub const APP: ScopeId = ScopeId(3);\n\n    /// The ScopeId of the topmost error boundary in the tree.\n    pub const ROOT_ERROR_BOUNDARY: ScopeId = ScopeId(2);\n\n    /// The ScopeId of the topmost suspense boundary in the tree.\n    pub const ROOT_SUSPENSE_BOUNDARY: ScopeId = ScopeId(1);\n\n    /// The ScopeId of the topmost scope in the tree.\n    /// This will be higher up in the tree than [`ScopeId::APP`] because dioxus inserts a default [`crate::SuspenseBoundary`] and [`crate::ErrorBoundary`] at the root of the tree.\n    // ScopeId(0) is the root scope wrapper\n    pub const ROOT: ScopeId = ScopeId(0);\n\n    pub(crate) const PLACEHOLDER: ScopeId = ScopeId(usize::MAX);\n\n    pub(crate) fn is_placeholder(&self) -> bool {\n        *self == Self::PLACEHOLDER\n    }\n}\n\n/// A component's rendered state.\n///\n/// This state erases the type of the component's props. It is used to store the state of a component in the runtime.\npub struct ScopeState {\n    pub(crate) runtime: Rc<Runtime>,\n    pub(crate) context_id: ScopeId,\n    /// The last node that has been rendered for this component. This node may not ben mounted\n    /// During suspense, this component can be rendered in the background multiple times\n    pub(crate) last_rendered_node: Option<LastRenderedNode>,\n    pub(crate) props: BoxedAnyProps,\n    pub(crate) reactive_context: ReactiveContext,\n}\n\nimpl ScopeState {\n    /// Get a handle to the currently active head node arena for this Scope\n    ///\n    /// This is useful for traversing the tree outside of the VirtualDom, such as in a custom renderer or in SSR.\n    ///\n    /// Panics if the tree has not been built yet.\n    pub fn root_node(&self) -> &VNode {\n        self.try_root_node()\n            .expect(\"The tree has not been built yet. Make sure to call rebuild on the tree before accessing its nodes.\")\n    }\n\n    /// Try to get a handle to the currently active head node arena for this Scope\n    ///\n    /// This is useful for traversing the tree outside of the VirtualDom, such as in a custom renderer or in SSR.\n    ///\n    /// Returns [`None`] if the tree has not been built yet.\n    pub fn try_root_node(&self) -> Option<&VNode> {\n        match &self.last_rendered_node {\n            Some(LastRenderedNode::Real(vnode)) => Some(vnode),\n            Some(LastRenderedNode::Placeholder(vnode, _)) => Some(vnode),\n            None => None,\n        }\n    }\n\n    /// Returns the scope id of this [`ScopeState`].\n    pub fn id(&self) -> ScopeId {\n        self.context_id\n    }\n\n    pub(crate) fn state(&self) -> Ref<'_, Scope> {\n        self.runtime.get_state(self.context_id)\n    }\n\n    /// Returns the height of this scope in the tree.\n    pub fn height(&self) -> u32 {\n        self.state().height()\n    }\n}\n\n#[derive(Clone, PartialEq, Debug)]\npub enum LastRenderedNode {\n    Real(VNode),\n    Placeholder(VNode, RenderError),\n}\n\nimpl std::ops::Deref for LastRenderedNode {\n    type Target = VNode;\n\n    fn deref(&self) -> &Self::Target {\n        match self {\n            LastRenderedNode::Real(vnode) => vnode,\n            LastRenderedNode::Placeholder(vnode, _err) => vnode,\n        }\n    }\n}\n\nimpl LastRenderedNode {\n    pub fn new(node: Element) -> Self {\n        match node {\n            Ok(vnode) => LastRenderedNode::Real(vnode),\n            Err(err) => LastRenderedNode::Placeholder(VNode::placeholder(), err),\n        }\n    }\n\n    pub fn as_vnode(&self) -> &VNode {\n        match self {\n            LastRenderedNode::Real(vnode) => vnode,\n            LastRenderedNode::Placeholder(vnode, _err) => vnode,\n        }\n    }\n}\n\nimpl Drop for ScopeState {\n    fn drop(&mut self) {\n        self.runtime.remove_scope(self.context_id);\n    }\n}\n"
  },
  {
    "path": "packages/core/src/suspense/component.rs",
    "content": "use crate::{innerlude::*, scope_context::SuspenseLocation};\n\n/// Properties for the [`SuspenseBoundary()`] component.\n#[allow(non_camel_case_types)]\npub struct SuspenseBoundaryProps {\n    fallback: Callback<SuspenseContext, Element>,\n    /// The children of the suspense boundary\n    children: LastRenderedNode,\n}\n\nimpl Clone for SuspenseBoundaryProps {\n    fn clone(&self) -> Self {\n        Self {\n            fallback: self.fallback,\n            children: self.children.clone(),\n        }\n    }\n}\n\nimpl SuspenseBoundaryProps {\n    /**\n    Create a builder for building `SuspenseBoundaryProps`.\n    On the builder, call `.fallback(...)`, `.children(...)`(optional) to set the values of the fields.\n    Finally, call `.build()` to create the instance of `SuspenseBoundaryProps`.\n                        */\n    #[allow(dead_code, clippy::type_complexity)]\n    fn builder() -> SuspenseBoundaryPropsBuilder<((), ())> {\n        SuspenseBoundaryPropsBuilder {\n            owner: Owner::default(),\n            fields: ((), ()),\n            _phantom: ::core::default::Default::default(),\n        }\n    }\n}\n#[must_use]\n#[doc(hidden)]\n#[allow(dead_code, non_camel_case_types, non_snake_case)]\npub struct SuspenseBoundaryPropsBuilder<TypedBuilderFields> {\n    owner: Owner,\n    fields: TypedBuilderFields,\n    _phantom: (),\n}\nimpl Properties for SuspenseBoundaryProps\nwhere\n    Self: Clone,\n{\n    type Builder = SuspenseBoundaryPropsBuilder<((), ())>;\n    fn builder() -> Self::Builder {\n        SuspenseBoundaryProps::builder()\n    }\n    fn memoize(&mut self, new: &Self) -> bool {\n        let equal = self == new;\n        self.fallback.__point_to(&new.fallback);\n        if !equal {\n            let new_clone = new.clone();\n            self.children = new_clone.children;\n        }\n        equal\n    }\n}\n#[doc(hidden)]\n#[allow(dead_code, non_camel_case_types, non_snake_case)]\npub trait SuspenseBoundaryPropsBuilder_Optional<T> {\n    fn into_value<F: FnOnce() -> T>(self, default: F) -> T;\n}\nimpl<T> SuspenseBoundaryPropsBuilder_Optional<T> for () {\n    fn into_value<F: FnOnce() -> T>(self, default: F) -> T {\n        default()\n    }\n}\nimpl<T> SuspenseBoundaryPropsBuilder_Optional<T> for (T,) {\n    fn into_value<F: FnOnce() -> T>(self, _: F) -> T {\n        self.0\n    }\n}\n#[allow(dead_code, non_camel_case_types, missing_docs)]\nimpl<__children> SuspenseBoundaryPropsBuilder<((), __children)> {\n    #[allow(clippy::type_complexity)]\n    pub fn fallback<__Marker>(\n        self,\n        fallback: impl SuperInto<Callback<SuspenseContext, Element>, __Marker>,\n    ) -> SuspenseBoundaryPropsBuilder<((Callback<SuspenseContext, Element>,), __children)> {\n        let fallback = (with_owner(self.owner.clone(), move || {\n            SuperInto::super_into(fallback)\n        }),);\n        let (_, children) = self.fields;\n        SuspenseBoundaryPropsBuilder {\n            owner: self.owner,\n            fields: (fallback, children),\n            _phantom: self._phantom,\n        }\n    }\n}\n#[doc(hidden)]\n#[allow(dead_code, non_camel_case_types, non_snake_case)]\npub enum SuspenseBoundaryPropsBuilder_Error_Repeated_field_fallback {}\n#[doc(hidden)]\n#[allow(dead_code, non_camel_case_types, missing_docs)]\nimpl<__children> SuspenseBoundaryPropsBuilder<((Callback<SuspenseContext, Element>,), __children)> {\n    #[deprecated(note = \"Repeated field fallback\")]\n    #[allow(clippy::type_complexity)]\n    pub fn fallback(\n        self,\n        _: SuspenseBoundaryPropsBuilder_Error_Repeated_field_fallback,\n    ) -> SuspenseBoundaryPropsBuilder<((Callback<SuspenseContext, Element>,), __children)> {\n        self\n    }\n}\n#[allow(dead_code, non_camel_case_types, missing_docs)]\nimpl<__fallback> SuspenseBoundaryPropsBuilder<(__fallback, ())> {\n    #[allow(clippy::type_complexity)]\n    pub fn children(\n        self,\n        children: Element,\n    ) -> SuspenseBoundaryPropsBuilder<(__fallback, (Element,))> {\n        let children = (children,);\n        let (fallback, _) = self.fields;\n        SuspenseBoundaryPropsBuilder {\n            owner: self.owner,\n            fields: (fallback, children),\n            _phantom: self._phantom,\n        }\n    }\n}\n#[doc(hidden)]\n#[allow(dead_code, non_camel_case_types, non_snake_case)]\npub enum SuspenseBoundaryPropsBuilder_Error_Repeated_field_children {}\n#[doc(hidden)]\n#[allow(dead_code, non_camel_case_types, missing_docs)]\nimpl<__fallback> SuspenseBoundaryPropsBuilder<(__fallback, (Element,))> {\n    #[deprecated(note = \"Repeated field children\")]\n    #[allow(clippy::type_complexity)]\n    pub fn children(\n        self,\n        _: SuspenseBoundaryPropsBuilder_Error_Repeated_field_children,\n    ) -> SuspenseBoundaryPropsBuilder<(__fallback, (Element,))> {\n        self\n    }\n}\n#[doc(hidden)]\n#[allow(dead_code, non_camel_case_types, non_snake_case)]\npub enum SuspenseBoundaryPropsBuilder_Error_Missing_required_field_fallback {}\n#[doc(hidden)]\n#[allow(dead_code, non_camel_case_types, missing_docs, clippy::panic)]\nimpl<__children> SuspenseBoundaryPropsBuilder<((), __children)> {\n    #[deprecated(note = \"Missing required field fallback\")]\n    pub fn build(\n        self,\n        _: SuspenseBoundaryPropsBuilder_Error_Missing_required_field_fallback,\n    ) -> SuspenseBoundaryProps {\n        panic!()\n    }\n}\n#[doc(hidden)]\n#[allow(dead_code, non_camel_case_types, missing_docs)]\npub struct SuspenseBoundaryPropsWithOwner {\n    inner: SuspenseBoundaryProps,\n    owner: Owner,\n}\n#[automatically_derived]\n#[allow(dead_code, non_camel_case_types, missing_docs)]\nimpl ::core::clone::Clone for SuspenseBoundaryPropsWithOwner {\n    #[inline]\n    fn clone(&self) -> SuspenseBoundaryPropsWithOwner {\n        SuspenseBoundaryPropsWithOwner {\n            inner: ::core::clone::Clone::clone(&self.inner),\n            owner: ::core::clone::Clone::clone(&self.owner),\n        }\n    }\n}\nimpl PartialEq for SuspenseBoundaryPropsWithOwner {\n    fn eq(&self, other: &Self) -> bool {\n        self.inner.eq(&other.inner)\n    }\n}\nimpl SuspenseBoundaryPropsWithOwner {\n    /// Create a component from the props.\n    pub fn into_vcomponent<M: 'static>(\n        self,\n        render_fn: impl ComponentFunction<SuspenseBoundaryProps, M>,\n    ) -> VComponent {\n        let component_name = std::any::type_name_of_val(&render_fn);\n        VComponent::new(\n            move |wrapper: Self| render_fn.rebuild(wrapper.inner),\n            self,\n            component_name,\n        )\n    }\n}\nimpl Properties for SuspenseBoundaryPropsWithOwner {\n    type Builder = ();\n    fn builder() -> Self::Builder {\n        unreachable!()\n    }\n    fn memoize(&mut self, new: &Self) -> bool {\n        self.inner.memoize(&new.inner)\n    }\n}\n#[allow(dead_code, non_camel_case_types, missing_docs)]\nimpl<__children: SuspenseBoundaryPropsBuilder_Optional<Element>>\n    SuspenseBoundaryPropsBuilder<((Callback<SuspenseContext, Element>,), __children)>\n{\n    pub fn build(self) -> SuspenseBoundaryPropsWithOwner {\n        let (fallback, children) = self.fields;\n        let fallback = fallback.0;\n        let children = SuspenseBoundaryPropsBuilder_Optional::into_value(children, VNode::empty);\n        SuspenseBoundaryPropsWithOwner {\n            inner: SuspenseBoundaryProps {\n                fallback,\n                children: LastRenderedNode::new(children),\n            },\n            owner: self.owner,\n        }\n    }\n}\n#[automatically_derived]\n#[allow(non_camel_case_types)]\nimpl ::core::cmp::PartialEq for SuspenseBoundaryProps {\n    #[inline]\n    fn eq(&self, other: &SuspenseBoundaryProps) -> bool {\n        self.fallback == other.fallback && self.children == other.children\n    }\n}\n\n/// Suspense Boundaries let you render a fallback UI while a child component is suspended.\n///\n/// # Example\n///\n/// ```rust\n/// # use dioxus::prelude::*;\n/// # fn Article() -> Element { rsx! { \"Article\" } }\n/// fn App() -> Element {\n///     rsx! {\n///         SuspenseBoundary {\n///             fallback: |_| rsx! { \"Loading...\" },\n///             Article {}\n///         }\n///     }\n/// }\n/// ```\n#[allow(non_snake_case)]\npub fn SuspenseBoundary(mut __props: SuspenseBoundaryProps) -> Element {\n    unreachable!(\"SuspenseBoundary should not be called directly\")\n}\n#[allow(non_snake_case)]\n#[doc(hidden)]\nmod SuspenseBoundary_completions {\n    #[doc(hidden)]\n    #[allow(non_camel_case_types)]\n    /// This enum is generated to help autocomplete the braces after the component. It does nothing\n    pub enum Component {\n        SuspenseBoundary {},\n    }\n}\nuse generational_box::Owner;\n#[allow(unused)]\npub use SuspenseBoundary_completions::Component::SuspenseBoundary;\n\n/// Suspense has a custom diffing algorithm that diffs the suspended nodes in the background without rendering them\nimpl SuspenseBoundaryProps {\n    /// Try to downcast [`AnyProps`] to [`SuspenseBoundaryProps`]\n    pub(crate) fn downcast_from_props(props: &mut dyn AnyProps) -> Option<&mut Self> {\n        let inner: Option<&mut SuspenseBoundaryPropsWithOwner> = props.props_mut().downcast_mut();\n        inner.map(|inner| &mut inner.inner)\n    }\n\n    pub(crate) fn create<M: WriteMutations>(\n        mount: MountId,\n        idx: usize,\n        component: &VComponent,\n        parent: Option<ElementRef>,\n        dom: &mut VirtualDom,\n        to: Option<&mut M>,\n    ) -> usize {\n        let mut scope_id = ScopeId(dom.get_mounted_dyn_node(mount, idx));\n        // If the ScopeId is a placeholder, we need to load up a new scope for this vcomponent. If it's already mounted, then we can just use that\n        if scope_id.is_placeholder() {\n            {\n                let suspense_context = SuspenseContext::new();\n\n                let suspense_boundary_location =\n                    crate::scope_context::SuspenseLocation::SuspenseBoundary(\n                        suspense_context.clone(),\n                    );\n                dom.runtime\n                    .clone()\n                    .with_suspense_location(suspense_boundary_location, || {\n                        let scope_state = dom\n                            .new_scope(component.props.duplicate(), component.name)\n                            .state();\n                        suspense_context.mount(scope_state.id);\n                        scope_id = scope_state.id;\n                    });\n            }\n\n            // Store the scope id for the next render\n            dom.set_mounted_dyn_node(mount, idx, scope_id.0);\n        }\n        dom.runtime.clone().with_scope_on_stack(scope_id, || {\n            let scope_state = &mut dom.scopes[scope_id.0];\n            let props = Self::downcast_from_props(&mut *scope_state.props).unwrap();\n            let suspense_context =\n                SuspenseContext::downcast_suspense_boundary_from_scope(&dom.runtime, scope_id)\n                    .unwrap();\n\n            let children = props.children.clone();\n\n            // First always render the children in the background. Rendering the children may cause this boundary to suspend\n            suspense_context.under_suspense_boundary(&dom.runtime(), || {\n                children.create(dom, parent, None::<&mut M>);\n            });\n\n            // Store the (now mounted) children back into the scope state\n            let scope_state = &mut dom.scopes[scope_id.0];\n            let props = Self::downcast_from_props(&mut *scope_state.props).unwrap();\n            props.children.clone_from(&children);\n\n            let scope_state = &mut dom.scopes[scope_id.0];\n            let suspense_context = scope_state\n                .state()\n                .suspense_location()\n                .suspense_context()\n                .unwrap()\n                .clone();\n\n            // If there are suspended futures, render the fallback\n            let nodes_created = if !suspense_context.suspended_futures().is_empty() {\n                let (node, nodes_created) =\n                    suspense_context.in_suspense_placeholder(&dom.runtime(), || {\n                        let scope_state = &mut dom.scopes[scope_id.0];\n                        let props = Self::downcast_from_props(&mut *scope_state.props).unwrap();\n                        let suspense_context =\n                            SuspenseContext::downcast_suspense_boundary_from_scope(\n                                &dom.runtime,\n                                scope_id,\n                            )\n                            .unwrap();\n                        suspense_context.set_suspended_nodes(children.as_vnode().clone());\n                        let suspense_placeholder =\n                            LastRenderedNode::new(props.fallback.call(suspense_context));\n                        let nodes_created = suspense_placeholder.create(dom, parent, to);\n                        (suspense_placeholder, nodes_created)\n                    });\n\n                let scope_state = &mut dom.scopes[scope_id.0];\n                scope_state.last_rendered_node = Some(node);\n\n                nodes_created\n            } else {\n                // Otherwise just render the children in the real dom\n                debug_assert!(children.mount.get().mounted());\n                let nodes_created = suspense_context\n                    .under_suspense_boundary(&dom.runtime(), || children.create(dom, parent, to));\n                let scope_state = &mut dom.scopes[scope_id.0];\n                scope_state.last_rendered_node = children.into();\n                let suspense_context =\n                    SuspenseContext::downcast_suspense_boundary_from_scope(&dom.runtime, scope_id)\n                        .unwrap();\n                suspense_context.take_suspended_nodes();\n                mark_suspense_resolved(&suspense_context, dom, scope_id);\n\n                nodes_created\n            };\n            nodes_created\n        })\n    }\n\n    #[doc(hidden)]\n    /// Manually rerun the children of this suspense boundary without diffing against the old nodes.\n    ///\n    /// This should only be called by dioxus-web after the suspense boundary has been streamed in from the server.\n    pub fn resolve_suspense<M: WriteMutations>(\n        scope_id: ScopeId,\n        dom: &mut VirtualDom,\n        to: &mut M,\n        only_write_templates: impl FnOnce(&mut M),\n        replace_with: usize,\n    ) {\n        dom.runtime.clone().with_scope_on_stack(scope_id, || {\n            let _runtime = RuntimeGuard::new(dom.runtime());\n            let Some(scope_state) = dom.scopes.get_mut(scope_id.0) else {\n                return;\n            };\n\n            // Reset the suspense context\n            let suspense_context = scope_state\n                .state()\n                .suspense_location()\n                .suspense_context()\n                .unwrap()\n                .clone();\n            suspense_context.inner.suspended_tasks.borrow_mut().clear();\n\n            // Get the parent of the suspense boundary to later create children with the right parent\n            let currently_rendered = scope_state.last_rendered_node.clone().unwrap();\n            let mount = currently_rendered.mount.get();\n            let parent = {\n                let mounts = dom.runtime.mounts.borrow();\n                mounts\n                    .get(mount.0)\n                    .expect(\"suspense placeholder is not mounted\")\n                    .parent\n            };\n\n            let props = Self::downcast_from_props(&mut *scope_state.props).unwrap();\n\n            // Unmount any children to reset any scopes under this suspense boundary\n            let children = props.children.clone();\n            let suspense_context =\n                SuspenseContext::downcast_suspense_boundary_from_scope(&dom.runtime, scope_id)\n                    .unwrap();\n\n            // Take the suspended nodes out of the suspense boundary so the children know that the boundary is not suspended while diffing\n            let suspended = suspense_context.take_suspended_nodes();\n            if let Some(node) = suspended {\n                node.remove_node(&mut *dom, None::<&mut M>, None);\n            }\n\n            // Replace the rendered nodes with resolved nodes\n            currently_rendered.remove_node(&mut *dom, Some(to), Some(replace_with));\n\n            // Switch to only writing templates\n            only_write_templates(to);\n\n            children.mount.take();\n\n            // First always render the children in the background. Rendering the children may cause this boundary to suspend\n            suspense_context.under_suspense_boundary(&dom.runtime(), || {\n                children.create(dom, parent, Some(to));\n            });\n\n            // Store the (now mounted) children back into the scope state\n            let scope_state = &mut dom.scopes[scope_id.0];\n            let props = Self::downcast_from_props(&mut *scope_state.props).unwrap();\n            props.children.clone_from(&children);\n            scope_state.last_rendered_node = Some(children);\n\n            // Run any closures that were waiting for the suspense to resolve\n            suspense_context.run_resolved_closures(&dom.runtime);\n        })\n    }\n\n    pub(crate) fn diff<M: WriteMutations>(\n        scope_id: ScopeId,\n        dom: &mut VirtualDom,\n        to: Option<&mut M>,\n    ) {\n        dom.runtime.clone().with_scope_on_stack(scope_id, || {\n            let scope = &mut dom.scopes[scope_id.0];\n            let myself = Self::downcast_from_props(&mut *scope.props)\n                .unwrap()\n                .clone();\n\n            let last_rendered_node = scope.last_rendered_node.clone().unwrap();\n\n            let Self {\n                fallback, children, ..\n            } = myself;\n\n            let suspense_context = scope.state().suspense_boundary().unwrap().clone();\n            let suspended_nodes = suspense_context.suspended_nodes();\n            let suspended = !suspense_context.suspended_futures().is_empty();\n            match (suspended_nodes, suspended) {\n                // We already have suspended nodes that still need to be suspended\n                // Just diff the normal and suspended nodes\n                (Some(suspended_nodes), true) => {\n                    let new_suspended_nodes: VNode = children.as_vnode().clone();\n\n                    // Diff the placeholder nodes in the dom\n                    let new_placeholder =\n                        suspense_context.in_suspense_placeholder(&dom.runtime(), || {\n                            let old_placeholder = last_rendered_node;\n                            let new_placeholder =\n                                LastRenderedNode::new(fallback.call(suspense_context.clone()));\n\n                            old_placeholder.diff_node(&new_placeholder, dom, to);\n                            new_placeholder\n                        });\n\n                    // Set the last rendered node to the placeholder\n                    dom.scopes[scope_id.0].last_rendered_node = Some(new_placeholder);\n\n                    // Diff the suspended nodes in the background\n                    suspense_context.under_suspense_boundary(&dom.runtime(), || {\n                        suspended_nodes.diff_node(&new_suspended_nodes, dom, None::<&mut M>);\n                    });\n\n                    let suspense_context = SuspenseContext::downcast_suspense_boundary_from_scope(\n                        &dom.runtime,\n                        scope_id,\n                    )\n                    .unwrap();\n                    suspense_context.set_suspended_nodes(new_suspended_nodes);\n                }\n                // We have no suspended nodes, and we are not suspended. Just diff the children like normal\n                (None, false) => {\n                    let old_children = last_rendered_node;\n                    let new_children = children;\n\n                    suspense_context.under_suspense_boundary(&dom.runtime(), || {\n                        old_children.diff_node(&new_children, dom, to);\n                    });\n\n                    // Set the last rendered node to the new children\n                    dom.scopes[scope_id.0].last_rendered_node = new_children.into();\n                }\n                // We have no suspended nodes, but we just became suspended. Move the children to the background\n                (None, true) => {\n                    let old_children = last_rendered_node;\n                    let new_children: VNode = children.as_vnode().clone();\n\n                    let new_placeholder =\n                        LastRenderedNode::new(fallback.call(suspense_context.clone()));\n\n                    // Move the children to the background\n                    let mount = old_children.mount.get();\n                    let parent = dom.get_mounted_parent(mount);\n\n                    suspense_context.in_suspense_placeholder(&dom.runtime(), || {\n                        old_children.move_node_to_background(\n                            std::slice::from_ref(&new_placeholder),\n                            parent,\n                            dom,\n                            to,\n                        );\n                    });\n\n                    // Then diff the new children in the background\n                    suspense_context.under_suspense_boundary(&dom.runtime(), || {\n                        old_children.diff_node(&new_children, dom, None::<&mut M>);\n                    });\n\n                    // Set the last rendered node to the new suspense placeholder\n                    dom.scopes[scope_id.0].last_rendered_node = Some(new_placeholder);\n\n                    let suspense_context = SuspenseContext::downcast_suspense_boundary_from_scope(\n                        &dom.runtime,\n                        scope_id,\n                    )\n                    .unwrap();\n                    suspense_context.set_suspended_nodes(new_children);\n\n                    un_resolve_suspense(dom, scope_id);\n                }\n                // We have suspended nodes, but we just got out of suspense. Move the suspended nodes to the foreground\n                (Some(_), false) => {\n                    // Take the suspended nodes out of the suspense boundary so the children know that the boundary is not suspended while diffing\n                    let old_suspended_nodes = suspense_context.take_suspended_nodes().unwrap();\n                    let old_placeholder = last_rendered_node;\n                    let new_children = children;\n\n                    // First diff the two children nodes in the background\n                    suspense_context.under_suspense_boundary(&dom.runtime(), || {\n                        old_suspended_nodes.diff_node(&new_children, dom, None::<&mut M>);\n\n                        // Then replace the placeholder with the new children\n                        let mount = old_placeholder.mount.get();\n                        let parent = dom.get_mounted_parent(mount);\n                        old_placeholder.replace(\n                            std::slice::from_ref(&new_children),\n                            parent,\n                            dom,\n                            to,\n                        );\n                    });\n\n                    // Set the last rendered node to the new children\n                    dom.scopes[scope_id.0].last_rendered_node = Some(new_children);\n\n                    mark_suspense_resolved(&suspense_context, dom, scope_id);\n                }\n            }\n        })\n    }\n}\n\n/// Move to a resolved suspense state\nfn mark_suspense_resolved(\n    suspense_context: &SuspenseContext,\n    dom: &mut VirtualDom,\n    scope_id: ScopeId,\n) {\n    dom.resolved_scopes.push(scope_id);\n    // Run any closures that were waiting for the suspense to resolve\n    suspense_context.run_resolved_closures(&dom.runtime);\n}\n\n/// Move from a resolved suspense state to an suspended state\nfn un_resolve_suspense(dom: &mut VirtualDom, scope_id: ScopeId) {\n    dom.resolved_scopes.retain(|&id| id != scope_id);\n}\n\nimpl SuspenseContext {\n    /// Run a closure under a suspense boundary\n    pub(crate) fn under_suspense_boundary<O>(&self, runtime: &Runtime, f: impl FnOnce() -> O) -> O {\n        runtime.with_suspense_location(SuspenseLocation::UnderSuspense(self.clone()), f)\n    }\n\n    /// Run a closure under a suspense placeholder\n    pub(crate) fn in_suspense_placeholder<O>(&self, runtime: &Runtime, f: impl FnOnce() -> O) -> O {\n        runtime.with_suspense_location(SuspenseLocation::InSuspensePlaceholder(self.clone()), f)\n    }\n\n    /// Try to get a suspense boundary from a scope id\n    pub fn downcast_suspense_boundary_from_scope(\n        runtime: &Runtime,\n        scope_id: ScopeId,\n    ) -> Option<Self> {\n        runtime\n            .try_get_state(scope_id)\n            .and_then(|scope| scope.suspense_boundary())\n    }\n\n    pub(crate) fn remove_suspended_nodes<M: WriteMutations>(\n        dom: &mut VirtualDom,\n        scope_id: ScopeId,\n        destroy_component_state: bool,\n    ) {\n        let Some(scope) = Self::downcast_suspense_boundary_from_scope(&dom.runtime, scope_id)\n        else {\n            return;\n        };\n        // Remove the suspended nodes\n        if let Some(node) = scope.take_suspended_nodes() {\n            node.remove_node_inner(dom, None::<&mut M>, destroy_component_state, None)\n        }\n    }\n}\n"
  },
  {
    "path": "packages/core/src/suspense/mod.rs",
    "content": "//! Suspense allows you to render a placeholder while nodes are waiting for data in the background\n//!\n//! During suspense on the server:\n//! - Rebuild once\n//! - Send page with loading placeholders down to the client\n//! - loop\n//!   - Poll (only) suspended futures\n//!   - If a scope is marked as dirty and that scope is a suspense boundary, under a suspended boundary, or the suspense placeholder, rerun the scope\n//!     - If it is a different scope, ignore it and warn the user\n//!   - Rerender the scope on the server and send down the nodes under a hidden div with serialized data\n//!\n//! During suspense on the web:\n//! - Rebuild once without running server futures\n//! - Rehydrate the placeholders that were initially sent down. At this point, no suspense nodes are resolved so the client and server pages should be the same\n//! - loop\n//!   - Wait for work or suspense data\n//!   - If suspense data comes in\n//!     - replace the suspense placeholder\n//!     - get any data associated with the suspense placeholder and rebuild nodes under the suspense that was resolved\n//!     - rehydrate the suspense placeholders that were at that node\n//!   - If work comes in\n//!     - Just do the work; this may remove suspense placeholders that the server hasn't yet resolved. If we see new data come in from the server about that node, ignore it\n//!\n//! Generally suspense placeholders should not be stateful because they are driven from the server. If they are stateful and the client renders something different, hydration will fail.\n\nmod component;\npub use component::*;\n\nuse crate::innerlude::*;\nuse std::{\n    cell::{Cell, Ref, RefCell},\n    fmt::Debug,\n    rc::Rc,\n};\n\n/// A task that has been suspended which may have an optional loading placeholder\n#[derive(Clone, PartialEq, Debug)]\npub struct SuspendedFuture {\n    origin: ScopeId,\n    task: TaskId,\n}\n\nimpl SuspendedFuture {\n    /// Create a new suspended future\n    pub fn new(task: Task) -> Self {\n        Self {\n            task: task.id,\n            origin: current_scope_id(),\n        }\n    }\n\n    /// Get the task that was suspended\n    pub fn task(&self) -> Task {\n        Task::from_id(self.task)\n    }\n\n    /// Create a deep clone of this suspended future\n    pub(crate) fn deep_clone(&self) -> Self {\n        Self {\n            task: self.task,\n            origin: self.origin,\n        }\n    }\n}\n\nimpl std::fmt::Display for SuspendedFuture {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(f, \"SuspendedFuture {{ task: {:?} }}\", self.task)\n    }\n}\n\n/// A context with information about suspended components\n#[derive(Debug, Clone)]\npub struct SuspenseContext {\n    inner: Rc<SuspenseBoundaryInner>,\n}\n\nimpl PartialEq for SuspenseContext {\n    fn eq(&self, other: &Self) -> bool {\n        Rc::ptr_eq(&self.inner, &other.inner)\n    }\n}\n\nimpl SuspenseContext {\n    /// Create a new suspense boundary in a specific scope\n    pub(crate) fn new() -> Self {\n        Self {\n            inner: Rc::new(SuspenseBoundaryInner {\n                rt: Runtime::current(),\n                suspended_tasks: RefCell::new(vec![]),\n                id: Cell::new(ScopeId::ROOT),\n                suspended_nodes: Default::default(),\n                frozen: Default::default(),\n                after_suspense_resolved: Default::default(),\n            }),\n        }\n    }\n\n    /// Mount the context in a specific scope\n    pub(crate) fn mount(&self, scope: ScopeId) {\n        self.inner.id.set(scope);\n    }\n\n    /// Get the suspense boundary's suspended nodes\n    pub fn suspended_nodes(&self) -> Option<VNode> {\n        self.inner\n            .suspended_nodes\n            .borrow()\n            .as_ref()\n            .map(|node| node.clone())\n    }\n\n    /// Set the suspense boundary's suspended nodes\n    pub(crate) fn set_suspended_nodes(&self, suspended_nodes: VNode) {\n        self.inner\n            .suspended_nodes\n            .borrow_mut()\n            .replace(suspended_nodes);\n    }\n\n    /// Take the suspense boundary's suspended nodes\n    pub(crate) fn take_suspended_nodes(&self) -> Option<VNode> {\n        self.inner.suspended_nodes.borrow_mut().take()\n    }\n\n    /// Check if the suspense boundary is resolved and frozen\n    pub fn frozen(&self) -> bool {\n        self.inner.frozen.get()\n    }\n\n    /// Resolve the suspense boundary on the server and freeze it to prevent future reruns of any child nodes of the suspense boundary\n    pub fn freeze(&self) {\n        self.inner.frozen.set(true);\n    }\n\n    /// Check if there are any suspended tasks\n    pub fn has_suspended_tasks(&self) -> bool {\n        !self.inner.suspended_tasks.borrow().is_empty()\n    }\n\n    /// Check if the suspense boundary is currently rendered as suspended\n    pub fn is_suspended(&self) -> bool {\n        self.inner.suspended_nodes.borrow().is_some()\n    }\n\n    /// Add a suspended task\n    pub(crate) fn add_suspended_task(&self, task: SuspendedFuture) {\n        self.inner.suspended_tasks.borrow_mut().push(task);\n        self.inner.rt.needs_update(self.inner.id.get());\n    }\n\n    /// Remove a suspended task\n    pub(crate) fn remove_suspended_task(&self, task: Task) {\n        self.inner\n            .suspended_tasks\n            .borrow_mut()\n            .retain(|t| t.task != task.id);\n        self.inner.rt.needs_update(self.inner.id.get());\n    }\n\n    /// Get all suspended tasks\n    pub fn suspended_futures(&self) -> Ref<'_, [SuspendedFuture]> {\n        Ref::map(self.inner.suspended_tasks.borrow(), |tasks| {\n            tasks.as_slice()\n        })\n    }\n\n    /// Run a closure after suspense is resolved\n    pub fn after_suspense_resolved(&self, callback: impl FnOnce() + 'static) {\n        let mut closures = self.inner.after_suspense_resolved.borrow_mut();\n        closures.push(Box::new(callback));\n    }\n\n    /// Run all closures that were queued to run after suspense is resolved\n    pub(crate) fn run_resolved_closures(&self, runtime: &Runtime) {\n        runtime.while_not_rendering(|| {\n            self.inner\n                .after_suspense_resolved\n                .borrow_mut()\n                .drain(..)\n                .for_each(|f| f());\n        })\n    }\n}\n\n/// A boundary that will capture any errors from child components\npub struct SuspenseBoundaryInner {\n    rt: Rc<Runtime>,\n\n    suspended_tasks: RefCell<Vec<SuspendedFuture>>,\n\n    id: Cell<ScopeId>,\n\n    /// The nodes that are suspended under this boundary\n    suspended_nodes: RefCell<Option<VNode>>,\n\n    /// On the server, you can only resolve a suspense boundary once. This is used to track if the suspense boundary has been resolved and if it should be frozen\n    frozen: Cell<bool>,\n\n    /// Closures queued to run after the suspense boundary is resolved\n    after_suspense_resolved: RefCell<Vec<Box<dyn FnOnce()>>>,\n}\n\nimpl Debug for SuspenseBoundaryInner {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_struct(\"SuspenseBoundaryInner\")\n            .field(\"suspended_tasks\", &self.suspended_tasks)\n            .field(\"id\", &self.id)\n            .field(\"suspended_nodes\", &self.suspended_nodes)\n            .field(\"frozen\", &self.frozen)\n            .finish()\n    }\n}\n"
  },
  {
    "path": "packages/core/src/tasks.rs",
    "content": "use crate::innerlude::Effect;\nuse crate::innerlude::ScopeOrder;\nuse crate::innerlude::{remove_future, spawn, Runtime};\nuse crate::scope_context::ScopeStatus;\nuse crate::scope_context::SuspenseLocation;\nuse crate::ScopeId;\nuse futures_util::task::ArcWake;\nuse slotmap::DefaultKey;\nuse std::marker::PhantomData;\nuse std::sync::Arc;\nuse std::task::Waker;\nuse std::{cell::Cell, future::Future};\nuse std::{cell::RefCell, rc::Rc};\nuse std::{pin::Pin, task::Poll};\n\n/// A task's unique identifier.\n///\n/// `Task` is a unique identifier for a task that has been spawned onto the runtime. It can be used to cancel the task\n#[cfg_attr(feature = \"serialize\", derive(serde::Serialize, serde::Deserialize))]\n#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]\npub struct Task {\n    pub(crate) id: TaskId,\n    // We add a raw pointer to make this !Send + !Sync\n    unsend: PhantomData<*const ()>,\n}\n\npub(crate) type TaskId = slotmap::DefaultKey;\n\nimpl Task {\n    /// Create a task from a raw id\n    pub(crate) const fn from_id(id: slotmap::DefaultKey) -> Self {\n        Self {\n            id,\n            unsend: PhantomData,\n        }\n    }\n\n    /// Start a new future on the same thread as the rest of the VirtualDom.\n    ///\n    /// This future will not contribute to suspense resolving, so you should primarily use this for reacting to changes\n    /// and long running tasks.\n    ///\n    /// Whenever the component that owns this future is dropped, the future will be dropped as well.\n    ///\n    /// Spawning a future onto the root scope will cause it to be dropped when the root component is dropped - which\n    /// will only occur when the VirtualDom itself has been dropped.\n    pub fn new(task: impl Future<Output = ()> + 'static) -> Self {\n        spawn(task)\n    }\n\n    /// Drop the task immediately.\n    pub fn cancel(self) {\n        remove_future(self);\n    }\n\n    /// Pause the task.\n    pub fn pause(&self) {\n        self.set_active(false);\n    }\n\n    /// Resume the task.\n    pub fn resume(&self) {\n        self.set_active(true);\n    }\n\n    /// Check if the task is paused.\n    pub fn paused(&self) -> bool {\n        Runtime::with(|rt| {\n            if let Some(task) = rt.tasks.borrow().get(self.id) {\n                !task.active.get()\n            } else {\n                false\n            }\n        })\n    }\n\n    /// Wake the task.\n    #[track_caller]\n    pub fn wake(&self) {\n        Runtime::with(|rt| {\n            _ = rt\n                .sender\n                .unbounded_send(SchedulerMsg::TaskNotified(self.id))\n        })\n    }\n\n    /// Poll the task immediately.\n    #[track_caller]\n    pub fn poll_now(&self) -> Poll<()> {\n        Runtime::with(|rt| rt.handle_task_wakeup(*self))\n    }\n\n    /// Set the task as active or paused.\n    #[track_caller]\n    pub fn set_active(&self, active: bool) {\n        Runtime::with(|rt| {\n            if let Some(task) = rt.tasks.borrow().get(self.id) {\n                let was_active = task.active.replace(active);\n                if !was_active && active {\n                    _ = rt\n                        .sender\n                        .unbounded_send(SchedulerMsg::TaskNotified(self.id));\n                }\n            }\n        })\n    }\n}\n\nimpl Runtime {\n    /// Start a new future on the same thread as the rest of the VirtualDom.\n    ///\n    /// **You should generally use `spawn` instead of this method unless you specifically need to need to run a task during suspense**\n    ///\n    /// This future will not contribute to suspense resolving but it will run during suspense.\n    ///\n    /// Because this future runs during suspense, you need to be careful to work with hydration. It is not recommended to do any async IO work in this future, as it can easily cause hydration issues. However, you can use isomorphic tasks to do work that can be consistently replicated on the server and client like logging or responding to state changes.\n    ///\n    /// ```rust, no_run\n    /// # use dioxus::prelude::*;\n    /// # use dioxus_core::spawn_isomorphic;\n    /// // ❌ Do not do requests in isomorphic tasks. It may resolve at a different time on the server and client, causing hydration issues.\n    /// let mut state = use_signal(|| None);\n    /// spawn_isomorphic(async move {\n    ///     state.set(Some(reqwest::get(\"https://api.example.com\").await));\n    /// });\n    ///\n    /// // ✅ You may wait for a signal to change and then log it\n    /// let mut state = use_signal(|| 0);\n    /// spawn_isomorphic(async move {\n    ///     loop {\n    ///         tokio::time::sleep(std::time::Duration::from_secs(1)).await;\n    ///         println!(\"State is {state}\");\n    ///     }\n    /// });\n    /// ```\n    pub fn spawn_isomorphic(\n        &self,\n        scope: ScopeId,\n        task: impl Future<Output = ()> + 'static,\n    ) -> Task {\n        self.spawn_task_of_type(scope, task, TaskType::Isomorphic)\n    }\n\n    /// Start a new future on the same thread as the rest of the VirtualDom.\n    ///\n    /// This future will not contribute to suspense resolving, so you should primarily use this for reacting to changes\n    /// and long running tasks.\n    ///\n    /// Whenever the component that owns this future is dropped, the future will be dropped as well.\n    ///\n    /// Spawning a future onto the root scope will cause it to be dropped when the root component is dropped - which\n    /// will only occur when the VirtualDom itself has been dropped.\n    pub fn spawn(&self, scope: ScopeId, task: impl Future<Output = ()> + 'static) -> Task {\n        self.spawn_task_of_type(scope, task, TaskType::ClientOnly)\n    }\n\n    fn spawn_task_of_type(\n        &self,\n        scope: ScopeId,\n        task: impl Future<Output = ()> + 'static,\n        ty: TaskType,\n    ) -> Task {\n        self.spawn_task_of_type_inner(scope, Box::pin(task), ty)\n    }\n\n    // a non-momorphic version of spawn_task_of_type, helps with binari sizes\n    fn spawn_task_of_type_inner(\n        &self,\n        scope: ScopeId,\n        pinned_task: Pin<Box<dyn Future<Output = ()>>>,\n        ty: TaskType,\n    ) -> Task {\n        // Insert the task, temporarily holding a borrow on the tasks map\n        let (task, task_id) = {\n            let mut tasks = self.tasks.borrow_mut();\n\n            let mut task_id = Task::from_id(DefaultKey::default());\n            let mut local_task = None;\n            tasks.insert_with_key(|key| {\n                task_id = Task::from_id(key);\n\n                let new_task = Rc::new(LocalTask {\n                    scope,\n                    active: Cell::new(true),\n                    parent: self.current_task(),\n                    task: RefCell::new(pinned_task),\n                    waker: futures_util::task::waker(Arc::new(LocalTaskHandle {\n                        id: task_id.id,\n                        tx: self.sender.clone(),\n                    })),\n                    ty: RefCell::new(ty),\n                });\n\n                local_task = Some(new_task.clone());\n\n                new_task\n            });\n\n            (local_task.unwrap(), task_id)\n        };\n\n        // Get a borrow on the task, holding no borrows on the tasks map\n        debug_assert!(self.tasks.try_borrow_mut().is_ok());\n        debug_assert!(task.task.try_borrow_mut().is_ok());\n\n        self.sender\n            .unbounded_send(SchedulerMsg::TaskNotified(task_id.id))\n            .expect(\"Scheduler should exist\");\n\n        task_id\n    }\n\n    /// Queue an effect to run after the next render\n    pub(crate) fn queue_effect(&self, id: ScopeId, f: impl FnOnce() + 'static) {\n        let effect = Box::new(f) as Box<dyn FnOnce() + 'static>;\n        let Some(scope) = self.try_get_state(id) else {\n            return;\n        };\n        let mut status = scope.status.borrow_mut();\n        match &mut *status {\n            ScopeStatus::Mounted => {\n                self.queue_effect_on_mounted_scope(id, effect);\n            }\n            ScopeStatus::Unmounted { effects_queued, .. } => {\n                effects_queued.push(effect);\n            }\n        }\n    }\n\n    /// Queue an effect to run after the next render without checking if the scope is mounted\n    pub(crate) fn queue_effect_on_mounted_scope(\n        &self,\n        id: ScopeId,\n        f: Box<dyn FnOnce() + 'static>,\n    ) {\n        // Add the effect to the queue of effects to run after the next render for the given scope\n        let mut effects = self.pending_effects.borrow_mut();\n        let height = self.get_state(id).height();\n        let scope_order = ScopeOrder::new(height, id);\n        match effects.get(&scope_order) {\n            Some(effects) => effects.push_back(f),\n            None => {\n                effects.insert(Effect::new(scope_order, f));\n            }\n        }\n    }\n\n    /// Get the currently running task\n    pub fn current_task(&self) -> Option<Task> {\n        self.current_task.get()\n    }\n\n    /// Get the parent task of the given task, if it exists\n    pub fn parent_task(&self, task: Task) -> Option<Task> {\n        self.tasks.borrow().get(task.id)?.parent\n    }\n\n    pub(crate) fn task_scope(&self, task: Task) -> Option<ScopeId> {\n        self.tasks.borrow().get(task.id).map(|t| t.scope)\n    }\n\n    #[track_caller]\n    pub(crate) fn handle_task_wakeup(&self, id: Task) -> Poll<()> {\n        #[cfg(debug_assertions)]\n        {\n            // Ensure we are currently inside a `Runtime`.\n            Runtime::current();\n        }\n\n        let task = self.tasks.borrow().get(id.id).cloned();\n\n        // The task was removed from the scheduler, so we can just ignore it\n        let Some(task) = task else {\n            return Poll::Ready(());\n        };\n\n        // If a task woke up but is paused, we can just ignore it\n        if !task.active.get() {\n            return Poll::Pending;\n        }\n\n        let mut cx = std::task::Context::from_waker(&task.waker);\n\n        // poll the future with the scope on the stack\n        let poll_result = self.with_scope_on_stack(task.scope, || {\n            self.current_task.set(Some(id));\n\n            let poll_result = task.task.borrow_mut().as_mut().poll(&mut cx);\n\n            if poll_result.is_ready() {\n                // Remove it from the scope so we dont try to double drop it when the scope dropes\n                self.get_state(task.scope)\n                    .spawned_tasks\n                    .borrow_mut()\n                    .remove(&id);\n\n                self.remove_task(id);\n            }\n\n            poll_result\n        });\n        self.current_task.set(None);\n\n        poll_result\n    }\n\n    /// Drop the future with the given Task\n    ///\n    /// This does not abort the task, so you'll want to wrap it in an abort handle if that's important to you\n    pub(crate) fn remove_task(&self, id: Task) -> Option<Rc<LocalTask>> {\n        // Remove the task from the task list\n        let task = self.tasks.borrow_mut().remove(id.id);\n\n        if let Some(task) = &task {\n            // Remove the task from suspense\n            if let TaskType::Suspended { boundary } = &*task.ty.borrow() {\n                self.suspended_tasks.set(self.suspended_tasks.get() - 1);\n                if let SuspenseLocation::UnderSuspense(boundary) = boundary {\n                    boundary.remove_suspended_task(id);\n                }\n            }\n\n            // Remove the task from pending work. We could reuse the slot before the task is polled and discarded so we need to remove it from pending work instead of filtering out dead tasks when we try to poll them\n            if let Some(scope) = self.try_get_state(task.scope) {\n                let order = ScopeOrder::new(scope.height(), scope.id);\n                if let Some(dirty_tasks) = self.dirty_tasks.borrow_mut().get(&order) {\n                    dirty_tasks.remove(id);\n                }\n            }\n        }\n\n        task\n    }\n\n    /// Check if a task should be run during suspense\n    pub(crate) fn task_runs_during_suspense(&self, task: Task) -> bool {\n        let borrow = self.tasks.borrow();\n        let task: Option<&LocalTask> = borrow.get(task.id).map(|t| &**t);\n        matches!(task, Some(LocalTask { ty, .. }) if ty.borrow().runs_during_suspense())\n    }\n}\n\n/// the task itself is the waker\npub(crate) struct LocalTask {\n    scope: ScopeId,\n    parent: Option<Task>,\n    task: RefCell<Pin<Box<dyn Future<Output = ()> + 'static>>>,\n    waker: Waker,\n    ty: RefCell<TaskType>,\n    active: Cell<bool>,\n}\n\nimpl LocalTask {\n    /// Suspend the task, returns true if the task was already suspended\n    pub(crate) fn suspend(&self, boundary: SuspenseLocation) -> bool {\n        // Make this a suspended task so it runs during suspense\n        let old_type = self.ty.replace(TaskType::Suspended { boundary });\n        matches!(old_type, TaskType::Suspended { .. })\n    }\n}\n\n#[derive(Clone)]\nenum TaskType {\n    ClientOnly,\n    Suspended { boundary: SuspenseLocation },\n    Isomorphic,\n}\n\nimpl TaskType {\n    fn runs_during_suspense(&self) -> bool {\n        matches!(self, TaskType::Isomorphic | TaskType::Suspended { .. })\n    }\n}\n\n/// The type of message that can be sent to the scheduler.\n///\n/// These messages control how the scheduler will process updates to the UI.\n#[derive(Debug)]\npub(crate) enum SchedulerMsg {\n    /// All components have been marked as dirty, requiring a full render\n    #[allow(unused)]\n    AllDirty,\n\n    /// Immediate updates from Components that mark them as dirty\n    Immediate(ScopeId),\n\n    /// A task has woken and needs to be progressed\n    TaskNotified(slotmap::DefaultKey),\n\n    /// An effect has been queued to run after the next render\n    EffectQueued,\n}\n\nstruct LocalTaskHandle {\n    id: slotmap::DefaultKey,\n    tx: futures_channel::mpsc::UnboundedSender<SchedulerMsg>,\n}\n\nimpl ArcWake for LocalTaskHandle {\n    fn wake_by_ref(arc_self: &Arc<Self>) {\n        _ = arc_self\n            .tx\n            .unbounded_send(SchedulerMsg::TaskNotified(arc_self.id));\n    }\n}\n"
  },
  {
    "path": "packages/core/src/virtual_dom.rs",
    "content": "//! # Virtual DOM Implementation for Rust\n//!\n//! This module provides the primary mechanics to create a hook-based, concurrent VDOM for Rust.\n\nuse crate::properties::RootProps;\nuse crate::root_wrapper::RootScopeWrapper;\nuse crate::{\n    arena::ElementId,\n    innerlude::{NoOpMutations, SchedulerMsg, ScopeOrder, ScopeState, VProps, WriteMutations},\n    runtime::{Runtime, RuntimeGuard},\n    scopes::ScopeId,\n    ComponentFunction, Element, Mutations,\n};\nuse crate::{innerlude::Work, scopes::LastRenderedNode};\nuse crate::{Task, VComponent};\nuse futures_util::StreamExt;\nuse slab::Slab;\nuse std::collections::BTreeSet;\nuse std::{any::Any, rc::Rc};\nuse tracing::instrument;\n\n/// A virtual node system that progresses user events and diffs UI trees.\n///\n/// ## Guide\n///\n/// Components are defined as simple functions that take [`crate::properties::Properties`] and return an [`Element`].\n///\n/// ```rust\n/// # use dioxus::prelude::*;\n///\n/// #[derive(Props, PartialEq, Clone)]\n/// struct AppProps {\n///     title: String\n/// }\n///\n/// fn app(cx: AppProps) -> Element {\n///     rsx!(\n///         div {\"hello, {cx.title}\"}\n///     )\n/// }\n/// ```\n///\n/// Components may be composed to make complex apps.\n///\n/// ```rust\n/// # #![allow(unused)]\n/// # use dioxus::prelude::*;\n///\n/// # #[derive(Props, PartialEq, Clone)]\n/// # struct AppProps {\n/// #     title: String\n/// # }\n///\n/// static ROUTES: &str = \"\";\n///\n/// #[component]\n/// fn app(cx: AppProps) -> Element {\n///     rsx!(\n///         NavBar { routes: ROUTES }\n///         Title { \"{cx.title}\" }\n///         Footer {}\n///     )\n/// }\n///\n/// #[component]\n/// fn NavBar( routes: &'static str) -> Element {\n///     rsx! {\n///         div { \"Routes: {routes}\" }\n///     }\n/// }\n///\n/// #[component]\n/// fn Footer() -> Element {\n///     rsx! { div { \"Footer\" } }\n/// }\n///\n/// #[component]\n/// fn Title( children: Element) -> Element {\n///     rsx! {\n///         div { id: \"title\", {children} }\n///     }\n/// }\n/// ```\n///\n/// To start an app, create a [`VirtualDom`] and call [`VirtualDom::rebuild`] to get the list of edits required to\n/// draw the UI.\n///\n/// ```rust\n/// # use dioxus::prelude::*;\n/// # use dioxus_core::*;\n/// # fn app() -> Element { rsx! { div {} } }\n///\n/// let mut vdom = VirtualDom::new(app);\n/// let edits = vdom.rebuild_to_vec();\n/// ```\n///\n/// To call listeners inside the VirtualDom, call [`Runtime::handle_event`] with the appropriate event data.\n///\n/// ```rust, no_run\n/// # use dioxus::prelude::*;\n/// # use dioxus_core::*;\n/// # fn app() -> Element { rsx! { div {} } }\n/// # let mut vdom = VirtualDom::new(app);\n/// # let runtime = vdom.runtime();\n/// let event = Event::new(std::rc::Rc::new(0) as std::rc::Rc<dyn std::any::Any>, true);\n/// runtime.handle_event(\"onclick\", event, ElementId(0));\n/// ```\n///\n/// While no events are ready, call [`VirtualDom::wait_for_work`] to poll any futures inside the VirtualDom.\n///\n/// ```rust, no_run\n/// # use dioxus::prelude::*;\n/// # use dioxus_core::*;\n/// # fn app() -> Element { rsx! { div {} } }\n/// # let mut vdom = VirtualDom::new(app);\n/// tokio::runtime::Runtime::new().unwrap().block_on(async {\n///     vdom.wait_for_work().await;\n/// });\n/// ```\n///\n/// Once work is ready, call [`VirtualDom::render_immediate`] to compute the differences between the previous and\n/// current UI trees. This will write edits to a [`WriteMutations`] object you pass in that contains with edits that need to be\n/// handled by the renderer.\n///\n/// ```rust, no_run\n/// # use dioxus::prelude::*;\n/// # use dioxus_core::*;\n/// # fn app() -> Element { rsx! { div {} } }\n/// # let mut vdom = VirtualDom::new(app);\n/// let mut mutations = Mutations::default();\n///\n/// vdom.render_immediate(&mut mutations);\n/// ```\n///\n/// To not wait for suspense while diffing the VirtualDom, call [`VirtualDom::render_immediate`].\n///\n///\n/// ## Building an event loop around Dioxus:\n///\n/// Putting everything together, you can build an event loop around Dioxus by using the methods outlined above.\n/// ```rust, no_run\n/// # use dioxus::prelude::*;\n/// # use dioxus_core::*;\n/// # struct RealDom;\n/// # struct Event {}\n/// # impl RealDom {\n/// #     fn new() -> Self {\n/// #         Self {}\n/// #     }\n/// #     fn apply(&mut self) -> Mutations {\n/// #         unimplemented!()\n/// #     }\n/// #     async fn wait_for_event(&mut self) -> std::rc::Rc<dyn std::any::Any> {\n/// #         unimplemented!()\n/// #     }\n/// # }\n/// #\n/// # tokio::runtime::Runtime::new().unwrap().block_on(async {\n/// let mut real_dom = RealDom::new();\n///\n/// #[component]\n/// fn app() -> Element {\n///     rsx! {\n///         div { \"Hello World\" }\n///     }\n/// }\n///\n/// let mut dom = VirtualDom::new(app);\n///\n/// dom.rebuild(&mut real_dom.apply());\n///\n/// loop {\n///     tokio::select! {\n///         _ = dom.wait_for_work() => {}\n///         evt = real_dom.wait_for_event() => {\n///             let evt = dioxus_core::Event::new(evt, true);\n///             dom.runtime().handle_event(\"onclick\", evt, ElementId(0))\n///         },\n///     }\n///\n///     dom.render_immediate(&mut real_dom.apply());\n/// }\n/// # });\n/// ```\n///\n/// ## Waiting for suspense\n///\n/// Because Dioxus supports suspense, you can use it for server-side rendering, static site generation, and other use cases\n/// where waiting on portions of the UI to finish rendering is important. To wait for suspense, use the\n/// [`VirtualDom::wait_for_suspense`] method:\n///\n/// ```rust, no_run\n/// # use dioxus::prelude::*;\n/// # use dioxus_core::*;\n/// # fn app() -> Element { rsx! { div {} } }\n/// tokio::runtime::Runtime::new().unwrap().block_on(async {\n///     let mut dom = VirtualDom::new(app);\n///\n///     dom.rebuild_in_place();\n///     dom.wait_for_suspense().await;\n/// });\n///\n/// // Render the virtual dom\n/// ```\npub struct VirtualDom {\n    pub(crate) scopes: Slab<ScopeState>,\n\n    pub(crate) dirty_scopes: BTreeSet<ScopeOrder>,\n\n    pub(crate) runtime: Rc<Runtime>,\n\n    // The scopes that have been resolved since the last render\n    pub(crate) resolved_scopes: Vec<ScopeId>,\n\n    rx: futures_channel::mpsc::UnboundedReceiver<SchedulerMsg>,\n}\n\nimpl VirtualDom {\n    /// Create a new VirtualDom with a component that does not have special props.\n    ///\n    /// # Description\n    ///\n    /// Later, the props can be updated by calling \"update\" with a new set of props, causing a set of re-renders.\n    ///\n    /// This is useful when a component tree can be driven by external state (IE SSR) but it would be too expensive\n    /// to toss out the entire tree.\n    ///\n    ///\n    /// # Example\n    /// ```rust, no_run\n    /// # use dioxus::prelude::*;\n    /// # use dioxus_core::*;\n    /// fn Example() -> Element  {\n    ///     rsx!( div { \"hello world\" } )\n    /// }\n    ///\n    /// let dom = VirtualDom::new(Example);\n    /// ```\n    ///\n    /// Note: the VirtualDom is not progressed, you must either \"run_with_deadline\" or use \"rebuild\" to progress it.\n    pub fn new(app: fn() -> Element) -> Self {\n        Self::new_with_props(app, ())\n    }\n\n    /// Create a new VirtualDom with the given properties for the root component.\n    ///\n    /// # Description\n    ///\n    /// Later, the props can be updated by calling \"update\" with a new set of props, causing a set of re-renders.\n    ///\n    /// This is useful when a component tree can be driven by external state (IE SSR) but it would be too expensive\n    /// to toss out the entire tree.\n    ///\n    ///\n    /// # Example\n    /// ```rust, no_run\n    /// # use dioxus::prelude::*;\n    /// # use dioxus_core::*;\n    /// #[derive(PartialEq, Props, Clone)]\n    /// struct SomeProps {\n    ///     name: &'static str\n    /// }\n    ///\n    /// fn Example(cx: SomeProps) -> Element  {\n    ///     rsx! { div { \"hello {cx.name}\" } }\n    /// }\n    ///\n    /// let dom = VirtualDom::new_with_props(Example, SomeProps { name: \"world\" });\n    /// ```\n    ///\n    /// Note: the VirtualDom is not progressed on creation. You must either \"run_with_deadline\" or use \"rebuild\" to progress it.\n    ///\n    /// ```rust, no_run\n    /// # use dioxus::prelude::*;\n    /// # use dioxus_core::*;\n    /// # #[derive(PartialEq, Props, Clone)]\n    /// # struct SomeProps {\n    /// #     name: &'static str\n    /// # }\n    /// # fn Example(cx: SomeProps) -> Element  {\n    /// #     rsx! { div { \"hello {cx.name}\" } }\n    /// # }\n    /// let mut dom = VirtualDom::new_with_props(Example, SomeProps { name: \"jane\" });\n    /// dom.rebuild_in_place();\n    /// ```\n    pub fn new_with_props<P: Clone + 'static, M: 'static>(\n        root: impl ComponentFunction<P, M>,\n        root_props: P,\n    ) -> Self {\n        let render_fn = root.fn_ptr();\n        let props = VProps::new(root, |_, _| true, root_props, \"Root\");\n        Self::new_with_component(VComponent {\n            name: \"root\",\n            render_fn,\n            props: Box::new(props),\n        })\n    }\n\n    /// Create a new virtualdom and build it immediately\n    pub fn prebuilt(app: fn() -> Element) -> Self {\n        let mut dom = Self::new(app);\n        dom.rebuild_in_place();\n        dom\n    }\n\n    /// Create a new VirtualDom from a VComponent\n    #[instrument(skip(root), level = \"trace\", name = \"VirtualDom::new\")]\n    pub(crate) fn new_with_component(root: VComponent) -> Self {\n        let (tx, rx) = futures_channel::mpsc::unbounded();\n\n        let mut dom = Self {\n            rx,\n            runtime: Runtime::new(tx),\n            scopes: Default::default(),\n            dirty_scopes: Default::default(),\n            resolved_scopes: Default::default(),\n        };\n\n        let root = VProps::new(\n            RootScopeWrapper,\n            |_, _| true,\n            RootProps(root),\n            \"RootWrapper\",\n        );\n        dom.new_scope(Box::new(root), \"app\");\n\n        #[cfg(debug_assertions)]\n        dom.register_subsecond_handler();\n\n        dom\n    }\n\n    /// Get the state for any scope given its ID\n    ///\n    /// This is useful for inserting or removing contexts from a scope, or rendering out its root node\n    pub fn get_scope(&self, id: ScopeId) -> Option<&ScopeState> {\n        self.scopes.get(id.0)\n    }\n\n    /// Get the single scope at the top of the VirtualDom tree that will always be around\n    ///\n    /// This scope has a ScopeId of 0 and is the root of the tree\n    pub fn base_scope(&self) -> &ScopeState {\n        self.get_scope(ScopeId::ROOT).unwrap()\n    }\n\n    /// Run a closure inside the dioxus runtime\n    #[instrument(skip(self, f), level = \"trace\", name = \"VirtualDom::in_runtime\")]\n    pub fn in_runtime<O>(&self, f: impl FnOnce() -> O) -> O {\n        let _runtime = RuntimeGuard::new(self.runtime.clone());\n        f()\n    }\n\n    /// Run a closure inside a specific scope\n    pub fn in_scope<T>(&self, scope: ScopeId, f: impl FnOnce() -> T) -> T {\n        self.runtime.in_scope(scope, f)\n    }\n\n    /// Build the virtualdom with a global context inserted into the base scope\n    ///\n    /// This is useful for what is essentially dependency injection when building the app\n    pub fn with_root_context<T: Clone + 'static>(self, context: T) -> Self {\n        self.base_scope().state().provide_context(context);\n        self\n    }\n\n    /// Provide a context to the root scope\n    pub fn provide_root_context<T: Clone + 'static>(&self, context: T) {\n        self.base_scope().state().provide_context(context);\n    }\n\n    /// Build the virtualdom with a global context inserted into the base scope\n    ///\n    /// This method is useful for when you want to provide a context in your app without knowing its type\n    pub fn insert_any_root_context(&mut self, context: Box<dyn Any>) {\n        self.base_scope().state().provide_any_context(context);\n    }\n\n    /// Mark all scopes as dirty. Each scope will be re-rendered.\n    pub fn mark_all_dirty(&mut self) {\n        let mut orders = vec![];\n\n        for (_idx, scope) in self.scopes.iter() {\n            orders.push(ScopeOrder::new(scope.state().height(), scope.id()));\n        }\n\n        for order in orders {\n            self.queue_scope(order);\n        }\n    }\n\n    /// Manually mark a scope as requiring a re-render\n    ///\n    /// Whenever the Runtime \"works\", it will re-render this scope\n    pub fn mark_dirty(&mut self, id: ScopeId) {\n        let Some(scope) = self.runtime.try_get_state(id) else {\n            return;\n        };\n\n        tracing::event!(tracing::Level::TRACE, \"Marking scope {:?} as dirty\", id);\n        let order = ScopeOrder::new(scope.height(), id);\n        drop(scope);\n        self.queue_scope(order);\n    }\n\n    /// Mark a task as dirty\n    fn mark_task_dirty(&mut self, task: Task) {\n        let Some(scope) = self.runtime.task_scope(task) else {\n            return;\n        };\n        let Some(scope) = self.runtime.try_get_state(scope) else {\n            return;\n        };\n\n        tracing::event!(\n            tracing::Level::TRACE,\n            \"Marking task {:?} (spawned in {:?}) as dirty\",\n            task,\n            scope.id,\n        );\n\n        let order = ScopeOrder::new(scope.height(), scope.id);\n        drop(scope);\n        self.queue_task(task, order);\n    }\n\n    /// Wait for the scheduler to have any work.\n    ///\n    /// This method polls the internal future queue, waiting for suspense nodes, tasks, or other work. This completes when\n    /// any work is ready. If multiple scopes are marked dirty from a task or a suspense tree is finished, this method\n    /// will exit.\n    ///\n    /// This method is cancel-safe, so you're fine to discard the future in a select block.\n    ///\n    /// This lets us poll async tasks and suspended trees during idle periods without blocking the main thread.\n    ///\n    /// # Example\n    ///\n    /// ```rust, no_run\n    /// # use dioxus::prelude::*;\n    /// # fn app() -> Element { rsx! { div {} } }\n    /// let dom = VirtualDom::new(app);\n    /// ```\n    #[instrument(skip(self), level = \"trace\", name = \"VirtualDom::wait_for_work\")]\n    pub async fn wait_for_work(&mut self) {\n        loop {\n            // Process all events - Scopes are marked dirty, etc\n            // Sometimes when wakers fire we get a slew of updates at once, so its important that we drain this completely\n            self.process_events();\n\n            // Now that we have collected all queued work, we should check if we have any dirty scopes. If there are not, then we can poll any queued futures\n            if self.has_dirty_scopes() {\n                return;\n            }\n\n            // Make sure we set the runtime since we're running user code\n            let _runtime = RuntimeGuard::new(self.runtime.clone());\n\n            // There isn't any more work we can do synchronously. Wait for any new work to be ready\n            self.wait_for_event().await;\n        }\n    }\n\n    /// Wait for the next event to trigger and add it to the queue\n    #[instrument(skip(self), level = \"trace\", name = \"VirtualDom::wait_for_event\")]\n    async fn wait_for_event(&mut self) {\n        match self.rx.next().await.expect(\"channel should never close\") {\n            SchedulerMsg::Immediate(id) => self.mark_dirty(id),\n            SchedulerMsg::TaskNotified(id) => {\n                // Instead of running the task immediately, we insert it into the runtime's task queue.\n                // The task may be marked dirty at the same time as the scope that owns the task is dropped.\n                self.mark_task_dirty(Task::from_id(id));\n            }\n            SchedulerMsg::EffectQueued => {}\n            SchedulerMsg::AllDirty => self.mark_all_dirty(),\n        };\n    }\n\n    /// Queue any pending events\n    fn queue_events(&mut self) {\n        // Prevent a task from deadlocking the runtime by repeatedly queueing itself\n        while let Ok(Some(msg)) = self.rx.try_next() {\n            match msg {\n                SchedulerMsg::Immediate(id) => self.mark_dirty(id),\n                SchedulerMsg::TaskNotified(task) => self.mark_task_dirty(Task::from_id(task)),\n                SchedulerMsg::EffectQueued => {}\n                SchedulerMsg::AllDirty => self.mark_all_dirty(),\n            }\n        }\n    }\n\n    /// Process all events in the queue until there are no more left\n    #[instrument(skip(self), level = \"trace\", name = \"VirtualDom::process_events\")]\n    pub fn process_events(&mut self) {\n        self.queue_events();\n\n        // Now that we have collected all queued work, we should check if we have any dirty scopes. If there are not, then we can poll any queued futures\n        if self.has_dirty_scopes() {\n            return;\n        }\n\n        self.poll_tasks()\n    }\n\n    /// Poll any queued tasks\n    #[instrument(skip(self), level = \"trace\", name = \"VirtualDom::poll_tasks\")]\n    fn poll_tasks(&mut self) {\n        // Make sure we set the runtime since we're running user code\n        let _runtime = RuntimeGuard::new(self.runtime.clone());\n\n        // Keep polling tasks until there are no more effects or tasks to run\n        // Or until we have no more dirty scopes\n        while !self.runtime.dirty_tasks.borrow().is_empty()\n            || !self.runtime.pending_effects.borrow().is_empty()\n        {\n            // Next, run any queued tasks\n            // We choose not to poll the deadline since we complete pretty quickly anyways\n            while let Some(task) = self.pop_task() {\n                let _ = self.runtime.handle_task_wakeup(task);\n\n                // Running that task, may mark a scope higher up as dirty. If it does, return from the function early\n                self.queue_events();\n                if self.has_dirty_scopes() {\n                    return;\n                }\n            }\n\n            // At this point, we have finished running all tasks that are pending and we haven't found any scopes to rerun. This means it is safe to run our lowest priority work: effects\n            while let Some(effect) = self.pop_effect() {\n                effect.run();\n                // Check if any new scopes are queued for rerun\n                self.queue_events();\n                if self.has_dirty_scopes() {\n                    return;\n                }\n            }\n        }\n    }\n\n    /// Rebuild the virtualdom without handling any of the mutations\n    ///\n    /// This is useful for testing purposes and in cases where you render the output of the virtualdom without\n    /// handling any of its mutations.\n    pub fn rebuild_in_place(&mut self) {\n        self.rebuild(&mut NoOpMutations);\n    }\n\n    /// [`VirtualDom::rebuild`] to a vector of mutations for testing purposes\n    pub fn rebuild_to_vec(&mut self) -> Mutations {\n        let mut mutations = Mutations::default();\n        self.rebuild(&mut mutations);\n        mutations\n    }\n\n    /// Performs a *full* rebuild of the virtual dom, returning every edit required to generate the actual dom from scratch.\n    ///\n    /// The mutations item expects the RealDom's stack to be the root of the application.\n    ///\n    /// Tasks will not be polled with this method, nor will any events be processed from the event queue. Instead, the\n    /// root component will be run once and then diffed. All updates will flow out as mutations.\n    ///\n    /// All state stored in components will be completely wiped away.\n    ///\n    /// Any templates previously registered will remain.\n    ///\n    /// # Example\n    /// ```rust, no_run\n    /// # use dioxus::prelude::*;\n    /// # use dioxus_core::*;\n    /// fn app() -> Element {\n    ///     rsx! { \"hello world\" }\n    /// }\n    ///\n    /// let mut dom = VirtualDom::new(app);\n    /// let mut mutations = Mutations::default();\n    /// dom.rebuild(&mut mutations);\n    /// ```\n    #[instrument(skip(self, to), level = \"trace\", name = \"VirtualDom::rebuild\")]\n    pub fn rebuild(&mut self, to: &mut impl WriteMutations) {\n        let _runtime = RuntimeGuard::new(self.runtime.clone());\n        let new_nodes = self\n            .runtime\n            .clone()\n            .while_rendering(|| self.run_scope(ScopeId::ROOT));\n\n        let new_nodes = LastRenderedNode::new(new_nodes);\n\n        self.scopes[ScopeId::ROOT.0].last_rendered_node = Some(new_nodes.clone());\n\n        // Rebuilding implies we append the created elements to the root\n        let m = self.create_scope(Some(to), ScopeId::ROOT, new_nodes, None);\n\n        to.append_children(ElementId(0), m);\n    }\n\n    /// Render whatever the VirtualDom has ready as fast as possible without requiring an executor to progress\n    /// suspended subtrees.\n    #[instrument(skip(self, to), level = \"trace\", name = \"VirtualDom::render_immediate\")]\n    pub fn render_immediate(&mut self, to: &mut impl WriteMutations) {\n        // Process any events that might be pending in the queue\n        // Signals marked with .write() need a chance to be handled by the effect driver\n        // This also processes futures which might progress into immediately rerunning a scope\n        self.process_events();\n\n        // Next, diff any dirty scopes\n        // We choose not to poll the deadline since we complete pretty quickly anyways\n        let _runtime = RuntimeGuard::new(self.runtime.clone());\n        while let Some(work) = self.pop_work() {\n            match work {\n                Work::PollTask(task) => {\n                    _ = self.runtime.handle_task_wakeup(task);\n                    // Make sure we process any new events\n                    self.queue_events();\n                }\n                Work::RerunScope(scope) => {\n                    // If the scope is dirty, run the scope and get the mutations\n                    self.runtime.clone().while_rendering(|| {\n                        self.run_and_diff_scope(Some(to), scope.id);\n                    });\n                }\n            }\n        }\n\n        self.runtime.finish_render();\n    }\n\n    /// [`Self::render_immediate`] to a vector of mutations for testing purposes\n    pub fn render_immediate_to_vec(&mut self) -> Mutations {\n        let mut mutations = Mutations::default();\n        self.render_immediate(&mut mutations);\n        mutations\n    }\n\n    /// Render the virtual dom, waiting for all suspense to be finished\n    ///\n    /// The mutations will be thrown out, so it's best to use this method for things like SSR that have async content\n    ///\n    /// We don't call \"flush_sync\" here since there's no sync work to be done. Futures will be progressed like usual,\n    /// however any futures waiting on flush_sync will remain pending\n    #[instrument(skip(self), level = \"trace\", name = \"VirtualDom::wait_for_suspense\")]\n    pub async fn wait_for_suspense(&mut self) {\n        loop {\n            self.queue_events();\n\n            if !self.suspended_tasks_remaining() && !self.has_dirty_scopes() {\n                break;\n            }\n\n            self.wait_for_suspense_work().await;\n\n            self.render_suspense_immediate().await;\n        }\n    }\n\n    /// Check if there are any suspended tasks remaining\n    pub fn suspended_tasks_remaining(&self) -> bool {\n        self.runtime.suspended_tasks.get() > 0\n    }\n\n    /// Wait for the scheduler to have any work that should be run during suspense.\n    pub async fn wait_for_suspense_work(&mut self) {\n        // Wait for a work to be ready (IE new suspense leaves to pop up)\n        loop {\n            // Process all events - Scopes are marked dirty, etc\n            // Sometimes when wakers fire we get a slew of updates at once, so its important that we drain this completely\n            self.queue_events();\n\n            // Now that we have collected all queued work, we should check if we have any dirty scopes. If there are not, then we can poll any queued futures\n            if self.has_dirty_scopes() {\n                break;\n            }\n\n            {\n                // Make sure we set the runtime since we're running user code\n                let _runtime = RuntimeGuard::new(self.runtime.clone());\n                // Next, run any queued tasks\n                // We choose not to poll the deadline since we complete pretty quickly anyways\n                let mut tasks_polled = 0;\n                while let Some(task) = self.pop_task() {\n                    if self.runtime.task_runs_during_suspense(task) {\n                        let _ = self.runtime.handle_task_wakeup(task);\n                        // Running that task, may mark a scope higher up as dirty. If it does, return from the function early\n                        self.queue_events();\n                        if self.has_dirty_scopes() {\n                            return;\n                        }\n                    }\n                    tasks_polled += 1;\n                    // Once we have polled a few tasks, we manually yield to the scheduler to give it a chance to run other pending work\n                    if tasks_polled > 32 {\n                        yield_now().await;\n                        tasks_polled = 0;\n                    }\n                }\n            }\n\n            self.wait_for_event().await;\n        }\n    }\n\n    /// Render any dirty scopes immediately, but don't poll any futures that are client only on that scope\n    /// Returns a list of suspense boundaries that were resolved\n    pub async fn render_suspense_immediate(&mut self) -> Vec<ScopeId> {\n        // Queue any new events before we start working\n        self.queue_events();\n\n        // Render whatever work needs to be rendered, unlocking new futures and suspense leaves\n        let _runtime = RuntimeGuard::new(self.runtime.clone());\n\n        let mut work_done = 0;\n        while let Some(work) = self.pop_work() {\n            match work {\n                Work::PollTask(task) => {\n                    // During suspense, we only want to run tasks that are suspended\n                    if self.runtime.task_runs_during_suspense(task) {\n                        let _ = self.runtime.handle_task_wakeup(task);\n                    }\n                }\n                Work::RerunScope(scope) => {\n                    let scope_id: ScopeId = scope.id;\n                    let run_scope = self\n                        .runtime\n                        .try_get_state(scope.id)\n                        .filter(|scope| scope.should_run_during_suspense())\n                        .is_some();\n                    if run_scope {\n                        // If the scope is dirty, run the scope and get the mutations\n                        self.runtime.clone().while_rendering(|| {\n                            self.run_and_diff_scope(None::<&mut NoOpMutations>, scope_id);\n                        });\n\n                        tracing::trace!(\"Ran scope {:?} during suspense\", scope_id);\n                    } else {\n                        tracing::warn!(\n                            \"Scope {:?} was marked as dirty, but will not rerun during suspense. Only nodes that are under a suspense boundary rerun during suspense\",\n                            scope_id\n                        );\n                    }\n                }\n            }\n\n            // Queue any new events\n            self.queue_events();\n            work_done += 1;\n\n            // Once we have polled a few tasks, we manually yield to the scheduler to give it a chance to run other pending work\n            if work_done > 32 {\n                yield_now().await;\n                work_done = 0;\n            }\n        }\n\n        self.resolved_scopes\n            .sort_by_key(|&id| self.runtime.get_state(id).height);\n        std::mem::take(&mut self.resolved_scopes)\n    }\n\n    /// Get the current runtime\n    pub fn runtime(&self) -> Rc<Runtime> {\n        self.runtime.clone()\n    }\n\n    /// Handle an event with the Virtual Dom. This method is deprecated in favor of [VirtualDom::runtime().handle_event] and will be removed in a future release.\n    #[deprecated = \"Use [VirtualDom::runtime().handle_event] instead\"]\n    pub fn handle_event(&self, name: &str, event: Rc<dyn Any>, element: ElementId, bubbling: bool) {\n        let event = crate::Event::new(event, bubbling);\n        self.runtime().handle_event(name, event, element);\n    }\n\n    #[cfg(debug_assertions)]\n    fn register_subsecond_handler(&self) {\n        let sender = self.runtime().sender.clone();\n        subsecond::register_handler(std::sync::Arc::new(move || {\n            _ = sender.unbounded_send(SchedulerMsg::AllDirty);\n        }));\n    }\n}\n\nimpl Drop for VirtualDom {\n    fn drop(&mut self) {\n        // Drop all scopes in order of height\n        let mut scopes = self.scopes.drain().collect::<Vec<_>>();\n        scopes.sort_by_key(|scope| scope.state().height);\n        for scope in scopes.into_iter().rev() {\n            drop(scope);\n        }\n\n        // Drop the mounts, tasks, and effects, releasing any `Rc<Runtime>` references\n        self.runtime.pending_effects.borrow_mut().clear();\n        self.runtime.tasks.borrow_mut().clear();\n        self.runtime.mounts.borrow_mut().clear();\n    }\n}\n\n/// Yield control back to the async scheduler. This is used to give the scheduler a chance to run other pending work. Or cancel the task if the client has disconnected.\nasync fn yield_now() {\n    let mut yielded = false;\n    std::future::poll_fn::<(), _>(move |cx| {\n        if !yielded {\n            cx.waker().wake_by_ref();\n            yielded = true;\n            std::task::Poll::Pending\n        } else {\n            std::task::Poll::Ready(())\n        }\n    })\n    .await;\n}\n"
  },
  {
    "path": "packages/core/tests/.rustfmt.toml",
    "content": "struct_variant_width = 100\nstruct_lit_width = 80\n"
  },
  {
    "path": "packages/core/tests/README.md",
    "content": "# Testing of Dioxus core\n\n\nDiffing\n- [x] create elements\n- [x] create text\n- [x] create fragments\n- [x] create empty fragments (placeholders)\n- [x] diff elements\n- [x] diff from element/text to fragment\n- [x] diff from element/text to empty fragment\n- [x] diff to element with children works too\n- [x] replace with works forward\n- [x] replace with works backward\n- [x] un-keyed diffing\n- [x] keyed diffing\n- [x] keyed diffing out of order\n- [x] keyed diffing with prefix/suffix\n- [x] suspended nodes work\n\nLifecycle\n- [] Components mount properly\n- [] Components create new child components\n- [] Replaced components unmount old components and mount new\n- [] Post-render effects are called\n\nShared Context\n- [] Shared context propagates downwards\n- [] unwrapping shared context if it doesn't exist works too\n\nSuspense\n- [] use_suspense generates suspended nodes\n\n\nHooks\n- [] Drop order is maintained\n- [] Shared hook state is okay\n- [] use_hook works\n- [] use_ref works\n- [] use_noderef works\n- [] use_provide_state\n- [] use_consume_state\n\n\nVirtualDOM API\n- [] work\n- [] rebuild_to_vec\n- [] change props\n"
  },
  {
    "path": "packages/core/tests/attr_cleanup.rs",
    "content": "//! dynamic attributes in dioxus necessitate an allocated node ID.\n//!\n//! This tests to ensure we clean it up\n\nuse dioxus::prelude::*;\nuse dioxus_core::{generation, ElementId, IntoAttributeValue, Mutation::*};\n\n#[test]\nfn attrs_cycle() {\n    tracing_subscriber::fmt::init();\n\n    let mut dom = VirtualDom::new(|| {\n        let id = generation();\n        match id % 2 {\n            0 => rsx! { div {} },\n            1 => rsx! {\n                div { h1 { class: \"{id}\", id: \"{id}\" } }\n            },\n            _ => unreachable!(),\n        }\n    });\n\n    assert_eq!(\n        dom.rebuild_to_vec().edits,\n        [\n            LoadTemplate { index: 0, id: ElementId(1,) },\n            AppendChildren { m: 1, id: ElementId(0) },\n        ]\n    );\n\n    dom.mark_dirty(ScopeId::APP);\n    assert_eq!(\n        dom.render_immediate_to_vec().edits,\n        [\n            LoadTemplate { index: 0, id: ElementId(2,) },\n            AssignId { path: &[0,], id: ElementId(3,) },\n            SetAttribute { name: \"class\", value: \"1\".into_value(), id: ElementId(3,), ns: None },\n            SetAttribute { name: \"id\", value: \"1\".into_value(), id: ElementId(3,), ns: None },\n            ReplaceWith { id: ElementId(1,), m: 1 },\n        ]\n    );\n\n    dom.mark_dirty(ScopeId::APP);\n    assert_eq!(\n        dom.render_immediate_to_vec().edits,\n        [\n            LoadTemplate { index: 0, id: ElementId(1) },\n            ReplaceWith { id: ElementId(2), m: 1 }\n        ]\n    );\n\n    dom.mark_dirty(ScopeId::APP);\n    assert_eq!(\n        dom.render_immediate_to_vec().edits,\n        [\n            LoadTemplate { index: 0, id: ElementId(2) },\n            AssignId { path: &[0], id: ElementId(3) },\n            SetAttribute {\n                name: \"class\",\n                value: dioxus_core::AttributeValue::Text(\"3\".to_string()),\n                id: ElementId(3),\n                ns: None\n            },\n            SetAttribute {\n                name: \"id\",\n                value: dioxus_core::AttributeValue::Text(\"3\".to_string()),\n                id: ElementId(3),\n                ns: None\n            },\n            ReplaceWith { id: ElementId(1), m: 1 }\n        ]\n    );\n\n    // we take the node taken by attributes since we reused it\n    dom.mark_dirty(ScopeId::APP);\n    assert_eq!(\n        dom.render_immediate_to_vec().edits,\n        [\n            LoadTemplate { index: 0, id: ElementId(1) },\n            ReplaceWith { id: ElementId(2), m: 1 }\n        ]\n    );\n}\n"
  },
  {
    "path": "packages/core/tests/attributes_pass.rs",
    "content": "use dioxus::prelude::*;\nuse dioxus_core::TemplateNode;\n\n/// Make sure that rsx! is parsing templates and their attributes properly\n#[test]\nfn attributes_pass_properly() {\n    let h = rsx! {\n        circle {\n            cx: 50,\n            cy: 50,\n            r: 40,\n            stroke: \"green\",\n            fill: \"yellow\"\n        }\n    };\n\n    let o = h.unwrap();\n\n    let template = &o.template;\n\n    assert_eq!(template.attr_paths.len(), 3);\n\n    let _circle = template.roots[0];\n    let TemplateNode::Element { attrs, tag, namespace, children } = _circle else {\n        panic!(\"Expected an element\");\n    };\n\n    assert_eq!(tag, \"circle\");\n    assert_eq!(namespace, Some(\"http://www.w3.org/2000/svg\"));\n    assert_eq!(children.len(), 0);\n    assert_eq!(attrs.len(), 5);\n}\n"
  },
  {
    "path": "packages/core/tests/boolattrs.rs",
    "content": "use dioxus::dioxus_core::{ElementId, Mutation::*};\nuse dioxus::prelude::*;\n\n#[test]\nfn bool_test() {\n    let mut app = VirtualDom::new(|| rsx!(div { hidden: false }));\n\n    assert_eq!(\n        app.rebuild_to_vec().edits,\n        [\n            LoadTemplate { index: 0, id: ElementId(1) },\n            SetAttribute {\n                name: \"hidden\",\n                value: dioxus_core::AttributeValue::Bool(false),\n                id: ElementId(1,),\n                ns: None\n            },\n            AppendChildren { m: 1, id: ElementId(0) },\n        ]\n    );\n}\n"
  },
  {
    "path": "packages/core/tests/bubble_error.rs",
    "content": "//! we should properly bubble up errors from components\n\nuse dioxus::prelude::*;\nuse dioxus_core::generation;\n\nfn app() -> Element {\n    let raw = match generation() % 2 {\n        0 => \"123.123\",\n        1 => \"123.123.123\",\n        _ => unreachable!(),\n    };\n\n    let value = raw.parse::<f32>().unwrap_or(123.123);\n\n    rsx! { div { \"hello {value}\" } }\n}\n\n#[test]\nfn bubbles_error() {\n    let mut dom = VirtualDom::new(app);\n\n    {\n        let _edits = dom.rebuild_to_vec();\n    }\n\n    dom.mark_dirty(ScopeId::APP);\n\n    _ = dom.render_immediate_to_vec();\n}\n"
  },
  {
    "path": "packages/core/tests/children_drop_futures.rs",
    "content": "//! Verify that when children are dropped, they drop their futures before they are polled\n\nuse std::{sync::atomic::AtomicUsize, time::Duration};\n\nuse dioxus::prelude::*;\nuse dioxus_core::generation;\n\n#[tokio::test]\nasync fn child_futures_drop_first() {\n    static POLL_COUNT: AtomicUsize = AtomicUsize::new(0);\n\n    fn app() -> Element {\n        if generation() == 0 {\n            rsx! { Child {} }\n        } else {\n            rsx! {}\n        }\n    }\n\n    #[component]\n    fn Child() -> Element {\n        // Spawn a task that will increment POLL_COUNT every 10 milliseconds\n        // This should be dropped after the second time the parent is run\n        use_hook(|| {\n            spawn(async {\n                POLL_COUNT.fetch_add(1, std::sync::atomic::Ordering::Relaxed);\n            });\n        });\n\n        rsx! {}\n    }\n\n    let mut dom = VirtualDom::new(app);\n\n    dom.rebuild(&mut dioxus_core::NoOpMutations);\n\n    // Here the parent and task could resolve at the same time, but because the task is in the child, dioxus should run the parent first because the child might be dropped\n    dom.mark_dirty(ScopeId::APP);\n\n    tokio::select! {\n        _ = dom.wait_for_work() => {}\n        _ = tokio::time::sleep(Duration::from_millis(500)) => panic!(\"timed out\")\n    };\n\n    dom.render_immediate(&mut dioxus_core::NoOpMutations);\n\n    // By the time the tasks are finished, we should've accumulated ticks from two tasks\n    // Be warned that by setting the delay to too short, tokio might not schedule in the tasks\n    assert_eq!(\n        POLL_COUNT.fetch_add(0, std::sync::atomic::Ordering::Relaxed),\n        0\n    );\n}\n"
  },
  {
    "path": "packages/core/tests/conditional_formatted_attributes.rs",
    "content": "use dioxus::prelude::*;\n\n/// Make sure that rsx! handles conditional attributes with one formatted branch correctly\n/// Regression test for https://github.com/DioxusLabs/dioxus/issues/2997\n#[test]\nfn partially_formatted_conditional_attribute() {\n    let width = \"1px\";\n    _ = rsx! {\n        div {\n            width: if true { \"{width}\" } else { \"100px\" }\n        }\n    };\n\n    // And make sure it works if one of those branches is an expression\n    // Regression test for https://github.com/DioxusLabs/dioxus/issues/3146\n    let opt = \"button\";\n\n    _ = rsx! {\n        input {\n            type: if true { opt } else { \"text\" },\n        }\n        input {\n            type: if true { opt.to_string() } else { \"text with\" },\n        }\n        input {\n            type: if true { opt.to_string() } else { \"text with {width}\" },\n        }\n        input {\n            type: if true { opt.to_string() } else if true { \"\" } else { \"text with {width}\" },\n        }\n        input {\n            type: if true { \"one\" } else if true { \"two\" } else { \"three\" },\n        }\n        input {\n            type: if true { \"one\" } else if true { \"two\" } else if true { \"three\" } else { opt },\n        }\n        input {\n            type: if true { \"one\" } else if true { if false { \"true\" } else { \"false\" } } else { \"three\" },\n        }\n        input {\n            type: if true { \"one\".to_string() },\n        }\n        input {\n            type: if true { \"one\" },\n        }\n    };\n}\n"
  },
  {
    "path": "packages/core/tests/context_api.rs",
    "content": "use dioxus::dioxus_core::{ElementId, Mutation::*};\nuse dioxus::prelude::*;\nuse dioxus_core::{consume_context_from_scope, generation};\n\n#[test]\nfn state_shares() {\n    fn app() -> Element {\n        provide_context(generation() as i32);\n\n        rsx!(child_1 {})\n    }\n\n    fn child_1() -> Element {\n        rsx!(child_2 {})\n    }\n\n    fn child_2() -> Element {\n        let value = consume_context::<i32>();\n        rsx!(\"Value is {value}\")\n    }\n\n    let mut dom = VirtualDom::new(app);\n    assert_eq!(\n        dom.rebuild_to_vec().edits,\n        [\n            CreateTextNode { value: \"Value is 0\".to_string(), id: ElementId(1,) },\n            AppendChildren { m: 1, id: ElementId(0) },\n        ]\n    );\n\n    dom.mark_dirty(ScopeId::APP);\n    _ = dom.render_immediate_to_vec();\n    dom.in_runtime(|| {\n        assert_eq!(consume_context_from_scope::<i32>(ScopeId::APP).unwrap(), 1);\n    });\n\n    dom.mark_dirty(ScopeId::APP);\n    _ = dom.render_immediate_to_vec();\n    dom.in_runtime(|| {\n        assert_eq!(consume_context_from_scope::<i32>(ScopeId::APP).unwrap(), 2);\n    });\n\n    dom.mark_dirty(ScopeId(ScopeId::APP.0 + 2));\n    assert_eq!(\n        dom.render_immediate_to_vec().edits,\n        [SetText { value: \"Value is 2\".to_string(), id: ElementId(1,) },]\n    );\n\n    dom.mark_dirty(ScopeId::APP);\n    dom.mark_dirty(ScopeId(ScopeId::APP.0 + 2));\n    let edits = dom.render_immediate_to_vec();\n    assert_eq!(\n        edits.edits,\n        [SetText { value: \"Value is 3\".to_string(), id: ElementId(1,) },]\n    );\n}\n"
  },
  {
    "path": "packages/core/tests/create_dom.rs",
    "content": "#![allow(unused, non_upper_case_globals, non_snake_case)]\n\n//! Prove that the dom works normally through virtualdom methods.\n//!\n//! This methods all use \"rebuild_to_vec\" which completely bypasses the scheduler.\n//! Hard rebuild_to_vecs don't consume any events from the event queue.\n\nuse dioxus::dioxus_core::Mutation::*;\nuse dioxus::prelude::*;\nuse dioxus_core::ElementId;\n\n#[test]\nfn test_original_diff() {\n    let mut dom = VirtualDom::new(|| {\n        rsx! {\n            div { div { \"Hello, world!\" } }\n        }\n    });\n\n    let edits = dom.rebuild_to_vec();\n\n    assert_eq!(\n        edits.edits,\n        [\n            // add to root\n            LoadTemplate { index: 0, id: ElementId(1) },\n            AppendChildren { m: 1, id: ElementId(0) }\n        ]\n    )\n}\n\n#[test]\nfn create() {\n    let mut dom = VirtualDom::new(|| {\n        rsx! {\n            div {\n                div {\n                    \"Hello, world!\"\n                    div {\n                        div {\n                            Fragment { \"hello\"\"world\" }\n                        }\n                    }\n                }\n            }\n        }\n    });\n\n    let _edits = dom.rebuild_to_vec();\n\n    // todo: we don't test template mutations anymore since the templates are passed along\n\n    // assert_eq!(\n    //     edits.templates,\n    //     [\n    //         // create template\n    //         CreateElement { name: \"div\" },\n    //         CreateElement { name: \"div\" },\n    //         CreateStaticText { value: \"Hello, world!\" },\n    //         CreateElement { name: \"div\" },\n    //         CreateElement { name: \"div\" },\n    //         CreateStaticPlaceholder {},\n    //         AppendChildren { m: 1 },\n    //         AppendChildren { m: 1 },\n    //         AppendChildren { m: 2 },\n    //         AppendChildren { m: 1 },\n    //         SaveTemplate {  m: 1 },\n    //         // The fragment child template\n    //         CreateStaticText { value: \"hello\" },\n    //         CreateStaticText { value: \"world\" },\n    //         SaveTemplate {  m: 2 },\n    //     ]\n    // );\n}\n\n#[test]\nfn create_list() {\n    let mut dom = VirtualDom::new(|| rsx! {{(0..3).map(|f| rsx!( div { \"hello\" } ))}});\n\n    let _edits = dom.rebuild_to_vec();\n\n    // note: we dont test template edits anymore\n    // assert_eq!(\n    //     edits.templates,\n    //     [\n    //         // create template\n    //         CreateElement { name: \"div\" },\n    //         CreateStaticText { value: \"hello\" },\n    //         AppendChildren { m: 1 },\n    //         SaveTemplate {  m: 1 }\n    //     ]\n    // );\n}\n\n#[test]\nfn create_simple() {\n    let mut dom = VirtualDom::new(|| {\n        rsx! {\n            div {}\n            div {}\n            div {}\n            div {}\n        }\n    });\n\n    let edits = dom.rebuild_to_vec();\n\n    // note: we dont test template edits anymore\n    // assert_eq!(\n    //     edits.templates,\n    //     [\n    //         // create template\n    //         CreateElement { name: \"div\" },\n    //         CreateElement { name: \"div\" },\n    //         CreateElement { name: \"div\" },\n    //         CreateElement { name: \"div\" },\n    //         // add to root\n    //         SaveTemplate {  m: 4 }\n    //     ]\n    // );\n}\n#[test]\nfn create_components() {\n    let mut dom = VirtualDom::new(|| {\n        rsx! {\n            Child { \"abc1\" }\n            Child { \"abc2\" }\n            Child { \"abc3\" }\n        }\n    });\n\n    #[derive(Props, Clone, PartialEq)]\n    struct ChildProps {\n        children: Element,\n    }\n\n    fn Child(cx: ChildProps) -> Element {\n        rsx! {\n            h1 {}\n            div { {cx.children} }\n            p {}\n        }\n    }\n\n    let _edits = dom.rebuild_to_vec();\n\n    // todo: test this\n}\n\n#[test]\nfn anchors() {\n    let mut dom = VirtualDom::new(|| {\n        rsx! {\n            if true {\n                 div { \"hello\" }\n            }\n            if false {\n                div { \"goodbye\" }\n            }\n        }\n    });\n\n    // note that the template under \"false\" doesn't show up since it's not loaded\n    let edits = dom.rebuild_to_vec();\n\n    // note: we dont test template edits anymore\n    // assert_eq!(\n    //     edits.templates,\n    //     [\n    //         // create each template\n    //         CreateElement { name: \"div\" },\n    //         CreateStaticText { value: \"hello\" },\n    //         AppendChildren { m: 1 },\n    //         SaveTemplate { m: 1, name: \"template\" },\n    //     ]\n    // );\n\n    assert_eq!(\n        edits.edits,\n        [\n            LoadTemplate { index: 0, id: ElementId(1) },\n            CreatePlaceholder { id: ElementId(2) },\n            AppendChildren { m: 2, id: ElementId(0) }\n        ]\n    )\n}\n"
  },
  {
    "path": "packages/core/tests/create_fragments.rs",
    "content": "//! Do we create fragments properly across complex boundaries?\n\nuse dioxus::dioxus_core::Mutation::*;\nuse dioxus::prelude::*;\nuse dioxus_core::ElementId;\n\n#[test]\nfn empty_fragment_creates_nothing() {\n    fn app() -> Element {\n        rsx!({})\n    }\n\n    let mut vdom = VirtualDom::new(app);\n    let edits = vdom.rebuild_to_vec();\n\n    assert_eq!(\n        edits.edits,\n        [\n            CreatePlaceholder { id: ElementId(1) },\n            AppendChildren { id: ElementId(0), m: 1 }\n        ]\n    );\n}\n\n#[test]\nfn root_fragments_work() {\n    let mut vdom = VirtualDom::new(|| {\n        rsx!(\n            div { \"hello\" }\n            div { \"goodbye\" }\n        )\n    });\n\n    assert_eq!(\n        vdom.rebuild_to_vec().edits.last().unwrap(),\n        &AppendChildren { id: ElementId(0), m: 2 }\n    );\n}\n\n#[test]\nfn fragments_nested() {\n    let mut vdom = VirtualDom::new(|| {\n        rsx!(\n            div { \"hello\" }\n            div { \"goodbye\" }\n            {rsx! {\n                div { \"hello\" }\n                div { \"goodbye\" }\n                {rsx! {\n                    div { \"hello\" }\n                    div { \"goodbye\" }\n                    {rsx! {\n                        div { \"hello\" }\n                        div { \"goodbye\" }\n                    }}\n                }}\n            }}\n        )\n    });\n\n    assert_eq!(\n        vdom.rebuild_to_vec().edits.last().unwrap(),\n        &AppendChildren { id: ElementId(0), m: 8 }\n    );\n}\n\n#[test]\nfn fragments_across_components() {\n    fn app() -> Element {\n        rsx! {\n            demo_child {}\n            demo_child {}\n            demo_child {}\n            demo_child {}\n        }\n    }\n\n    fn demo_child() -> Element {\n        let world = \"world\";\n        rsx! { \"hellO!\" {world} }\n    }\n\n    assert_eq!(\n        VirtualDom::new(app).rebuild_to_vec().edits.last().unwrap(),\n        &AppendChildren { id: ElementId(0), m: 8 }\n    );\n}\n\n#[test]\nfn list_fragments() {\n    fn app() -> Element {\n        rsx!(\n            h1 { \"hello\" }\n            {(0..6).map(|f| rsx!( span { \"{f}\" }))}\n        )\n    }\n    assert_eq!(\n        VirtualDom::new(app).rebuild_to_vec().edits.last().unwrap(),\n        &AppendChildren { id: ElementId(0), m: 7 }\n    );\n}\n"
  },
  {
    "path": "packages/core/tests/create_lists.rs",
    "content": "use dioxus::dioxus_core::Mutation::*;\nuse dioxus::prelude::*;\nuse dioxus_core::ElementId;\nuse pretty_assertions::assert_eq;\n\n// A real-world usecase of templates at peak performance\n// In react, this would be a lot of node creation.\n//\n// In Dioxus, we memoize the rsx! body and simplify it down to a few template loads\n//\n// Also note that the IDs increase linearly. This lets us drive a vec on the renderer for O(1) re-indexing\nfn app() -> Element {\n    rsx! {\n        div {\n            for i in 0..3 {\n                div {\n                    h1 { \"hello world! \"}\n                    p { \"{i}\" }\n                }\n            }\n        }\n    }\n}\n\n#[test]\nfn list_renders() {\n    let mut dom = VirtualDom::new(app);\n\n    let edits = dom.rebuild_to_vec();\n\n    // note: we dont test template edits anymore\n    // assert_eq!(\n    //     edits.templates,\n    //     [\n    //         // Create the outer div\n    //         CreateElement { name: \"div\" },\n    //         // todo: since this is the only child, we should just use\n    //         // append when modify the values (IE no need for a placeholder)\n    //         CreateStaticPlaceholder,\n    //         AppendChildren { m: 1 },\n    //         SaveTemplate {  m: 1 },\n    //         // Create the inner template div\n    //         CreateElement { name: \"div\" },\n    //         CreateElement { name: \"h1\" },\n    //         CreateStaticText { value: \"hello world! \" },\n    //         AppendChildren { m: 1 },\n    //         CreateElement { name: \"p\" },\n    //         CreateTextPlaceholder,\n    //         AppendChildren { m: 1 },\n    //         AppendChildren { m: 2 },\n    //         SaveTemplate {  m: 1 }\n    //     ],\n    // );\n\n    assert_eq!(\n        edits.edits,\n        [\n            // Load the outer div\n            LoadTemplate { index: 0, id: ElementId(1) },\n            // Load each template one-by-one, rehydrating it\n            LoadTemplate { index: 0, id: ElementId(2) },\n            CreateTextNode { value: \"0\".to_string(), id: ElementId(3) },\n            ReplacePlaceholder { path: &[1, 0], m: 1 },\n            LoadTemplate { index: 0, id: ElementId(4) },\n            CreateTextNode { value: \"1\".to_string(), id: ElementId(5) },\n            ReplacePlaceholder { path: &[1, 0], m: 1 },\n            LoadTemplate { index: 0, id: ElementId(6) },\n            CreateTextNode { value: \"2\".to_string(), id: ElementId(7) },\n            ReplacePlaceholder { path: &[1, 0], m: 1 },\n            // Replace the 0th childn on the div with the 3 templates on the stack\n            ReplacePlaceholder { m: 3, path: &[0] },\n            // Append the container div to the dom\n            AppendChildren { m: 1, id: ElementId(0) }\n        ],\n    )\n}\n"
  },
  {
    "path": "packages/core/tests/create_passthru.rs",
    "content": "use dioxus::dioxus_core::Mutation::*;\nuse dioxus::prelude::*;\nuse dioxus_core::ElementId;\n\n/// Should push the text node onto the stack and modify it\n#[test]\nfn nested_passthru_creates() {\n    fn app() -> Element {\n        rsx! {\n            PassThru {\n                PassThru {\n                    PassThru { div { \"hi\" } }\n                }\n            }\n        }\n    }\n\n    #[component]\n    fn PassThru(children: Element) -> Element {\n        rsx!({ children })\n    }\n\n    let mut dom = VirtualDom::new(app);\n    let edits = dom.rebuild_to_vec();\n\n    assert_eq!(\n        edits.edits,\n        [\n            LoadTemplate { index: 0, id: ElementId(1) },\n            AppendChildren { m: 1, id: ElementId(0) },\n        ]\n    )\n}\n\n/// Should load all the templates and append them\n///\n/// Take note on how we don't spit out the template for child_comp since it's entirely dynamic\n#[test]\nfn nested_passthru_creates_add() {\n    fn app() -> Element {\n        rsx! {\n            ChildComp {\n                \"1\"\n                ChildComp {\n                    \"2\"\n                    ChildComp {\n                        \"3\"\n                        div { \"hi\" }\n                    }\n                }\n            }\n        }\n    }\n\n    #[component]\n    fn ChildComp(children: Element) -> Element {\n        rsx! {{children}}\n    }\n\n    let mut dom = VirtualDom::new(app);\n\n    assert_eq!(\n        dom.rebuild_to_vec().edits,\n        [\n            // load 1\n            LoadTemplate { index: 0, id: ElementId(1) },\n            // load 2\n            LoadTemplate { index: 0, id: ElementId(2) },\n            // load 3\n            LoadTemplate { index: 0, id: ElementId(3) },\n            // load div that contains 4\n            LoadTemplate { index: 1, id: ElementId(4) },\n            AppendChildren { id: ElementId(0), m: 4 },\n        ]\n    );\n}\n\n/// note that the template is all dynamic roots - so it doesn't actually get cached as a template\n#[test]\nfn dynamic_node_as_root() {\n    fn app() -> Element {\n        let a = 123;\n        let b = 456;\n        rsx! { \"{a}\" \"{b}\" }\n    }\n\n    let mut dom = VirtualDom::new(app);\n    let edits = dom.rebuild_to_vec();\n\n    // Since the roots were all dynamic, they should not cause any template muations\n    // The root node is text, so we just create it on the spot\n    assert_eq!(\n        edits.edits,\n        [\n            CreateTextNode { value: \"123\".to_string(), id: ElementId(1) },\n            CreateTextNode { value: \"456\".to_string(), id: ElementId(2) },\n            AppendChildren { id: ElementId(0), m: 2 }\n        ]\n    )\n}\n"
  },
  {
    "path": "packages/core/tests/cycle.rs",
    "content": "use dioxus::dioxus_core::{ElementId, Mutation::*};\nuse dioxus::prelude::*;\nuse dioxus_core::generation;\n\n/// As we clean up old templates, the ID for the node should cycle\n#[test]\nfn cycling_elements() {\n    let mut dom = VirtualDom::new(|| match generation() % 2 {\n        0 => rsx! { div { \"wasd\" } },\n        1 => rsx! { div { \"abcd\" } },\n        _ => unreachable!(),\n    });\n\n    {\n        let edits = dom.rebuild_to_vec();\n        assert_eq!(\n            edits.edits,\n            [\n                LoadTemplate { index: 0, id: ElementId(1,) },\n                AppendChildren { m: 1, id: ElementId(0) },\n            ]\n        );\n    }\n\n    dom.mark_dirty(ScopeId::APP);\n    assert_eq!(\n        dom.render_immediate_to_vec().edits,\n        [\n            LoadTemplate { index: 0, id: ElementId(2,) },\n            ReplaceWith { id: ElementId(1,), m: 1 },\n        ]\n    );\n\n    // notice that the IDs cycle back to ElementId(1), preserving a minimal memory footprint\n    dom.mark_dirty(ScopeId::APP);\n    assert_eq!(\n        dom.render_immediate_to_vec().edits,\n        [\n            LoadTemplate { index: 0, id: ElementId(1,) },\n            ReplaceWith { id: ElementId(2,), m: 1 },\n        ]\n    );\n\n    dom.mark_dirty(ScopeId::APP);\n    assert_eq!(\n        dom.render_immediate_to_vec().edits,\n        [\n            LoadTemplate { index: 0, id: ElementId(2,) },\n            ReplaceWith { id: ElementId(1,), m: 1 },\n        ]\n    );\n}\n"
  },
  {
    "path": "packages/core/tests/diff_component.rs",
    "content": "use dioxus::dioxus_core::{ElementId, Mutation::*};\nuse dioxus::prelude::*;\nuse pretty_assertions::assert_eq;\n\n/// When returning sets of components, we do a light diff of the contents to preserve some react-like functionality\n///\n/// This means that nav_bar should never get re-created and that we should only be swapping out\n/// different pointers\n#[test]\nfn component_swap() {\n    // Check that templates with the same structure are deduplicated at compile time\n    // If they are not, this test will fail because it is being run in debug mode where templates are not deduped\n    let dynamic = 0;\n    let template_1 = rsx! { \"{dynamic}\" };\n    let template_2 = rsx! { \"{dynamic}\" };\n    if template_1.unwrap().template != template_2.unwrap().template {\n        return;\n    }\n\n    fn app() -> Element {\n        let mut render_phase = use_signal(|| 0);\n\n        render_phase += 1;\n\n        match render_phase() {\n            0 => rsx! {\n                nav_bar {}\n                dash_board {}\n            },\n            1 => rsx! {\n                nav_bar {}\n                dash_results {}\n            },\n            2 => rsx! {\n                nav_bar {}\n                dash_board {}\n            },\n            3 => rsx! {\n                nav_bar {}\n                dash_results {}\n            },\n            4 => rsx! {\n                nav_bar {}\n                dash_board {}\n            },\n            _ => rsx!(\"blah\"),\n        }\n    }\n\n    fn nav_bar() -> Element {\n        rsx! {\n            h1 {\n                \"NavBar\"\n                for _ in 0..3 {\n                    nav_link {}\n                }\n            }\n        }\n    }\n\n    fn nav_link() -> Element {\n        rsx!( h1 { \"nav_link\" } )\n    }\n\n    fn dash_board() -> Element {\n        rsx!( div { \"dashboard\" } )\n    }\n\n    fn dash_results() -> Element {\n        rsx!( div { \"results\" } )\n    }\n\n    let mut dom = VirtualDom::new(app);\n    {\n        let edits = dom.rebuild_to_vec();\n        assert_eq!(\n            edits.edits,\n            [\n                LoadTemplate { index: 0, id: ElementId(1) },\n                LoadTemplate { index: 0, id: ElementId(2) },\n                LoadTemplate { index: 0, id: ElementId(3) },\n                LoadTemplate { index: 0, id: ElementId(4) },\n                ReplacePlaceholder { path: &[1], m: 3 },\n                LoadTemplate { index: 0, id: ElementId(5) },\n                AppendChildren { m: 2, id: ElementId(0) }\n            ]\n        );\n    }\n\n    dom.mark_dirty(ScopeId::APP);\n    assert_eq!(\n        dom.render_immediate_to_vec().edits,\n        [\n            LoadTemplate { index: 0, id: ElementId(6) },\n            ReplaceWith { id: ElementId(5), m: 1 }\n        ]\n    );\n\n    dom.mark_dirty(ScopeId::APP);\n    assert_eq!(\n        dom.render_immediate_to_vec().edits,\n        [\n            LoadTemplate { index: 0, id: ElementId(5) },\n            ReplaceWith { id: ElementId(6), m: 1 }\n        ]\n    );\n\n    dom.mark_dirty(ScopeId::APP);\n    assert_eq!(\n        dom.render_immediate_to_vec().edits,\n        [\n            LoadTemplate { index: 0, id: ElementId(6) },\n            ReplaceWith { id: ElementId(5), m: 1 }\n        ]\n    );\n}\n"
  },
  {
    "path": "packages/core/tests/diff_dynamic_node.rs",
    "content": "use dioxus::dioxus_core::{ElementId, Mutation::*};\nuse dioxus::prelude::*;\nuse dioxus_core::generation;\nuse pretty_assertions::assert_eq;\n\n#[test]\nfn toggle_option_text() {\n    let mut dom = VirtualDom::new(|| {\n        let gen = generation();\n        let text = if gen % 2 != 0 { Some(\"hello\") } else { None };\n        println!(\"{:?}\", text);\n\n        rsx! {\n            div {\n                {text}\n            }\n        }\n    });\n\n    // load the div and then assign the None as a placeholder\n    assert_eq!(\n        dom.rebuild_to_vec().edits,\n        [\n            LoadTemplate { index: 0, id: ElementId(1,) },\n            CreatePlaceholder { id: ElementId(2,) },\n            ReplacePlaceholder { path: &[0], m: 1 },\n            AppendChildren { id: ElementId(0), m: 1 },\n        ]\n    );\n\n    // Rendering again should replace the placeholder with an text node\n    dom.mark_dirty(ScopeId::APP);\n    assert_eq!(\n        dom.render_immediate_to_vec().edits,\n        [\n            CreateTextNode { value: \"hello\".to_string(), id: ElementId(3,) },\n            ReplaceWith { id: ElementId(2,), m: 1 },\n        ]\n    );\n\n    // Rendering again should replace the placeholder with an text node\n    dom.mark_dirty(ScopeId::APP);\n    assert_eq!(\n        dom.render_immediate_to_vec().edits,\n        [\n            CreatePlaceholder { id: ElementId(2,) },\n            ReplaceWith { id: ElementId(3,), m: 1 },\n        ]\n    );\n}\n\n// Regression test for https://github.com/DioxusLabs/dioxus/issues/2815\n#[test]\nfn toggle_template() {\n    fn app() -> Element {\n        rsx!(\n            Comp {\n                if true {\n                    \"{true}\"\n                }\n            }\n        )\n    }\n\n    #[component]\n    fn Comp(children: Element) -> Element {\n        let show = generation() % 2 == 0;\n\n        rsx! {\n            if show {\n                {children}\n            }\n        }\n    }\n\n    let mut dom = VirtualDom::new(app);\n    dom.rebuild(&mut dioxus_core::NoOpMutations);\n\n    // Rendering again should replace the placeholder with an text node\n    dom.mark_dirty(ScopeId::APP);\n    assert_eq!(\n        dom.render_immediate_to_vec().edits,\n        [\n            CreatePlaceholder { id: ElementId(2) },\n            ReplaceWith { id: ElementId(1), m: 1 },\n        ]\n    );\n\n    dom.mark_dirty(ScopeId(ScopeId::APP.0 + 1));\n    assert_eq!(\n        dom.render_immediate_to_vec().edits,\n        [\n            CreateTextNode { value: \"true\".to_string(), id: ElementId(1) },\n            ReplaceWith { id: ElementId(2), m: 1 },\n        ]\n    );\n\n    dom.mark_dirty(ScopeId(ScopeId::APP.0 + 1));\n    assert_eq!(\n        dom.render_immediate_to_vec().edits,\n        [\n            CreatePlaceholder { id: ElementId(2) },\n            ReplaceWith { id: ElementId(1), m: 1 },\n        ]\n    );\n\n    dom.mark_dirty(ScopeId(ScopeId::APP.0 + 1));\n    assert_eq!(\n        dom.render_immediate_to_vec().edits,\n        [\n            CreateTextNode { value: \"true\".to_string(), id: ElementId(1) },\n            ReplaceWith { id: ElementId(2), m: 1 },\n        ]\n    );\n}\n"
  },
  {
    "path": "packages/core/tests/diff_element.rs",
    "content": "use dioxus::dioxus_core::Mutation::*;\nuse dioxus::dioxus_core::{AttributeValue, ElementId, NoOpMutations};\nuse dioxus::prelude::*;\nuse dioxus_core::generation;\n\n#[test]\nfn text_diff() {\n    fn app() -> Element {\n        let gen = generation();\n        rsx!( h1 { \"hello {gen}\" } )\n    }\n\n    let mut vdom = VirtualDom::new(app);\n    vdom.rebuild(&mut NoOpMutations);\n\n    vdom.mark_dirty(ScopeId::APP);\n    assert_eq!(\n        vdom.render_immediate_to_vec().edits,\n        [SetText { value: \"hello 1\".to_string(), id: ElementId(2) }]\n    );\n\n    vdom.mark_dirty(ScopeId::APP);\n    assert_eq!(\n        vdom.render_immediate_to_vec().edits,\n        [SetText { value: \"hello 2\".to_string(), id: ElementId(2) }]\n    );\n\n    vdom.mark_dirty(ScopeId::APP);\n    assert_eq!(\n        vdom.render_immediate_to_vec().edits,\n        [SetText { value: \"hello 3\".to_string(), id: ElementId(2) }]\n    );\n}\n\n#[test]\nfn element_swap() {\n    fn app() -> Element {\n        let gen = generation();\n\n        match gen % 2 {\n            0 => rsx!( h1 { \"hello 1\" } ),\n            1 => rsx!( h2 { \"hello 2\" } ),\n            _ => unreachable!(),\n        }\n    }\n\n    let mut vdom = VirtualDom::new(app);\n    vdom.rebuild(&mut NoOpMutations);\n\n    vdom.mark_dirty(ScopeId::APP);\n    assert_eq!(\n        vdom.render_immediate_to_vec().edits,\n        [\n            LoadTemplate { index: 0, id: ElementId(2,) },\n            ReplaceWith { id: ElementId(1,), m: 1 },\n        ]\n    );\n\n    vdom.mark_dirty(ScopeId::APP);\n    assert_eq!(\n        vdom.render_immediate_to_vec().edits,\n        [\n            LoadTemplate { index: 0, id: ElementId(1,) },\n            ReplaceWith { id: ElementId(2,), m: 1 },\n        ]\n    );\n\n    vdom.mark_dirty(ScopeId::APP);\n    assert_eq!(\n        vdom.render_immediate_to_vec().edits,\n        [\n            LoadTemplate { index: 0, id: ElementId(2,) },\n            ReplaceWith { id: ElementId(1,), m: 1 },\n        ]\n    );\n\n    vdom.mark_dirty(ScopeId::APP);\n    assert_eq!(\n        vdom.render_immediate_to_vec().edits,\n        [\n            LoadTemplate { index: 0, id: ElementId(1,) },\n            ReplaceWith { id: ElementId(2,), m: 1 },\n        ]\n    );\n}\n\n#[test]\nfn attribute_diff() {\n    fn app() -> Element {\n        let gen = generation();\n\n        // attributes have to be sorted by name\n        let attrs = match gen % 5 {\n            0 => vec![Attribute::new(\n                \"a\",\n                AttributeValue::Text(\"hello\".into()),\n                None,\n                false,\n            )],\n            1 => vec![\n                Attribute::new(\"a\", AttributeValue::Text(\"hello\".into()), None, false),\n                Attribute::new(\"b\", AttributeValue::Text(\"hello\".into()), None, false),\n                Attribute::new(\"c\", AttributeValue::Text(\"hello\".into()), None, false),\n            ],\n            2 => vec![\n                Attribute::new(\"c\", AttributeValue::Text(\"hello\".into()), None, false),\n                Attribute::new(\"d\", AttributeValue::Text(\"hello\".into()), None, false),\n                Attribute::new(\"e\", AttributeValue::Text(\"hello\".into()), None, false),\n            ],\n            3 => vec![Attribute::new(\n                \"d\",\n                AttributeValue::Text(\"world\".into()),\n                None,\n                false,\n            )],\n            _ => unreachable!(),\n        };\n\n        rsx!(\n            div {\n                ..attrs,\n                \"hello\"\n            }\n        )\n    }\n\n    let mut vdom = VirtualDom::new(app);\n    vdom.rebuild(&mut NoOpMutations);\n\n    vdom.mark_dirty(ScopeId::APP);\n    assert_eq!(\n        vdom.render_immediate_to_vec().edits,\n        [\n            SetAttribute {\n                name: \"b\",\n                value: (AttributeValue::Text(\"hello\".into())),\n                id: ElementId(1,),\n                ns: None,\n            },\n            SetAttribute {\n                name: \"c\",\n                value: (AttributeValue::Text(\"hello\".into())),\n                id: ElementId(1,),\n                ns: None,\n            },\n        ]\n    );\n\n    vdom.mark_dirty(ScopeId::APP);\n    assert_eq!(\n        vdom.render_immediate_to_vec().edits,\n        [\n            SetAttribute { name: \"a\", value: AttributeValue::None, id: ElementId(1,), ns: None },\n            SetAttribute { name: \"b\", value: AttributeValue::None, id: ElementId(1,), ns: None },\n            SetAttribute {\n                name: \"d\",\n                value: AttributeValue::Text(\"hello\".into()),\n                id: ElementId(1,),\n                ns: None,\n            },\n            SetAttribute {\n                name: \"e\",\n                value: AttributeValue::Text(\"hello\".into()),\n                id: ElementId(1,),\n                ns: None,\n            },\n        ]\n    );\n\n    vdom.mark_dirty(ScopeId::APP);\n    assert_eq!(\n        vdom.render_immediate_to_vec().edits,\n        [\n            SetAttribute { name: \"c\", value: AttributeValue::None, id: ElementId(1,), ns: None },\n            SetAttribute {\n                name: \"d\",\n                value: AttributeValue::Text(\"world\".into()),\n                id: ElementId(1,),\n                ns: None,\n            },\n            SetAttribute { name: \"e\", value: AttributeValue::None, id: ElementId(1,), ns: None },\n        ]\n    );\n}\n\n#[test]\nfn diff_empty() {\n    fn app() -> Element {\n        match generation() % 2 {\n            0 => rsx! { div { \"hello\" } },\n            1 => rsx! {},\n            _ => unreachable!(),\n        }\n    }\n\n    let mut vdom = VirtualDom::new(app);\n    vdom.rebuild(&mut NoOpMutations);\n\n    vdom.mark_dirty(ScopeId::APP);\n    let edits = vdom.render_immediate_to_vec().edits;\n\n    assert_eq!(\n        edits,\n        [\n            CreatePlaceholder { id: ElementId(2,) },\n            ReplaceWith { id: ElementId(1,), m: 1 },\n        ]\n    )\n}\n"
  },
  {
    "path": "packages/core/tests/diff_keyed_list.rs",
    "content": "//! Diffing Tests\n//!\n//! These tests only verify that the diffing algorithm works properly for single components.\n//!\n//! It does not validated that component lifecycles work properly. This is done in another test file.\n\nuse dioxus::dioxus_core::{ElementId, Mutation::*};\nuse dioxus::prelude::*;\nuse dioxus_core::generation;\n\n/// Should result in moves, but not removals or additions\n#[test]\nfn keyed_diffing_out_of_order() {\n    let mut dom = VirtualDom::new(|| {\n        let order = match generation() % 2 {\n            0 => &[0, 1, 2, 3, /**/ 4, 5, 6, /**/ 7, 8, 9],\n            1 => &[0, 1, 2, 3, /**/ 6, 4, 5, /**/ 7, 8, 9],\n            _ => unreachable!(),\n        };\n\n        rsx!({ order.iter().map(|i| rsx!(div { key: \"{i}\" })) })\n    });\n\n    {\n        assert_eq!(\n            dom.rebuild_to_vec().edits,\n            [\n                LoadTemplate { index: 0, id: ElementId(1,) },\n                LoadTemplate { index: 0, id: ElementId(2,) },\n                LoadTemplate { index: 0, id: ElementId(3,) },\n                LoadTemplate { index: 0, id: ElementId(4,) },\n                LoadTemplate { index: 0, id: ElementId(5,) },\n                LoadTemplate { index: 0, id: ElementId(6,) },\n                LoadTemplate { index: 0, id: ElementId(7,) },\n                LoadTemplate { index: 0, id: ElementId(8,) },\n                LoadTemplate { index: 0, id: ElementId(9,) },\n                LoadTemplate { index: 0, id: ElementId(10,) },\n                AppendChildren { m: 10, id: ElementId(0) },\n            ]\n        );\n    }\n\n    dom.mark_dirty(ScopeId::APP);\n    assert_eq!(\n        dom.render_immediate_to_vec().edits,\n        [\n            PushRoot { id: ElementId(7,) },\n            InsertBefore { id: ElementId(5,), m: 1 },\n        ]\n    );\n}\n\n/// Should result in moves only\n#[test]\nfn keyed_diffing_out_of_order_adds() {\n    let mut dom = VirtualDom::new(|| {\n        let order = match generation() % 2 {\n            0 => &[/**/ 4, 5, 6, 7, 8 /**/],\n            1 => &[/**/ 8, 7, 4, 5, 6 /**/],\n            _ => unreachable!(),\n        };\n\n        rsx!({ order.iter().map(|i| rsx!(div { key: \"{i}\" })) })\n    });\n\n    dom.rebuild(&mut dioxus_core::NoOpMutations);\n\n    dom.mark_dirty(ScopeId::APP);\n    assert_eq!(\n        dom.render_immediate_to_vec().edits,\n        [\n            PushRoot { id: ElementId(5,) },\n            PushRoot { id: ElementId(4,) },\n            InsertBefore { id: ElementId(1,), m: 2 },\n        ]\n    );\n}\n\n/// Should result in moves only\n#[test]\nfn keyed_diffing_out_of_order_adds_3() {\n    let mut dom = VirtualDom::new(|| {\n        let order = match generation() % 2 {\n            0 => &[/**/ 4, 5, 6, 7, 8 /**/],\n            1 => &[/**/ 4, 8, 7, 5, 6 /**/],\n            _ => unreachable!(),\n        };\n\n        rsx!({ order.iter().map(|i| rsx!(div { key: \"{i}\" })) })\n    });\n\n    dom.rebuild(&mut dioxus_core::NoOpMutations);\n\n    dom.mark_dirty(ScopeId::APP);\n    assert_eq!(\n        dom.render_immediate_to_vec().edits,\n        [\n            PushRoot { id: ElementId(5,) },\n            PushRoot { id: ElementId(4,) },\n            InsertBefore { id: ElementId(2,), m: 2 },\n        ]\n    );\n}\n\n/// Should result in moves only\n#[test]\nfn keyed_diffing_out_of_order_adds_4() {\n    let mut dom = VirtualDom::new(|| {\n        let order = match generation() % 2 {\n            0 => &[/**/ 4, 5, 6, 7, 8 /**/],\n            1 => &[/**/ 4, 5, 8, 7, 6 /**/],\n            _ => unreachable!(),\n        };\n\n        rsx!({ order.iter().map(|i| rsx!(div { key: \"{i}\" })) })\n    });\n\n    dom.rebuild(&mut dioxus_core::NoOpMutations);\n\n    dom.mark_dirty(ScopeId::APP);\n    assert_eq!(\n        dom.render_immediate_to_vec().edits,\n        [\n            PushRoot { id: ElementId(5,) },\n            PushRoot { id: ElementId(4,) },\n            InsertBefore { id: ElementId(3,), m: 2 },\n        ]\n    );\n}\n\n/// Should result in moves only\n#[test]\nfn keyed_diffing_out_of_order_adds_5() {\n    let mut dom = VirtualDom::new(|| {\n        let order = match generation() % 2 {\n            0 => &[/**/ 4, 5, 6, 7, 8 /**/],\n            1 => &[/**/ 4, 5, 6, 8, 7 /**/],\n            _ => unreachable!(),\n        };\n\n        rsx!({ order.iter().map(|i| rsx!(div { key: \"{i}\" })) })\n    });\n\n    dom.rebuild(&mut dioxus_core::NoOpMutations);\n\n    dom.mark_dirty(ScopeId::APP);\n    assert_eq!(\n        dom.render_immediate_to_vec().edits,\n        [\n            PushRoot { id: ElementId(5,) },\n            InsertBefore { id: ElementId(4,), m: 1 },\n        ]\n    );\n}\n\n/// Should result in moves only\n#[test]\nfn keyed_diffing_additions() {\n    let mut dom = VirtualDom::new(|| {\n        let order: &[_] = match generation() % 2 {\n            0 => &[/**/ 4, 5, 6, 7, 8 /**/],\n            1 => &[/**/ 4, 5, 6, 7, 8, 9, 10 /**/],\n            _ => unreachable!(),\n        };\n\n        rsx!({ order.iter().map(|i| rsx!(div { key: \"{i}\" })) })\n    });\n\n    dom.rebuild(&mut dioxus_core::NoOpMutations);\n\n    dom.mark_dirty(ScopeId::APP);\n    assert_eq!(\n        dom.render_immediate_to_vec().edits,\n        [\n            LoadTemplate { index: 0, id: ElementId(6) },\n            LoadTemplate { index: 0, id: ElementId(7) },\n            InsertAfter { id: ElementId(5), m: 2 }\n        ]\n    );\n}\n\n#[test]\nfn keyed_diffing_additions_and_moves_on_ends() {\n    let mut dom = VirtualDom::new(|| {\n        let order: &[_] = match generation() % 2 {\n            0 => &[/**/ 4, 5, 6, 7 /**/],\n            1 => &[/**/ 7, 4, 5, 6, 11, 12 /**/],\n            _ => unreachable!(),\n        };\n\n        rsx!({ order.iter().map(|i| rsx!(div { key: \"{i}\" })) })\n    });\n\n    dom.rebuild(&mut dioxus_core::NoOpMutations);\n\n    dom.mark_dirty(ScopeId::APP);\n    assert_eq!(\n        dom.render_immediate_to_vec().edits,\n        [\n            // create 11, 12\n            LoadTemplate { index: 0, id: ElementId(5) },\n            LoadTemplate { index: 0, id: ElementId(6) },\n            InsertAfter { id: ElementId(3), m: 2 },\n            // move 7 to the front\n            PushRoot { id: ElementId(4) },\n            InsertBefore { id: ElementId(1), m: 1 }\n        ]\n    );\n}\n\n#[test]\nfn keyed_diffing_additions_and_moves_in_middle() {\n    let mut dom = VirtualDom::new(|| {\n        let order: &[_] = match generation() % 2 {\n            0 => &[/**/ 1, 2, 3, 4 /**/],\n            1 => &[/**/ 4, 1, 7, 8, 2, 5, 6, 3 /**/],\n            _ => unreachable!(),\n        };\n\n        rsx!({ order.iter().map(|i| rsx!(div { key: \"{i}\" })) })\n    });\n\n    dom.rebuild(&mut dioxus_core::NoOpMutations);\n\n    // LIS: 4, 5, 6\n    dom.mark_dirty(ScopeId::APP);\n    assert_eq!(\n        dom.render_immediate_to_vec().edits,\n        [\n            // create 5, 6\n            LoadTemplate { index: 0, id: ElementId(5) },\n            LoadTemplate { index: 0, id: ElementId(6) },\n            InsertBefore { id: ElementId(3), m: 2 },\n            // create 7, 8\n            LoadTemplate { index: 0, id: ElementId(7) },\n            LoadTemplate { index: 0, id: ElementId(8) },\n            InsertBefore { id: ElementId(2), m: 2 },\n            // move 7\n            PushRoot { id: ElementId(4) },\n            InsertBefore { id: ElementId(1), m: 1 }\n        ]\n    );\n}\n\n#[test]\nfn controlled_keyed_diffing_out_of_order() {\n    let mut dom = VirtualDom::new(|| {\n        let order: &[_] = match generation() % 2 {\n            0 => &[4, 5, 6, 7],\n            1 => &[0, 5, 9, 6, 4],\n            _ => unreachable!(),\n        };\n\n        rsx!({ order.iter().map(|i| rsx!(div { key: \"{i}\" })) })\n    });\n\n    dom.rebuild(&mut dioxus_core::NoOpMutations);\n\n    // LIS: 5, 6\n    dom.mark_dirty(ScopeId::APP);\n    assert_eq!(\n        dom.render_immediate_to_vec().edits,\n        [\n            // remove 7\n            Remove { id: ElementId(4,) },\n            // move 4 to after 6\n            PushRoot { id: ElementId(1) },\n            InsertAfter { id: ElementId(3,), m: 1 },\n            // create 9 and insert before 6\n            LoadTemplate { index: 0, id: ElementId(4) },\n            InsertBefore { id: ElementId(3,), m: 1 },\n            // create 0 and insert before 5\n            LoadTemplate { index: 0, id: ElementId(5) },\n            InsertBefore { id: ElementId(2,), m: 1 },\n        ]\n    );\n}\n\n#[test]\nfn controlled_keyed_diffing_out_of_order_max_test() {\n    let mut dom = VirtualDom::new(|| {\n        let order: &[_] = match generation() % 2 {\n            0 => &[0, 1, 2, 3, 4],\n            1 => &[3, 0, 1, 10, 2],\n            _ => unreachable!(),\n        };\n\n        rsx!({ order.iter().map(|i| rsx!(div { key: \"{i}\" })) })\n    });\n\n    dom.rebuild(&mut dioxus_core::NoOpMutations);\n\n    dom.mark_dirty(ScopeId::APP);\n    assert_eq!(\n        dom.render_immediate_to_vec().edits,\n        [\n            Remove { id: ElementId(5,) },\n            LoadTemplate { index: 0, id: ElementId(5) },\n            InsertBefore { id: ElementId(3,), m: 1 },\n            PushRoot { id: ElementId(4) },\n            InsertBefore { id: ElementId(1,), m: 1 },\n        ]\n    );\n}\n\n// noticed some weird behavior in the desktop interpreter\n// just making sure it doesnt happen in the core implementation\n#[test]\nfn remove_list() {\n    let mut dom = VirtualDom::new(|| {\n        let order: &[_] = match generation() % 2 {\n            0 => &[9, 8, 7, 6, 5],\n            1 => &[9, 8],\n            _ => unreachable!(),\n        };\n\n        rsx!({ order.iter().map(|i| rsx!(div { key: \"{i}\" })) })\n    });\n\n    dom.rebuild(&mut dioxus_core::NoOpMutations);\n\n    dom.mark_dirty(ScopeId::APP);\n    assert_eq!(\n        dom.render_immediate_to_vec().edits,\n        [\n            Remove { id: ElementId(5) },\n            Remove { id: ElementId(4) },\n            Remove { id: ElementId(3) },\n        ]\n    );\n}\n\n#[test]\nfn no_common_keys() {\n    let mut dom = VirtualDom::new(|| {\n        let order: &[_] = match generation() % 2 {\n            0 => &[1, 2, 3],\n            1 => &[4, 5, 6],\n            _ => unreachable!(),\n        };\n\n        rsx!({ order.iter().map(|i| rsx!(div { key: \"{i}\" })) })\n    });\n\n    dom.rebuild(&mut dioxus_core::NoOpMutations);\n\n    dom.mark_dirty(ScopeId::APP);\n    assert_eq!(\n        dom.render_immediate_to_vec().edits,\n        [\n            LoadTemplate { index: 0, id: ElementId(4) },\n            LoadTemplate { index: 0, id: ElementId(5) },\n            LoadTemplate { index: 0, id: ElementId(6) },\n            Remove { id: ElementId(3) },\n            Remove { id: ElementId(2) },\n            ReplaceWith { id: ElementId(1), m: 3 }\n        ]\n    );\n}\n\n#[test]\nfn perfect_reverse() {\n    let mut dom = VirtualDom::new(|| {\n        let order: &[_] = match generation() % 2 {\n            0 => &[1, 2, 3, 4, 5, 6, 7, 8],\n            1 => &[9, 8, 7, 6, 5, 4, 3, 2, 1, 0],\n            _ => unreachable!(),\n        };\n\n        rsx!({ order.iter().map(|i| rsx!(div { key: \"{i}\" })) })\n    });\n\n    dom.rebuild(&mut dioxus_core::NoOpMutations);\n\n    dom.mark_dirty(ScopeId::APP);\n    let edits = dom.render_immediate_to_vec().edits;\n    assert_eq!(\n        edits,\n        [\n            LoadTemplate { index: 0, id: ElementId(9,) },\n            InsertAfter { id: ElementId(1,), m: 1 },\n            LoadTemplate { index: 0, id: ElementId(10,) },\n            PushRoot { id: ElementId(8,) },\n            PushRoot { id: ElementId(7,) },\n            PushRoot { id: ElementId(6,) },\n            PushRoot { id: ElementId(5,) },\n            PushRoot { id: ElementId(4,) },\n            PushRoot { id: ElementId(3,) },\n            PushRoot { id: ElementId(2,) },\n            InsertBefore { id: ElementId(1,), m: 8 },\n        ]\n    )\n}\n\n#[test]\nfn old_middle_empty_left_pivot() {\n    let mut dom = VirtualDom::new(|| {\n        let order: &[_] = match generation() % 2 {\n            0 => &[/* */ /* */ 6, 7, 8, 9, 10],\n            1 => &[/* */ 4, 5, /* */ 6, 7, 8, 9, 10],\n            _ => unreachable!(),\n        };\n\n        rsx!({ order.iter().map(|i| rsx!(div { key: \"{i}\" })) })\n    });\n\n    dom.rebuild(&mut dioxus_core::NoOpMutations);\n\n    dom.mark_dirty(ScopeId::APP);\n    let edits = dom.render_immediate_to_vec().edits;\n    assert_eq!(\n        edits,\n        [\n            LoadTemplate { index: 0, id: ElementId(6,) },\n            LoadTemplate { index: 0, id: ElementId(7,) },\n            InsertBefore { id: ElementId(1,), m: 2 },\n        ]\n    )\n}\n\n#[test]\nfn old_middle_empty_right_pivot() {\n    let mut dom = VirtualDom::new(|| {\n        let order: &[_] = match generation() % 2 {\n            0 => &[1, 2, 3, /*       */ 6, 7, 8, 9, 10],\n            1 => &[1, 2, 3, /* */ 4, 5, 6, 7, 8, 9, 10 /* */],\n\n            // 0 => &[/* */ 6, 7, 8, 9, 10],\n            // 1 => &[/* */ 6, 7, 8, 9, 10, /* */ 4, 5],\n            _ => unreachable!(),\n        };\n\n        rsx!({ order.iter().map(|i| rsx!(div { key: \"{i}\" })) })\n    });\n\n    dom.rebuild(&mut dioxus_core::NoOpMutations);\n\n    dom.mark_dirty(ScopeId::APP);\n    let edits = dom.render_immediate_to_vec().edits;\n    assert_eq!(\n        edits,\n        [\n            LoadTemplate { index: 0, id: ElementId(9) },\n            LoadTemplate { index: 0, id: ElementId(10) },\n            InsertBefore { id: ElementId(4), m: 2 },\n        ]\n    );\n}\n"
  },
  {
    "path": "packages/core/tests/diff_unkeyed_list.rs",
    "content": "use std::collections::HashSet;\n\nuse dioxus::dioxus_core::{ElementId, Mutation::*};\nuse dioxus::prelude::*;\nuse dioxus_core::{generation, Mutation};\nuse pretty_assertions::assert_eq;\n\n#[test]\nfn list_creates_one_by_one() {\n    let mut dom = VirtualDom::new(|| {\n        let gen = generation();\n\n        rsx! {\n            div {\n                for i in 0..gen {\n                    div { \"{i}\" }\n                }\n            }\n        }\n    });\n\n    // load the div and then assign the empty fragment as a placeholder\n    assert_eq!(\n        dom.rebuild_to_vec().edits,\n        [\n            LoadTemplate { index: 0, id: ElementId(1,) },\n            CreatePlaceholder { id: ElementId(2,) },\n            ReplacePlaceholder { path: &[0], m: 1 },\n            AppendChildren { id: ElementId(0), m: 1 },\n        ]\n    );\n\n    // Rendering the first item should replace the placeholder with an element\n    dom.mark_dirty(ScopeId::APP);\n    assert_eq!(\n        dom.render_immediate_to_vec().edits,\n        [\n            LoadTemplate { index: 0, id: ElementId(3,) },\n            CreateTextNode { value: \"0\".to_string(), id: ElementId(4,) },\n            ReplacePlaceholder { path: &[0], m: 1 },\n            ReplaceWith { id: ElementId(2,), m: 1 },\n        ]\n    );\n\n    // Rendering the next item should insert after the previous\n    dom.mark_dirty(ScopeId::APP);\n    assert_eq!(\n        dom.render_immediate_to_vec().edits,\n        [\n            LoadTemplate { index: 0, id: ElementId(2,) },\n            CreateTextNode { value: \"1\".to_string(), id: ElementId(5,) },\n            ReplacePlaceholder { path: &[0], m: 1 },\n            InsertAfter { id: ElementId(3,), m: 1 },\n        ]\n    );\n\n    // ... and again!\n    dom.mark_dirty(ScopeId::APP);\n    assert_eq!(\n        dom.render_immediate_to_vec().edits,\n        [\n            LoadTemplate { index: 0, id: ElementId(6,) },\n            CreateTextNode { value: \"2\".to_string(), id: ElementId(7,) },\n            ReplacePlaceholder { path: &[0], m: 1 },\n            InsertAfter { id: ElementId(2,), m: 1 },\n        ]\n    );\n\n    // once more\n    dom.mark_dirty(ScopeId::APP);\n    assert_eq!(\n        dom.render_immediate_to_vec().edits,\n        [\n            LoadTemplate { index: 0, id: ElementId(8,) },\n            CreateTextNode { value: \"3\".to_string(), id: ElementId(9,) },\n            ReplacePlaceholder { path: &[0], m: 1 },\n            InsertAfter { id: ElementId(6,), m: 1 },\n        ]\n    );\n}\n\n#[test]\nfn removes_one_by_one() {\n    let mut dom = VirtualDom::new(|| {\n        let gen = 3 - generation() % 4;\n\n        rsx! {\n            div {\n                for i in 0..gen {\n                    div { \"{i}\" }\n                }\n            }\n        }\n    });\n\n    // load the div and then assign the empty fragment as a placeholder\n    assert_eq!(\n        dom.rebuild_to_vec().edits,\n        [\n            // The container\n            LoadTemplate { index: 0, id: ElementId(1) },\n            // each list item\n            LoadTemplate { index: 0, id: ElementId(2) },\n            CreateTextNode { value: \"0\".to_string(), id: ElementId(3) },\n            ReplacePlaceholder { path: &[0], m: 1 },\n            LoadTemplate { index: 0, id: ElementId(4) },\n            CreateTextNode { value: \"1\".to_string(), id: ElementId(5) },\n            ReplacePlaceholder { path: &[0], m: 1 },\n            LoadTemplate { index: 0, id: ElementId(6) },\n            CreateTextNode { value: \"2\".to_string(), id: ElementId(7) },\n            ReplacePlaceholder { path: &[0], m: 1 },\n            // replace the placeholder in the template with the 3 templates on the stack\n            ReplacePlaceholder { m: 3, path: &[0] },\n            // Mount the div\n            AppendChildren { id: ElementId(0), m: 1 }\n        ]\n    );\n\n    // Remove div(3)\n    // Rendering the first item should replace the placeholder with an element\n    dom.mark_dirty(ScopeId::APP);\n    assert_eq!(\n        dom.render_immediate_to_vec().edits,\n        [Remove { id: ElementId(6) }]\n    );\n\n    // Remove div(2)\n    dom.mark_dirty(ScopeId::APP);\n    assert_eq!(\n        dom.render_immediate_to_vec().edits,\n        [Remove { id: ElementId(4) }]\n    );\n\n    // Remove div(1) and replace with a placeholder\n    // todo: this should just be a remove with no placeholder\n    dom.mark_dirty(ScopeId::APP);\n    assert_eq!(\n        dom.render_immediate_to_vec().edits,\n        [\n            CreatePlaceholder { id: ElementId(4) },\n            ReplaceWith { id: ElementId(2), m: 1 }\n        ]\n    );\n\n    // load the 3 and replace the placeholder\n    // todo: this should actually be append to, but replace placeholder is fine for now\n    dom.mark_dirty(ScopeId::APP);\n    assert_eq!(\n        dom.render_immediate_to_vec().edits,\n        [\n            LoadTemplate { index: 0, id: ElementId(2) },\n            CreateTextNode { value: \"0\".to_string(), id: ElementId(3) },\n            ReplacePlaceholder { path: &[0], m: 1 },\n            LoadTemplate { index: 0, id: ElementId(5) },\n            CreateTextNode { value: \"1\".to_string(), id: ElementId(6) },\n            ReplacePlaceholder { path: &[0], m: 1 },\n            LoadTemplate { index: 0, id: ElementId(7) },\n            CreateTextNode { value: \"2\".to_string(), id: ElementId(8) },\n            ReplacePlaceholder { path: &[0], m: 1 },\n            ReplaceWith { id: ElementId(4), m: 3 }\n        ]\n    );\n}\n\n#[test]\nfn list_shrink_multiroot() {\n    let mut dom = VirtualDom::new(|| {\n        rsx! {\n            div {\n                for i in 0..generation() {\n                    div { \"{i}\" }\n                    div { \"{i}\" }\n                }\n            }\n        }\n    });\n\n    assert_eq!(\n        dom.rebuild_to_vec().edits,\n        [\n            LoadTemplate { index: 0, id: ElementId(1,) },\n            CreatePlaceholder { id: ElementId(2,) },\n            ReplacePlaceholder { path: &[0,], m: 1 },\n            AppendChildren { id: ElementId(0), m: 1 }\n        ]\n    );\n\n    dom.mark_dirty(ScopeId::APP);\n    assert_eq!(\n        dom.render_immediate_to_vec().edits,\n        [\n            LoadTemplate { index: 0, id: ElementId(3) },\n            CreateTextNode { value: \"0\".to_string(), id: ElementId(4) },\n            ReplacePlaceholder { path: &[0], m: 1 },\n            LoadTemplate { index: 1, id: ElementId(5) },\n            CreateTextNode { value: \"0\".to_string(), id: ElementId(6) },\n            ReplacePlaceholder { path: &[0], m: 1 },\n            ReplaceWith { id: ElementId(2), m: 2 }\n        ]\n    );\n\n    dom.mark_dirty(ScopeId::APP);\n    assert_eq!(\n        dom.render_immediate_to_vec().edits,\n        [\n            LoadTemplate { index: 0, id: ElementId(2) },\n            CreateTextNode { value: \"1\".to_string(), id: ElementId(7) },\n            ReplacePlaceholder { path: &[0], m: 1 },\n            LoadTemplate { index: 1, id: ElementId(8) },\n            CreateTextNode { value: \"1\".to_string(), id: ElementId(9) },\n            ReplacePlaceholder { path: &[0], m: 1 },\n            InsertAfter { id: ElementId(5), m: 2 }\n        ]\n    );\n\n    dom.mark_dirty(ScopeId::APP);\n    assert_eq!(\n        dom.render_immediate_to_vec().edits,\n        [\n            LoadTemplate { index: 0, id: ElementId(10) },\n            CreateTextNode { value: \"2\".to_string(), id: ElementId(11) },\n            ReplacePlaceholder { path: &[0], m: 1 },\n            LoadTemplate { index: 1, id: ElementId(12) },\n            CreateTextNode { value: \"2\".to_string(), id: ElementId(13) },\n            ReplacePlaceholder { path: &[0], m: 1 },\n            InsertAfter { id: ElementId(8), m: 2 }\n        ]\n    );\n}\n\n#[test]\nfn removes_one_by_one_multiroot() {\n    let mut dom = VirtualDom::new(|| {\n        let gen = 3 - generation() % 4;\n\n        rsx! {\n            div {\n                {(0..gen).map(|i| rsx! {\n                    div { \"{i}\" }\n                    div { \"{i}\" }\n                })}\n            }\n        }\n    });\n\n    // load the div and then assign the empty fragment as a placeholder\n    assert_eq!(\n        dom.rebuild_to_vec().edits,\n        [\n            LoadTemplate { index: 0, id: ElementId(1) },\n            //\n            LoadTemplate { index: 0, id: ElementId(2) },\n            CreateTextNode { value: \"0\".to_string(), id: ElementId(3) },\n            ReplacePlaceholder { path: &[0], m: 1 },\n            LoadTemplate { index: 1, id: ElementId(4) },\n            CreateTextNode { value: \"0\".to_string(), id: ElementId(5) },\n            ReplacePlaceholder { path: &[0], m: 1 },\n            LoadTemplate { index: 0, id: ElementId(6) },\n            CreateTextNode { value: \"1\".to_string(), id: ElementId(7) },\n            ReplacePlaceholder { path: &[0], m: 1 },\n            LoadTemplate { index: 1, id: ElementId(8) },\n            CreateTextNode { value: \"1\".to_string(), id: ElementId(9) },\n            ReplacePlaceholder { path: &[0], m: 1 },\n            LoadTemplate { index: 0, id: ElementId(10) },\n            CreateTextNode { value: \"2\".to_string(), id: ElementId(11) },\n            ReplacePlaceholder { path: &[0], m: 1 },\n            LoadTemplate { index: 1, id: ElementId(12) },\n            CreateTextNode { value: \"2\".to_string(), id: ElementId(13) },\n            ReplacePlaceholder { path: &[0], m: 1 },\n            ReplacePlaceholder { path: &[0], m: 6 },\n            AppendChildren { id: ElementId(0), m: 1 }\n        ]\n    );\n\n    dom.mark_dirty(ScopeId::APP);\n    assert_eq!(\n        dom.render_immediate_to_vec().edits,\n        [Remove { id: ElementId(10) }, Remove { id: ElementId(12) }]\n    );\n\n    dom.mark_dirty(ScopeId::APP);\n    assert_eq!(\n        dom.render_immediate_to_vec().edits,\n        [Remove { id: ElementId(6) }, Remove { id: ElementId(8) }]\n    );\n\n    dom.mark_dirty(ScopeId::APP);\n    assert_eq!(\n        dom.render_immediate_to_vec().edits,\n        [\n            CreatePlaceholder { id: ElementId(8) },\n            Remove { id: ElementId(2) },\n            ReplaceWith { id: ElementId(4), m: 1 }\n        ]\n    );\n}\n\n#[test]\nfn two_equal_fragments_are_equal_static() {\n    let mut dom = VirtualDom::new(|| {\n        rsx! {\n            for _ in 0..5 {\n                div { \"hello\" }\n            }\n        }\n    });\n\n    dom.rebuild(&mut dioxus_core::NoOpMutations);\n    assert!(dom.render_immediate_to_vec().edits.is_empty());\n}\n\n#[test]\nfn two_equal_fragments_are_equal() {\n    let mut dom = VirtualDom::new(|| {\n        rsx! {\n            for i in 0..5 {\n                div { \"hello {i}\" }\n            }\n        }\n    });\n\n    dom.rebuild(&mut dioxus_core::NoOpMutations);\n    assert!(dom.render_immediate_to_vec().edits.is_empty());\n}\n\n#[test]\nfn remove_many() {\n    let mut dom = VirtualDom::new(|| {\n        let num = match generation() % 3 {\n            0 => 0,\n            1 => 1,\n            2 => 5,\n            _ => unreachable!(),\n        };\n\n        rsx! {\n            for i in 0..num {\n                div { \"hello {i}\" }\n            }\n        }\n    });\n\n    // len = 0\n    {\n        let edits = dom.rebuild_to_vec();\n        assert_eq!(\n            edits.edits,\n            [\n                CreatePlaceholder { id: ElementId(1,) },\n                AppendChildren { id: ElementId(0), m: 1 },\n            ]\n        );\n    }\n\n    // len = 1\n    {\n        dom.mark_dirty(ScopeId::APP);\n        let edits = dom.render_immediate_to_vec();\n        assert_eq!(\n            edits.edits,\n            [\n                LoadTemplate { index: 0, id: ElementId(2,) },\n                CreateTextNode { value: \"hello 0\".to_string(), id: ElementId(3,) },\n                ReplacePlaceholder { path: &[0,], m: 1 },\n                ReplaceWith { id: ElementId(1,), m: 1 },\n            ]\n        );\n    }\n\n    // len = 5\n    {\n        dom.mark_dirty(ScopeId::APP);\n        let edits = dom.render_immediate_to_vec();\n        assert_eq!(\n            edits.edits,\n            [\n                LoadTemplate { index: 0, id: ElementId(1,) },\n                CreateTextNode { value: \"hello 1\".to_string(), id: ElementId(4,) },\n                ReplacePlaceholder { path: &[0,], m: 1 },\n                LoadTemplate { index: 0, id: ElementId(5,) },\n                CreateTextNode { value: \"hello 2\".to_string(), id: ElementId(6,) },\n                ReplacePlaceholder { path: &[0,], m: 1 },\n                LoadTemplate { index: 0, id: ElementId(7,) },\n                CreateTextNode { value: \"hello 3\".to_string(), id: ElementId(8,) },\n                ReplacePlaceholder { path: &[0,], m: 1 },\n                LoadTemplate { index: 0, id: ElementId(9,) },\n                CreateTextNode { value: \"hello 4\".to_string(), id: ElementId(10,) },\n                ReplacePlaceholder { path: &[0,], m: 1 },\n                InsertAfter { id: ElementId(2,), m: 4 },\n            ]\n        );\n    }\n\n    // len = 0\n    {\n        dom.mark_dirty(ScopeId::APP);\n        let edits = dom.render_immediate_to_vec();\n        assert_eq!(edits.edits[0], CreatePlaceholder { id: ElementId(11,) });\n        let removed = edits.edits[1..5]\n            .iter()\n            .map(|edit| match edit {\n                Mutation::Remove { id } => *id,\n                _ => panic!(\"Expected remove\"),\n            })\n            .collect::<HashSet<_>>();\n        assert_eq!(\n            removed,\n            [ElementId(7), ElementId(5), ElementId(2), ElementId(1)]\n                .into_iter()\n                .collect::<HashSet<_>>()\n        );\n        assert_eq!(edits.edits[5..], [ReplaceWith { id: ElementId(9,), m: 1 },]);\n    }\n\n    // len = 1\n    {\n        dom.mark_dirty(ScopeId::APP);\n        let edits = dom.render_immediate_to_vec();\n        assert_eq!(\n            edits.edits,\n            [\n                LoadTemplate { index: 0, id: ElementId(9,) },\n                CreateTextNode { value: \"hello 0\".to_string(), id: ElementId(10,) },\n                ReplacePlaceholder { path: &[0,], m: 1 },\n                ReplaceWith { id: ElementId(11,), m: 1 },\n            ]\n        )\n    }\n}\n\n#[test]\nfn replace_and_add_items() {\n    let mut dom = VirtualDom::new(|| {\n        let items = (0..generation()).map(|_| {\n            if generation() % 2 == 0 {\n                VNode::empty()\n            } else {\n                rsx! {\n                    li {\n                        \"Fizz\"\n                    }\n                }\n            }\n        });\n\n        rsx! {\n            ul {\n                {items}\n            }\n        }\n    });\n\n    // The list starts empty with a placeholder\n    {\n        let edits = dom.rebuild_to_vec();\n        assert_eq!(\n            edits.edits,\n            [\n                LoadTemplate { index: 0, id: ElementId(1,) },\n                CreatePlaceholder { id: ElementId(2,) },\n                ReplacePlaceholder { path: &[0], m: 1 },\n                AppendChildren { id: ElementId(0), m: 1 },\n            ]\n        );\n    }\n\n    // Rerendering adds an a static template\n    {\n        dom.mark_dirty(ScopeId::APP);\n        let edits = dom.render_immediate_to_vec();\n        assert_eq!(\n            edits.edits,\n            [\n                LoadTemplate { index: 0, id: ElementId(3,) },\n                ReplaceWith { id: ElementId(2,), m: 1 },\n            ]\n        );\n    }\n\n    // Rerendering replaces the old node with a placeholder and adds a new placeholder\n    {\n        dom.mark_dirty(ScopeId::APP);\n        let edits = dom.render_immediate_to_vec();\n        assert_eq!(\n            edits.edits,\n            [\n                CreatePlaceholder { id: ElementId(2,) },\n                InsertAfter { id: ElementId(3,), m: 1 },\n                CreatePlaceholder { id: ElementId(4,) },\n                ReplaceWith { id: ElementId(3,), m: 1 },\n            ]\n        );\n    }\n\n    // Rerendering replaces both placeholders with the static nodes and add a new static node\n    {\n        dom.mark_dirty(ScopeId::APP);\n        let edits = dom.render_immediate_to_vec();\n        assert_eq!(\n            edits.edits,\n            [\n                LoadTemplate { index: 0, id: ElementId(3,) },\n                InsertAfter { id: ElementId(2,), m: 1 },\n                LoadTemplate { index: 0, id: ElementId(5,) },\n                ReplaceWith { id: ElementId(4,), m: 1 },\n                LoadTemplate { index: 0, id: ElementId(4,) },\n                ReplaceWith { id: ElementId(2,), m: 1 },\n            ]\n        );\n    }\n}\n\n// Simplified regression test for https://github.com/DioxusLabs/dioxus/issues/4924\n#[test]\nfn nested_unkeyed_lists() {\n    let mut dom = VirtualDom::new(|| {\n        let content = if generation() % 2 == 0 {\n            vec![\"5\\n6\"]\n        } else {\n            vec![\"1\\n2\", \"3\\n4\"]\n        };\n\n        rsx! {\n            for one in &content {\n                for line in one.lines() {\n                    p { \"{line}\" }\n                }\n            }\n        }\n    });\n\n    // The list starts with one placeholder\n    {\n        let edits = dom.rebuild_to_vec();\n        assert_eq!(\n            edits.edits,\n            [\n                // load the p tag template\n                LoadTemplate { index: 0, id: ElementId(1) },\n                // Create the first text node\n                CreateTextNode { value: \"5\".into(), id: ElementId(2) },\n                // Replace the placeholder inside the p tag with the text node\n                ReplacePlaceholder { path: &[0], m: 1 },\n                // load the p tag template\n                LoadTemplate { index: 0, id: ElementId(3) },\n                // Create the second text node\n                CreateTextNode { value: \"6\".into(), id: ElementId(4) },\n                // Replace the placeholder inside the p tag with the text node\n                ReplacePlaceholder { path: &[0], m: 1 },\n                // Add the text nodes to the root node\n                AppendChildren { id: ElementId(0), m: 2 }\n            ]\n        );\n    }\n\n    // DOM state:\n    // <pre> # Id 1 for if statement\n    // <p> # Id 2\n    //    \"5\" # Id 3\n    // <p> # Id 4\n    //    \"6\" # Id 5\n    //\n    // The diffing engine should add two new elements to the end and modify the first two elements in place\n    {\n        dom.mark_dirty(ScopeId::APP);\n        let edits = dom.render_immediate_to_vec();\n        assert_eq!(\n            edits.edits,\n            [\n                // load the p tag template\n                LoadTemplate { index: 0, id: ElementId(5) },\n                // Create the third text node\n                CreateTextNode { value: \"3\".into(), id: ElementId(6) },\n                // Replace the placeholder inside the p tag with the text node\n                ReplacePlaceholder { path: &[0], m: 1 },\n                // load the p tag template\n                LoadTemplate { index: 0, id: ElementId(7) },\n                // Create the fourth text node\n                CreateTextNode { value: \"4\".into(), id: ElementId(8) },\n                // Replace the placeholder inside the p tag with the text node\n                ReplacePlaceholder { path: &[0], m: 1 },\n                // Insert the text nodes after the second p tag\n                InsertAfter { id: ElementId(3), m: 2 },\n                // Set the first text node to \"1\"\n                SetText { value: \"1\".into(), id: ElementId(2) },\n                // Set the second text node to \"2\"\n                SetText { value: \"2\".into(), id: ElementId(4) }\n            ]\n        );\n    }\n}\n"
  },
  {
    "path": "packages/core/tests/error_boundary.rs",
    "content": "#![allow(non_snake_case)]\n\nuse dioxus::{prelude::*, CapturedError};\n\n#[test]\nfn catches_panic() {\n    let mut dom = VirtualDom::new(app);\n    dom.rebuild(&mut dioxus_core::NoOpMutations);\n}\n\nfn app() -> Element {\n    rsx! {\n        div {\n            h1 { \"Title\" }\n\n            NoneChild {}\n            ThrowChild {}\n        }\n    }\n}\n\nfn NoneChild() -> Element {\n    VNode::empty()\n}\n\nfn ThrowChild() -> Element {\n    Err(std::io::Error::new(std::io::ErrorKind::AddrInUse, \"asd\"))?;\n\n    let _g: i32 = \"123123\".parse()?;\n\n    rsx! { div {} }\n}\n\n#[test]\nfn clear_error_boundary() {\n    static THREW_ERROR: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);\n\n    #[component]\n    fn App() -> Element {\n        rsx! {\n            AutoClearError {}\n        }\n    }\n\n    #[component]\n    pub fn ThrowsError() -> Element {\n        if THREW_ERROR.load(std::sync::atomic::Ordering::SeqCst) {\n            THREW_ERROR.store(true, std::sync::atomic::Ordering::SeqCst);\n            Err(CapturedError::from_display(\"This is an error\").into())\n        } else {\n            rsx! {\n                \"We should see this\"\n            }\n        }\n    }\n\n    #[component]\n    pub fn AutoClearError() -> Element {\n        rsx! {\n            ErrorBoundary {\n                handle_error: |error: ErrorContext| {\n                    error.clear_errors();\n\n                    rsx! { \"We cleared it\" }\n                },\n\n                ThrowsError {}\n            }\n        }\n    }\n\n    let mut dom = VirtualDom::new(App);\n    dom.rebuild(&mut dioxus_core::NoOpMutations);\n    let out = dioxus_ssr::render(&dom);\n\n    assert_eq!(out, \"We should see this\");\n}\n"
  },
  {
    "path": "packages/core/tests/event_propagation.rs",
    "content": "use dioxus::prelude::*;\nuse dioxus_core::ElementId;\nuse std::{any::Any, rc::Rc, sync::Mutex};\n\nstatic CLICKS: Mutex<usize> = Mutex::new(0);\n\n#[test]\nfn events_propagate() {\n    set_event_converter(Box::new(dioxus::html::SerializedHtmlEventConverter));\n\n    let mut dom = VirtualDom::new(app);\n    dom.rebuild(&mut dioxus_core::NoOpMutations);\n\n    // Top-level click is registered\n    let event = Event::new(\n        Rc::new(PlatformEventData::new(Box::<SerializedMouseData>::default())) as Rc<dyn Any>,\n        true,\n    );\n    dom.runtime().handle_event(\"click\", event, ElementId(1));\n    assert_eq!(*CLICKS.lock().unwrap(), 1);\n\n    // break reference....\n    for _ in 0..5 {\n        dom.mark_dirty(ScopeId(0));\n        _ = dom.render_immediate_to_vec();\n    }\n\n    // Lower click is registered\n    let event = Event::new(\n        Rc::new(PlatformEventData::new(Box::<SerializedMouseData>::default())) as Rc<dyn Any>,\n        true,\n    );\n    dom.runtime().handle_event(\"click\", event, ElementId(2));\n    assert_eq!(*CLICKS.lock().unwrap(), 3);\n\n    // break reference....\n    for _ in 0..5 {\n        dom.mark_dirty(ScopeId(0));\n        _ = dom.render_immediate_to_vec();\n    }\n\n    // Stop propagation occurs\n    let event = Event::new(\n        Rc::new(PlatformEventData::new(Box::<SerializedMouseData>::default())) as Rc<dyn Any>,\n        true,\n    );\n    dom.runtime().handle_event(\"click\", event, ElementId(2));\n    assert_eq!(*CLICKS.lock().unwrap(), 3);\n}\n\nfn app() -> Element {\n    rsx! {\n        div { onclick: move |_| {\n                println!(\"top clicked\");\n                *CLICKS.lock().unwrap() += 1;\n            },\n\n            {vec![\n                rsx! {\n                    problematic_child {}\n                }\n            ].into_iter()}\n        }\n    }\n}\n\nfn problematic_child() -> Element {\n    rsx! {\n        button { onclick: move |evt| {\n                println!(\"bottom clicked\");\n                let mut clicks = CLICKS.lock().unwrap();\n                if *clicks == 3 {\n                    evt.stop_propagation();\n                } else {\n                    *clicks += 1;\n                }\n            } }\n    }\n}\n"
  },
  {
    "path": "packages/core/tests/fuzzing.rs",
    "content": "#![cfg(not(miri))]\n\nuse dioxus::prelude::*;\nuse dioxus_core::{AttributeValue, DynamicNode, NoOpMutations, Template, VComponent, VNode, *};\nuse std::{any::Any, cell::RefCell, cfg, collections::HashSet, default::Default, rc::Rc};\n\nfn random_ns() -> Option<&'static str> {\n    let namespace = rand::random::<u8>() % 2;\n    match namespace {\n        0 => None,\n        1 => Some(Box::leak(\n            format!(\"ns{}\", rand::random::<u8>()).into_boxed_str(),\n        )),\n        _ => unreachable!(),\n    }\n}\n\nfn create_random_attribute(attr_idx: &mut usize) -> TemplateAttribute {\n    match rand::random::<u8>() % 2 {\n        0 => TemplateAttribute::Static {\n            name: Box::leak(format!(\"attr{}\", rand::random::<u8>()).into_boxed_str()),\n            value: Box::leak(format!(\"value{}\", rand::random::<u8>()).into_boxed_str()),\n            namespace: random_ns(),\n        },\n        1 => TemplateAttribute::Dynamic {\n            id: {\n                let old_idx = *attr_idx;\n                *attr_idx += 1;\n                old_idx\n            },\n        },\n        _ => unreachable!(),\n    }\n}\n\nfn create_random_template_node(\n    dynamic_node_types: &mut Vec<DynamicNodeType>,\n    template_idx: &mut usize,\n    attr_idx: &mut usize,\n    depth: usize,\n) -> TemplateNode {\n    match rand::random::<u8>() % 4 {\n        0 => {\n            let attrs = {\n                let attrs: Vec<_> = (0..(rand::random::<u8>() % 10))\n                    .map(|_| create_random_attribute(attr_idx))\n                    .collect();\n                Box::leak(attrs.into_boxed_slice())\n            };\n            TemplateNode::Element {\n                tag: Box::leak(format!(\"tag{}\", rand::random::<u8>()).into_boxed_str()),\n                namespace: random_ns(),\n                attrs,\n                children: {\n                    if depth > 4 {\n                        &[]\n                    } else {\n                        let children: Vec<_> = (0..(rand::random::<u8>() % 3))\n                            .map(|_| {\n                                create_random_template_node(\n                                    dynamic_node_types,\n                                    template_idx,\n                                    attr_idx,\n                                    depth + 1,\n                                )\n                            })\n                            .collect();\n                        Box::leak(children.into_boxed_slice())\n                    }\n                },\n            }\n        }\n        1 => TemplateNode::Text {\n            text: Box::leak(format!(\"{}\", rand::random::<u8>()).into_boxed_str()),\n        },\n        2 => TemplateNode::Dynamic {\n            id: {\n                let old_idx = *template_idx;\n                *template_idx += 1;\n                dynamic_node_types.push(DynamicNodeType::Text);\n                old_idx\n            },\n        },\n        3 => TemplateNode::Dynamic {\n            id: {\n                let old_idx = *template_idx;\n                *template_idx += 1;\n                dynamic_node_types.push(DynamicNodeType::Other);\n                old_idx\n            },\n        },\n        _ => unreachable!(),\n    }\n}\n\nfn generate_paths(\n    node: &TemplateNode,\n    current_path: &[u8],\n    node_paths: &mut Vec<Vec<u8>>,\n    attr_paths: &mut Vec<Vec<u8>>,\n) {\n    match node {\n        TemplateNode::Element { children, attrs, .. } => {\n            for attr in *attrs {\n                match attr {\n                    TemplateAttribute::Static { .. } => {}\n                    TemplateAttribute::Dynamic { .. } => {\n                        attr_paths.push(current_path.to_vec());\n                    }\n                }\n            }\n            for (i, child) in children.iter().enumerate() {\n                let mut current_path = current_path.to_vec();\n                current_path.push(i as u8);\n                generate_paths(child, &current_path, node_paths, attr_paths);\n            }\n        }\n        TemplateNode::Text { .. } => {}\n        TemplateNode::Dynamic { .. } => {\n            node_paths.push(current_path.to_vec());\n        }\n    }\n}\n\nenum DynamicNodeType {\n    Text,\n    Other,\n}\n\nfn create_random_template(depth: u8) -> (Template, Box<[DynamicNode]>) {\n    let mut dynamic_node_types = Vec::new();\n    let mut template_idx = 0;\n    let mut attr_idx = 0;\n    let roots = (0..(1 + rand::random::<u8>() % 5))\n        .map(|_| {\n            create_random_template_node(\n                &mut dynamic_node_types,\n                &mut template_idx,\n                &mut attr_idx,\n                0,\n            )\n        })\n        .collect::<Vec<_>>();\n    assert!(!roots.is_empty());\n    let roots = Box::leak(roots.into_boxed_slice());\n    let mut node_paths = Vec::new();\n    let mut attr_paths = Vec::new();\n    for (i, root) in roots.iter().enumerate() {\n        generate_paths(root, &[i as u8], &mut node_paths, &mut attr_paths);\n    }\n    let node_paths = Box::leak(\n        node_paths\n            .into_iter()\n            .map(|v| &*Box::leak(v.into_boxed_slice()))\n            .collect::<Vec<_>>()\n            .into_boxed_slice(),\n    );\n    let attr_paths = Box::leak(\n        attr_paths\n            .into_iter()\n            .map(|v| &*Box::leak(v.into_boxed_slice()))\n            .collect::<Vec<_>>()\n            .into_boxed_slice(),\n    );\n    let dynamic_nodes = dynamic_node_types\n        .iter()\n        .map(|ty| match ty {\n            DynamicNodeType::Text => {\n                DynamicNode::Text(VText::new(format!(\"{}\", rand::random::<u8>())))\n            }\n            DynamicNodeType::Other => create_random_dynamic_node(depth + 1),\n        })\n        .collect();\n    (Template { roots, node_paths, attr_paths }, dynamic_nodes)\n}\n\nfn create_random_dynamic_node(depth: u8) -> DynamicNode {\n    let range = if depth > 5 { 1 } else { 3 };\n    match rand::random::<u8>() % range {\n        0 => DynamicNode::Placeholder(Default::default()),\n        1 => (0..(rand::random::<u8>() % 5))\n            .map(|_| {\n                VNode::new(\n                    None,\n                    Template {\n                        roots: &[TemplateNode::Dynamic { id: 0 }],\n                        node_paths: &[&[0]],\n                        attr_paths: &[],\n                    },\n                    Box::new([DynamicNode::Component(VComponent::new(\n                        create_random_element,\n                        DepthProps { depth, root: false },\n                        \"create_random_element\",\n                    ))]),\n                    Box::new([]),\n                )\n            })\n            .into_dyn_node(),\n        2 => DynamicNode::Component(VComponent::new(\n            create_random_element,\n            DepthProps { depth, root: false },\n            \"create_random_element\",\n        )),\n        _ => unreachable!(),\n    }\n}\n\nfn create_random_dynamic_attr() -> Attribute {\n    let value = match rand::random::<u8>() % 7 {\n        0 => AttributeValue::Text(format!(\"{}\", rand::random::<u8>())),\n        1 => AttributeValue::Float(rand::random()),\n        2 => AttributeValue::Int(rand::random()),\n        3 => AttributeValue::Bool(rand::random()),\n        4 => AttributeValue::any_value(rand::random::<u8>()),\n        5 => AttributeValue::None,\n        6 => {\n            let value = AttributeValue::listener(|e: Event<String>| println!(\"{:?}\", e));\n            return Attribute::new(\"ondata\", value, None, false);\n        }\n        _ => unreachable!(),\n    };\n    Attribute::new(\n        Box::leak(format!(\"attr{}\", rand::random::<u8>()).into_boxed_str()),\n        value,\n        random_ns(),\n        rand::random(),\n    )\n}\n\n#[derive(PartialEq, Props, Clone)]\nstruct DepthProps {\n    depth: u8,\n    root: bool,\n}\n\nfn create_random_element(cx: DepthProps) -> Element {\n    let last_template = use_hook(|| Rc::new(RefCell::new(None)));\n    if rand::random::<u8>() % 10 == 0 {\n        needs_update();\n    }\n    let range = if cx.root { 2 } else { 3 };\n    let node = match rand::random::<u8>() % range {\n        // Change both the template and the dynamic nodes\n        0 => {\n            let (template, dynamic_nodes) = create_random_template(cx.depth + 1);\n            last_template.replace(Some(template));\n            VNode::new(\n                None,\n                template,\n                dynamic_nodes,\n                (0..template.attr_paths.len())\n                    .map(|_| Box::new([create_random_dynamic_attr()]) as Box<[Attribute]>)\n                    .collect(),\n            )\n        }\n        // Change just the dynamic nodes\n        1 => {\n            let (template, dynamic_nodes) = match *last_template.borrow() {\n                Some(template) => (\n                    template,\n                    (0..template.node_paths.len())\n                        .map(|_| create_random_dynamic_node(cx.depth + 1))\n                        .collect(),\n                ),\n                None => create_random_template(cx.depth + 1),\n            };\n            VNode::new(\n                None,\n                template,\n                dynamic_nodes,\n                (0..template.attr_paths.len())\n                    .map(|_| Box::new([create_random_dynamic_attr()]) as Box<[Attribute]>)\n                    .collect(),\n            )\n        }\n        // Remove the template\n        _ => VNode::default(),\n    };\n    Element::Ok(node)\n}\n\n// test for panics when creating random nodes and templates\n#[test]\nfn create() {\n    let repeat_count = if cfg!(miri) { 100 } else { 1000 };\n    for _ in 0..repeat_count {\n        let mut vdom =\n            VirtualDom::new_with_props(create_random_element, DepthProps { depth: 0, root: true });\n        vdom.rebuild(&mut NoOpMutations);\n    }\n}\n\n// test for panics when diffing random nodes\n// This test will change the template every render which is not very realistic, but it helps stress the system\n#[test]\nfn diff() {\n    let repeat_count = if cfg!(miri) { 100 } else { 1000 };\n    for _ in 0..repeat_count {\n        let mut vdom =\n            VirtualDom::new_with_props(create_random_element, DepthProps { depth: 0, root: true });\n        vdom.rebuild(&mut NoOpMutations);\n        // A list of all elements that have had event listeners\n        // This is intentionally never cleared, so that we can test that calling event listeners that are removed doesn't cause a panic\n        let mut event_listeners = HashSet::new();\n        for _ in 0..100 {\n            for &id in &event_listeners {\n                println!(\"firing event on {:?}\", id);\n                let event = Event::new(\n                    std::rc::Rc::new(String::from(\"hello world\")) as Rc<dyn Any>,\n                    true,\n                );\n                vdom.runtime().handle_event(\"data\", event, id);\n            }\n            {\n                vdom.render_immediate(&mut InsertEventListenerMutationHandler(\n                    &mut event_listeners,\n                ));\n            }\n        }\n    }\n}\n\nstruct InsertEventListenerMutationHandler<'a>(&'a mut HashSet<ElementId>);\n\nimpl WriteMutations for InsertEventListenerMutationHandler<'_> {\n    fn append_children(&mut self, _: ElementId, _: usize) {}\n\n    fn assign_node_id(&mut self, _: &'static [u8], _: ElementId) {}\n\n    fn create_placeholder(&mut self, _: ElementId) {}\n\n    fn create_text_node(&mut self, _: &str, _: ElementId) {}\n\n    fn load_template(&mut self, _: Template, _: usize, _: ElementId) {}\n\n    fn replace_node_with(&mut self, _: ElementId, _: usize) {}\n\n    fn replace_placeholder_with_nodes(&mut self, _: &'static [u8], _: usize) {}\n\n    fn insert_nodes_after(&mut self, _: ElementId, _: usize) {}\n\n    fn insert_nodes_before(&mut self, _: ElementId, _: usize) {}\n\n    fn set_attribute(\n        &mut self,\n        _: &'static str,\n        _: Option<&'static str>,\n        _: &AttributeValue,\n        _: ElementId,\n    ) {\n    }\n\n    fn set_node_text(&mut self, _: &str, _: ElementId) {}\n\n    fn create_event_listener(&mut self, name: &'static str, id: ElementId) {\n        println!(\"new event listener on {:?} for {:?}\", id, name);\n        self.0.insert(id);\n    }\n\n    fn remove_event_listener(&mut self, _: &'static str, _: ElementId) {}\n\n    fn remove_node(&mut self, _: ElementId) {}\n\n    fn push_root(&mut self, _: ElementId) {}\n}\n"
  },
  {
    "path": "packages/core/tests/hotreloading.rs",
    "content": "//! It should be possible to swap out templates at runtime, enabling hotreloading\n"
  },
  {
    "path": "packages/core/tests/kitchen_sink.rs",
    "content": "use dioxus::dioxus_core::{ElementId, Mutation};\nuse dioxus::prelude::*;\nuse dioxus_core::IntoAttributeValue;\nuse pretty_assertions::assert_eq;\n\nfn basic_syntax_is_a_template() -> Element {\n    let asd = 123;\n    let var = 123;\n\n    rsx! {\n        div {\n            key: \"{asd}\",\n            class: \"asd\",\n            class: \"{asd}\",\n            class: if true { \"{asd}\" },\n            class: if false { \"{asd}\" },\n            onclick: move |_| {},\n            div { \"{var}\" }\n            div {\n                h1 { \"var\" }\n                p { \"you're great!\" }\n                div { background_color: \"red\",\n                    h1 { \"var\" }\n                    div {\n                        b { \"asd\" }\n                        \"not great\"\n                    }\n                }\n                p { \"you're great!\" }\n            }\n        }\n    }\n}\n\n#[test]\nfn dual_stream() {\n    let mut dom = VirtualDom::new(basic_syntax_is_a_template);\n    let edits = dom.rebuild_to_vec();\n\n    use Mutation::*;\n    assert_eq!(edits.edits, {\n        [\n            LoadTemplate { index: 0, id: ElementId(1) },\n            SetAttribute {\n                name: \"class\",\n                value: \"asd 123 123 \".into_value(),\n                id: ElementId(1),\n                ns: None,\n            },\n            NewEventListener { name: \"click\".to_string(), id: ElementId(1) },\n            CreateTextNode { value: \"123\".to_string(), id: ElementId(2) },\n            ReplacePlaceholder { path: &[0, 0], m: 1 },\n            AppendChildren { id: ElementId(0), m: 1 },\n        ]\n    });\n}\n"
  },
  {
    "path": "packages/core/tests/lifecycle.rs",
    "content": "#![allow(unused, non_upper_case_globals)]\n#![allow(non_snake_case)]\n\n//! Tests for the lifecycle of components.\nuse dioxus::dioxus_core::{ElementId, Mutation::*};\nuse dioxus::html::SerializedHtmlEventConverter;\nuse dioxus::prelude::*;\nuse std::any::Any;\nuse std::rc::Rc;\nuse std::sync::{Arc, Mutex};\n\ntype Shared<T> = Arc<Mutex<T>>;\n\n#[test]\nfn manual_diffing() {\n    #[derive(Clone)]\n    struct AppProps {\n        value: Shared<&'static str>,\n    }\n\n    fn app(cx: AppProps) -> Element {\n        let val = cx.value.lock().unwrap();\n        rsx! { div { \"{val}\" } }\n    };\n\n    let value = Arc::new(Mutex::new(\"Hello\"));\n    let mut dom = VirtualDom::new_with_props(app, AppProps { value: value.clone() });\n\n    dom.rebuild(&mut dioxus_core::NoOpMutations);\n\n    *value.lock().unwrap() = \"goodbye\";\n\n    assert_eq!(\n        dom.rebuild_to_vec().edits,\n        [\n            LoadTemplate { index: 0, id: ElementId(3) },\n            CreateTextNode { value: \"goodbye\".to_string(), id: ElementId(4) },\n            ReplacePlaceholder { path: &[0], m: 1 },\n            AppendChildren { m: 1, id: ElementId(0) }\n        ]\n    );\n}\n\n#[test]\nfn events_generate() {\n    set_event_converter(Box::new(SerializedHtmlEventConverter));\n    fn app() -> Element {\n        let mut count = use_signal(|| 0);\n\n        match count() {\n            0 => rsx! {\n                div { onclick: move |_| count += 1,\n                    div { \"nested\" }\n                    \"Click me!\"\n                }\n            },\n            _ => VNode::empty(),\n        }\n    };\n\n    let mut dom = VirtualDom::new(app);\n    dom.rebuild(&mut dioxus_core::NoOpMutations);\n\n    let event = Event::new(\n        Rc::new(PlatformEventData::new(Box::<SerializedMouseData>::default())) as Rc<dyn Any>,\n        true,\n    );\n    dom.runtime().handle_event(\"click\", event, ElementId(1));\n\n    dom.mark_dirty(ScopeId::APP);\n    let edits = dom.render_immediate_to_vec();\n\n    assert_eq!(\n        edits.edits,\n        [\n            CreatePlaceholder { id: ElementId(2) },\n            ReplaceWith { id: ElementId(1), m: 1 }\n        ]\n    )\n}\n"
  },
  {
    "path": "packages/core/tests/many_roots.rs",
    "content": "#![allow(non_snake_case)]\n\nuse dioxus::dioxus_core::Mutation::*;\nuse dioxus::prelude::*;\nuse dioxus_core::{AttributeValue, ElementId};\nuse pretty_assertions::assert_eq;\n\n/// Should push the text node onto the stack and modify it\n/// Regression test for https://github.com/DioxusLabs/dioxus/issues/2809 and https://github.com/DioxusLabs/dioxus/issues/3055\n#[test]\nfn many_roots() {\n    fn app() -> Element {\n        let width = \"100%\";\n        rsx! {\n            div {\n                MyNav {}\n                MyOutlet {}\n                div {\n                    // We need to make sure that dynamic attributes are set before the nodes before them are expanded\n                    // If they are set after, then the paths are incorrect\n                    width,\n                }\n            }\n        }\n    }\n\n    fn MyNav() -> Element {\n        rsx!(\n            div { \"trailing nav\" }\n            div { \"whhhhh\"}\n            div { \"bhhhh\" }\n        )\n    }\n\n    fn MyOutlet() -> Element {\n        rsx!(\n            div { \"homepage 1\" }\n        )\n    }\n\n    let mut dom = VirtualDom::new(app);\n    let edits = dom.rebuild_to_vec();\n\n    assert_eq!(\n        edits.edits,\n        [\n            // load the div {} container\n            LoadTemplate { index: 0, id: ElementId(1) },\n            // Set the width attribute first\n            AssignId { path: &[2], id: ElementId(2,) },\n            SetAttribute {\n                name: \"width\",\n                ns: Some(\"style\",),\n                value: AttributeValue::Text(\"100%\".to_string()),\n                id: ElementId(2,),\n            },\n            // Load MyOutlet next\n            LoadTemplate { index: 0, id: ElementId(3) },\n            ReplacePlaceholder { path: &[1], m: 1 },\n            // Then MyNav\n            LoadTemplate { index: 0, id: ElementId(4) },\n            LoadTemplate { index: 1, id: ElementId(5) },\n            LoadTemplate { index: 2, id: ElementId(6) },\n            ReplacePlaceholder { path: &[0], m: 3 },\n            // Then mount the div to the dom\n            AppendChildren { m: 1, id: ElementId(0) },\n        ]\n    )\n}\n"
  },
  {
    "path": "packages/core/tests/memory_leak.rs",
    "content": "#![allow(non_snake_case)]\nuse dioxus::prelude::dioxus_core::NoOpMutations;\nuse dioxus::prelude::*;\nuse sysinfo::{ProcessRefreshKind, RefreshKind, System};\n\n// Regression test for https://github.com/DioxusLabs/dioxus/issues/3421\n#[tokio::test]\nasync fn test_for_memory_leaks() {\n    fn app() -> Element {\n        let mut count = use_signal(|| 0);\n\n        use_hook(|| {\n            spawn(async move {\n                loop {\n                    tokio::time::sleep(std::time::Duration::from_nanos(1)).await;\n                    let val = *count.peek_unchecked();\n                    if val == 70 {\n                        count.set(0);\n                    } else {\n                        count.set(val + 1);\n                    }\n                }\n            })\n        });\n\n        rsx! {\n            for el in 0..*count.read() {\n                div {\n                    key: \"{el}\",\n                    div {\n                        onclick: move |_| { println!(\"click\"); },\n                    }\n                    AcceptsEventHandlerAndReadSignal {\n                        event_handler: move |_| { println!(\"click\"); },\n                        signal: el,\n                    }\n                }\n            }\n        }\n    }\n\n    // Event handlers and ReadSignals have extra logic on component boundaries that has caused memory leaks\n    // in the past\n    #[component]\n    fn AcceptsEventHandlerAndReadSignal(\n        event_handler: EventHandler<MouseEvent>,\n        signal: ReadSignal<i32>,\n    ) -> Element {\n        rsx! {\n            div {\n                onclick: event_handler,\n                \"{signal}\"\n            }\n        }\n    }\n\n    // create the vdom, the real_dom, and the binding layer between them\n    let mut vdom = VirtualDom::new(app);\n\n    vdom.rebuild(&mut NoOpMutations);\n\n    let pid = sysinfo::get_current_pid().expect(\"failed to get PID\");\n\n    let refresh =\n        RefreshKind::nothing().with_processes(ProcessRefreshKind::nothing().with_memory());\n    let mut system = System::new_with_specifics(refresh);\n\n    let mut get_memory_usage = || {\n        system.refresh_specifics(refresh);\n        let this_process = system.process(pid).expect(\"failed to get process\");\n        this_process.memory()\n    };\n\n    let initial_memory_usage = get_memory_usage();\n\n    // we need to run the vdom in a async runtime\n    for i in 0..=10000 {\n        // wait for the vdom to update\n        vdom.wait_for_work().await;\n\n        // get the mutations from the vdom\n        vdom.render_immediate(&mut NoOpMutations);\n\n        if i % 1000 == 0 {\n            let new_memory_usage = get_memory_usage();\n            println!(\"iteration: {} memory usage: {}\", i, new_memory_usage);\n\n            // Memory usage might increase as arenas fill up, but it shouldn't double from the initial render\n            assert!(new_memory_usage < initial_memory_usage * 2);\n        }\n    }\n}\n"
  },
  {
    "path": "packages/core/tests/miri_full_app.rs",
    "content": "use dioxus::prelude::*;\nuse dioxus_core::ElementId;\nuse dioxus_elements::SerializedHtmlEventConverter;\nuse std::{any::Any, rc::Rc};\n\n#[test]\nfn miri_rollover() {\n    set_event_converter(Box::new(SerializedHtmlEventConverter));\n    let mut dom = VirtualDom::new(app);\n\n    dom.rebuild(&mut dioxus_core::NoOpMutations);\n\n    for _ in 0..3 {\n        let event = Event::new(\n            Rc::new(PlatformEventData::new(Box::<SerializedMouseData>::default())) as Rc<dyn Any>,\n            true,\n        );\n        dom.runtime().handle_event(\"click\", event, ElementId(2));\n        dom.process_events();\n        _ = dom.render_immediate_to_vec();\n    }\n}\n\nfn app() -> Element {\n    let mut idx = use_signal(|| 0);\n    let onhover = |_| println!(\"go!\");\n\n    rsx! {\n        div {\n            button {\n                onclick: move |_| {\n                    idx += 1;\n                    println!(\"Clicked\");\n                },\n                \"+\"\n            }\n            button { onclick: move |_| idx -= 1, \"-\" }\n            ul {\n                {(0..idx()).map(|i| rsx! {\n                    ChildExample { i: i, onhover: onhover }\n                })}\n            }\n        }\n    }\n}\n\n#[component]\nfn ChildExample(i: i32, onhover: EventHandler<MouseEvent>) -> Element {\n    rsx! { li { onmouseover: move |e| onhover.call(e), \"{i}\" } }\n}\n"
  },
  {
    "path": "packages/core/tests/miri_simple.rs",
    "content": "use dioxus::prelude::*;\nuse dioxus_core::generation;\n\n#[test]\nfn app_drops() {\n    fn app() -> Element {\n        rsx! { div {} }\n    }\n\n    let mut dom = VirtualDom::new(app);\n\n    dom.rebuild(&mut dioxus_core::NoOpMutations);\n    dom.mark_dirty(ScopeId::APP);\n    _ = dom.render_immediate_to_vec();\n}\n\n#[test]\nfn hooks_drop() {\n    fn app() -> Element {\n        use_hook(|| String::from(\"asd\"));\n        use_hook(|| String::from(\"asd\"));\n        use_hook(|| String::from(\"asd\"));\n        use_hook(|| String::from(\"asd\"));\n\n        rsx! { div {} }\n    }\n\n    let mut dom = VirtualDom::new(app);\n\n    dom.rebuild(&mut dioxus_core::NoOpMutations);\n    dom.mark_dirty(ScopeId::APP);\n    _ = dom.render_immediate_to_vec();\n}\n\n#[test]\nfn contexts_drop() {\n    fn app() -> Element {\n        provide_context(String::from(\"asd\"));\n\n        rsx! {\n            div { ChildComp {} }\n        }\n    }\n\n    #[allow(non_snake_case)]\n    fn ChildComp() -> Element {\n        let el = consume_context::<String>();\n\n        rsx! { div { \"hello {el}\" } }\n    }\n\n    let mut dom = VirtualDom::new(app);\n\n    dom.rebuild(&mut dioxus_core::NoOpMutations);\n    dom.mark_dirty(ScopeId::APP);\n    _ = dom.render_immediate_to_vec();\n}\n\n#[test]\nfn tasks_drop() {\n    fn app() -> Element {\n        spawn(async {\n            // tokio::time::sleep(std::time::Duration::from_millis(100000)).await;\n        });\n\n        rsx! { div {} }\n    }\n\n    let mut dom = VirtualDom::new(app);\n\n    dom.rebuild(&mut dioxus_core::NoOpMutations);\n    dom.mark_dirty(ScopeId::APP);\n    _ = dom.render_immediate_to_vec();\n}\n\n#[test]\nfn root_props_drop() {\n    #[derive(Clone)]\n    struct RootProps(String);\n\n    let mut dom = VirtualDom::new_with_props(\n        |cx: RootProps| rsx!( div { \"{cx.0}\" } ),\n        RootProps(\"asdasd\".to_string()),\n    );\n\n    dom.rebuild(&mut dioxus_core::NoOpMutations);\n    dom.mark_dirty(ScopeId::APP);\n    _ = dom.render_immediate_to_vec();\n}\n\n#[test]\nfn diffing_drops_old() {\n    fn app() -> Element {\n        rsx! {\n            div {\n                match generation() % 2 {\n                    0 => rsx!( ChildComp1 { name: \"asdasd\".to_string() }),\n                    1 => rsx!( ChildComp2 { name: \"asdasd\".to_string() }),\n                    _ => unreachable!()\n                }\n            }\n        }\n    }\n\n    #[component]\n    fn ChildComp1(name: String) -> Element {\n        rsx! {\"Hello {name}\"}\n    }\n\n    #[component]\n    fn ChildComp2(name: String) -> Element {\n        rsx! {\"Goodbye {name}\"}\n    }\n\n    let mut dom = VirtualDom::new(app);\n    dom.rebuild(&mut dioxus_core::NoOpMutations);\n    dom.mark_dirty(ScopeId::APP);\n\n    _ = dom.render_immediate_to_vec();\n}\n\n#[test]\nfn hooks_drop_before_contexts() {\n    fn app() -> Element {\n        provide_context(123i32);\n        use_hook(|| {\n            #[derive(Clone)]\n            struct ReadsContextOnDrop;\n\n            impl Drop for ReadsContextOnDrop {\n                fn drop(&mut self) {\n                    assert_eq!(123, consume_context::<i32>());\n                }\n            }\n\n            ReadsContextOnDrop\n        });\n\n        rsx! { div {} }\n    }\n\n    let mut dom = VirtualDom::new(app);\n\n    dom.rebuild(&mut dioxus_core::NoOpMutations);\n    dom.mark_dirty(ScopeId::APP);\n    _ = dom.render_immediate_to_vec();\n}\n"
  },
  {
    "path": "packages/core/tests/miri_stress.rs",
    "content": "#![allow(non_snake_case)]\n\nuse std::rc::Rc;\n\nuse dioxus::prelude::*;\nuse dioxus_core::{generation, NoOpMutations};\n\n/// This test checks that we should release all memory used by the virtualdom when it exits.\n///\n/// When miri runs, it'll let us know if we leaked or aliased.\n#[test]\nfn test_memory_leak() {\n    fn app() -> Element {\n        let val = generation();\n\n        spawn(async {});\n\n        if val == 2 || val == 4 {\n            return rsx!({});\n        }\n\n        let mut name = use_hook(|| String::from(\"numbers: \"));\n\n        name.push_str(\"123 \");\n\n        rsx!(\n            div { \"Hello, world!\" }\n            Child {}\n            Child {}\n            Child {}\n            Child {}\n            Child {}\n            Child {}\n            BorrowedChild { name: name.clone() }\n            BorrowedChild { name: name.clone() }\n            BorrowedChild { name: name.clone() }\n            BorrowedChild { name: name.clone() }\n            BorrowedChild { name: name.clone() }\n        )\n    }\n\n    #[derive(Props, Clone, PartialEq)]\n    struct BorrowedProps {\n        name: String,\n    }\n\n    fn BorrowedChild(cx: BorrowedProps) -> Element {\n        rsx! {\n            div {\n                \"goodbye {cx.name}\"\n                Child {}\n                Child {}\n            }\n        }\n    }\n\n    fn Child() -> Element {\n        rsx!( div { \"goodbye world\" } )\n    }\n\n    let mut dom = VirtualDom::new(app);\n\n    dom.rebuild(&mut dioxus_core::NoOpMutations);\n\n    for _ in 0..5 {\n        dom.mark_dirty(ScopeId::APP);\n        _ = dom.render_immediate_to_vec();\n    }\n}\n\n#[test]\nfn memo_works_properly() {\n    fn app() -> Element {\n        let val = generation();\n\n        if val == 2 || val == 4 {\n            return Element::Ok(VNode::default());\n        }\n\n        let name = use_hook(|| String::from(\"asd\"));\n\n        rsx!(\n            div { \"Hello, world! {name}\" }\n            Child { na: \"asdfg\".to_string() }\n        )\n    }\n\n    #[derive(PartialEq, Clone, Props)]\n    struct ChildProps {\n        na: String,\n    }\n\n    fn Child(_props: ChildProps) -> Element {\n        rsx!( div { \"goodbye world\" } )\n    }\n\n    let mut dom = VirtualDom::new(app);\n\n    dom.rebuild(&mut dioxus_core::NoOpMutations);\n}\n\n#[test]\nfn free_works_on_root_hooks() {\n    /*\n    On Drop, scopearena drops all the hook contents. and props\n    */\n    #[derive(PartialEq, Clone, Props)]\n    struct AppProps {\n        inner: Rc<String>,\n    }\n\n    fn app(cx: AppProps) -> Element {\n        let name: AppProps = use_hook(|| cx.clone());\n        rsx!(child_component { inner: name.inner.clone() })\n    }\n\n    fn child_component(props: AppProps) -> Element {\n        rsx!( div { \"{props.inner}\" } )\n    }\n\n    let ptr = Rc::new(\"asdasd\".to_string());\n    let mut dom = VirtualDom::new_with_props(app, AppProps { inner: ptr.clone() });\n    dom.rebuild(&mut dioxus_core::NoOpMutations);\n\n    // ptr gets cloned into props and then into the hook\n    assert!(Rc::strong_count(&ptr) > 1);\n\n    drop(dom);\n\n    assert_eq!(Rc::strong_count(&ptr), 1);\n}\n\n#[test]\nfn supports_async() {\n    use std::time::Duration;\n    use tokio::time::sleep;\n\n    fn app() -> Element {\n        let mut colors = use_signal(|| vec![\"green\", \"blue\", \"red\"]);\n        let mut padding = use_signal(|| 10);\n\n        use_hook(|| {\n            spawn(async move {\n                loop {\n                    sleep(Duration::from_millis(1000)).await;\n                    colors.with_mut(|colors| colors.reverse());\n                }\n            })\n        });\n\n        use_hook(|| {\n            spawn(async move {\n                loop {\n                    sleep(Duration::from_millis(10)).await;\n                    padding.with_mut(|padding| {\n                        if *padding < 65 {\n                            *padding += 1;\n                        } else {\n                            *padding = 5;\n                        }\n                    });\n                }\n            })\n        });\n\n        let colors = colors.read();\n        let big = colors[0];\n        let mid = colors[1];\n        let small = colors[2];\n\n        rsx! {\n            div { background: \"{big}\", height: \"stretch\", width: \"stretch\", padding: \"50\",\n                label { \"hello\" }\n                div { background: \"{mid}\", height: \"auto\", width: \"stretch\", padding: \"{padding}\",\n                    label { \"World\" }\n                    div { background: \"{small}\", height: \"auto\", width: \"stretch\", padding: \"20\", label { \"ddddddd\" } }\n                }\n            }\n        }\n    }\n\n    let rt = tokio::runtime::Builder::new_current_thread()\n        .enable_time()\n        .build()\n        .unwrap();\n\n    rt.block_on(async {\n        let mut dom = VirtualDom::new(app);\n        dom.rebuild(&mut dioxus_core::NoOpMutations);\n\n        for _ in 0..10 {\n            dom.wait_for_work().await;\n            dom.render_immediate(&mut NoOpMutations);\n        }\n    });\n}\n"
  },
  {
    "path": "packages/core/tests/safety.rs",
    "content": "//! Tests related to safety of the library.\n\nuse dioxus::prelude::*;\n\n/// Ensure no issues with not calling rebuild_to_vec\n#[test]\nfn root_node_isnt_null() {\n    let dom = VirtualDom::new(|| rsx!(\"Hello world!\"));\n\n    let scope = dom.base_scope();\n\n    // We haven't built the tree, so trying to get out the root node should fail\n    assert!(scope.try_root_node().is_none());\n\n    dom.in_runtime(|| {\n        // The height should be 0\n        assert_eq!(scope.height(), 0);\n    });\n}\n"
  },
  {
    "path": "packages/core/tests/suspense.rs",
    "content": "use dioxus::prelude::*;\nuse dioxus_core::{generation, AttributeValue, ElementId, Mutation};\nuse pretty_assertions::assert_eq;\nuse std::future::poll_fn;\nuse std::task::Poll;\n\nasync fn poll_three_times() {\n    // Poll each task 3 times\n    let mut count = 0;\n    poll_fn(|cx| {\n        println!(\"polling... {}\", count);\n        if count < 3 {\n            count += 1;\n            cx.waker().wake_by_ref();\n            Poll::Pending\n        } else {\n            Poll::Ready(())\n        }\n    })\n    .await;\n}\n\n#[test]\nfn suspense_resolves_ssr() {\n    // wait just a moment, not enough time for the boundary to resolve\n    tokio::runtime::Builder::new_current_thread()\n        .build()\n        .unwrap()\n        .block_on(async {\n            let mut dom = VirtualDom::new(app);\n            dom.rebuild_in_place();\n            dom.wait_for_suspense().await;\n            dom.render_immediate(&mut dioxus_core::NoOpMutations);\n            let out = dioxus_ssr::render(&dom);\n\n            assert_eq!(out, \"<div>Waiting for... child</div>\");\n        });\n}\n\nfn app() -> Element {\n    rsx!(\n        div {\n            \"Waiting for... \"\n            SuspenseBoundary {\n                fallback: |_| rsx! { \"fallback\" },\n                suspended_child {}\n            }\n        }\n    )\n}\n\nfn suspended_child() -> Element {\n    let mut val = use_signal(|| 0);\n\n    // Tasks that are not suspended should never be polled\n    spawn(async move {\n        panic!(\"Non-suspended task was polled\");\n    });\n\n    // Memos should still work like normal\n    let memo = use_memo(move || val * 2);\n    assert_eq!(memo, val * 2);\n\n    if val() < 3 {\n        let task = spawn(async move {\n            poll_three_times().await;\n            println!(\"waiting... {}\", val);\n            val += 1;\n        });\n        suspend(task)?;\n    }\n\n    rsx!(\"child\")\n}\n\n/// When switching from a suspense fallback to the real child, the state of that component must be kept\n#[test]\nfn suspense_keeps_state() {\n    tokio::runtime::Builder::new_current_thread()\n        .enable_time()\n        .build()\n        .unwrap()\n        .block_on(async {\n            let mut dom = VirtualDom::new(app);\n            dom.rebuild(&mut dioxus_core::NoOpMutations);\n            dom.render_suspense_immediate().await;\n\n            let out = dioxus_ssr::render(&dom);\n\n            assert_eq!(out, \"fallback\");\n\n            dom.wait_for_suspense().await;\n            let out = dioxus_ssr::render(&dom);\n\n            assert_eq!(out, \"<div>child with future resolved</div>\");\n        });\n\n    fn app() -> Element {\n        rsx! {\n            SuspenseBoundary {\n                fallback: |_| rsx! { \"fallback\" },\n                Child {}\n            }\n        }\n    }\n\n    #[component]\n    fn Child() -> Element {\n        let mut future_resolved = use_signal(|| false);\n\n        let task = use_hook(|| {\n            spawn(async move {\n                tokio::time::sleep(std::time::Duration::from_millis(100)).await;\n                future_resolved.set(true);\n            })\n        });\n        if !future_resolved() {\n            suspend(task)?;\n        }\n\n        println!(\"future resolved: {future_resolved:?}\");\n\n        if future_resolved() {\n            rsx! {\n                div { \"child with future resolved\" }\n            }\n        } else {\n            rsx! {\n                div { \"this should never be rendered\" }\n            }\n        }\n    }\n}\n\n/// spawn doesn't run in suspense\n#[test]\nfn suspense_does_not_poll_spawn() {\n    tokio::runtime::Builder::new_current_thread()\n        .enable_time()\n        .build()\n        .unwrap()\n        .block_on(async {\n            let mut dom = VirtualDom::new(app);\n            dom.rebuild(&mut dioxus_core::NoOpMutations);\n\n            dom.wait_for_suspense().await;\n            let out = dioxus_ssr::render(&dom);\n\n            assert_eq!(out, \"<div>child with future resolved</div>\");\n        });\n\n    fn app() -> Element {\n        rsx! {\n            SuspenseBoundary {\n                fallback: |_| rsx! { \"fallback\" },\n                Child {}\n            }\n        }\n    }\n\n    #[component]\n    fn Child() -> Element {\n        let mut future_resolved = use_signal(|| false);\n\n        // futures that are spawned, but not suspended should never be polled\n        use_hook(|| {\n            spawn(async move {\n                panic!(\"Non-suspended task was polled\");\n            });\n        });\n\n        let task = use_hook(|| {\n            spawn(async move {\n                tokio::time::sleep(std::time::Duration::from_millis(100)).await;\n                future_resolved.set(true);\n            })\n        });\n        if !future_resolved() {\n            suspend(task)?;\n        }\n\n        rsx! {\n            div { \"child with future resolved\" }\n        }\n    }\n}\n\n/// suspended nodes are not mounted, so they should not run effects\n#[test]\nfn suspended_nodes_dont_trigger_effects() {\n    tokio::runtime::Builder::new_current_thread()\n        .enable_time()\n        .build()\n        .unwrap()\n        .block_on(async {\n            let mut dom = VirtualDom::new(app);\n            dom.rebuild(&mut dioxus_core::NoOpMutations);\n\n            let work = async move {\n                loop {\n                    dom.wait_for_work().await;\n                    dom.render_immediate(&mut dioxus_core::NoOpMutations);\n                }\n            };\n            tokio::select! {\n                _ = work => {},\n                _ = tokio::time::sleep(std::time::Duration::from_millis(100)) => {}\n            }\n        });\n\n    fn app() -> Element {\n        rsx! {\n            SuspenseBoundary {\n                fallback: |_| rsx! { \"fallback\" },\n                Child {}\n            }\n        }\n    }\n\n    #[component]\n    fn RerendersFrequently() -> Element {\n        let mut count = use_signal(|| 0);\n\n        use_future(move || async move {\n            for _ in 0..100 {\n                tokio::time::sleep(std::time::Duration::from_millis(10)).await;\n                count.set(count() + 1);\n            }\n        });\n\n        rsx! {\n            div { \"rerenders frequently\" }\n        }\n    }\n\n    #[component]\n    fn Child() -> Element {\n        let mut future_resolved = use_signal(|| false);\n\n        use_effect(|| panic!(\"effects should not run during suspense\"));\n\n        let task = use_hook(|| {\n            spawn(async move {\n                tokio::time::sleep(std::time::Duration::from_millis(500)).await;\n                future_resolved.set(true);\n            })\n        });\n        if !future_resolved() {\n            suspend(task)?;\n        }\n\n        rsx! {\n            div { \"child with future resolved\" }\n        }\n    }\n}\n\n/// Make sure we keep any state of components when we switch from a resolved future to a suspended future\n#[test]\nfn resolved_to_suspended() {\n    static SUSPENDED: GlobalSignal<bool> = Signal::global(|| false);\n\n    tokio::runtime::Builder::new_current_thread()\n        .enable_time()\n        .build()\n        .unwrap()\n        .block_on(async {\n            let mut dom = VirtualDom::new(app);\n            dom.rebuild(&mut dioxus_core::NoOpMutations);\n\n            let out = dioxus_ssr::render(&dom);\n\n            assert_eq!(out, \"rendered 1 times\");\n\n            dom.in_scope(ScopeId::APP, || *SUSPENDED.write() = true);\n\n            dom.render_suspense_immediate().await;\n            let out = dioxus_ssr::render(&dom);\n\n            assert_eq!(out, \"fallback\");\n\n            dom.wait_for_suspense().await;\n            let out = dioxus_ssr::render(&dom);\n\n            assert_eq!(out, \"rendered 3 times\");\n        });\n\n    fn app() -> Element {\n        rsx! {\n            SuspenseBoundary {\n                fallback: |_| rsx! { \"fallback\" },\n                Child {}\n            }\n        }\n    }\n\n    #[component]\n    fn Child() -> Element {\n        let mut render_count = use_signal(|| 0);\n        render_count += 1;\n\n        let mut task = use_hook(|| CopyValue::new(None));\n\n        tracing::info!(\"render_count: {}\", render_count.peek());\n\n        if SUSPENDED() {\n            if task().is_none() {\n                task.set(Some(spawn(async move {\n                    tokio::time::sleep(std::time::Duration::from_millis(100)).await;\n                    tracing::info!(\"task finished\");\n                    *SUSPENDED.write() = false;\n                })));\n            }\n            suspend(task().unwrap())?;\n        }\n\n        rsx! {\n            \"rendered {render_count.peek()} times\"\n        }\n    }\n}\n\n/// Make sure suspense tells the renderer that a suspense boundary was resolved\n#[test]\nfn suspense_tracks_resolved() {\n    tokio::runtime::Builder::new_current_thread()\n        .enable_time()\n        .build()\n        .unwrap()\n        .block_on(async {\n            let mut dom = VirtualDom::new(app);\n            dom.rebuild(&mut dioxus_core::NoOpMutations);\n\n            dom.render_suspense_immediate().await;\n            dom.wait_for_suspense_work().await;\n            assert_eq!(\n                dom.render_suspense_immediate().await,\n                vec![ScopeId(ScopeId::APP.0 + 1)]\n            );\n        });\n\n    fn app() -> Element {\n        rsx! {\n            SuspenseBoundary {\n                fallback: |_| rsx! { \"fallback\" },\n                Child {}\n            }\n        }\n    }\n\n    #[component]\n    fn Child() -> Element {\n        let mut resolved = use_signal(|| false);\n        let task = use_hook(|| {\n            spawn(async move {\n                tokio::time::sleep(std::time::Duration::from_millis(100)).await;\n                tracing::info!(\"task finished\");\n                resolved.set(true);\n            })\n        });\n\n        if resolved() {\n            println!(\"suspense is resolved\");\n        } else {\n            println!(\"suspense is not resolved\");\n            suspend(task)?;\n        }\n\n        rsx! {\n            \"child\"\n        }\n    }\n}\n\n// Regression test for https://github.com/DioxusLabs/dioxus/issues/2783\n#[test]\nfn toggle_suspense() {\n    use dioxus::prelude::*;\n\n    fn app() -> Element {\n        rsx! {\n            SuspenseBoundary {\n                fallback: |_| rsx! { \"fallback\" },\n                if generation() % 2 == 0 {\n                    Page {}\n                } else {\n                    Home {}\n                }\n            }\n        }\n    }\n\n    #[component]\n    pub fn Home() -> Element {\n        let _calculation = use_resource(|| async move {\n            tokio::time::sleep(std::time::Duration::from_secs(1)).await;\n            1 + 1\n        })\n        .suspend()?;\n        rsx! {\n            \"hello world\"\n        }\n    }\n\n    #[component]\n    pub fn Page() -> Element {\n        rsx! {\n            \"goodbye world\"\n        }\n    }\n\n    tokio::runtime::Builder::new_current_thread()\n        .enable_time()\n        .build()\n        .unwrap()\n        .block_on(async {\n            let mut dom = VirtualDom::new(app);\n            let mutations = dom.rebuild_to_vec();\n\n            // First create goodbye world\n            println!(\"{:#?}\", mutations);\n            assert_eq!(\n                mutations.edits,\n                [\n                    Mutation::LoadTemplate { index: 0, id: ElementId(1) },\n                    Mutation::AppendChildren { id: ElementId(0), m: 1 }\n                ]\n            );\n\n            dom.mark_dirty(ScopeId::APP);\n            let mutations = dom.render_immediate_to_vec();\n\n            // Then replace that with nothing\n            println!(\"{:#?}\", mutations);\n            assert_eq!(\n                mutations.edits,\n                [\n                    Mutation::CreatePlaceholder { id: ElementId(2) },\n                    Mutation::ReplaceWith { id: ElementId(1), m: 1 },\n                ]\n            );\n\n            dom.wait_for_work().await;\n            let mutations = dom.render_immediate_to_vec();\n\n            // Then replace it with a placeholder\n            println!(\"{:#?}\", mutations);\n            assert_eq!(\n                mutations.edits,\n                [\n                    Mutation::LoadTemplate { index: 0, id: ElementId(1) },\n                    Mutation::ReplaceWith { id: ElementId(2), m: 1 },\n                ]\n            );\n\n            dom.wait_for_work().await;\n            let mutations = dom.render_immediate_to_vec();\n\n            // Then replace it with the resolved node\n            println!(\"{:#?}\", mutations);\n            assert_eq!(\n                mutations.edits,\n                [\n                    Mutation::CreatePlaceholder { id: ElementId(2,) },\n                    Mutation::ReplaceWith { id: ElementId(1,), m: 1 },\n                    Mutation::LoadTemplate { index: 0, id: ElementId(1) },\n                    Mutation::ReplaceWith { id: ElementId(2), m: 1 },\n                ]\n            );\n        });\n}\n\n#[test]\nfn nested_suspense_resolves_client() {\n    use Mutation::*;\n\n    async fn poll_three_times() {\n        // Poll each task 3 times\n        let mut count = 0;\n        poll_fn(|cx| {\n            println!(\"polling... {}\", count);\n            if count < 3 {\n                count += 1;\n                cx.waker().wake_by_ref();\n                Poll::Pending\n            } else {\n                Poll::Ready(())\n            }\n        })\n        .await;\n    }\n\n    fn app() -> Element {\n        rsx! {\n            SuspenseBoundary {\n                fallback: move |_| rsx! {},\n                LoadTitle {}\n            }\n            MessageWithLoader { id: 0 }\n        }\n    }\n\n    #[component]\n    fn MessageWithLoader(id: usize) -> Element {\n        rsx! {\n            SuspenseBoundary {\n                fallback: move |_| rsx! {\n                    \"Loading {id}...\"\n                },\n                Message { id }\n            }\n        }\n    }\n\n    #[component]\n    fn LoadTitle() -> Element {\n        let title = use_resource(move || async_content(0)).suspend()?();\n\n        rsx! {\n            document::Title { \"{title.title}\" }\n        }\n    }\n\n    #[component]\n    fn Message(id: usize) -> Element {\n        let message = use_resource(move || async_content(id)).suspend()?();\n\n        rsx! {\n            h2 {\n                id: \"title-{id}\",\n                \"{message.title}\"\n            }\n            p {\n                id: \"body-{id}\",\n                \"{message.body}\"\n            }\n            div {\n                id: \"children-{id}\",\n                padding: \"10px\",\n                for child in message.children {\n                    MessageWithLoader { id: child }\n                }\n            }\n        }\n    }\n\n    #[derive(Clone)]\n    pub struct Content {\n        title: String,\n        body: String,\n        children: Vec<usize>,\n    }\n\n    async fn async_content(id: usize) -> Content {\n        let content_tree = [\n            Content {\n                title: \"The robot says hello world\".to_string(),\n                body: \"The robot becomes sentient and says hello world\".to_string(),\n                children: vec![1, 2],\n            },\n            Content {\n                title: \"The world says hello back\".to_string(),\n                body: \"In a stunning turn of events, the world collectively unites and says hello back\"\n                    .to_string(),\n                children: vec![],\n            },\n            Content {\n                title: \"Goodbye Robot\".to_string(),\n                body: \"The robot says goodbye\".to_string(),\n                children: vec![3],\n            },\n            Content {\n                title: \"Goodbye Robot again\".to_string(),\n                body: \"The robot says goodbye again\".to_string(),\n                children: vec![],\n            },\n        ];\n        poll_three_times().await;\n        content_tree[id].clone()\n    }\n\n    // wait just a moment, not enough time for the boundary to resolve\n    tokio::runtime::Builder::new_current_thread()\n        .build()\n        .unwrap()\n        .block_on(async {\n            let mut dom = VirtualDom::new(app);\n            let mutations = dom.rebuild_to_vec();\n            // Initial loading message and loading title\n            assert_eq!(\n                mutations.edits,\n                vec![\n                    CreatePlaceholder { id: ElementId(1,) },\n                    CreateTextNode { value: \"Loading 0...\".to_string(), id: ElementId(2,) },\n                    AppendChildren { id: ElementId(0,), m: 2 },\n                ]\n            );\n\n            dom.wait_for_work().await;\n            // DOM STATE:\n            // placeholder // ID: 1\n            // \"Loading 0...\" // ID: 2\n            let mutations = dom.render_immediate_to_vec();\n            // Fill in the contents of the initial message and start loading the nested suspense\n            // The title also finishes loading\n            assert_eq!(\n                mutations.edits,\n                vec![\n                    // Creating and swapping these placeholders doesn't do anything\n                    // It is just extra work that we are forced to do because mutations are not\n                    // reversible. We start rendering the children and then realize it is suspended.\n                    // Then we need to replace what we just rendered with the suspense placeholder\n                    CreatePlaceholder { id: ElementId(3,) },\n                    ReplaceWith { id: ElementId(1,), m: 1 },\n\n                    // Replace the pending placeholder with the title placeholder\n                    CreatePlaceholder { id: ElementId(1,) },\n                    ReplaceWith { id: ElementId(3,), m: 1 },\n\n                    // Replace loading... with a placeholder for us to fill in later\n                    CreatePlaceholder { id: ElementId(3,) },\n                    ReplaceWith { id: ElementId(2,), m: 1 },\n\n                    // Load the title\n                    LoadTemplate {  index: 0, id: ElementId(2,) },\n                    SetAttribute {\n                        name: \"id\",\n                        ns: None,\n                        value: AttributeValue::Text(\"title-0\".to_string()),\n                        id: ElementId(2,),\n                    },\n                    CreateTextNode { value: \"The robot says hello world\".to_string(), id: ElementId(4,) },\n                    ReplacePlaceholder { path: &[0,], m: 1 },\n\n                    // Then load the body\n                    LoadTemplate {  index: 1, id: ElementId(5,) },\n                    SetAttribute {\n                        name: \"id\",\n                        ns: None,\n                        value: AttributeValue::Text(\"body-0\".to_string()),\n                        id: ElementId(5,),\n                    },\n                    CreateTextNode { value: \"The robot becomes sentient and says hello world\".to_string(), id: ElementId(6,) },\n                    ReplacePlaceholder { path: &[0,], m: 1 },\n\n                    // Then load the suspended children\n                    LoadTemplate {  index: 2, id: ElementId(7,) },\n                    SetAttribute {\n                        name: \"id\",\n                        ns: None,\n                        value: AttributeValue::Text(\"children-0\".to_string()),\n                        id: ElementId(7,),\n                    },\n                    CreateTextNode { value: \"Loading 1...\".to_string(), id: ElementId(8,) },\n                    CreateTextNode { value: \"Loading 2...\".to_string(), id: ElementId(9,) },\n                    ReplacePlaceholder { path: &[0,], m: 2 },\n\n                    // Finally replace the loading placeholder in the body with the resolved children\n                    ReplaceWith { id: ElementId(3,), m: 3 },\n                ]\n            );\n\n            dom.wait_for_work().await;\n            // DOM STATE:\n            // placeholder // ID: 1\n            // h2 // ID: 2\n            // p // ID: 5\n            // div // ID: 7\n            //   \"Loading 1...\" // ID: 8\n            //   \"Loading 2...\" // ID: 9\n            let mutations = dom.render_immediate_to_vec();\n            assert_eq!(\n                mutations.edits,\n                vec![\n                    // Replace the first loading placeholder with a placeholder for us to fill in later\n                    CreatePlaceholder {\n                        id: ElementId(\n                            3,\n                        ),\n                    },\n                    ReplaceWith {\n                        id: ElementId(\n                            8,\n                        ),\n                        m: 1,\n                    },\n\n                    // Load the nested suspense\n                    LoadTemplate {\n\n                        index: 0,\n                        id: ElementId(\n                            8,\n                        ),\n                    },\n                    SetAttribute {\n                        name: \"id\",\n                        ns: None,\n                        value: AttributeValue::Text(\"title-1\".to_string()),\n                        id: ElementId(\n                            8,\n                        ),\n                    },\n                    CreateTextNode { value: \"The world says hello back\".to_string(), id: ElementId(10,) },\n                    ReplacePlaceholder {\n                        path: &[\n                            0,\n                        ],\n                        m: 1,\n                    },\n                    LoadTemplate {\n                        index: 1,\n                        id: ElementId(\n                            11,\n                        ),\n                    },\n                    SetAttribute {\n                        name: \"id\",\n                        ns: None,\n                        value: AttributeValue::Text(\"body-1\".to_string()),\n                        id: ElementId(\n                            11,\n                        ),\n                    },\n                    CreateTextNode { value: \"In a stunning turn of events, the world collectively unites and says hello back\".to_string(), id: ElementId(12,) },\n                    ReplacePlaceholder {\n                        path: &[\n                            0,\n                        ],\n                        m: 1,\n                    },\n                    LoadTemplate {\n                        index: 2,\n                        id: ElementId(\n                            13,\n                        ),\n                    },\n                    SetAttribute {\n                        name: \"id\",\n                        ns: None,\n                        value: AttributeValue::Text(\"children-1\".to_string()),\n                        id: ElementId(\n                            13,\n                        ),\n                    },\n                    CreatePlaceholder { id: ElementId(14,) },\n                    ReplacePlaceholder {\n                        path: &[\n                            0,\n                        ],\n                        m: 1,\n                    },\n                    ReplaceWith {\n                        id: ElementId(\n                            3,\n                        ),\n                        m: 3,\n                    },\n\n                    // Replace the second loading placeholder with a placeholder for us to fill in later\n                    CreatePlaceholder {\n                        id: ElementId(\n                            3,\n                        ),\n                    },\n                    ReplaceWith {\n                        id: ElementId(\n                            9,\n                        ),\n                        m: 1,\n                    },\n                    LoadTemplate {\n                        index: 0,\n                        id: ElementId(\n                            9,\n                        ),\n                    },\n                    SetAttribute {\n                        name: \"id\",\n                        ns: None,\n                        value: AttributeValue::Text(\"title-2\".to_string()),\n                        id: ElementId(\n                            9,\n                        ),\n                    },\n                    CreateTextNode { value: \"Goodbye Robot\".to_string(), id: ElementId(15,) },\n                    ReplacePlaceholder {\n                        path: &[\n                            0,\n                        ],\n                        m: 1,\n                    },\n                    LoadTemplate {\n                        index: 1,\n                        id: ElementId(\n                            16,\n                        ),\n                    },\n                    SetAttribute {\n                        name: \"id\",\n                        ns: None,\n                        value: AttributeValue::Text(\"body-2\".to_string()),\n                        id: ElementId(\n                            16,\n                        ),\n                    },\n                    CreateTextNode { value: \"The robot says goodbye\".to_string(), id: ElementId(17,) },\n                    ReplacePlaceholder {\n                        path: &[\n                            0,\n                        ],\n                        m: 1,\n                    },\n                    LoadTemplate {\n\n                        index: 2,\n                        id: ElementId(\n                            18,\n                        ),\n                    },\n                    SetAttribute {\n                        name: \"id\",\n                        ns: None,\n                        value: AttributeValue::Text(\"children-2\".to_string()),\n                        id: ElementId(\n                            18,\n                        ),\n                    },\n                    // Create a placeholder for the resolved children\n                    CreateTextNode { value: \"Loading 3...\".to_string(), id: ElementId(19,) },\n                    ReplacePlaceholder { path: &[0,], m: 1 },\n\n                    // Replace the loading placeholder with the resolved children\n                    ReplaceWith {\n                        id: ElementId(\n                            3,\n                        ),\n                        m: 3,\n                    },\n                ]\n            );\n\n            dom.wait_for_work().await;\n            let mutations = dom.render_immediate_to_vec();\n            assert_eq!(\n                mutations.edits,\n                vec![\n                    CreatePlaceholder {\n                        id: ElementId(\n                            3,\n                        ),\n                    },\n                    ReplaceWith {\n                        id: ElementId(\n                            19,\n                        ),\n                        m: 1,\n                    },\n                    LoadTemplate {\n\n                        index: 0,\n                        id: ElementId(\n                            19,\n                        ),\n                    },\n                    SetAttribute {\n                        name: \"id\",\n                        ns: None,\n                        value: AttributeValue::Text(\"title-3\".to_string()),\n                        id: ElementId(\n                            19,\n                        ),\n                    },\n                    CreateTextNode { value: \"Goodbye Robot again\".to_string(), id: ElementId(20,) },\n                    ReplacePlaceholder {\n                        path: &[\n                            0,\n                        ],\n                        m: 1,\n                    },\n                    LoadTemplate {\n                        index: 1,\n                        id: ElementId(\n                            21,\n                        ),\n                    },\n                    SetAttribute {\n                        name: \"id\",\n                        ns: None,\n                        value: AttributeValue::Text(\"body-3\".to_string()),\n                        id: ElementId(\n                            21,\n                        ),\n                    },\n                    CreateTextNode { value: \"The robot says goodbye again\".to_string(), id: ElementId(22,) },\n                    ReplacePlaceholder {\n                        path: &[\n                            0,\n                        ],\n                        m: 1,\n                    },\n                    LoadTemplate {\n                        index: 2,\n                        id: ElementId(\n                            23,\n                        ),\n                    },\n                    SetAttribute {\n                        name: \"id\",\n                        ns: None,\n                        value: AttributeValue::Text(\"children-3\".to_string()),\n                        id: ElementId(\n                            23,\n                        ),\n                    },\n                    CreatePlaceholder { id: ElementId(24,) },\n                    ReplacePlaceholder {\n                        path: &[\n                            0\n                        ],\n                        m: 1,\n                    },\n                    ReplaceWith {\n                        id: ElementId(\n                            3,\n                        ),\n                        m: 3,\n                    },\n                ]\n            )\n        });\n}\n"
  },
  {
    "path": "packages/core/tests/task.rs",
    "content": "//! Verify that tasks get polled by the virtualdom properly, and that we escape wait_for_work safely\n\nuse std::{sync::atomic::AtomicUsize, time::Duration};\n\nuse dioxus::prelude::*;\nuse dioxus_core::{generation, needs_update, spawn_forever};\n\nasync fn run_vdom(app: fn() -> Element) {\n    let mut dom = VirtualDom::new(app);\n\n    dom.rebuild(&mut dioxus_core::NoOpMutations);\n\n    tokio::select! {\n        _ = dom.wait_for_work() => {}\n        _ = tokio::time::sleep(Duration::from_millis(500)) => {}\n    };\n}\n\n#[tokio::test]\nasync fn running_async() {\n    static POLL_COUNT: AtomicUsize = AtomicUsize::new(0);\n\n    fn app() -> Element {\n        use_hook(|| {\n            spawn(async {\n                for x in 0..10 {\n                    tokio::time::sleep(Duration::from_micros(50)).await;\n                    POLL_COUNT.fetch_add(x, std::sync::atomic::Ordering::Relaxed);\n                }\n            });\n\n            spawn(async {\n                for x in 0..10 {\n                    tokio::time::sleep(Duration::from_micros(25)).await;\n                    POLL_COUNT.fetch_add(x * 2, std::sync::atomic::Ordering::Relaxed);\n                }\n            });\n        });\n\n        rsx!({})\n    }\n\n    run_vdom(app).await;\n\n    // By the time the tasks are finished, we should've accumulated ticks from two tasks\n    // Be warned that by setting the delay to too short, tokio might not schedule in the tasks\n    assert_eq!(\n        POLL_COUNT.fetch_add(0, std::sync::atomic::Ordering::Relaxed),\n        135\n    );\n}\n\n#[tokio::test]\nasync fn spawn_forever_persists() {\n    use std::sync::atomic::Ordering;\n    static POLL_COUNT: AtomicUsize = AtomicUsize::new(0);\n\n    fn app() -> Element {\n        if generation() > 0 {\n            rsx!(div {})\n        } else {\n            needs_update();\n            rsx!(Child {})\n        }\n    }\n\n    #[component]\n    fn Child() -> Element {\n        spawn_forever(async move {\n            for _ in 0..10 {\n                POLL_COUNT.fetch_add(1, Ordering::Relaxed);\n                tokio::time::sleep(Duration::from_millis(50)).await;\n            }\n        });\n\n        rsx!(div {})\n    }\n\n    let mut dom = VirtualDom::new(app);\n\n    dom.rebuild(&mut dioxus_core::NoOpMutations);\n    dom.render_immediate(&mut dioxus_core::NoOpMutations);\n\n    tokio::select! {\n        _ = dom.wait_for_work() => {}\n        // We intentionally wait a bit longer than 50ms*10 to make sure the test has time to finish\n        // Without the extra time, the test can fail on windows\n        _ = tokio::time::sleep(Duration::from_millis(1000)) => {}\n    };\n\n    // By the time the tasks are finished, we should've accumulated ticks from two tasks\n    // Be warned that by setting the delay to too short, tokio might not schedule in the tasks\n    assert_eq!(POLL_COUNT.load(Ordering::Relaxed), 10);\n}\n\n/// Prove that yield_now doesn't cause a deadlock\n#[tokio::test]\nasync fn yield_now_works() {\n    thread_local! {\n        static SEQUENCE: std::cell::RefCell<Vec<usize>> = const { std::cell::RefCell::new(Vec::new()) };\n    }\n\n    fn app() -> Element {\n        // these two tasks should yield to eachother\n        use_hook(|| {\n            spawn(async move {\n                for _ in 0..10 {\n                    tokio::task::yield_now().await;\n                    SEQUENCE.with(|s| s.borrow_mut().push(1));\n                }\n            })\n        });\n\n        use_hook(|| {\n            spawn(async move {\n                for _ in 0..10 {\n                    tokio::task::yield_now().await;\n                    SEQUENCE.with(|s| s.borrow_mut().push(2));\n                }\n            })\n        });\n\n        rsx!({})\n    }\n\n    run_vdom(app).await;\n\n    SEQUENCE.with(|s| assert_eq!(s.borrow().len(), 20));\n}\n"
  },
  {
    "path": "packages/core/tests/tracing.rs",
    "content": "use dioxus::html::SerializedHtmlEventConverter;\nuse dioxus::prelude::*;\nuse dioxus_core::{ElementId, Event};\nuse std::{any::Any, rc::Rc};\nuse tracing_fluent_assertions::{AssertionRegistry, AssertionsLayer};\nuse tracing_subscriber::{layer::SubscriberExt, Registry};\n\n#[test]\nfn basic_tracing() {\n    // setup tracing\n    let assertion_registry = AssertionRegistry::default();\n    let base_subscriber = Registry::default();\n    // log to standard out for testing\n    let std_out_log = tracing_subscriber::fmt::layer().pretty();\n    let subscriber = base_subscriber\n        .with(std_out_log)\n        .with(AssertionsLayer::new(&assertion_registry));\n    tracing::subscriber::set_global_default(subscriber).unwrap();\n\n    let new_virtual_dom = assertion_registry\n        .build()\n        .with_name(\"VirtualDom::new\")\n        .was_created()\n        .was_entered_exactly(1)\n        .was_closed()\n        .finalize();\n\n    let edited_virtual_dom = assertion_registry\n        .build()\n        .with_name(\"VirtualDom::rebuild\")\n        .was_created()\n        .was_entered_exactly(1)\n        .was_closed()\n        .finalize();\n\n    set_event_converter(Box::new(SerializedHtmlEventConverter));\n    let mut dom = VirtualDom::new(app);\n\n    dom.rebuild(&mut dioxus_core::NoOpMutations);\n\n    new_virtual_dom.assert();\n    edited_virtual_dom.assert();\n\n    for _ in 0..3 {\n        let event = Event::new(\n            Rc::new(PlatformEventData::new(Box::<SerializedMouseData>::default())) as Rc<dyn Any>,\n            true,\n        );\n        dom.runtime().handle_event(\"click\", event, ElementId(2));\n        dom.process_events();\n        _ = dom.render_immediate_to_vec();\n    }\n}\n\nfn app() -> Element {\n    let mut idx = use_signal(|| 0);\n    let onhover = |_| println!(\"go!\");\n\n    rsx! {\n        div {\n            button {\n                onclick: move |_| {\n                    idx += 1;\n                    println!(\"Clicked\");\n                },\n                \"+\"\n            }\n            button { onclick: move |_| idx -= 1, \"-\" }\n            ul {\n                {(0..idx()).map(|i| rsx! {\n                    ChildExample { i: i, onhover: onhover }\n                })}\n            }\n        }\n    }\n}\n\n#[component]\nfn ChildExample(i: i32, onhover: EventHandler<MouseEvent>) -> Element {\n    rsx! { li { onmouseover: move |e| onhover.call(e), \"{i}\" } }\n}\n"
  },
  {
    "path": "packages/core/tests/use_drop.rs",
    "content": "//! Tests the use_drop hook\nuse dioxus::dioxus_core::use_drop;\nuse dioxus::prelude::*;\nuse std::sync::{Arc, Mutex};\n\ntype Shared<T> = Arc<Mutex<T>>;\n\n#[derive(Clone, Props)]\nstruct AppProps {\n    render_child: Shared<bool>,\n    drop_count: Shared<u32>,\n}\n\nimpl PartialEq for AppProps {\n    fn eq(&self, other: &Self) -> bool {\n        Arc::ptr_eq(&self.drop_count, &other.drop_count)\n    }\n}\n\nfn app(props: AppProps) -> Element {\n    let render_child = props.render_child.clone();\n    let render_child = *render_child.lock().unwrap();\n    println!(\n        \"Rendering app component with render_child: {}\",\n        render_child\n    );\n    rsx! {\n        if render_child {\n            child_component {\n                drop_count: props.drop_count.clone(),\n                render_child: props.render_child.clone()\n            }\n        }\n    }\n}\n\nfn child_component(props: AppProps) -> Element {\n    println!(\"Rendering child component\");\n    use_drop(move || {\n        println!(\"Child component is being dropped\");\n        let mut count = props.drop_count.lock().unwrap();\n        *count += 1;\n    });\n\n    rsx! {}\n}\n\n#[test]\nfn drop_runs() {\n    let drop_count = Arc::new(Mutex::new(0));\n    let render_child = Arc::new(Mutex::new(true));\n    let mut dom = VirtualDom::new_with_props(\n        app,\n        AppProps { drop_count: drop_count.clone(), render_child: render_child.clone() },\n    );\n\n    dom.rebuild_in_place();\n\n    assert_eq!(*drop_count.lock().unwrap(), 0);\n    *render_child.lock().unwrap() = false;\n\n    dom.mark_dirty(ScopeId::APP);\n    dom.render_immediate(&mut dioxus_core::NoOpMutations);\n\n    assert_eq!(*drop_count.lock().unwrap(), 1);\n    *render_child.lock().unwrap() = false;\n}\n"
  },
  {
    "path": "packages/core-macro/.vscode/settings.json",
    "content": "{\n    \"rust-analyzer.check.workspace\": false,\n    // \"rust-analyzer.check.command\": \"check alsjdlaskdjljkasd\",\n    \"rust-analyzer.check.allTargets\": false,\n    \"rust-analyzer.cargo.buildScripts.enable\": true,\n    \"rust-analyzer.cargo.buildScripts.overrideCommand\": [\n        \"cargo\",\n        \"check\",\n        \"--quiet\",\n        // \"--package\",\n        // \"dioxus-core-macro\",\n        \"--message-format\",\n        \"json\",\n        \"--all-targets\"\n    ],\n    \"rust-analyzer.cargo.buildScripts.rebuildOnSave\": false,\n    \"rust-analyzer.cargo.buildScripts.invocationLocation\": \"root\",\n    \"rust-analyzer.cargo.buildScripts.invocationStrategy\": \"once\",\n    // \"rust-analyzer.check.command\": \"check --package dioxus-core-macro\"\n}\n"
  },
  {
    "path": "packages/core-macro/Cargo.toml",
    "content": "[package]\nname = \"dioxus-core-macro\"\nversion = { workspace = true }\nauthors = [\"Jonathan Kelley\"]\nedition = \"2021\"\ndescription = \"Core macro for Dioxus Virtual DOM\"\nlicense = \"MIT OR Apache-2.0\"\nrepository = \"https://github.com/DioxusLabs/dioxus/\"\nhomepage = \"https://dioxuslabs.com\"\nkeywords = [\"dom\", \"ui\", \"gui\", \"react\"]\n\n[lib]\nproc-macro = true\n\n[dependencies]\nproc-macro2 = { workspace = true }\nquote = { workspace = true }\nsyn = { workspace = true, features = [\"full\", \"extra-traits\", \"visit\"] }\ndioxus-rsx = { workspace = true }\nconvert_case = { workspace = true }\n\n[dev-dependencies]\ndioxus = { workspace = true }\ndioxus-html = { workspace = true, features = [\"serialize\"]}\nrustversion = { workspace = true }\ntokio = { workspace = true, features = [\"full\", \"time\"] }\ntrybuild = { workspace = true }\n\n[features]\ndefault = []\n\n[package.metadata.docs.rs]\ncargo-args = [\"-Zunstable-options\", \"-Zrustdoc-scrape-examples\"]\n"
  },
  {
    "path": "packages/core-macro/README.md",
    "content": "# dioxus-core-macro\n\n[![Crates.io][crates-badge]][crates-url]\n[![MIT licensed][mit-badge]][mit-url]\n[![Build Status][actions-badge]][actions-url]\n[![Discord chat][discord-badge]][discord-url]\n\n[crates-badge]: https://img.shields.io/crates/v/dioxus-core-macro.svg\n[crates-url]: https://crates.io/crates/dioxus-core-macro\n[mit-badge]: https://img.shields.io/badge/license-MIT-blue.svg\n[mit-url]: https://github.com/dioxuslabs/dioxus/blob/main/LICENSE-MIT\n[actions-badge]: https://github.com/dioxuslabs/dioxus/actions/workflows/main.yml/badge.svg\n[actions-url]: https://github.com/dioxuslabs/dioxus/actions?query=workflow%3ACI+branch%3Amaster\n[discord-badge]: https://img.shields.io/discord/899851952891002890.svg?logo=discord&style=flat-square\n[discord-url]: https://discord.gg/XgGxMSkvUM\n\n[Website](https://dioxuslabs.com) |\n[Guides](https://dioxuslabs.com/learn/0.7/) |\n[API Docs](https://docs.rs/dioxus-core-macro/latest/dioxus_core_macro) |\n[Chat](https://discord.gg/XgGxMSkvUM)\n\n## Overview\n\n`dioxus-core-macro` provides a handful of helpful macros used by the `dioxus` crate. These include:\n\n- The `rsx!` macro that underpins templates and node creation.\n- The `component` attribute macro denotes a function as a Dioxus component. Currently, this:\n  - Transforms function arguments into an auto-derived struct.\n  - Ensures that your component name uses PascalCase.\n  - Probably more stuff in the future. This macro allows us to have a way of distinguishing functions and components, which can be quite handy.\n- The `format_args_f` macro which allows f-string formatting with support for expressions.\n\n## Contributing\n\n- Report issues on our [issue tracker](https://github.com/dioxuslabs/dioxus/issues).\n- Join the discord and ask questions!\n\n## License\n\nThis project is licensed under the [MIT license].\n\n[mit license]: https://github.com/dioxuslabs/dioxus/blob/main/LICENSE-MIT\n\nUnless you explicitly state otherwise, any contribution intentionally submitted\nfor inclusion in Dioxus by you shall be licensed as MIT without any additional\nterms or conditions.\n"
  },
  {
    "path": "packages/core-macro/docs/component.md",
    "content": "# Component\n\nThe component macro turns a function with arguments that are [`Clone`] and [`PartialEq`] into a component. This is the recommended way of creating most components. If you want more fine grained control over how the overall prop struct implements the `Properties` trait, you can use an explicit props struct with the [`Props`] derive macro instead.\n\n## Arguments\n\n- `no_case_check` - Doesn't enforce `PascalCase` on your component names.\n  **This will be removed/deprecated in a future update in favor of a more complete Clippy-backed linting system.**\n  The reasoning behind this is that Clippy allows more robust and powerful lints, whereas\n  macros are extremely limited.\n\n## Features\n\nThis attribute:\n\n- Enforces that your component uses `PascalCase` or `snake_case` with at least one underscore.\n- Automatically creates a prop struct for your component if the function has arguments.\n- Verifies the function signature is valid as a component.\n\n## Examples\n\n- Without props:\n\n```rust, no_run\n# use dioxus::prelude::*;\n#[component]\nfn GreetBob() -> Element {\n    rsx! { \"hello, bob\" }\n}\n```\n\n- With props:\n\n```rust, no_run\n# use dioxus::prelude::*;\n#[component]\nfn GreetBob(bob: String) -> Element {\n    rsx! { \"hello, {bob}\" }\n}\n```\n\n## Prop Modifiers\n\nYou can use the `#[props()]` attribute to modify the behavior of the props the component macro creates:\n\n- [`#[props(default)]`](#default-props) - Makes the field optional in the component and uses the default value if it is not set when creating the component.\n- [`#[props(!optional)]`](#optional-props) - Makes a field with the type `Option<T>` required.\n- [`#[props(into)]`](#converting-props) - Converts a field into the correct type by using the [`Into`] trait.\n- [`#[props(extends = GlobalAttributes)]`](#extending-elements) - Extends the props with all the attributes from an element or the global element attributes.\n\nProps also act slightly differently when used with:\n\n- [`Option<T>`](#optional-props) - The field is automatically optional with a default value of `None`.\n- [`ReadSignal<T>`](#reactive-props) - The props macro will automatically convert `T` into `ReadSignal<T>` when it is passed as a prop.\n- [`String`](#formatted-props) - The props macro will accept formatted strings for any prop field with the type `String`.\n- [`children`](#children-props) - The props macro will accept child elements if you include the `children` prop.\n\n### Default Props\n\nThe `default` attribute lets you define a default value for a field if it isn't set when creating the component\n\n```rust, no_run\n# use dioxus::prelude::*;\n#[component]\nfn Button(\n    // The default attributes makes your field optional in the component and uses the default value if it is not set.\n    #[props(default)]\n    text: String,\n    // You can also set an explicit default value instead of using the `Default` implementation.\n    #[props(default = \"red\".to_string())]\n    color: String,\n) -> Element {\n    rsx! {\n        button {\n            color: color,\n            \"{text}\"\n        }\n    }\n}\n\nrsx! {\n    // You can skip setting props that have a default value when you use the component.\n    Button {}\n};\n```\n\n### Optional Props\n\nWhen defining a component, you may want to make a prop optional without defining an explicit default value. Any fields with the type `Option<T>` are automatically optional with a default value of `None`.\n\n```rust, no_run\n# use dioxus::prelude::*;\n#[component]\nfn Button(\n    // Since the `text` field is optional, you don't need to set it when you use the component.\n    text: Option<String>,\n) -> Element {\n    rsx! {\n        button { {text.unwrap_or(\"button\".to_string())} }\n    }\n}\n\nrsx! {\n    Button {}\n};\n```\n\nIf you want to make your `Option<T>` field required, you can use the `!optional` attribute:\n\n```rust, no_run\n# use dioxus::prelude::*;\n#[component]\nfn Button(\n    // You can use the `!optional` attribute on a field with the type `Option<T>` to make it required.\n    #[props(!optional)]\n    text: Option<String>,\n) -> Element {\n    rsx! {\n        button { {text.unwrap_or(\"button\".to_string())} }\n    }\n}\n\nrsx! {\n    Button {\n        text: None\n    }\n};\n```\n\n### Converting Props\n\nYou can automatically convert a field into the correct type by using the `into` attribute. Any type you pass into the field will be converted with the [`Into`] trait:\n\n```rust, no_run\n# use dioxus::prelude::*;\n#[component]\nfn Button(\n    // You can use the `into` attribute on a field to convert types you pass in with the Into trait.\n    #[props(into)]\n    number: u64,\n) -> Element {\n    rsx! {\n        button { \"{number}\" }\n    }\n}\n\nrsx! {\n    Button {\n        // Because we used the into attribute, we can pass in any type that implements Into<u64>\n        number: 10u8\n    }\n};\n```\n\n### Formatted Props\n\nYou can use formatted strings in attributes just like you would in an element. Any prop field with the type `String` can accept a formatted string:\n\n```rust, no_run\n# use dioxus::prelude::*;\n#[component]\nfn Button(text: String,) -> Element {\n    rsx! {\n        button { \"{text}\" }\n    }\n}\n\nlet name = \"Bob\";\nrsx! {\n    Button {\n        // You can use formatted strings in props that accept String just like you would in an element.\n        text: \"Hello {name}!\"\n    }\n};\n```\n\n### Children Props\n\nRather than passing the RSX through a regular prop, you may wish to accept children similarly to how elements can have children. The \"magic\" children prop lets you achieve this:\n\n```rust, no_run\n# use dioxus::prelude::*;\n#[component]\nfn Clickable(\n    href: String,\n    children: Element,\n) -> Element {\n    rsx! {\n        a {\n            href: \"{href}\",\n            class: \"fancy-button\",\n            {children}\n        }\n    }\n}\n```\n\nThis makes providing children to the component much simpler: simply put the RSX inside the {} brackets:\n\n```rust, no_run\n# use dioxus::prelude::*;\n# #[component]\n# fn Clickable(\n#     href: String,\n#     children: Element,\n# ) -> Element {\n#     rsx! {\n#         a {\n#             href: \"{href}\",\n#             class: \"fancy-button\",\n#             {children}\n#         }\n#     }\n# }\nrsx! {\n    Clickable {\n        href: \"https://www.youtube.com/watch?v=C-M2hs3sXGo\",\n        \"How to \"\n        i { \"not\" }\n        \" be seen\"\n    }\n};\n```\n\n### Reactive Props\n\nIn dioxus, when a prop changes, the component will rerun with the new value to update the UI. For example, if count changes from 0 to 1, this component will rerun and update the UI to show \"Count: 1\":\n\n```rust, no_run\n# use dioxus::prelude::*;\n#[component]\nfn Counter(count: i32) -> Element {\n    rsx! {\n        div {\n            \"Count: {count}\"\n        }\n    }\n}\n```\n\nGenerally, just rerunning the component is enough to update the UI. However, if you use your prop inside reactive hooks like `use_memo` or `use_resource`, you may also want to restart those hooks when the prop changes:\n\n```rust, no_run\n# use dioxus::prelude::*;\n#[component]\nfn Counter(count: i32) -> Element {\n    // We can use a memo to calculate the doubled count. Since this memo will only be created the first time the component is run and `count` is not reactive, it will never update when `count` changes.\n    let doubled_count = use_memo(move || count * 2);\n    rsx! {\n        div {\n            \"Count: {count}\"\n            \"Doubled Count: {doubled_count}\"\n        }\n    }\n}\n```\n\nTo fix this issue you can either:\n\n1. Make the prop reactive by wrapping it in `ReadSignal` (recommended):\n\n`ReadSignal` is a `Copy` reactive value. Dioxus will automatically convert any value into a `ReadSignal` when it is passed as a prop.\n\n```rust, no_run\n# use dioxus::prelude::*;\n#[component]\nfn Counter(count: ReadSignal<i32>) -> Element {\n    // Since we made count reactive, the memo will automatically rerun when count changes.\n    let doubled_count = use_memo(move || count() * 2);\n    rsx! {\n        div {\n            \"Count: {count}\"\n            \"Doubled Count: {doubled_count}\"\n        }\n    }\n}\n```\n\n2. Explicitly add the prop as a dependency to the reactive hook with [`use_reactive`](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/macro.use_reactive.html):\n\n```rust, no_run\n# use dioxus::prelude::*;\n#[component]\nfn Counter(count: i32) -> Element {\n    // We can add the count prop as an explicit dependency to every reactive hook that uses it with use_reactive.\n    // The use_reactive macro takes a closure with explicit dependencies as its argument.\n    let doubled_count = use_memo(use_reactive!(|count| count * 2));\n    rsx! {\n        div {\n            \"Count: {count}\"\n            \"Doubled Count: {doubled_count}\"\n        }\n    }\n}\n```\n\n### Extending Elements\n\nThe `extends` attribute lets you extend your props with all the attributes from an element or the global element attributes.\n\n```rust, no_run\n# use dioxus::prelude::*;\n#[component]\nfn Card(\n    // You can use the `extends` attribute on a field with the type `Vec<Attribute>` to extend the props with all the attributes from an element or the global element attributes.\n    #[props(extends = GlobalAttributes)]\n    attributes: Vec<Attribute>,\n) -> Element {\n    rsx! {\n        // Instead of copying over every single attribute, we can just spread the attributes from the props into the element.\n        div { ..attributes, \"card\" }\n    }\n}\n\nrsx! {\n    // Since we extend global attributes, you can use any attribute that would normally appear on elements.\n    Card {\n        width: \"10px\",\n        height: \"10px\",\n        color: \"red\",\n    }\n};\n```\n\nTo extend the props with both the global attributes and the attributes of a specific element, you can use the `extends` attribute multiple times:\n\n```rust, no_run\n# use dioxus::prelude::*;\n#[component]\nfn Button(\n    #[props(extends = GlobalAttributes, extends = button)]\n    attributes: Vec<Attribute>,\n) -> Element {\n    rsx! {\n        button { ..attributes, \"button\" }\n    }\n}\n\nrsx! {\n    Button {\n        // A global attribute\n        width: \"10px\",\n        // A button specific attribute\n        disabled: true,\n    }\n};\n```\n\nNote that extending from multiple elements will only work if the elements don't have conflicting attributes.\n"
  },
  {
    "path": "packages/core-macro/docs/props.md",
    "content": "# Props\n\nThe props derive macro allows you to define what props your component accepts and how to accept those props. Every component must either accept no arguments or accept a single argument that implements the `Properties` trait.\n\n> Note: You should generally prefer using the `#[component]` macro instead of the `#[derive(Props)]` macro with explicit props. The `#[component]` macro will automatically generate the props struct for you and perform extra checks to ensure that your component is valid.\n\n## Example\n\n```rust, no_run\n# use dioxus::prelude::*;\n#[derive(Props, PartialEq, Clone)]\nstruct ButtonProps {\n    /// The text of the button\n    text: String,\n\n    /// The color of the button\n    color: String,\n}\n\nfn Button(props: ButtonProps) -> Element {\n    rsx! {\n        button {\n            color: props.color,\n            \"{props.text}\"\n        }\n    }\n}\n\nrsx! {\n    // Any fields you defined on the props struct will be turned into props for the component.\n    Button {\n        text: \"Click me!\",\n        color: \"red\",\n    }\n};\n```\n\n## Prop Modifiers\n\nYou can use the `#[props()]` attribute to modify the behavior of the props derive macro:\n\n- [`#[props(default)]`](#default-props) - Makes the field optional in the component and uses the default value if it is not set when creating the component.\n- [`#[props(!optional)]`](#optional-props) - Makes a field with the type `Option<T>` required.\n- [`#[props(into)]`](#converting-props) - Converts a field into the correct type by using the [`Into`] trait.\n- [`#[props(extends = GlobalAttributes)]`](#extending-elements) - Extends the props with all the attributes from an element or the global element attributes.\n\nProps also act slightly differently when used with:\n\n- [`Option<T>`](#optional-props) - The field is automatically optional with a default value of `None`.\n- [`ReadSignal<T>`](#reactive-props) - The props macro will automatically convert `T` into `ReadSignal<T>` when it is passed as a prop.\n- [`String`](#formatted-props) - The props macro will accept formatted strings for any prop field with the type `String`.\n- [`children`](#children-props) - The props macro will accept child elements if you include the `children` prop.\n\n### Default Props\n\nThe `default` attribute lets you define a default value for a field if it isn't set when creating the component\n\n```rust, no_run\n# use dioxus::prelude::*;\n#[derive(Props, PartialEq, Clone)]\nstruct ButtonProps {\n    // The default attributes makes your field optional in the component and uses the default value if it is not set.\n    #[props(default)]\n    text: String,\n\n    /// You can also set an explicit default value instead of using the `Default` implementation.\n    #[props(default = \"red\".to_string())]\n    color: String,\n}\n\nfn Button(props: ButtonProps) -> Element {\n    rsx! {\n        button {\n            color: props.color,\n            \"{props.text}\"\n        }\n    }\n}\n\nrsx! {\n    // You can skip setting props that have a default value when you use the component.\n    Button {}\n};\n```\n\n### Optional Props\n\nWhen defining props, you may want to make a prop optional without defining an explicit default value. Any fields with the type `Option<T>` are automatically optional with a default value of `None`.\n\n```rust, no_run\n# use dioxus::prelude::*;\n#[derive(Props, PartialEq, Clone)]\nstruct ButtonProps {\n    // Since the `text` field is optional, you don't need to set it when you use the component.\n    text: Option<String>,\n}\n\nfn Button(props: ButtonProps) -> Element {\n    rsx! {\n        button { {props.text.unwrap_or(\"button\".to_string())} }\n    }\n}\n\nrsx! {\n    Button {}\n};\n```\n\nIf you want to make your `Option<T>` field required, you can use the `!optional` attribute:\n\n```rust, no_run\n# use dioxus::prelude::*;\n#[derive(Props, PartialEq, Clone)]\nstruct ButtonProps {\n    /// You can use the `!optional` attribute on a field with the type `Option<T>` to make it required.\n    #[props(!optional)]\n    text: Option<String>,\n}\n\nfn Button(props: ButtonProps) -> Element {\n    rsx! {\n        button { {props.text.unwrap_or(\"button\".to_string())} }\n    }\n}\n\nrsx! {\n    Button {\n        text: None\n    }\n};\n```\n\n### Converting Props\n\nYou can automatically convert a field into the correct type by using the `into` attribute. Any type you pass into the field will be converted with the [`Into`] trait:\n\n```rust, no_run\n# use dioxus::prelude::*;\n#[derive(Props, PartialEq, Clone)]\nstruct ButtonProps {\n    /// You can use the `into` attribute on a field to convert types you pass in with the Into trait.\n    #[props(into)]\n    number: u64,\n}\n\nfn Button(props: ButtonProps) -> Element {\n    rsx! {\n        button { \"{props.number}\" }\n    }\n}\n\nrsx! {\n    Button {\n        // Because we used the into attribute, we can pass in any type that implements Into<u64>\n        number: 10u8\n    }\n};\n```\n\n### Formatted Props\n\nYou can use formatted strings in attributes just like you would in an element. Any prop field with the type `String` can accept a formatted string:\n\n```rust, no_run\n# use dioxus::prelude::*;\n#[derive(Props, PartialEq, Clone)]\nstruct ButtonProps {\n    text: String,\n}\n\nfn Button(props: ButtonProps) -> Element {\n    rsx! {\n        button { \"{props.text}\" }\n    }\n}\n\nlet name = \"Bob\";\nrsx! {\n    Button {\n        // You can use formatted strings in props that accept String just like you would in an element.\n        text: \"Hello {name}!\"\n    }\n};\n```\n\n### Children Props\n\nRather than passing the RSX through a regular prop, you may wish to accept children similarly to how elements can have children. The \"magic\" children prop lets you achieve this:\n\n```rust, no_run\n# use dioxus::prelude::*;\n#[derive(PartialEq, Clone, Props)]\nstruct ClickableProps {\n    href: String,\n    children: Element,\n}\n\nfn Clickable(props: ClickableProps) -> Element {\n    rsx! {\n        a {\n            href: \"{props.href}\",\n            class: \"fancy-button\",\n            {props.children}\n        }\n    }\n}\n```\n\nThis makes providing children to the component much simpler: simply put the RSX inside the {} brackets:\n\n```rust, no_run\n# use dioxus::prelude::*;\n# #[derive(PartialEq, Clone, Props)]\n# struct ClickableProps {\n#     href: String,\n#     children: Element,\n# }\n#\n# fn Clickable(props: ClickableProps) -> Element {\n#     rsx! {\n#         a {\n#             href: \"{props.href}\",\n#             class: \"fancy-button\",\n#             {props.children}\n#         }\n#     }\n# }\nrsx! {\n    Clickable {\n        href: \"https://www.youtube.com/watch?v=C-M2hs3sXGo\",\n        \"How to \"\n        i { \"not\" }\n        \" be seen\"\n    }\n};\n```\n\n### Reactive Props\n\nIn dioxus, when a prop changes, the component will rerun with the new value to update the UI. For example, if count changes from 0 to 1, this component will rerun and update the UI to show \"Count: 1\":\n\n```rust, no_run\n# use dioxus::prelude::*;\n#[component]\nfn Counter(count: i32) -> Element {\n    rsx! {\n        div {\n            \"Count: {count}\"\n        }\n    }\n}\n```\n\nGenerally, just rerunning the component is enough to update the UI. However, if you use your prop inside reactive hooks like `use_memo` or `use_resource`, you may also want to restart those hooks when the prop changes:\n\n```rust, no_run\n# use dioxus::prelude::*;\n#[component]\nfn Counter(count: i32) -> Element {\n    // We can use a memo to calculate the doubled count. Since this memo will only be created the first time the component is run and `count` is not reactive, it will never update when `count` changes.\n    let doubled_count = use_memo(move || count * 2);\n    rsx! {\n        div {\n            \"Count: {count}\"\n            \"Doubled Count: {doubled_count}\"\n        }\n    }\n}\n```\n\nTo fix this issue you can either:\n\n1. Make the prop reactive by wrapping it in `ReadSignal` (recommended):\n\n`ReadSignal` is a `Copy` reactive value. Dioxus will automatically convert any value into a `ReadSignal` when it is passed as a prop.\n\n```rust, no_run\n# use dioxus::prelude::*;\n#[component]\nfn Counter(count: ReadSignal<i32>) -> Element {\n    // Since we made count reactive, the memo will automatically rerun when count changes.\n    let doubled_count = use_memo(move || count() * 2);\n    rsx! {\n        div {\n            \"Count: {count}\"\n            \"Doubled Count: {doubled_count}\"\n        }\n    }\n}\n```\n\n2. Explicitly add the prop as a dependency to the reactive hook with [`use_reactive`](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/macro.use_reactive.html):\n\n```rust, no_run\n# use dioxus::prelude::*;\n#[component]\nfn Counter(count: i32) -> Element {\n    // We can add the count prop as an explicit dependency to every reactive hook that uses it with use_reactive.\n    // The use_reactive macro takes a closure with explicit dependencies as its argument.\n    let doubled_count = use_memo(use_reactive!(|count| count * 2));\n    rsx! {\n        div {\n            \"Count: {count}\"\n            \"Doubled Count: {doubled_count}\"\n        }\n    }\n}\n```\n\n### Extending Elements\n\nThe `extends` attribute lets you extend your props with all the attributes from an element or the global element attributes.\n\n```rust, no_run\n# use dioxus::prelude::*;\n#[derive(Props, PartialEq, Clone)]\nstruct CardProps {\n    /// You can use the `extends` attribute on a field with the type `Vec<Attribute>` to extend the props with all the attributes from an element or the global element attributes.\n    #[props(extends = GlobalAttributes)]\n    attributes: Vec<Attribute>,\n}\n\n#[component]\nfn Card(props: CardProps) -> Element {\n    rsx! {\n        // Instead of copying over every single attribute, we can just spread the attributes from the props into the element.\n        div { ..props.attributes, \"card\" }\n    }\n}\n\nrsx! {\n    // Since we extend global attributes, you can use any attribute that would normally appear on elements.\n    Card {\n        width: \"10px\",\n        height: \"10px\",\n        color: \"red\",\n    }\n};\n```\n\nTo extend the props with both the global attributes and the attributes of a specific element, you can use the `extends` attribute multiple times:\n\n```rust, no_run\n# use dioxus::prelude::*;\n\n#[derive(Props, PartialEq, Clone)]\nstruct ButtonProps {\n    #[props(extends = GlobalAttributes, extends = button)]\n    attributes: Vec<Attribute>,\n}\n\n#[component]\nfn Button(props: ButtonProps) -> Element {\n    rsx! {\n        button { ..props.attributes, \"button\" }\n    }\n}\n\nrsx! {\n    Button {\n        // A global attribute\n        width: \"10px\",\n        // A button specific attribute\n        disabled: true,\n    }\n};\n```\n\nNote that extending from multiple elements will only work if the elements don't have conflicting attributes.\n"
  },
  {
    "path": "packages/core-macro/docs/rsx.md",
    "content": "The rsx! macro makes it easy for developers to write jsx-style markup in their components.\n\n## Elements\n\nYou can render elements with rsx! with the element name and then braces surrounding the attributes and children.\n\n```rust, no_run\n# use dioxus::prelude::*;\nrsx! {\n    div {\n        div {}\n    }\n};\n```\n\n<details>\n<summary>Web Components</summary>\n\nDioxus will automatically render any elements with `-` as a untyped web component:\n\n```rust, no_run\n# use dioxus::prelude::*;\nrsx! {\n    div-component {\n        div {}\n    }\n};\n```\n\nYou can wrap your web component in a custom component to add type checking:\n\n```rust, no_run\n# use dioxus::prelude::*;\n#[component]\nfn MyDivComponent(width: i64) -> Element {\n    rsx! {\n        div-component {\n            \"width\": width\n        }\n    }\n}\n```\n\n</details>\n\n## Attributes\n\nYou can add attributes to any element inside the braces. Attributes are key-value pairs separated by a colon.\n\n```rust, no_run\n# use dioxus::prelude::*;\nlet width = 100;\nrsx! {\n    div {\n        // Set the class attribute to \"my-class\"\n        class: \"my-class\",\n        // attribute strings are automatically formatted with the format macro\n        width: \"{width}px\",\n    }\n};\n```\n\n### Optional Attributes\n\nYou can include optional attributes with an unterminated if statement as the value of the attribute:\n\n```rust, no_run\n# use dioxus::prelude::*;\n# let first_boolean = true;\n# let second_boolean = false;\nrsx! {\n    div {\n        // Set the class attribute to \"my-class\" if true\n        class: if first_boolean {\n            \"my-class\"\n        },\n        // Set the class attribute to \"my-other-class\" if false\n        class: if second_boolean {\n            \"my-other-class\"\n        }\n    }\n};\n```\n\n### Raw Attributes\n\nDioxus defaults to attributes that are type checked as html. If you want to include an attribute that is not included in the html spec, you can use the `raw` attribute surrounded by quotes:\n\n```rust, no_run\n# use dioxus::prelude::*;\nrsx! {\n    div {\n        // Set the data-count attribute to \"1\"\n        \"data-count\": \"1\"\n    }\n};\n```\n\n## Text\n\nYou can include text in your markup as a string literal:\n\n```rust, no_run\n# use dioxus::prelude::*;\nlet name = \"World\";\nrsx! {\n    div {\n        \"Hello World\"\n        // Just like attributes, you can included formatted segments inside your text\n        \"Hello {name}\"\n    }\n};\n```\n\n## Components\n\nYou can render any [`macro@crate::component`]s you created inside your markup just like elements. Components must either start with a capital letter or contain a `_` character.\n\n```rust, no_run\n# use dioxus::prelude::*;\n#[component]\nfn HelloWorld() -> Element {\n    rsx! { \"hello world!\" }\n}\n\nrsx! {\n    div {\n        HelloWorld {}\n    }\n};\n```\n\n## If statements\n\nYou can use if statements to conditionally render children. The body of the for if statement is parsed as rsx markup:\n\n```rust, no_run\n# use dioxus::prelude::*;\nlet first_boolean = true;\nlet second_boolean = false;\nrsx! {\n    if first_boolean {\n        div {\n            \"first\"\n        }\n    }\n\n    if second_boolean {\n        \"second\"\n    }\n};\n```\n\n## For loops\n\nYou can also use for loops to iterate over a collection of items. The body of the for loop is parsed as rsx markup:\n\n```rust, no_run\n# use dioxus::prelude::*;\nlet numbers = vec![1, 2, 3];\nrsx! {\n    for number in numbers {\n        div {\n            \"{number}\"\n        }\n    }\n};\n```\n\n## Raw Expressions\n\nYou can include raw expressions inside your markup inside curly braces. Your expression must implement the [`IntoDynNode`](https://docs.rs/dioxus-core/latest/dioxus_core/trait.IntoDynNode.html) trait:\n\n```rust, no_run\n# use dioxus::prelude::*;\nlet name = \"World\";\nrsx! {\n    div {\n        // Text can be converted into a dynamic node in rsx\n        {name}\n    }\n    // Iterators can also be converted into dynamic nodes\n    {(0..10).map(|n| n * n).map(|number| rsx! { div { \"{number}\" } })}\n};\n```\n"
  },
  {
    "path": "packages/core-macro/src/component.rs",
    "content": "use proc_macro2::TokenStream;\nuse quote::{format_ident, quote, ToTokens, TokenStreamExt};\nuse syn::parse::{Parse, ParseStream};\nuse syn::punctuated::Punctuated;\nuse syn::spanned::Spanned;\nuse syn::*;\n\npub struct ComponentBody {\n    pub item_fn: ItemFn,\n    pub options: ComponentMacroOptions,\n}\n\nimpl Parse for ComponentBody {\n    fn parse(input: ParseStream) -> Result<Self> {\n        let item_fn: ItemFn = input.parse()?;\n        validate_component_fn(&item_fn)?;\n        Ok(Self {\n            item_fn,\n            options: ComponentMacroOptions::default(),\n        })\n    }\n}\n\nimpl ComponentBody {\n    pub fn with_options(mut self, options: ComponentMacroOptions) -> Self {\n        self.options = options;\n        self\n    }\n}\n\nimpl ToTokens for ComponentBody {\n    fn to_tokens(&self, tokens: &mut TokenStream) {\n        // https://github.com/DioxusLabs/dioxus/issues/1938\n        // If there's only one input and the input is `props: Props`, we don't need to generate a props struct\n        // Just attach the non_snake_case attribute to the function\n        // eventually we'll dump this metadata into devtooling that lets us find all these components\n        //\n        // Components can also use the struct pattern to \"inline\" their props.\n        // Freya uses this a bunch (because it's clean),\n        // e.g. `fn Navbar(NavbarProps { title }: NavbarProps)` was previously being incorrectly parsed\n        if self.is_explicit_props_ident() || self.has_struct_parameter_pattern() {\n            let comp_fn = &self.item_fn;\n            tokens.append_all(allow_camel_case_for_fn_ident(comp_fn).into_token_stream());\n            return;\n        }\n\n        let comp_fn = self.comp_fn();\n\n        // If there's no props declared, we simply omit the props argument\n        // This is basically so you can annotate the App component with #[component] and still be compatible with the\n        // launch signatures that take fn() -> Element\n        let props_struct = match self.item_fn.sig.inputs.is_empty() {\n            // No props declared, so we don't need to generate a props struct\n            true => quote! {},\n\n            // Props declared, so we generate a props struct and then also attach the doc attributes to it\n            false => {\n                let doc = format!(\"Properties for the [`{}`] component.\", &comp_fn.sig.ident);\n                let (props_struct, props_impls) = self.props_struct();\n                quote! {\n                    #[doc = #doc]\n                    #[allow(missing_docs)]\n                    #props_struct\n\n                    #(#props_impls)*\n                }\n            }\n        };\n\n        let completion_hints = self.completion_hints();\n\n        tokens.append_all(quote! {\n            #props_struct\n            #comp_fn\n\n            #completion_hints\n        });\n    }\n}\n\nimpl ComponentBody {\n    // build a new item fn, transforming the original item fn\n    fn comp_fn(&self) -> ItemFn {\n        let ComponentBody { item_fn, .. } = self;\n        let ItemFn {\n            attrs,\n            vis,\n            sig,\n            block,\n        } = item_fn;\n        let Signature {\n            inputs,\n            ident: fn_ident,\n            generics,\n            output: fn_output,\n            ..\n        } = sig;\n\n        let Generics { where_clause, .. } = generics;\n        let (_, impl_generics, _) = generics.split_for_impl();\n\n        // We generate a struct with the same name as the component but called `Props`\n        let struct_ident = Ident::new(&format!(\"{fn_ident}Props\"), fn_ident.span());\n\n        // We pull in the field names from the original function signature, but need to strip off the mutability\n        let struct_field_names = inputs.iter().map(rebind_mutability);\n\n        let props_docs = self.props_docs(inputs.iter().collect());\n\n        let inlined_props_argument = if inputs.is_empty() {\n            quote! {}\n        } else {\n            quote! { #struct_ident { #(#struct_field_names),* }: #struct_ident #impl_generics }\n        };\n\n        // Defer to the lazy_body if we're using lazy\n        let body: TokenStream = if self.options.lazy {\n            self.lazy_body(\n                &struct_ident,\n                generics,\n                &impl_generics,\n                fn_output,\n                where_clause,\n                &inlined_props_argument,\n                block,\n            )\n        } else {\n            quote! { #block }\n        };\n\n        // We need a props type to exist even if the inputs are empty with lazy components\n        let emit_props = if self.options.lazy {\n            if inputs.is_empty() {\n                quote! {props: ()}\n            } else {\n                quote!(props: #struct_ident #impl_generics)\n            }\n        } else {\n            inlined_props_argument\n        };\n\n        // The extra nest is for the snake case warning to kick back in\n        parse_quote! {\n            #(#attrs)*\n            #(#props_docs)*\n            #[allow(non_snake_case)]\n            #vis fn #fn_ident #generics (#emit_props) #fn_output #where_clause {\n                {\n                    #body\n                }\n            }\n        }\n    }\n\n    /// Generate the body of the lazy component\n    ///\n    /// This extracts the body into a new component that is wrapped in a lazy loader\n    #[allow(clippy::too_many_arguments)]\n    fn lazy_body(\n        &self,\n        struct_ident: &Ident,\n        generics: &Generics,\n        impl_generics: &TypeGenerics,\n        fn_output: &ReturnType,\n        where_clause: &Option<WhereClause>,\n        inlined_props_argument: &TokenStream,\n        block: &Block,\n    ) -> TokenStream {\n        let fn_ident = &self.item_fn.sig.ident;\n        let inputs = &self.item_fn.sig.inputs;\n\n        let lazy_name = format_ident!(\"Lazy{fn_ident}\");\n        let out_ty = match &self.item_fn.sig.output {\n            ReturnType::Default => quote! { () },\n            ReturnType::Type(_, ty) => quote! { #ty },\n        };\n        let props_ty = if inputs.is_empty() {\n            quote! { () }\n        } else {\n            quote! { #struct_ident #impl_generics }\n        };\n        let anon_props = if inputs.is_empty() {\n            quote! { props: () }\n        } else {\n            quote! { #inlined_props_argument}\n        };\n\n        quote! {\n            fn #lazy_name #generics (#anon_props) #fn_output #where_clause {\n                #block\n            }\n\n            dioxus::config_macros::maybe_wasm_split! {\n                if wasm_split {\n                    {\n                        static __MODULE: wasm_split::LazyLoader<#props_ty, #out_ty> =\n                            wasm_split::lazy_loader!(extern \"lazy\" fn #lazy_name(props: #props_ty,) -> #out_ty);\n\n                        use_resource(|| async move { __MODULE.load().await }).suspend()?;\n                        __MODULE.call(props).unwrap()\n                    }\n                } else {\n                    {\n                        #lazy_name(props)\n                    }\n                }\n            }\n        }\n    }\n\n    /// Build an associated struct for the props of the component\n    ///\n    /// This will expand to the typed-builder implementation that we have vendored in this crate.\n    /// TODO: don't vendor typed-builder and instead transform the tokens we give it before expansion.\n    /// TODO: cache these tokens since this codegen is rather expensive (lots of tokens)\n    ///\n    /// We try our best to transfer over any declared doc attributes from the original function signature onto the\n    /// props struct fields.\n    fn props_struct(&self) -> (ItemStruct, Vec<ItemImpl>) {\n        let ItemFn { vis, sig, .. } = &self.item_fn;\n        let Signature {\n            inputs,\n            ident,\n            generics,\n            ..\n        } = sig;\n\n        let generic_arguments = if !generics.params.is_empty() {\n            let generic_arguments = generics\n                .params\n                .iter()\n                .map(make_prop_struct_generics)\n                .collect::<Punctuated<_, Token![,]>>();\n            quote! { <#generic_arguments> }\n        } else {\n            quote! {}\n        };\n        let where_clause = &generics.where_clause;\n        let struct_fields = inputs.iter().map(move |f| make_prop_struct_field(f, vis));\n        let struct_field_idents = inputs\n            .iter()\n            .map(make_prop_struct_field_idents)\n            .collect::<Vec<_>>();\n        let struct_ident = Ident::new(&format!(\"{ident}Props\"), ident.span());\n\n        let item_struct = parse_quote! {\n            #[derive(Props)]\n            #[allow(non_camel_case_types)]\n            #vis struct #struct_ident #generics #where_clause {\n                #(#struct_fields),*\n            }\n        };\n\n        let item_impl_clone = parse_quote! {\n            impl #generics ::core::clone::Clone for #struct_ident #generic_arguments #where_clause {\n                #[inline]\n                fn clone(&self) -> Self {\n                    Self {\n                        #(#struct_field_idents: ::core::clone::Clone::clone(&self.#struct_field_idents)),*\n                    }\n                }\n            }\n        };\n\n        let item_impl_partial_eq = parse_quote! {\n            impl #generics ::core::cmp::PartialEq for #struct_ident #generic_arguments #where_clause {\n                #[inline]\n                fn eq(&self, other: &Self) -> bool {\n                    #(\n                        self.#struct_field_idents == other.#struct_field_idents &&\n                    )*\n                    true\n                }\n            }\n        };\n\n        (item_struct, vec![item_impl_clone, item_impl_partial_eq])\n    }\n\n    /// Convert a list of function arguments into a list of doc attributes for the props struct\n    ///\n    /// This lets us generate set of attributes that we can apply to the props struct to give it a nice docstring.\n    fn props_docs(&self, inputs: Vec<&FnArg>) -> Vec<Attribute> {\n        let fn_ident = &self.item_fn.sig.ident;\n\n        if inputs.is_empty() {\n            return Vec::new();\n        }\n\n        let arg_docs = inputs\n            .iter()\n            .filter_map(|f| build_doc_fields(f))\n            .collect::<Vec<_>>();\n\n        let mut props_docs = Vec::with_capacity(5);\n        let props_def_link = fn_ident.to_string() + \"Props\";\n        let header =\n            format!(\"# Props\\n*For details, see the [props struct definition]({props_def_link}).*\");\n\n        props_docs.push(parse_quote! {\n            #[doc = #header]\n        });\n\n        for arg in arg_docs {\n            let DocField {\n                arg_name,\n                arg_type,\n                deprecation,\n                input_arg_doc,\n            } = arg;\n\n            let arg_name = strip_pat_mutability(arg_name).to_token_stream().to_string();\n            let arg_type = crate::utils::format_type_string(arg_type);\n\n            let input_arg_doc = keep_up_to_n_consecutive_chars(input_arg_doc.trim(), 2, '\\n')\n                .replace(\"\\n\\n\", \"</p><p>\");\n            let prop_def_link = format!(\"{props_def_link}::{arg_name}\");\n            let mut arg_doc = format!(\"- [`{arg_name}`]({prop_def_link}) : `{arg_type}`\");\n\n            if let Some(deprecation) = deprecation {\n                arg_doc.push_str(\"<p>👎 Deprecated\");\n\n                if let Some(since) = deprecation.since {\n                    arg_doc.push_str(&format!(\" since {since}\"));\n                }\n\n                if let Some(note) = deprecation.note {\n                    let note = keep_up_to_n_consecutive_chars(&note, 1, '\\n').replace('\\n', \" \");\n                    let note = keep_up_to_n_consecutive_chars(&note, 1, '\\t').replace('\\t', \" \");\n\n                    arg_doc.push_str(&format!(\": {note}\"));\n                }\n\n                arg_doc.push_str(\"</p>\");\n\n                if !input_arg_doc.is_empty() {\n                    arg_doc.push_str(\"<hr/>\");\n                }\n            }\n\n            if !input_arg_doc.is_empty() {\n                arg_doc.push_str(&format!(\"<p>{input_arg_doc}</p>\"));\n            }\n\n            props_docs.push(parse_quote! { #[doc = #arg_doc] });\n        }\n\n        props_docs\n    }\n\n    fn is_explicit_props_ident(&self) -> bool {\n        if let Some(FnArg::Typed(PatType { pat, .. })) = self.item_fn.sig.inputs.first() {\n            if let Pat::Ident(ident) = pat.as_ref() {\n                return ident.ident == \"props\";\n            }\n        }\n\n        false\n    }\n\n    fn has_struct_parameter_pattern(&self) -> bool {\n        if let Some(FnArg::Typed(PatType { pat, .. })) = self.item_fn.sig.inputs.first() {\n            if matches!(pat.as_ref(), Pat::Struct(_)) {\n                return true;\n            }\n        }\n\n        false\n    }\n\n    // We generate an extra enum to help us autocomplete the braces after the component.\n    // This is a bit of a hack, but it's the only way to get the braces to autocomplete.\n    fn completion_hints(&self) -> TokenStream {\n        let comp_fn = &self.item_fn.sig.ident;\n        let completions_mod = Ident::new(&format!(\"{}_completions\", comp_fn), comp_fn.span());\n\n        let vis = &self.item_fn.vis;\n\n        quote! {\n            #[allow(non_snake_case)]\n            #[doc(hidden)]\n            mod #completions_mod {\n                #[doc(hidden)]\n                #[allow(non_camel_case_types)]\n                /// This enum is generated to help autocomplete the braces after the component. It does nothing\n                pub enum Component {\n                    #comp_fn {}\n                }\n            }\n\n            #[allow(unused)]\n            #vis use #completions_mod::Component::#comp_fn;\n        }\n    }\n}\n\nstruct DocField<'a> {\n    arg_name: &'a Pat,\n    arg_type: &'a Type,\n    deprecation: Option<crate::utils::DeprecatedAttribute>,\n    input_arg_doc: String,\n}\n\nfn build_doc_fields(f: &FnArg) -> Option<DocField<'_>> {\n    let FnArg::Typed(pt) = f else { unreachable!() };\n\n    let arg_doc = pt\n        .attrs\n        .iter()\n        .filter_map(|attr| {\n            // TODO: Error reporting\n            // Check if the path of the attribute is \"doc\"\n            if !is_attr_doc(attr) {\n                return None;\n            };\n\n            let Meta::NameValue(meta_name_value) = &attr.meta else {\n                return None;\n            };\n\n            let Expr::Lit(doc_lit) = &meta_name_value.value else {\n                return None;\n            };\n\n            let Lit::Str(doc_lit_str) = &doc_lit.lit else {\n                return None;\n            };\n\n            Some(doc_lit_str.value())\n        })\n        .fold(String::new(), |mut doc, next_doc_line| {\n            doc.push('\\n');\n            doc.push_str(&next_doc_line);\n            doc\n        });\n\n    Some(DocField {\n        arg_name: &pt.pat,\n        arg_type: &pt.ty,\n        deprecation: pt.attrs.iter().find_map(|attr| {\n            if !attr.path().is_ident(\"deprecated\") {\n                return None;\n            }\n\n            let res = crate::utils::DeprecatedAttribute::from_meta(&attr.meta);\n\n            match res {\n                Err(e) => panic!(\"{}\", e.to_string()),\n                Ok(v) => Some(v),\n            }\n        }),\n        input_arg_doc: arg_doc,\n    })\n}\n\nfn validate_component_fn(item_fn: &ItemFn) -> Result<()> {\n    // Do some validation....\n    // 1. Ensure the component returns *something*\n    if item_fn.sig.output == ReturnType::Default {\n        return Err(Error::new(\n            item_fn.sig.output.span(),\n            \"Must return a <dioxus_core::Element>\".to_string(),\n        ));\n    }\n\n    // 2. make sure there's no lifetimes on the component - we don't know how to handle those\n    if item_fn.sig.generics.lifetimes().count() > 0 {\n        return Err(Error::new(\n            item_fn.sig.generics.span(),\n            \"Lifetimes are not supported in components\".to_string(),\n        ));\n    }\n\n    // 3. we can't handle async components\n    if item_fn.sig.asyncness.is_some() {\n        return Err(Error::new(\n            item_fn.sig.asyncness.span(),\n            \"Async components are not supported\".to_string(),\n        ));\n    }\n\n    // 4. we can't handle const components\n    if item_fn.sig.constness.is_some() {\n        return Err(Error::new(\n            item_fn.sig.constness.span(),\n            \"Const components are not supported\".to_string(),\n        ));\n    }\n\n    // 5. no receiver parameters\n    if item_fn\n        .sig\n        .inputs\n        .iter()\n        .any(|f| matches!(f, FnArg::Receiver(_)))\n    {\n        return Err(Error::new(\n            item_fn.sig.inputs.span(),\n            \"Receiver parameters are not supported\".to_string(),\n        ));\n    }\n\n    Ok(())\n}\n\n/// Convert a function arg with a given visibility (provided by the function) and then generate a field for the\n/// associated props struct.\nfn make_prop_struct_field(f: &FnArg, vis: &Visibility) -> TokenStream {\n    // There's no receivers (&self) allowed in the component body\n    let FnArg::Typed(pt) = f else { unreachable!() };\n\n    let arg_pat = match pt.pat.as_ref() {\n        // rip off mutability\n        // todo: we actually don't want any of the extra bits of the field pattern\n        Pat::Ident(f) => {\n            let mut f = f.clone();\n            f.mutability = None;\n            quote! { #f }\n        }\n        a => quote! { #a },\n    };\n\n    let PatType {\n        attrs,\n        ty,\n        colon_token,\n        ..\n    } = pt;\n\n    quote! {\n        #(#attrs)*\n        #vis #arg_pat #colon_token #ty\n    }\n}\n\n/// Get ident from a function arg\nfn make_prop_struct_field_idents(f: &FnArg) -> &Ident {\n    // There's no receivers (&self) allowed in the component body\n    let FnArg::Typed(pt) = f else { unreachable!() };\n\n    match pt.pat.as_ref() {\n        // rip off mutability\n        // todo: we actually don't want any of the extra bits of the field pattern\n        Pat::Ident(f) => &f.ident,\n        _ => unreachable!(),\n    }\n}\n\nfn make_prop_struct_generics(generics: &GenericParam) -> TokenStream {\n    match generics {\n        GenericParam::Type(ty) => {\n            let ident = &ty.ident;\n            quote! { #ident }\n        }\n        GenericParam::Lifetime(lifetime) => {\n            let lifetime = &lifetime.lifetime;\n            quote! { #lifetime }\n        }\n        GenericParam::Const(c) => {\n            let ident = &c.ident;\n            quote! { #ident }\n        }\n    }\n}\n\nfn rebind_mutability(f: &FnArg) -> TokenStream {\n    // There's no receivers (&self) allowed in the component body\n    let FnArg::Typed(pt) = f else { unreachable!() };\n\n    let immutable = strip_pat_mutability(&pt.pat);\n\n    quote!(mut #immutable)\n}\n\nfn strip_pat_mutability(pat: &Pat) -> Pat {\n    let mut pat = pat.clone();\n    // rip off mutability, but still write it out eventually\n    if let Pat::Ident(ref mut pat_ident) = &mut pat {\n        pat_ident.mutability = None;\n    }\n\n    pat\n}\n\n/// Checks if the attribute is a `#[doc]` attribute.\nfn is_attr_doc(attr: &Attribute) -> bool {\n    attr.path() == &parse_quote!(doc)\n}\n\nfn keep_up_to_n_consecutive_chars(\n    input: &str,\n    n_of_consecutive_chars_allowed: usize,\n    target_char: char,\n) -> String {\n    let mut output = String::new();\n    let mut prev_char: Option<char> = None;\n    let mut consecutive_count = 0;\n\n    for c in input.chars() {\n        match prev_char {\n            Some(prev) if c == target_char && prev == target_char => {\n                if consecutive_count < n_of_consecutive_chars_allowed {\n                    output.push(c);\n                    consecutive_count += 1;\n                }\n            }\n            _ => {\n                output.push(c);\n                prev_char = Some(c);\n                consecutive_count = 1;\n            }\n        }\n    }\n\n    output\n}\n\n/// Takes a function and returns a clone of it where an `UpperCamelCase` identifier is allowed by the compiler.\nfn allow_camel_case_for_fn_ident(item_fn: &ItemFn) -> ItemFn {\n    let mut clone = item_fn.clone();\n    let block = &item_fn.block;\n\n    clone.attrs.push(parse_quote! { #[allow(non_snake_case)] });\n\n    clone.block = parse_quote! {\n        {\n            #block\n        }\n    };\n\n    clone\n}\n\n#[derive(Default)]\npub struct ComponentMacroOptions {\n    pub lazy: bool,\n}\n\nimpl Parse for ComponentMacroOptions {\n    fn parse(input: ParseStream) -> Result<Self> {\n        let mut lazy_load = false;\n\n        while !input.is_empty() {\n            let ident = input.parse::<Ident>()?;\n            let ident_name = ident.to_string();\n            if ident_name == \"lazy\" {\n                lazy_load = true;\n            } else if ident_name == \"no_case_check\" {\n                // we used to have this?\n            } else {\n                return Err(Error::new(\n                    ident.span(),\n                    \"Unknown option for component macro\",\n                ));\n            }\n\n            if input.peek(Token![,]) {\n                input.parse::<Token![,]>()?;\n            }\n        }\n\n        Ok(Self { lazy: lazy_load })\n    }\n}\n"
  },
  {
    "path": "packages/core-macro/src/lib.rs",
    "content": "#![doc = include_str!(\"../README.md\")]\n#![doc(html_logo_url = \"https://avatars.githubusercontent.com/u/79236386\")]\n#![doc(html_favicon_url = \"https://avatars.githubusercontent.com/u/79236386\")]\n\nuse component::{ComponentBody, ComponentMacroOptions};\nuse proc_macro::TokenStream;\nuse quote::ToTokens;\nuse syn::parse_macro_input;\n\nmod component;\nmod props;\nmod utils;\n\nuse dioxus_rsx as rsx;\n\n#[doc = include_str!(\"../docs/props.md\")]\n#[proc_macro_derive(Props, attributes(props))]\npub fn derive_props(input: TokenStream) -> TokenStream {\n    let input = parse_macro_input!(input as syn::DeriveInput);\n    match props::impl_my_derive(&input) {\n        Ok(output) => output.into(),\n        Err(error) => error.to_compile_error().into(),\n    }\n}\n\n#[doc = include_str!(\"../docs/rsx.md\")]\n#[proc_macro]\npub fn rsx(tokens: TokenStream) -> TokenStream {\n    match syn::parse::<rsx::CallBody>(tokens) {\n        Err(err) => err.to_compile_error().into(),\n        Ok(body) => body.into_token_stream().into(),\n    }\n}\n\n#[doc = include_str!(\"../docs/component.md\")]\n#[proc_macro_attribute]\npub fn component(_args: TokenStream, input: TokenStream) -> TokenStream {\n    parse_macro_input!(input as ComponentBody)\n        .with_options(parse_macro_input!(_args as ComponentMacroOptions))\n        .into_token_stream()\n        .into()\n}\n"
  },
  {
    "path": "packages/core-macro/src/props/mod.rs",
    "content": "//! This code mostly comes from idanarye/rust-typed-builder\n//!\n//! However, it has been adopted to fit the Dioxus Props builder pattern.\n//!\n//! For Dioxus, we make a few changes:\n//! - [x] Automatically implement [`Into<Option>`] on the setters (IE the strip setter option)\n//! - [x] Automatically implement a default of none for optional fields (those explicitly wrapped with [`Option<T>`])\n\nuse proc_macro2::TokenStream;\n\nuse syn::punctuated::Punctuated;\nuse syn::spanned::Spanned;\nuse syn::{parse::Error, PathArguments};\n\nuse quote::quote;\nuse syn::{parse_quote, GenericArgument, Ident, PathSegment, Type};\n\npub fn impl_my_derive(ast: &syn::DeriveInput) -> Result<TokenStream, Error> {\n    let data = match &ast.data {\n        syn::Data::Struct(data) => match &data.fields {\n            syn::Fields::Named(fields) => {\n                let struct_info = struct_info::StructInfo::new(ast, fields.named.iter())?;\n                let builder_creation = struct_info.builder_creation_impl()?;\n                let conversion_helper = struct_info.conversion_helper_impl()?;\n                let fields = struct_info\n                    .included_fields()\n                    .map(|f| struct_info.field_impl(f))\n                    .collect::<Result<Vec<_>, _>>()?;\n                let extends = struct_info\n                    .extend_fields()\n                    .map(|f| struct_info.extends_impl(f))\n                    .collect::<Result<Vec<_>, _>>()?;\n                let fields = quote!(#(#fields)*).into_iter();\n                let required_fields = struct_info\n                    .included_fields()\n                    .filter(|f| {\n                        f.builder_attr.default.is_none() && f.builder_attr.extends.is_empty()\n                    })\n                    .map(|f| struct_info.required_field_impl(f))\n                    .collect::<Result<Vec<_>, _>>()?;\n                let build_method = struct_info.build_method_impl();\n\n                quote! {\n                    #builder_creation\n                    #conversion_helper\n                    #( #fields )*\n                    #( #extends )*\n                    #( #required_fields )*\n                    #build_method\n                }\n            }\n            syn::Fields::Unnamed(_) => {\n                return Err(Error::new(\n                    ast.span(),\n                    \"Props is not supported for tuple structs\",\n                ))\n            }\n            syn::Fields::Unit => {\n                return Err(Error::new(\n                    ast.span(),\n                    \"Props is not supported for unit structs\",\n                ))\n            }\n        },\n        syn::Data::Enum(_) => {\n            return Err(Error::new(ast.span(), \"Props is not supported for enums\"))\n        }\n        syn::Data::Union(_) => {\n            return Err(Error::new(ast.span(), \"Props is not supported for unions\"))\n        }\n    };\n    Ok(data)\n}\n\nmod util {\n    use quote::ToTokens;\n\n    pub fn path_to_single_string(path: &syn::Path) -> Option<String> {\n        if path.leading_colon.is_some() {\n            return None;\n        }\n        let mut it = path.segments.iter();\n        let segment = it.next()?;\n        if it.next().is_some() {\n            // Multipart path\n            return None;\n        }\n        if segment.arguments != syn::PathArguments::None {\n            return None;\n        }\n        Some(segment.ident.to_string())\n    }\n\n    pub fn expr_to_single_string(expr: &syn::Expr) -> Option<String> {\n        if let syn::Expr::Path(path) = expr {\n            path_to_single_string(&path.path)\n        } else {\n            None\n        }\n    }\n\n    pub fn ident_to_type(ident: syn::Ident) -> syn::Type {\n        let mut path = syn::Path {\n            leading_colon: None,\n            segments: Default::default(),\n        };\n        path.segments.push(syn::PathSegment {\n            ident,\n            arguments: Default::default(),\n        });\n        syn::Type::Path(syn::TypePath { qself: None, path })\n    }\n\n    pub fn empty_type() -> syn::Type {\n        syn::TypeTuple {\n            paren_token: Default::default(),\n            elems: Default::default(),\n        }\n        .into()\n    }\n\n    pub fn type_tuple(elems: impl Iterator<Item = syn::Type>) -> syn::TypeTuple {\n        let mut result = syn::TypeTuple {\n            paren_token: Default::default(),\n            elems: elems.collect(),\n        };\n        if !result.elems.empty_or_trailing() {\n            result.elems.push_punct(Default::default());\n        }\n        result\n    }\n\n    pub fn empty_type_tuple() -> syn::TypeTuple {\n        syn::TypeTuple {\n            paren_token: Default::default(),\n            elems: Default::default(),\n        }\n    }\n\n    pub fn make_punctuated_single<T, P: Default>(value: T) -> syn::punctuated::Punctuated<T, P> {\n        let mut punctuated = syn::punctuated::Punctuated::new();\n        punctuated.push(value);\n        punctuated\n    }\n\n    pub fn modify_types_generics_hack<F>(\n        ty_generics: &syn::TypeGenerics,\n        mut mutator: F,\n    ) -> syn::AngleBracketedGenericArguments\n    where\n        F: FnMut(&mut syn::punctuated::Punctuated<syn::GenericArgument, syn::token::Comma>),\n    {\n        let mut abga: syn::AngleBracketedGenericArguments =\n            syn::parse(ty_generics.clone().into_token_stream().into()).unwrap_or_else(|_| {\n                syn::AngleBracketedGenericArguments {\n                    colon2_token: None,\n                    lt_token: Default::default(),\n                    args: Default::default(),\n                    gt_token: Default::default(),\n                }\n            });\n        mutator(&mut abga.args);\n        abga\n    }\n\n    pub fn strip_raw_ident_prefix(mut name: String) -> String {\n        if name.starts_with(\"r#\") {\n            name.replace_range(0..2, \"\");\n        }\n        name\n    }\n}\n\nmod field_info {\n    use crate::props::{looks_like_store_type, looks_like_write_type, type_from_inside_option};\n    use proc_macro2::TokenStream;\n    use quote::{format_ident, quote};\n    use syn::spanned::Spanned;\n    use syn::{parse::Error, punctuated::Punctuated};\n    use syn::{parse_quote, Expr, Path};\n\n    use super::util::{\n        expr_to_single_string, ident_to_type, path_to_single_string, strip_raw_ident_prefix,\n    };\n\n    #[derive(Debug)]\n    pub struct FieldInfo<'a> {\n        pub ordinal: usize,\n        pub name: &'a syn::Ident,\n        pub generic_ident: syn::Ident,\n        pub ty: &'a syn::Type,\n        pub builder_attr: FieldBuilderAttr,\n    }\n\n    impl FieldInfo<'_> {\n        pub fn new(\n            ordinal: usize,\n            field: &syn::Field,\n            field_defaults: FieldBuilderAttr,\n        ) -> Result<FieldInfo<'_>, Error> {\n            if let Some(ref name) = field.ident {\n                let mut builder_attr = field_defaults.with(&field.attrs)?;\n\n                let strip_option_auto = builder_attr.strip_option\n                    || !builder_attr.ignore_option && type_from_inside_option(&field.ty).is_some();\n\n                // children field is automatically defaulted to an empty VNode unless it is marked as optional (in which case it defaults to None)\n                if name == \"children\" && !strip_option_auto {\n                    builder_attr.default =\n                        Some(syn::parse(quote!(dioxus_core::VNode::empty()).into()).unwrap());\n                }\n\n                // String fields automatically use impl Display\n                if field.ty == parse_quote!(::std::string::String)\n                    || field.ty == parse_quote!(std::string::String)\n                    || field.ty == parse_quote!(string::String)\n                    || field.ty == parse_quote!(String)\n                {\n                    builder_attr.from_displayable = true;\n                    // ToString is both more general and provides a more useful error message than From<String>. If the user tries to use `#[into]`, use ToString instead.\n                    if builder_attr.auto_into {\n                        builder_attr.auto_to_string = true;\n                    }\n                    builder_attr.auto_into = false;\n                }\n\n                // Write and Store fields automatically use impl Into\n                if looks_like_write_type(&field.ty) || looks_like_store_type(&field.ty) {\n                    builder_attr.auto_into = true;\n                }\n\n                // If this is a child field or extends, default to Default::default() if a default isn't set\n                if !builder_attr.extends.is_empty() {\n                    builder_attr.default.get_or_insert_with(|| {\n                        syn::parse(quote!(::core::default::Default::default()).into()).unwrap()\n                    });\n                }\n\n                // auto detect optional\n                if !builder_attr.strip_option && strip_option_auto {\n                    builder_attr.strip_option = true;\n                    // only change the default if it isn't manually set above\n                    builder_attr.default.get_or_insert_with(|| {\n                        syn::parse(quote!(::core::default::Default::default()).into()).unwrap()\n                    });\n                }\n\n                Ok(FieldInfo {\n                    ordinal,\n                    name,\n                    generic_ident: syn::Ident::new(\n                        &format!(\"__{}\", strip_raw_ident_prefix(name.to_string())),\n                        name.span(),\n                    ),\n                    ty: &field.ty,\n                    builder_attr,\n                })\n            } else {\n                Err(Error::new(field.span(), \"Nameless field in struct\"))\n            }\n        }\n\n        pub fn generic_ty_param(&self) -> syn::GenericParam {\n            syn::GenericParam::Type(self.generic_ident.clone().into())\n        }\n\n        pub fn type_ident(&self) -> syn::Type {\n            ident_to_type(self.generic_ident.clone())\n        }\n\n        pub fn tuplized_type_ty_param(&self) -> syn::Type {\n            let mut types = syn::punctuated::Punctuated::default();\n            types.push(self.ty.clone());\n            types.push_punct(Default::default());\n            syn::TypeTuple {\n                paren_token: Default::default(),\n                elems: types,\n            }\n            .into()\n        }\n\n        pub fn extends_vec_ident(&self) -> Option<syn::Ident> {\n            (!self.builder_attr.extends.is_empty()).then(|| {\n                let ident = &self.name;\n                format_ident!(\"__{ident}_attributes\")\n            })\n        }\n    }\n\n    #[derive(Debug, Default, Clone)]\n    pub struct FieldBuilderAttr {\n        pub default: Option<syn::Expr>,\n        pub docs: Vec<syn::Attribute>,\n        pub skip: bool,\n        pub auto_into: bool,\n        pub auto_to_string: bool,\n        pub from_displayable: bool,\n        pub strip_option: bool,\n        pub ignore_option: bool,\n        pub extends: Vec<Path>,\n    }\n\n    impl FieldBuilderAttr {\n        pub fn with(mut self, attrs: &[syn::Attribute]) -> Result<Self, Error> {\n            let mut skip_tokens = None;\n            for attr in attrs {\n                if attr.path().is_ident(\"doc\") {\n                    self.docs.push(attr.clone());\n                    continue;\n                }\n\n                if path_to_single_string(attr.path()).as_deref() != Some(\"props\") {\n                    continue;\n                }\n\n                match &attr.meta {\n                    syn::Meta::List(list) => {\n                        if list.tokens.is_empty() {\n                            continue;\n                        }\n                    }\n                    _ => {\n                        continue;\n                    }\n                }\n\n                let as_expr = attr.parse_args_with(\n                    Punctuated::<Expr, syn::Token![,]>::parse_separated_nonempty,\n                )?;\n\n                for expr in as_expr.into_iter() {\n                    self.apply_meta(expr)?;\n                }\n\n                // Stash its span for later (we don’t yet know if it’ll be an error)\n                if self.skip && skip_tokens.is_none() {\n                    skip_tokens = Some(attr.meta.clone());\n                }\n            }\n\n            if self.skip && self.default.is_none() {\n                return Err(Error::new_spanned(\n                    skip_tokens.unwrap(),\n                    \"#[props(skip)] must be accompanied by default or default_code\",\n                ));\n            }\n\n            Ok(self)\n        }\n\n        pub fn apply_meta(&mut self, expr: syn::Expr) -> Result<(), Error> {\n            match expr {\n                // #[props(default = \"...\")]\n                syn::Expr::Assign(assign) => {\n                    let name = expr_to_single_string(&assign.left)\n                        .ok_or_else(|| Error::new_spanned(&assign.left, \"Expected identifier\"))?;\n                    match name.as_str() {\n                        \"extends\" => {\n                            if let syn::Expr::Path(path) = *assign.right {\n                                self.extends.push(path.path);\n                                Ok(())\n                            } else {\n                                Err(Error::new_spanned(\n                                    assign.right,\n                                    \"Expected simple identifier\",\n                                ))\n                            }\n                        }\n                        \"default\" => {\n                            self.default = Some(*assign.right);\n                            Ok(())\n                        }\n                        \"default_code\" => {\n                            if let syn::Expr::Lit(syn::ExprLit {\n                                lit: syn::Lit::Str(code),\n                                ..\n                            }) = *assign.right\n                            {\n                                use std::str::FromStr;\n                                let tokenized_code = TokenStream::from_str(&code.value())?;\n                                self.default = Some(\n                                    syn::parse(tokenized_code.into())\n                                        .map_err(|e| Error::new_spanned(code, format!(\"{e}\")))?,\n                                );\n                            } else {\n                                return Err(Error::new_spanned(assign.right, \"Expected string\"));\n                            }\n                            Ok(())\n                        }\n                        _ => Err(Error::new_spanned(\n                            &assign,\n                            format!(\"Unknown parameter {name:?}\"),\n                        )),\n                    }\n                }\n\n                // #[props(default)]\n                syn::Expr::Path(path) => {\n                    let name = path_to_single_string(&path.path)\n                        .ok_or_else(|| Error::new_spanned(&path, \"Expected identifier\"))?;\n                    match name.as_str() {\n                        \"default\" => {\n                            self.default = Some(\n                                syn::parse(quote!(::core::default::Default::default()).into())\n                                    .unwrap(),\n                            );\n                            Ok(())\n                        }\n\n                        \"optional\" => {\n                            self.default = Some(\n                                syn::parse(quote!(::core::default::Default::default()).into())\n                                    .unwrap(),\n                            );\n                            self.strip_option = true;\n                            Ok(())\n                        }\n\n                        \"extend\" => {\n                            self.extends.push(path.path);\n                            Ok(())\n                        }\n\n                        _ => {\n                            macro_rules! handle_fields {\n                                ( $( $flag:expr, $field:ident, $already:expr; )* ) => {\n                                    match name.as_str() {\n                                        $(\n                                            $flag => {\n                                                if self.$field {\n                                                    Err(Error::new(path.span(), concat!(\"Illegal setting - field is already \", $already)))\n                                                } else {\n                                                    self.$field = true;\n                                                    Ok(())\n                                                }\n                                            }\n                                        )*\n                                        _ => Err(Error::new_spanned(\n                                                &path,\n                                                format!(\"Unknown setter parameter {:?}\", name),\n                                        ))\n                                    }\n                                }\n                            }\n                            handle_fields!(\n                                \"skip\", skip, \"skipped\";\n                                \"into\", auto_into, \"calling into() on the argument\";\n                                \"displayable\", from_displayable, \"calling to_string() on the argument\";\n                                \"strip_option\", strip_option, \"putting the argument in Some(...)\";\n                            )\n                        }\n                    }\n                }\n\n                syn::Expr::Unary(syn::ExprUnary {\n                    op: syn::UnOp::Not(_),\n                    expr,\n                    ..\n                }) => {\n                    if let syn::Expr::Path(path) = *expr {\n                        let name = path_to_single_string(&path.path)\n                            .ok_or_else(|| Error::new_spanned(&path, \"Expected identifier\"))?;\n                        match name.as_str() {\n                            \"default\" => {\n                                self.default = None;\n                                Ok(())\n                            }\n                            \"skip\" => {\n                                self.skip = false;\n                                Ok(())\n                            }\n                            \"auto_into\" => {\n                                self.auto_into = false;\n                                Ok(())\n                            }\n                            \"displayable\" => {\n                                self.from_displayable = false;\n                                Ok(())\n                            }\n                            \"optional\" => {\n                                self.strip_option = false;\n                                self.ignore_option = true;\n                                Ok(())\n                            }\n                            _ => Err(Error::new_spanned(path, \"Unknown setting\".to_owned())),\n                        }\n                    } else {\n                        Err(Error::new_spanned(\n                            expr,\n                            \"Expected simple identifier\".to_owned(),\n                        ))\n                    }\n                }\n                _ => Err(Error::new_spanned(expr, \"Expected (<...>=<...>)\")),\n            }\n        }\n    }\n}\n\nfn type_from_inside_option(ty: &Type) -> Option<&Type> {\n    let Type::Path(type_path) = ty else {\n        return None;\n    };\n\n    if type_path.qself.is_some() {\n        return None;\n    }\n\n    let path = &type_path.path;\n    let seg = path.segments.last()?;\n\n    // If the segment is a supported optional type, provide the inner type.\n    // Return the inner type if the pattern is `Option<T>` or `ReadSignal<Option<T>>``\n    if seg.ident == \"ReadSignal\" || seg.ident == \"ReadOnlySignal\" {\n        // Get the inner type. E.g. the `u16` in `ReadSignal<u16>` or `Option` in `ReadSignal<Option<bool>>`\n        let inner_type = extract_inner_type_from_segment(seg)?;\n        let Type::Path(inner_path) = inner_type else {\n            // If it isn't a path, the inner type isn't option\n            return None;\n        };\n\n        // If we're entering an `Option`, we must get the innermost type\n        let inner_seg = inner_path.path.segments.last()?;\n        if inner_seg.ident == \"Option\" {\n            // Get the innermost type.\n            let innermost_type = extract_inner_type_from_segment(inner_seg)?;\n            return Some(innermost_type);\n        }\n    } else if seg.ident == \"Option\" {\n        // Grab the inner time. E.g. Option<u16>\n        let inner_type = extract_inner_type_from_segment(seg)?;\n        return Some(inner_type);\n    }\n\n    None\n}\n\n// Extract the inner type from a path segment.\nfn extract_inner_type_from_segment(segment: &PathSegment) -> Option<&Type> {\n    let PathArguments::AngleBracketed(generic_args) = &segment.arguments else {\n        return None;\n    };\n\n    let GenericArgument::Type(final_type) = generic_args.args.first()? else {\n        return None;\n    };\n\n    Some(final_type)\n}\n\nmod struct_info {\n    use convert_case::{Case, Casing};\n    use proc_macro2::TokenStream;\n    use quote::{quote, ToTokens};\n    use syn::parse::Error;\n    use syn::punctuated::Punctuated;\n    use syn::spanned::Spanned;\n    use syn::{parse_quote, Expr, Ident};\n\n    use crate::props::strip_option;\n\n    use super::field_info::{FieldBuilderAttr, FieldInfo};\n    use super::util::{\n        empty_type, empty_type_tuple, expr_to_single_string, make_punctuated_single,\n        modify_types_generics_hack, path_to_single_string, strip_raw_ident_prefix, type_tuple,\n    };\n    use super::{child_owned_type, looks_like_callback_type, looks_like_signal_type};\n\n    #[derive(Debug)]\n    pub struct StructInfo<'a> {\n        pub vis: &'a syn::Visibility,\n        pub name: &'a syn::Ident,\n        pub generics: &'a syn::Generics,\n        pub fields: Vec<FieldInfo<'a>>,\n\n        pub builder_attr: TypeBuilderAttr,\n        pub builder_name: syn::Ident,\n        pub conversion_helper_trait_name: syn::Ident,\n        #[allow(unused)]\n        pub core: syn::Ident,\n    }\n\n    impl<'a> StructInfo<'a> {\n        pub fn included_fields(&self) -> impl Iterator<Item = &FieldInfo<'a>> {\n            self.fields.iter().filter(|f| !f.builder_attr.skip)\n        }\n\n        pub fn extend_fields(&self) -> impl Iterator<Item = &FieldInfo<'a>> {\n            self.fields\n                .iter()\n                .filter(|f| !f.builder_attr.extends.is_empty())\n        }\n\n        pub fn new(\n            ast: &'a syn::DeriveInput,\n            fields: impl Iterator<Item = &'a syn::Field>,\n        ) -> Result<StructInfo<'a>, Error> {\n            let builder_attr = TypeBuilderAttr::new(&ast.attrs)?;\n            let builder_name = strip_raw_ident_prefix(format!(\"{}Builder\", ast.ident));\n            Ok(StructInfo {\n                vis: &ast.vis,\n                name: &ast.ident,\n                generics: &ast.generics,\n                fields: fields\n                    .enumerate()\n                    .map(|(i, f)| FieldInfo::new(i, f, builder_attr.field_defaults.clone()))\n                    .collect::<Result<_, _>>()?,\n                builder_attr,\n                builder_name: syn::Ident::new(&builder_name, ast.ident.span()),\n                conversion_helper_trait_name: syn::Ident::new(\n                    &format!(\"{builder_name}_Optional\"),\n                    ast.ident.span(),\n                ),\n                core: syn::Ident::new(&format!(\"{builder_name}_core\"), ast.ident.span()),\n            })\n        }\n\n        fn modify_generics<F: FnMut(&mut syn::Generics)>(&self, mut mutator: F) -> syn::Generics {\n            let mut generics = self.generics.clone();\n            mutator(&mut generics);\n            generics\n        }\n\n        /// Checks if the props have any fields that should be owned by the child. For example, when converting T to `ReadSignal<T>`, the new signal should be owned by the child\n        fn has_child_owned_fields(&self) -> bool {\n            self.fields.iter().any(|f| child_owned_type(f.ty))\n        }\n\n        fn memoize_impl(&self) -> Result<TokenStream, Error> {\n            // First check if there are any ReadSignal fields, if there are not, we can just use the partialEq impl\n            let signal_fields: Vec<_> = self\n                .included_fields()\n                .filter(|f| looks_like_signal_type(f.ty))\n                .map(|f| {\n                    let name = f.name;\n                    quote!(#name)\n                })\n                .collect();\n\n            let move_signal_fields = quote! {\n                trait NonPartialEq: Sized {\n                    fn compare(&self, other: &Self) -> bool;\n                }\n\n                impl<T> NonPartialEq for &&T {\n                    fn compare(&self, other: &Self) -> bool {\n                        false\n                    }\n                }\n\n                trait CanPartialEq: PartialEq {\n                    fn compare(&self, other: &Self) -> bool;\n                }\n\n                impl<T: PartialEq> CanPartialEq for T {\n                    fn compare(&self, other: &Self) -> bool {\n                        self == other\n                    }\n                }\n\n                // If they are equal, we don't need to rerun the component we can just update the existing signals\n                #(\n                    // Try to memo the signal\n                    let field_eq = {\n                        let old_value: &_ = &*#signal_fields.peek();\n                        let new_value: &_ = &*new.#signal_fields.peek();\n                        (&old_value).compare(&&new_value)\n                    };\n                    // Make the old fields point to the new fields\n                    #signal_fields.point_to(new.#signal_fields).unwrap();\n                    if !field_eq {\n                        // If the fields are not equal, mark the signal as dirty to rerun any subscribers\n                        (#signal_fields).mark_dirty();\n                    }\n                    // Move the old value back\n                    self.#signal_fields = #signal_fields;\n                )*\n            };\n\n            let event_handlers_fields: Vec<_> = self\n                .included_fields()\n                .filter(|f| looks_like_callback_type(f.ty))\n                .collect();\n\n            let regular_fields: Vec<_> = self\n                .included_fields()\n                .filter(|f| !looks_like_signal_type(f.ty) && !looks_like_callback_type(f.ty))\n                .map(|f| {\n                    let name = f.name;\n                    quote!(#name)\n                })\n                .collect();\n\n            let move_event_handlers: TokenStream = event_handlers_fields.iter().map(|field| {\n                // If this is an optional event handler, we need to check if it's None before we try to update it\n                let optional = strip_option(field.ty).is_some();\n                let name = field.name;\n                if optional {\n                    quote! {\n                        // If both event handler are Some, update them in place\n                        if let (Some(old_handler), Some(new_handler)) = (self.#name.as_mut(), new.#name.as_ref()) {\n                            old_handler.__point_to(new_handler);\n                        }\n                        // Otherwise just move the new handler into self\n                        else {\n                            self.#name = new.#name;\n                        }\n                    }\n                } else {\n                    quote! {\n                        // Update the event handlers\n                        self.#name.__point_to(&new.#name);\n                    }\n                }\n            }).collect();\n\n            // If there are signals, we automatically try to memoize the signals\n            if !signal_fields.is_empty() {\n                Ok(quote! {\n                    // First check if the fields are equal. This will compare the signal fields by pointer\n                    let exactly_equal = self == new;\n                    if exactly_equal {\n                        // If they are return early, they can be memoized without any changes\n                        return true;\n                    }\n\n                    // If they are not, move the signal fields into self and check if they are equal now that the signal fields are equal\n                    #(\n                        let mut #signal_fields = self.#signal_fields;\n                        self.#signal_fields = new.#signal_fields;\n                    )*\n\n                    // Then check if the fields are equal now that we know the signal fields are equal\n                    // NOTE: we don't compare other fields individually because we want to let users opt-out of memoization for certain fields by implementing PartialEq themselves\n                    let non_signal_fields_equal = self == new;\n\n                    // If they are not equal, we need to move over all the fields that are not event handlers or signals to self\n                    if !non_signal_fields_equal {\n                        let new_clone = new.clone();\n                        #(\n                            self.#regular_fields = new_clone.#regular_fields;\n                        )*\n                    }\n                    // Move any signal and event fields into their old container.\n                    // We update signals and event handlers in place so that they are always up to date even if they were moved into a future in a previous render\n                    #move_signal_fields\n                    #move_event_handlers\n\n                    non_signal_fields_equal\n                })\n            } else {\n                Ok(quote! {\n                    let equal = self == new;\n                    // Move any signal and event fields into their old container.\n                    #move_event_handlers\n                    // If they are not equal, we need to move over all the fields that are not event handlers to self\n                    if !equal {\n                        let new_clone = new.clone();\n                        #(\n                            self.#regular_fields = new_clone.#regular_fields;\n                        )*\n                    }\n                    equal\n                })\n            }\n        }\n\n        pub fn builder_creation_impl(&self) -> Result<TokenStream, Error> {\n            let StructInfo {\n                ref vis,\n                ref name,\n                ref builder_name,\n                ..\n            } = *self;\n\n            let generics = self.generics.clone();\n            let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();\n            let (_, b_initial_generics, _) = self.generics.split_for_impl();\n            let all_fields_param = syn::GenericParam::Type(\n                syn::Ident::new(\"TypedBuilderFields\", proc_macro2::Span::call_site()).into(),\n            );\n            let b_generics = self.modify_generics(|g| {\n                g.params.insert(0, all_fields_param.clone());\n            });\n            let empties_tuple = type_tuple(self.included_fields().map(|_| empty_type()));\n            let generics_with_empty = modify_types_generics_hack(&b_initial_generics, |args| {\n                args.insert(0, syn::GenericArgument::Type(empties_tuple.clone().into()));\n            });\n            let phantom_generics = self.generics.params.iter().filter_map(|param| match param {\n                syn::GenericParam::Lifetime(lifetime) => {\n                    let lifetime = &lifetime.lifetime;\n                    Some(quote!(::core::marker::PhantomData<&#lifetime ()>))\n                }\n                syn::GenericParam::Type(ty) => {\n                    let ty = &ty.ident;\n                    Some(quote!(::core::marker::PhantomData<#ty>))\n                }\n                syn::GenericParam::Const(_cnst) => None,\n            });\n            let builder_method_doc = match self.builder_attr.builder_method_doc {\n                Some(ref doc) => quote!(#doc),\n                None => {\n                    let doc = format!(\n                        \"\nCreate a builder for building `{name}`.\nOn the builder, call {setters} to set the values of the fields.\nFinally, call `.build()` to create the instance of `{name}`.\n                    \",\n                        name = self.name,\n                        setters = {\n                            let mut result = String::new();\n                            let mut is_first = true;\n                            for field in self.included_fields() {\n                                use std::fmt::Write;\n                                if is_first {\n                                    is_first = false;\n                                } else {\n                                    write!(&mut result, \", \").unwrap();\n                                }\n                                write!(&mut result, \"`.{}(...)`\", field.name).unwrap();\n                                if field.builder_attr.default.is_some() {\n                                    write!(&mut result, \"(optional)\").unwrap();\n                                }\n                            }\n                            result\n                        }\n                    );\n                    quote!(#doc)\n                }\n            };\n            let builder_type_doc = if self.builder_attr.doc {\n                match self.builder_attr.builder_type_doc {\n                    Some(ref doc) => quote!(#[doc = #doc]),\n                    None => {\n                        let doc = format!(\n                        \"Builder for [`{name}`] instances.\\n\\nSee [`{name}::builder()`] for more info.\",\n                    );\n                        quote!(#[doc = #doc])\n                    }\n                }\n            } else {\n                quote!(#[doc(hidden)])\n            };\n\n            let (_, _, b_generics_where_extras_predicates) = b_generics.split_for_impl();\n            let mut b_generics_where: syn::WhereClause = syn::parse2(quote! {\n                where Self: Clone\n            })?;\n            if let Some(predicates) = b_generics_where_extras_predicates {\n                b_generics_where\n                    .predicates\n                    .extend(predicates.predicates.clone());\n            }\n\n            let memoize = self.memoize_impl()?;\n\n            let global_fields = self\n                .extend_fields()\n                .map(|f| {\n                    let name = f.extends_vec_ident();\n                    let ty = f.ty;\n                    quote!(#name: #ty)\n                })\n                .chain(\n                    self.has_child_owned_fields()\n                        .then(|| quote!(owner: dioxus_core::internal::generational_box::Owner)),\n                );\n            let global_fields_value = self\n                .extend_fields()\n                .map(|f| {\n                    let name = f.extends_vec_ident();\n                    quote!(#name: Vec::new())\n                })\n                .chain(self.has_child_owned_fields().then(\n                    || quote!(owner: dioxus_core::internal::generational_box::Owner::default()),\n                ));\n\n            Ok(quote! {\n                impl #impl_generics #name #ty_generics #where_clause {\n                    #[doc = #builder_method_doc]\n                    #[allow(dead_code, clippy::type_complexity)]\n                    #vis fn builder() -> #builder_name #generics_with_empty {\n                        #builder_name {\n                            #(#global_fields_value,)*\n                            fields: #empties_tuple,\n                            _phantom: ::core::default::Default::default(),\n                        }\n                    }\n                }\n\n                #[must_use]\n                #builder_type_doc\n                #[allow(dead_code, non_camel_case_types, non_snake_case)]\n                #vis struct #builder_name #b_generics {\n                    #(#global_fields,)*\n                    fields: #all_fields_param,\n                    _phantom: (#( #phantom_generics ),*),\n                }\n\n                impl #impl_generics dioxus_core::Properties for #name #ty_generics\n                #b_generics_where\n                {\n                    type Builder = #builder_name #generics_with_empty;\n                    fn builder() -> Self::Builder {\n                        #name::builder()\n                    }\n                    fn memoize(&mut self, new: &Self) -> bool {\n                        #memoize\n                    }\n                }\n            })\n        }\n\n        // TODO: once the proc-macro crate limitation is lifted, make this an util trait of this\n        // crate.\n        pub fn conversion_helper_impl(&self) -> Result<TokenStream, Error> {\n            let trait_name = &self.conversion_helper_trait_name;\n            Ok(quote! {\n                #[doc(hidden)]\n                #[allow(dead_code, non_camel_case_types, non_snake_case)]\n                pub trait #trait_name<T> {\n                    fn into_value<F: FnOnce() -> T>(self, default: F) -> T;\n                }\n\n                impl<T> #trait_name<T> for () {\n                    fn into_value<F: FnOnce() -> T>(self, default: F) -> T {\n                        default()\n                    }\n                }\n\n                impl<T> #trait_name<T> for (T,) {\n                    fn into_value<F: FnOnce() -> T>(self, _: F) -> T {\n                        self.0\n                    }\n                }\n            })\n        }\n\n        pub fn extends_impl(&self, field: &FieldInfo) -> Result<TokenStream, Error> {\n            let StructInfo {\n                ref builder_name, ..\n            } = *self;\n\n            let field_name = field.extends_vec_ident().unwrap();\n\n            let descructuring = self.included_fields().map(|f| {\n                let name = f.name;\n                quote!(#name)\n            });\n            let reconstructing = self.included_fields().map(|f| f.name);\n\n            let mut ty_generics: Vec<syn::GenericArgument> = self\n                .generics\n                .params\n                .iter()\n                .map(|generic_param| match generic_param {\n                    syn::GenericParam::Type(type_param) => {\n                        let ident = type_param.ident.clone();\n                        syn::parse(quote!(#ident).into()).unwrap()\n                    }\n                    syn::GenericParam::Lifetime(lifetime_def) => {\n                        syn::GenericArgument::Lifetime(lifetime_def.lifetime.clone())\n                    }\n                    syn::GenericParam::Const(const_param) => {\n                        let ident = const_param.ident.clone();\n                        syn::parse(quote!(#ident).into()).unwrap()\n                    }\n                })\n                .collect();\n            let mut target_generics_tuple = empty_type_tuple();\n            let mut ty_generics_tuple = empty_type_tuple();\n            let generics = self.modify_generics(|g| {\n                let index_after_lifetime_in_generics = g\n                    .params\n                    .iter()\n                    .filter(|arg| matches!(arg, syn::GenericParam::Lifetime(_)))\n                    .count();\n                for f in self.included_fields() {\n                    if f.ordinal == field.ordinal {\n                        g.params.insert(\n                            index_after_lifetime_in_generics,\n                            syn::GenericParam::Type(self.generic_builder_param(f)),\n                        );\n                        let generic_argument: syn::Type = f.type_ident();\n                        ty_generics_tuple.elems.push_value(generic_argument.clone());\n                        target_generics_tuple\n                            .elems\n                            .push_value(f.tuplized_type_ty_param());\n                    } else {\n                        g.params\n                            .insert(index_after_lifetime_in_generics, f.generic_ty_param());\n                        let generic_argument: syn::Type = f.type_ident();\n                        ty_generics_tuple.elems.push_value(generic_argument.clone());\n                        target_generics_tuple.elems.push_value(generic_argument);\n                    }\n                    ty_generics_tuple.elems.push_punct(Default::default());\n                    target_generics_tuple.elems.push_punct(Default::default());\n                }\n            });\n            let mut target_generics = ty_generics.clone();\n            let index_after_lifetime_in_generics = target_generics\n                .iter()\n                .filter(|arg| matches!(arg, syn::GenericArgument::Lifetime(_)))\n                .count();\n            target_generics.insert(\n                index_after_lifetime_in_generics,\n                syn::GenericArgument::Type(target_generics_tuple.into()),\n            );\n            ty_generics.insert(\n                index_after_lifetime_in_generics,\n                syn::GenericArgument::Type(ty_generics_tuple.into()),\n            );\n            let (impl_generics, _, where_clause) = generics.split_for_impl();\n\n            let forward_extended_fields = self.extend_fields().map(|f| {\n                let name = f.extends_vec_ident();\n                quote!(#name: self.#name)\n            });\n\n            let forward_owner = self\n                .has_child_owned_fields()\n                .then(|| quote!(owner: self.owner))\n                .into_iter();\n\n            let extends_impl = field.builder_attr.extends.iter().map(|path| {\n                let name_str = path_to_single_string(path).unwrap();\n                let camel_name = name_str.to_case(Case::UpperCamel);\n                let marker_name = Ident::new(\n                    format!(\"{}Extension\", &camel_name).as_str(),\n                    path.span(),\n                );\n                quote! {\n                    #[allow(dead_code, non_camel_case_types, missing_docs)]\n                    impl #impl_generics dioxus_elements::extensions::#marker_name for #builder_name < #( #ty_generics ),* > #where_clause {}\n                }\n            });\n\n            Ok(quote! {\n                #[allow(dead_code, non_camel_case_types, missing_docs)]\n                impl #impl_generics dioxus_core::HasAttributes for #builder_name < #( #ty_generics ),* > #where_clause {\n                    fn push_attribute<L>(\n                        mut self,\n                        ____name: &'static str,\n                        ____ns: Option<&'static str>,\n                        ____attr: impl dioxus_core::IntoAttributeValue<L>,\n                        ____volatile: bool\n                    ) -> Self {\n                        let ( #(#descructuring,)* ) = self.fields;\n                        self.#field_name.push(\n                            dioxus_core::Attribute::new(\n                                ____name,\n                                dioxus_core::IntoAttributeValue::<L>::into_value(____attr),\n                                ____ns,\n                                ____volatile,\n                            )\n                        );\n                        #builder_name {\n                            #(#forward_extended_fields,)*\n                            #(#forward_owner,)*\n                            fields: ( #(#reconstructing,)* ),\n                            _phantom: self._phantom,\n                        }\n                    }\n                }\n\n                #(#extends_impl)*\n            })\n        }\n\n        pub fn field_impl(&self, field: &FieldInfo) -> Result<TokenStream, Error> {\n            let FieldInfo {\n                name: field_name, ..\n            } = field;\n            if *field_name == \"key\" {\n                return Err(Error::new_spanned(field_name, \"Naming a prop `key` is not allowed because the name can conflict with the built in key attribute. See https://dioxuslabs.com/learn/0.7/essentials/ui/iteration for more information about keys\"));\n            }\n            let StructInfo {\n                ref builder_name, ..\n            } = *self;\n\n            let descructuring = self.included_fields().map(|f| {\n                if f.ordinal == field.ordinal {\n                    quote!(_)\n                } else {\n                    let name = f.name;\n                    quote!(#name)\n                }\n            });\n            let reconstructing = self.included_fields().map(|f| f.name);\n\n            let FieldInfo {\n                name: field_name,\n                ty: field_type,\n                ..\n            } = field;\n            let mut ty_generics: Vec<syn::GenericArgument> = self\n                .generics\n                .params\n                .iter()\n                .map(|generic_param| match generic_param {\n                    syn::GenericParam::Type(type_param) => {\n                        let ident = type_param.ident.clone();\n                        syn::parse(quote!(#ident).into()).unwrap()\n                    }\n                    syn::GenericParam::Lifetime(lifetime_def) => {\n                        syn::GenericArgument::Lifetime(lifetime_def.lifetime.clone())\n                    }\n                    syn::GenericParam::Const(const_param) => {\n                        let ident = const_param.ident.clone();\n                        syn::parse(quote!(#ident).into()).unwrap()\n                    }\n                })\n                .collect();\n            let mut target_generics_tuple = empty_type_tuple();\n            let mut ty_generics_tuple = empty_type_tuple();\n            let generics = self.modify_generics(|g| {\n                let index_after_lifetime_in_generics = g\n                    .params\n                    .iter()\n                    .filter(|arg| matches!(arg, syn::GenericParam::Lifetime(_)))\n                    .count();\n                for f in self.included_fields() {\n                    if f.ordinal == field.ordinal {\n                        ty_generics_tuple.elems.push_value(empty_type());\n                        target_generics_tuple\n                            .elems\n                            .push_value(f.tuplized_type_ty_param());\n                    } else {\n                        g.params\n                            .insert(index_after_lifetime_in_generics, f.generic_ty_param());\n                        let generic_argument: syn::Type = f.type_ident();\n                        ty_generics_tuple.elems.push_value(generic_argument.clone());\n                        target_generics_tuple.elems.push_value(generic_argument);\n                    }\n                    ty_generics_tuple.elems.push_punct(Default::default());\n                    target_generics_tuple.elems.push_punct(Default::default());\n                }\n            });\n            let mut target_generics = ty_generics.clone();\n            let index_after_lifetime_in_generics = target_generics\n                .iter()\n                .filter(|arg| matches!(arg, syn::GenericArgument::Lifetime(_)))\n                .count();\n            target_generics.insert(\n                index_after_lifetime_in_generics,\n                syn::GenericArgument::Type(target_generics_tuple.into()),\n            );\n            ty_generics.insert(\n                index_after_lifetime_in_generics,\n                syn::GenericArgument::Type(ty_generics_tuple.into()),\n            );\n\n            let (impl_generics, _, where_clause) = generics.split_for_impl();\n            let docs = &field.builder_attr.docs;\n\n            let arg_type = field_type;\n            // If the field is auto_into, we need to add a generic parameter to the builder for specialization\n            let mut marker = None;\n            let (arg_type, arg_expr) = if child_owned_type(arg_type) {\n                let marker_ident = syn::Ident::new(\"__Marker\", proc_macro2::Span::call_site());\n                marker = Some(marker_ident.clone());\n                (\n                    quote!(impl dioxus_core::SuperInto<#arg_type, #marker_ident>),\n                    // If this looks like a signal type, we automatically convert it with SuperInto and use the props struct as the owner\n                    quote!(dioxus_core::with_owner(self.owner.clone(), move || dioxus_core::SuperInto::super_into(#field_name))),\n                )\n            } else if field.builder_attr.auto_into || field.builder_attr.strip_option {\n                let marker_ident = syn::Ident::new(\"__Marker\", proc_macro2::Span::call_site());\n                marker = Some(marker_ident.clone());\n                (\n                    quote!(impl dioxus_core::SuperInto<#arg_type, #marker_ident>),\n                    quote!(dioxus_core::SuperInto::super_into(#field_name)),\n                )\n            } else if field.builder_attr.from_displayable {\n                (\n                    quote!(impl ::core::fmt::Display),\n                    quote!(#field_name.to_string()),\n                )\n            } else {\n                (quote!(#arg_type), quote!(#field_name))\n            };\n\n            let repeated_fields_error_type_name = syn::Ident::new(\n                &format!(\n                    \"{}_Error_Repeated_field_{}\",\n                    builder_name,\n                    strip_raw_ident_prefix(field_name.to_string())\n                ),\n                builder_name.span(),\n            );\n            let repeated_fields_error_message = format!(\"Repeated field {field_name}\");\n\n            let forward_fields = self\n                .extend_fields()\n                .map(|f| {\n                    let name = f.extends_vec_ident();\n                    quote!(#name: self.#name)\n                })\n                .chain(\n                    self.has_child_owned_fields()\n                        .then(|| quote!(owner: self.owner)),\n                );\n\n            Ok(quote! {\n                #[allow(dead_code, non_camel_case_types, missing_docs)]\n                impl #impl_generics #builder_name < #( #ty_generics ),* > #where_clause {\n                    #( #docs )*\n                    #[allow(clippy::type_complexity)]\n                    pub fn #field_name < #marker > (self, #field_name: #arg_type) -> #builder_name < #( #target_generics ),* > {\n                        let #field_name = (#arg_expr,);\n                        let ( #(#descructuring,)* ) = self.fields;\n                        #builder_name {\n                            #(#forward_fields,)*\n                            fields: ( #(#reconstructing,)* ),\n                            _phantom: self._phantom,\n                        }\n                    }\n                }\n                #[doc(hidden)]\n                #[allow(dead_code, non_camel_case_types, non_snake_case)]\n                pub enum #repeated_fields_error_type_name {}\n                #[doc(hidden)]\n                #[allow(dead_code, non_camel_case_types, missing_docs)]\n                impl #impl_generics #builder_name < #( #target_generics ),* > #where_clause {\n                    #[deprecated(\n                        note = #repeated_fields_error_message\n                    )]\n                    #[allow(clippy::type_complexity)]\n                    pub fn #field_name< #marker > (self, _: #repeated_fields_error_type_name) -> #builder_name < #( #target_generics ),* > {\n                        self\n                    }\n                }\n            })\n        }\n\n        pub fn required_field_impl(&self, field: &FieldInfo) -> Result<TokenStream, Error> {\n            let StructInfo {\n                ref name,\n                ref builder_name,\n                ..\n            } = self;\n\n            let FieldInfo {\n                name: ref field_name,\n                ..\n            } = field;\n            let mut builder_generics: Vec<syn::GenericArgument> = self\n                .generics\n                .params\n                .iter()\n                .map(|generic_param| match generic_param {\n                    syn::GenericParam::Type(type_param) => {\n                        let ident = &type_param.ident;\n                        syn::parse(quote!(#ident).into()).unwrap()\n                    }\n                    syn::GenericParam::Lifetime(lifetime_def) => {\n                        syn::GenericArgument::Lifetime(lifetime_def.lifetime.clone())\n                    }\n                    syn::GenericParam::Const(const_param) => {\n                        let ident = &const_param.ident;\n                        syn::parse(quote!(#ident).into()).unwrap()\n                    }\n                })\n                .collect();\n            let mut builder_generics_tuple = empty_type_tuple();\n            let generics = self.modify_generics(|g| {\n                let index_after_lifetime_in_generics = g\n                    .params\n                    .iter()\n                    .filter(|arg| matches!(arg, syn::GenericParam::Lifetime(_)))\n                    .count();\n                for f in self.included_fields() {\n                    if f.builder_attr.default.is_some() {\n                        // `f` is not mandatory - it does not have it's own fake `build` method, so `field` will need\n                        // to warn about missing `field` whether or not `f` is set.\n                        assert!(\n                            f.ordinal != field.ordinal,\n                            \"`required_field_impl` called for optional field {}\",\n                            field.name\n                        );\n                        g.params\n                            .insert(index_after_lifetime_in_generics, f.generic_ty_param());\n                        builder_generics_tuple.elems.push_value(f.type_ident());\n                    } else if f.ordinal < field.ordinal {\n                        // Only add a `build` method that warns about missing `field` if `f` is set. If `f` is not set,\n                        // `f`'s `build` method will warn, since it appears earlier in the argument list.\n                        builder_generics_tuple\n                            .elems\n                            .push_value(f.tuplized_type_ty_param());\n                    } else if f.ordinal == field.ordinal {\n                        builder_generics_tuple.elems.push_value(empty_type());\n                    } else {\n                        // `f` appears later in the argument list after `field`, so if they are both missing we will\n                        // show a warning for `field` and not for `f` - which means this warning should appear whether\n                        // or not `f` is set.\n                        g.params\n                            .insert(index_after_lifetime_in_generics, f.generic_ty_param());\n                        builder_generics_tuple.elems.push_value(f.type_ident());\n                    }\n\n                    builder_generics_tuple.elems.push_punct(Default::default());\n                }\n            });\n\n            let index_after_lifetime_in_generics = builder_generics\n                .iter()\n                .filter(|arg| matches!(arg, syn::GenericArgument::Lifetime(_)))\n                .count();\n            builder_generics.insert(\n                index_after_lifetime_in_generics,\n                syn::GenericArgument::Type(builder_generics_tuple.into()),\n            );\n            let (impl_generics, _, where_clause) = generics.split_for_impl();\n            let (_, ty_generics, _) = self.generics.split_for_impl();\n\n            let early_build_error_type_name = syn::Ident::new(\n                &format!(\n                    \"{}_Error_Missing_required_field_{}\",\n                    builder_name,\n                    strip_raw_ident_prefix(field_name.to_string())\n                ),\n                builder_name.span(),\n            );\n            let early_build_error_message = format!(\"Missing required field {field_name}\");\n\n            Ok(quote! {\n                #[doc(hidden)]\n                #[allow(dead_code, non_camel_case_types, non_snake_case)]\n                pub enum #early_build_error_type_name {}\n                #[doc(hidden)]\n                #[allow(dead_code, non_camel_case_types, missing_docs, clippy::panic)]\n                impl #impl_generics #builder_name < #( #builder_generics ),* > #where_clause {\n                    #[deprecated(\n                        note = #early_build_error_message\n                    )]\n                    pub fn build(self, _: #early_build_error_type_name) -> #name #ty_generics {\n                        panic!()\n                    }\n                }\n            })\n        }\n\n        fn generic_builder_param(&self, field: &FieldInfo) -> syn::TypeParam {\n            let trait_ref = syn::TraitBound {\n                paren_token: None,\n                lifetimes: None,\n                modifier: syn::TraitBoundModifier::None,\n                path: syn::PathSegment {\n                    ident: self.conversion_helper_trait_name.clone(),\n                    arguments: syn::PathArguments::AngleBracketed(\n                        syn::AngleBracketedGenericArguments {\n                            colon2_token: None,\n                            lt_token: Default::default(),\n                            args: make_punctuated_single(syn::GenericArgument::Type(\n                                field.ty.clone(),\n                            )),\n                            gt_token: Default::default(),\n                        },\n                    ),\n                }\n                .into(),\n            };\n            let mut generic_param: syn::TypeParam = field.generic_ident.clone().into();\n            generic_param.bounds.push(trait_ref.into());\n            generic_param\n        }\n\n        pub fn build_method_impl(&self) -> TokenStream {\n            let StructInfo {\n                ref name,\n                ref builder_name,\n                ..\n            } = *self;\n\n            let generics = self.modify_generics(|g| {\n                let index_after_lifetime_in_generics = g\n                    .params\n                    .iter()\n                    .filter(|arg| matches!(arg, syn::GenericParam::Lifetime(_)))\n                    .count();\n                for field in self.included_fields() {\n                    if field.builder_attr.default.is_some() {\n                        let generic_param = self.generic_builder_param(field);\n                        g.params\n                            .insert(index_after_lifetime_in_generics, generic_param.into());\n                    }\n                }\n            });\n            let (impl_generics, _, _) = generics.split_for_impl();\n\n            let (original_impl_generics, ty_generics, where_clause) =\n                self.generics.split_for_impl();\n\n            let modified_ty_generics = modify_types_generics_hack(&ty_generics, |args| {\n                args.insert(\n                    0,\n                    syn::GenericArgument::Type(\n                        type_tuple(self.included_fields().map(|field| {\n                            if field.builder_attr.default.is_some() {\n                                field.type_ident()\n                            } else {\n                                field.tuplized_type_ty_param()\n                            }\n                        }))\n                        .into(),\n                    ),\n                );\n            });\n\n            let descructuring = self.included_fields().map(|f| f.name);\n\n            let helper_trait_name = &self.conversion_helper_trait_name;\n            // The default of a field can refer to earlier-defined fields, which we handle by\n            // writing out a bunch of `let` statements first, which can each refer to earlier ones.\n            // This means that field ordering may actually be significant, which isn’t ideal. We could\n            // relax that restriction by calculating a DAG of field default dependencies and\n            // reordering based on that, but for now this much simpler thing is a reasonable approach.\n            let assignments = self.fields.iter().map(|field| {\n                let name = &field.name;\n                if let Some(extends_vec) = field.extends_vec_ident() {\n                    quote!{\n                        let mut #name = #helper_trait_name::into_value(#name, || ::core::default::Default::default());\n                        #name.extend(self.#extends_vec);\n                    }\n                } else if let Some(ref default) = field.builder_attr.default {\n                    // If field has `into`, apply it to the default value.\n                    // Ignore any blank defaults as it causes type inference errors.\n                    let is_default = *default == parse_quote!(::core::default::Default::default());\n                    // If this is a signal type, we use super_into and the props struct as the owner\n                    let is_child_owned_type = child_owned_type(field.ty);\n\n\n                    let body = if !is_default {\n                        if is_child_owned_type {\n                            quote!{ dioxus_core::SuperInto::super_into(#default) }\n                        } else if field.builder_attr.auto_into {\n                            quote!{ (#default).into() }\n                        } else if field.builder_attr.auto_to_string {\n                            quote!{ (#default).to_string() }\n                        } else {\n                            default.to_token_stream()\n                        }\n                    } else {\n                        default.to_token_stream()\n                    };\n\n                    if field.builder_attr.skip {\n                        quote!(let #name = #body;)\n                    } else if is_child_owned_type {\n                        quote!(let #name = #helper_trait_name::into_value(#name, || dioxus_core::with_owner(self.owner.clone(), move || #body));)\n                    } else {\n                        quote!(let #name = #helper_trait_name::into_value(#name, || #body);)\n                    }\n                } else {\n                    quote!(let #name = #name.0;)\n                }\n            });\n            let field_names = self.fields.iter().map(|field| field.name);\n            let doc = if self.builder_attr.doc {\n                match self.builder_attr.build_method_doc {\n                    Some(ref doc) => quote!(#[doc = #doc]),\n                    None => {\n                        // I’d prefer “a” or “an” to “its”, but determining which is grammatically\n                        // correct is roughly impossible.\n                        let doc =\n                            format!(\"Finalize the builder and create its [`{name}`] instance\");\n                        quote!(#[doc = #doc])\n                    }\n                }\n            } else {\n                quote!()\n            };\n\n            if self.has_child_owned_fields() {\n                let name = Ident::new(&format!(\"{}WithOwner\", name), name.span());\n                let original_name = &self.name;\n                let vis = &self.vis;\n                let generics_with_bounds = &self.generics;\n                let where_clause = &self.generics.where_clause;\n\n                quote! {\n                    #[doc(hidden)]\n                    #[allow(dead_code, non_camel_case_types, missing_docs)]\n                    #[derive(Clone)]\n                    #vis struct #name #generics_with_bounds #where_clause {\n                        inner: #original_name #ty_generics,\n                        owner: dioxus_core::internal::generational_box::Owner,\n                    }\n\n                    impl #original_impl_generics PartialEq for #name #ty_generics #where_clause {\n                        fn eq(&self, other: &Self) -> bool {\n                            self.inner.eq(&other.inner)\n                        }\n                    }\n\n                    impl #original_impl_generics #name #ty_generics #where_clause {\n                        /// Create a component from the props.\n                        pub fn into_vcomponent<M: 'static>(\n                            self,\n                            render_fn: impl dioxus_core::ComponentFunction<#original_name #ty_generics, M>,\n                        ) -> dioxus_core::VComponent {\n                            use dioxus_core::ComponentFunction;\n                            let component_name = ::std::any::type_name_of_val(&render_fn);\n                            dioxus_core::VComponent::new(move |wrapper: Self| render_fn.rebuild(wrapper.inner), self, component_name)\n                        }\n                    }\n\n                    impl #original_impl_generics dioxus_core::Properties for #name #ty_generics #where_clause {\n                        type Builder = ();\n                        fn builder() -> Self::Builder {\n                            unreachable!()\n                        }\n                        fn memoize(&mut self, new: &Self) -> bool {\n                            self.inner.memoize(&new.inner)\n                        }\n                    }\n\n                    #[allow(dead_code, non_camel_case_types, missing_docs)]\n                    impl #impl_generics #builder_name #modified_ty_generics #where_clause {\n                        #doc\n                        pub fn build(self) -> #name #ty_generics {\n                            let ( #(#descructuring,)* ) = self.fields;\n                            #( #assignments )*\n                            #name {\n                                inner: #original_name {\n                                    #( #field_names ),*\n                                },\n                                owner: self.owner,\n                            }\n                        }\n                    }\n                }\n            } else {\n                quote!(\n                    #[allow(dead_code, non_camel_case_types, missing_docs)]\n                    impl #impl_generics #builder_name #modified_ty_generics #where_clause {\n                        #doc\n                        pub fn build(self) -> #name #ty_generics {\n                            let ( #(#descructuring,)* ) = self.fields;\n                            #( #assignments )*\n                            #name {\n                                #( #field_names ),*\n                            }\n                        }\n                    }\n                )\n            }\n        }\n    }\n\n    #[derive(Debug, Default)]\n    pub struct TypeBuilderAttr {\n        /// Whether to show docs for the `TypeBuilder` type (rather than hiding them).\n        pub doc: bool,\n\n        /// Docs on the `Type::builder()` method.\n        pub builder_method_doc: Option<syn::Expr>,\n\n        /// Docs on the `TypeBuilder` type. Specifying this implies `doc`, but you can just specify\n        /// `doc` instead and a default value will be filled in here.\n        pub builder_type_doc: Option<syn::Expr>,\n\n        /// Docs on the `TypeBuilder.build()` method. Specifying this implies `doc`, but you can just\n        /// specify `doc` instead and a default value will be filled in here.\n        pub build_method_doc: Option<syn::Expr>,\n\n        pub field_defaults: FieldBuilderAttr,\n    }\n\n    impl TypeBuilderAttr {\n        pub fn new(attrs: &[syn::Attribute]) -> Result<TypeBuilderAttr, Error> {\n            let mut result = TypeBuilderAttr::default();\n            for attr in attrs {\n                if path_to_single_string(attr.path()).as_deref() != Some(\"builder\") {\n                    continue;\n                }\n\n                match &attr.meta {\n                    syn::Meta::List(list) => {\n                        if list.tokens.is_empty() {\n                            continue;\n                        }\n                    }\n                    _ => {\n                        continue;\n                    }\n                }\n\n                let as_expr = attr.parse_args_with(\n                    Punctuated::<Expr, syn::Token![,]>::parse_separated_nonempty,\n                )?;\n\n                for expr in as_expr.into_iter() {\n                    result.apply_meta(expr)?;\n                }\n            }\n\n            Ok(result)\n        }\n\n        fn apply_meta(&mut self, expr: syn::Expr) -> Result<(), Error> {\n            match expr {\n                syn::Expr::Assign(assign) => {\n                    let name = expr_to_single_string(&assign.left)\n                        .ok_or_else(|| Error::new_spanned(&assign.left, \"Expected identifier\"))?;\n                    match name.as_str() {\n                        \"builder_method_doc\" => {\n                            self.builder_method_doc = Some(*assign.right);\n                            Ok(())\n                        }\n                        \"builder_type_doc\" => {\n                            self.builder_type_doc = Some(*assign.right);\n                            self.doc = true;\n                            Ok(())\n                        }\n                        \"build_method_doc\" => {\n                            self.build_method_doc = Some(*assign.right);\n                            self.doc = true;\n                            Ok(())\n                        }\n                        _ => Err(Error::new_spanned(\n                            &assign,\n                            format!(\"Unknown parameter {name:?}\"),\n                        )),\n                    }\n                }\n                syn::Expr::Path(path) => {\n                    let name = path_to_single_string(&path.path)\n                        .ok_or_else(|| Error::new_spanned(&path, \"Expected identifier\"))?;\n                    match name.as_str() {\n                        \"doc\" => {\n                            self.doc = true;\n                            Ok(())\n                        }\n                        _ => Err(Error::new_spanned(\n                            &path,\n                            format!(\"Unknown parameter {name:?}\"),\n                        )),\n                    }\n                }\n                syn::Expr::Call(call) => {\n                    let subsetting_name = if let syn::Expr::Path(path) = &*call.func {\n                        path_to_single_string(&path.path)\n                    } else {\n                        None\n                    }\n                    .ok_or_else(|| {\n                        let call_func = &call.func;\n                        let call_func = quote!(#call_func);\n                        Error::new_spanned(\n                            &call.func,\n                            format!(\"Illegal builder setting group {call_func}\"),\n                        )\n                    })?;\n                    match subsetting_name.as_str() {\n                        \"field_defaults\" => {\n                            for arg in call.args {\n                                self.field_defaults.apply_meta(arg)?;\n                            }\n                            Ok(())\n                        }\n                        _ => Err(Error::new_spanned(\n                            &call.func,\n                            format!(\"Illegal builder setting group name {subsetting_name}\"),\n                        )),\n                    }\n                }\n                _ => Err(Error::new_spanned(expr, \"Expected (<...>=<...>)\")),\n            }\n        }\n    }\n}\n\n/// A helper function for paring types with a single generic argument.\nfn extract_base_type_without_generics(ty: &Type) -> Option<syn::Path> {\n    let Type::Path(ty) = ty else {\n        return None;\n    };\n    if ty.qself.is_some() {\n        return None;\n    }\n\n    let path = &ty.path;\n\n    let mut path_segments_without_generics = Vec::new();\n\n    let mut generic_arg_count = 0;\n\n    for segment in &path.segments {\n        let mut segment = segment.clone();\n        match segment.arguments {\n            PathArguments::AngleBracketed(_) => generic_arg_count += 1,\n            PathArguments::Parenthesized(_) => {\n                return None;\n            }\n            _ => {}\n        }\n        segment.arguments = syn::PathArguments::None;\n        path_segments_without_generics.push(segment);\n    }\n\n    // If there is more than the type and the single generic argument, it doesn't look like the type we want\n    if generic_arg_count > 2 {\n        return None;\n    }\n\n    let path_without_generics = syn::Path {\n        leading_colon: None,\n        segments: Punctuated::from_iter(path_segments_without_generics),\n    };\n\n    Some(path_without_generics)\n}\n\n/// Returns the type inside the Option wrapper if it exists\nfn strip_option(type_: &Type) -> Option<Type> {\n    if let Type::Path(ty) = &type_ {\n        let mut segments_iter = ty.path.segments.iter().peekable();\n        // Strip any leading std||core::option:: prefix\n        let allowed_segments: &[&[&str]] = &[&[\"std\", \"core\"], &[\"option\"]];\n        let mut allowed_segments_iter = allowed_segments.iter();\n        while let Some(segment) = segments_iter.peek() {\n            let Some(allowed_segments) = allowed_segments_iter.next() else {\n                break;\n            };\n            if !allowed_segments.contains(&segment.ident.to_string().as_str()) {\n                break;\n            }\n            segments_iter.next();\n        }\n        // The last segment should be Option\n        let option_segment = segments_iter.next()?;\n        if option_segment.ident == \"Option\" && segments_iter.next().is_none() {\n            // It should have a single generic argument\n            if let PathArguments::AngleBracketed(generic_arg) = &option_segment.arguments {\n                if let Some(syn::GenericArgument::Type(ty)) = generic_arg.args.first() {\n                    return Some(ty.clone());\n                }\n            }\n        }\n    }\n    None\n}\n\n/// Remove the Option wrapper from a type\nfn remove_option_wrapper(type_: Type) -> Type {\n    strip_option(&type_).unwrap_or(type_)\n}\n\n/// Check if a type should be owned by the child component after conversion\nfn child_owned_type(ty: &Type) -> bool {\n    looks_like_signal_type(ty) || looks_like_write_type(ty) || looks_like_callback_type(ty)\n}\n\n/// Check if the path without generics matches the type we are looking for\nfn last_segment_matches(ty: &Type, expected: &Ident) -> bool {\n    extract_base_type_without_generics(ty).is_some_and(|path_without_generics| {\n        path_without_generics\n            .segments\n            .last()\n            .is_some_and(|seg| seg.ident == *expected)\n    })\n}\n\nfn looks_like_signal_type(ty: &Type) -> bool {\n    last_segment_matches(ty, &parse_quote!(ReadOnlySignal))\n        || last_segment_matches(ty, &parse_quote!(ReadSignal))\n}\n\nfn looks_like_write_type(ty: &Type) -> bool {\n    last_segment_matches(ty, &parse_quote!(WriteSignal))\n}\n\nfn looks_like_store_type(ty: &Type) -> bool {\n    last_segment_matches(ty, &parse_quote!(Store))\n        || last_segment_matches(ty, &parse_quote!(ReadStore))\n        || last_segment_matches(ty, &parse_quote!(WriteStore))\n}\n\nfn looks_like_callback_type(ty: &Type) -> bool {\n    let type_without_option = remove_option_wrapper(ty.clone());\n    last_segment_matches(&type_without_option, &parse_quote!(EventHandler))\n        || last_segment_matches(&type_without_option, &parse_quote!(Callback))\n}\n\n#[test]\nfn test_looks_like_type() {\n    assert!(!looks_like_signal_type(&parse_quote!(\n        Option<ReadOnlySignal<i32>>\n    )));\n    assert!(looks_like_signal_type(&parse_quote!(ReadOnlySignal<i32>)));\n    assert!(looks_like_signal_type(\n        &parse_quote!(ReadOnlySignal<i32, SyncStorage>)\n    ));\n    assert!(looks_like_signal_type(&parse_quote!(\n        ReadOnlySignal<Option<i32>, UnsyncStorage>\n    )));\n\n    assert!(!looks_like_signal_type(&parse_quote!(\n        Option<ReadSignal<i32>>\n    )));\n    assert!(looks_like_signal_type(&parse_quote!(ReadSignal<i32>)));\n    assert!(looks_like_signal_type(\n        &parse_quote!(ReadSignal<i32, SyncStorage>)\n    ));\n    assert!(looks_like_signal_type(&parse_quote!(\n        ReadSignal<Option<i32>, UnsyncStorage>\n    )));\n\n    assert!(looks_like_callback_type(&parse_quote!(\n        Option<EventHandler>\n    )));\n    assert!(looks_like_callback_type(&parse_quote!(\n        std::option::Option<EventHandler<i32>>\n    )));\n    assert!(looks_like_callback_type(&parse_quote!(\n        Option<EventHandler<MouseEvent>>\n    )));\n\n    assert!(looks_like_callback_type(&parse_quote!(EventHandler<i32>)));\n    assert!(looks_like_callback_type(&parse_quote!(EventHandler)));\n\n    assert!(looks_like_callback_type(&parse_quote!(Callback<i32>)));\n    assert!(looks_like_callback_type(&parse_quote!(Callback<i32, u32>)));\n}\n\n#[test]\nfn test_remove_option_wrapper() {\n    let type_without_option = remove_option_wrapper(parse_quote!(Option<i32>));\n    assert_eq!(type_without_option, parse_quote!(i32));\n\n    let type_without_option = remove_option_wrapper(parse_quote!(Option<Option<i32>>));\n    assert_eq!(type_without_option, parse_quote!(Option<i32>));\n\n    let type_without_option = remove_option_wrapper(parse_quote!(Option<Option<Option<i32>>>));\n    assert_eq!(type_without_option, parse_quote!(Option<Option<i32>>));\n}\n"
  },
  {
    "path": "packages/core-macro/src/utils.rs",
    "content": "use quote::ToTokens;\nuse syn::parse::{Parse, ParseStream};\nuse syn::spanned::Spanned;\nuse syn::{parse_quote, Expr, Lit, Meta, Token, Type};\n\n/// Attempts to convert the given literal to a string.\n/// Converts ints and floats to their base 10 counterparts.\n///\n/// Returns `None` if the literal is [`Lit::Verbatim`] or if the literal is [`Lit::ByteStr`]\n/// and the byte string could not be converted to UTF-8.\npub fn lit_to_string(lit: Lit) -> Option<String> {\n    match lit {\n        Lit::Str(l) => Some(l.value()),\n        Lit::ByteStr(l) => String::from_utf8(l.value()).ok(),\n        Lit::Byte(l) => Some(String::from(l.value() as char)),\n        Lit::Char(l) => Some(l.value().to_string()),\n        Lit::Int(l) => Some(l.base10_digits().to_string()),\n        Lit::Float(l) => Some(l.base10_digits().to_string()),\n        Lit::Bool(l) => Some(l.value().to_string()),\n        Lit::Verbatim(_) => None,\n        _ => None,\n    }\n}\n\npub fn format_type_string(ty: &Type) -> String {\n    let ty_unformatted = ty.into_token_stream().to_string();\n    let ty_unformatted = ty_unformatted.trim();\n\n    // simply remove all whitespace\n    let ty_formatted = ty_unformatted.replace(' ', \"\");\n\n    ty_formatted.to_string()\n}\n\n/// Represents the `#[deprecated]` attribute.\n///\n/// You can use the [`DeprecatedAttribute::from_meta`] function to try to parse an attribute to this struct.\n#[derive(Default)]\npub struct DeprecatedAttribute {\n    pub since: Option<String>,\n    pub note: Option<String>,\n}\n\nimpl DeprecatedAttribute {\n    /// Returns `None` if the given attribute was not a valid form of the `#[deprecated]` attribute.\n    pub fn from_meta(meta: &Meta) -> syn::Result<Self> {\n        if meta.path() != &parse_quote!(deprecated) {\n            return Err(syn::Error::new(\n                meta.span(),\n                \"attribute path is not `deprecated`\",\n            ));\n        }\n\n        match &meta {\n            Meta::Path(_) => Ok(Self::default()),\n            Meta::NameValue(name_value) => {\n                let Expr::Lit(expr_lit) = &name_value.value else {\n                    return Err(syn::Error::new(\n                        name_value.span(),\n                        \"literal in `deprecated` value must be a string\",\n                    ));\n                };\n\n                Ok(Self {\n                    since: None,\n                    note: lit_to_string(expr_lit.lit.clone()).map(|s| s.trim().to_string()),\n                })\n            }\n            Meta::List(list) => {\n                let parsed = list.parse_args::<DeprecatedAttributeArgsParser>()?;\n\n                Ok(Self {\n                    since: parsed.since.map(|s| s.trim().to_string()),\n                    note: parsed.note.map(|s| s.trim().to_string()),\n                })\n            }\n        }\n    }\n}\n\nmod kw {\n    use syn::custom_keyword;\n    custom_keyword!(since);\n    custom_keyword!(note);\n}\n\nstruct DeprecatedAttributeArgsParser {\n    since: Option<String>,\n    note: Option<String>,\n}\n\nimpl Parse for DeprecatedAttributeArgsParser {\n    fn parse(input: ParseStream) -> syn::Result<Self> {\n        let mut since: Option<String> = None;\n        let mut note: Option<String> = None;\n\n        if input.peek(kw::since) {\n            input.parse::<kw::since>()?;\n            input.parse::<Token![=]>()?;\n\n            since = lit_to_string(input.parse()?);\n        }\n\n        if input.peek(Token![,]) && input.peek2(kw::note) {\n            input.parse::<Token![,]>()?;\n            input.parse::<kw::note>()?;\n            input.parse::<Token![=]>()?;\n\n            note = lit_to_string(input.parse()?);\n        }\n\n        Ok(Self { since, note })\n    }\n}\n"
  },
  {
    "path": "packages/core-macro/tests/event_handler.rs",
    "content": "use dioxus::prelude::*;\n\n// This test just checks that event handlers compile without explicit type annotations\n// It will not actually run any code\n#[test]\n#[allow(unused)]\nfn event_handlers_compile() {\n    fn app() -> Element {\n        let mut todos = use_signal(String::new);\n        rsx! {\n            input {\n                // Normal event handlers work without explicit type annotations\n                oninput: move |evt| todos.set(evt.value()),\n            }\n            button {\n                // async event handlers work without explicit type annotations\n                onclick: |event| async move {\n                    println!(\"{event:?}\");\n                },\n            }\n\n            // New! You can now use async closures for custom event handlers!\n            // This shouldn't require an explicit type annotation\n            TakesEventHandler { onclick: |event| async move {\n                println!(\"{event:?}\");\n            } }\n            // Or you can accept a callback that returns a value\n            // This shouldn't require an explicit type annotation\n            TakesEventHandlerWithArg { double: move |value| (value * 2) as i32 }\n        }\n    }\n\n    #[component]\n    fn TakesEventHandler(onclick: EventHandler<MouseEvent>) -> Element {\n        rsx! {\n            button {\n                // You can pass in EventHandlers directly to events\n                onclick: onclick,\n                \"Click!\"\n            }\n            button {\n                // Or use the shorthand syntax\n                onclick,\n                \"Click!\"\n            }\n\n            // You should also be able to forward event handlers to other components with the shorthand syntax\n            TakesEventHandler {\n                onclick\n            }\n        }\n    }\n\n    #[component]\n    fn TakesEventHandlerWithArg(double: Callback<u32, i32>) -> Element {\n        let mut count = use_signal(|| 2);\n        rsx! {\n            button {\n                // Callbacks let you easily inject custom logic into your components\n                onclick: move |_| count.set(double(count()) as u32),\n                \"{count}\"\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "packages/core-macro/tests/generics.rs",
    "content": "use std::fmt::Display;\n\nuse dioxus::prelude::*;\n\n// This test just checks that props compile with generics\n// It will not actually run any code\n#[test]\n#[allow(unused)]\n#[allow(non_snake_case)]\nfn generic_props_compile() {\n    fn app() -> Element {\n        rsx! {\n            TakesClone {\n                value: \"hello world\"\n            }\n            TakesCloneManual {\n                value: \"hello world\"\n            }\n            TakesCloneManualWhere {\n                value: \"hello world\"\n            }\n            GenericFnWhereClause {\n                value: \"hello world\"\n            }\n        }\n    }\n\n    #[component]\n    fn TakesClone<T: Clone + PartialEq + 'static>(value: T) -> Element {\n        rsx! {}\n    }\n\n    #[component]\n    fn TakesCloneArc<T: PartialEq + 'static>(value: std::sync::Arc<T>) -> Element {\n        rsx! {}\n    }\n\n    struct MyBox<T>(std::marker::PhantomData<T>);\n\n    impl<T: Display> Clone for MyBox<T> {\n        fn clone(&self) -> Self {\n            MyBox(std::marker::PhantomData)\n        }\n    }\n\n    impl<T: Display> PartialEq for MyBox<T> {\n        fn eq(&self, _: &Self) -> bool {\n            true\n        }\n    }\n\n    #[component]\n    #[allow(clippy::multiple_bound_locations)]\n    fn TakesCloneMyBox<T: 'static>(value: MyBox<T>) -> Element\n    where\n        T: Display,\n    {\n        rsx! {}\n    }\n\n    #[derive(Props, Clone, PartialEq)]\n    struct TakesCloneManualProps<T: Clone + PartialEq + 'static> {\n        value: T,\n    }\n\n    fn TakesCloneManual<T: Clone + PartialEq>(props: TakesCloneManualProps<T>) -> Element {\n        rsx! {}\n    }\n\n    #[derive(Props, Clone, PartialEq)]\n    struct TakesCloneManualWhereProps<T>\n    where\n        T: Clone + PartialEq + 'static,\n    {\n        value: T,\n    }\n\n    fn TakesCloneManualWhere<T: Clone + PartialEq>(\n        props: TakesCloneManualWhereProps<T>,\n    ) -> Element {\n        rsx! {}\n    }\n\n    #[derive(Props, Clone, PartialEq)]\n    struct TakesCloneManualWhereWithOwnerProps<T>\n    where\n        T: Clone + PartialEq + 'static,\n    {\n        value: EventHandler<T>,\n    }\n\n    fn TakesCloneManualWhereWithOwner<T: Clone + PartialEq>(\n        props: TakesCloneManualWhereWithOwnerProps<T>,\n    ) -> Element {\n        rsx! {}\n    }\n\n    #[component]\n    fn GenericFnWhereClause<T>(value: T) -> Element\n    where\n        T: Clone + PartialEq + Display + 'static,\n    {\n        rsx! {\n            p { \"{value}\" }\n        }\n    }\n}\n"
  },
  {
    "path": "packages/core-macro/tests/rsx/trailing-comma-0.rs",
    "content": "// Given an `rsx!` invocation with a missing trailing comma,\n// ensure the stderr output has an informative span.\n\nuse dioxus::prelude::*;\n\nfn main() {\n    rsx! {\n        p {\n            class: \"foo bar\"\n            \"Hello world\"\n        }\n    };\n}\n"
  },
  {
    "path": "packages/core-macro/tests/rsx/trailing-comma-0.stderr",
    "content": "error: Attributes must be separated by commas\n       = help: Did you forget a comma?\n --> tests/rsx/trailing-comma-0.rs:9:13\n  |\n9 |             class: \"foo bar\"\n  |             ^^^^^\n"
  },
  {
    "path": "packages/core-macro/tests/rsx.rs",
    "content": "#[test]\nfn rsx() {\n    let t = trybuild::TestCases::new();\n    t.compile_fail(\"tests/rsx/trailing-comma-0.rs\");\n}\n\n/// This test ensures that automatic `into` conversion occurs for default values.\n///\n/// These are compile-time tests.\n/// See https://github.com/DioxusLabs/dioxus/issues/2373\n#[cfg(test)]\nmod test_default_into {\n    use dioxus::prelude::*;\n\n    #[derive(Props, Clone, PartialEq)]\n    struct MyCoolProps {\n        // Test different into configurations\n        #[props(into, default = true)]\n        pub val_into_w_default_val: u16,\n\n        #[props(into, default)]\n        pub val_into_w_default: u16,\n\n        #[props(default = true.into())]\n        pub val_default: u16,\n\n        // Test different into configurations with strings\n        #[props(into, default = \"abc\")]\n        pub str_into_w_default_val: String,\n\n        #[props(into, default)]\n        pub str_into_w_default: String,\n\n        #[props(default = \"abc\".into())]\n        pub str_default: String,\n\n        // Test options\n        #[props(into, default = Some(\"abc\".into()))]\n        pub opt_into_w_default_val: Option<String>,\n\n        #[props(into, default)]\n        pub opt_into_w_default: Option<String>,\n\n        #[props(default = Some(\"abc\".into()))]\n        pub opt_default: Option<String>,\n\n        pub opt_element: Option<Element>,\n\n        // Test no default\n        #[props(into)]\n        pub some_data: bool,\n\n        pub some_other_data: bool,\n\n        // Test default values for signals\n        #[props(default)]\n        read_only_w_default: ReadSignal<bool>,\n\n        #[props(default = true)]\n        read_only_w_default_val: ReadSignal<bool>,\n\n        #[props(default = ReadSignal::new(Signal::new(true)))]\n        read_only_w_default_val_explicit: ReadSignal<bool>,\n\n        // Test default values for callbacks/event handlers\n        #[props(default)]\n        callback_w_default: Callback,\n\n        #[props(default = move |_| {})]\n        callback_w_default_val_closure: Callback,\n\n        #[props(default = {\n            fn test(_: ()) {}\n            test\n        })]\n        callback_w_default_val_expr_fn: Callback,\n\n        #[props(default = Callback::new(move |_: ()| {}))]\n        callback_w_default_val_explicit: Callback,\n\n        #[props(default)]\n        event_handler_w_default: EventHandler<KeyboardEvent>,\n\n        #[props(default = move |_| {})]\n        event_handler_w_default_val_closure: EventHandler<KeyboardEvent>,\n\n        #[props(default = {\n            fn test(_: KeyboardEvent) {}\n            test\n        })]\n        event_handler_w_default_val_expr_fn: EventHandler<KeyboardEvent>,\n\n        #[props(default = EventHandler::new(move |_: KeyboardEvent| {}))]\n        event_handler_w_default_val_explicit: EventHandler<KeyboardEvent>,\n    }\n}\n\n/// This test ensures that read-only signals that contain an option (`Signal<Option<u16>>`)\n/// are correctly created as default when not provided.\n///\n/// These are compile-time tests.\n/// See https://github.com/DioxusLabs/dioxus/issues/2648\n#[cfg(test)]\n#[allow(unused)]\nmod test_optional_signals {\n    use dioxus::prelude::*;\n\n    // Test if test components fail to compile.\n    #[component]\n    fn UsesComponents() -> Element {\n        rsx! {\n            PropsStruct {\n                regular_read_signal: ReadSignal::new(Signal::new(1234)),\n            }\n            PropsStruct {\n                optional_read_signal: 1234,\n                regular_read_signal: 123u16,\n            }\n            PropParams {}\n            PropParams {\n                opt_read_sig: 1234\n            }\n            DoubleOption {}\n            DoubleOption { optional: Some(1234) }\n        }\n    }\n\n    // Test props as struct param.\n    #[derive(Props, Clone, PartialEq)]\n    struct MyTestProps {\n        pub optional_read_signal: ReadSignal<Option<u16>>,\n        pub regular_read_signal: ReadSignal<u16>,\n    }\n\n    #[component]\n    fn PropsStruct(props: MyTestProps) -> Element {\n        rsx! { \"hi\" }\n    }\n\n    // Test props as params.\n    #[component]\n    fn PropParams(opt_read_sig: ReadSignal<Option<u16>>) -> Element {\n        rsx! { \"hi!\" }\n    }\n\n    #[component]\n    fn DoubleOption(optional: Option<Option<u16>>) -> Element {\n        rsx! { \"hi!\" }\n    }\n}\n"
  },
  {
    "path": "packages/core-macro/tests/values_memoize_in_place.rs",
    "content": "use dioxus::{\n    core::{generation, needs_update},\n    prelude::*,\n};\nuse dioxus_core::ElementId;\nuse std::{any::Any, rc::Rc};\n\n#[tokio::test]\nasync fn values_memoize_in_place() {\n    thread_local! {\n        static DROP_COUNT: std::cell::RefCell<usize> = const { std::cell::RefCell::new(0) };\n    }\n\n    struct CountsDrop;\n\n    impl Drop for CountsDrop {\n        fn drop(&mut self) {\n            DROP_COUNT.with(|c| *c.borrow_mut() += 1);\n        }\n    }\n\n    fn app() -> Element {\n        let mut count = use_signal(|| 0);\n        let x = CountsDrop;\n\n        use_hook(|| {\n            spawn(async move {\n                for _ in 0..15 {\n                    tokio::time::sleep(std::time::Duration::from_millis(1)).await;\n                    count += 1;\n                }\n            });\n        });\n\n        rsx! {\n            TakesEventHandler {\n                click: move |num| {\n                    // Force the closure to own the drop counter\n                    let _ = &x;\n                    println!(\"num is {num}\");\n                },\n                number: count() / 2\n            }\n            TakesSignal { sig: count(), number: count() / 2 }\n        }\n    }\n\n    set_event_converter(Box::new(dioxus::html::SerializedHtmlEventConverter));\n    let mut dom = VirtualDom::new(app);\n\n    let mutations = dom.rebuild_to_vec();\n    println!(\"{:#?}\", mutations);\n    dom.mark_dirty(ScopeId::APP);\n    for _ in 0..40 {\n        let event = Event::new(\n            Rc::new(PlatformEventData::new(Box::<SerializedMouseData>::default())) as Rc<dyn Any>,\n            true,\n        );\n        dom.runtime().handle_event(\"click\", event, ElementId(1));\n        tokio::select! {\n            _ = tokio::time::sleep(std::time::Duration::from_millis(20)) => {},\n            _ = dom.wait_for_work() => {}\n        }\n        dom.render_immediate(&mut dioxus_core::NoOpMutations);\n    }\n    dom.render_immediate(&mut dioxus_core::NoOpMutations);\n    // As we rerun the app, the drop count should be 15 one for each render of the app component\n    let drop_count = DROP_COUNT.with(|c| *c.borrow());\n    assert_eq!(drop_count, 16);\n}\n\n// We move over event handlers in place. Make sure we do that in a way that doesn't destroy the original event handler\n#[test]\nfn cloning_event_handler_components_work() {\n    fn app() -> Element {\n        let rsx_with_event_handler_component = rsx! {\n            TakesEventHandler {\n                click: move |evt| {\n                    println!(\"Clicked {evt:?}!\");\n                },\n                number: 0\n            }\n        };\n\n        rsx! {\n            {rsx_with_event_handler_component.clone()}\n            {rsx_with_event_handler_component.clone()}\n            {rsx_with_event_handler_component.clone()}\n            {rsx_with_event_handler_component}\n        }\n    }\n\n    set_event_converter(Box::new(dioxus::html::SerializedHtmlEventConverter));\n    let mut dom = VirtualDom::new(app);\n\n    let mutations = dom.rebuild_to_vec();\n    println!(\"{:#?}\", mutations);\n    dom.mark_dirty(ScopeId::APP);\n    for _ in 0..20 {\n        let event = Event::new(\n            Rc::new(PlatformEventData::new(Box::<SerializedMouseData>::default())) as Rc<dyn Any>,\n            true,\n        );\n        dom.runtime().handle_event(\"click\", event, ElementId(1));\n        dom.render_immediate(&mut dioxus_core::NoOpMutations);\n    }\n    dom.render_immediate(&mut dioxus_core::NoOpMutations);\n}\n\n#[component]\nfn TakesEventHandler(click: EventHandler<usize>, number: usize) -> Element {\n    let first_render_click = use_hook(move || click);\n    if generation() > 0 {\n        // Make sure the event handler is memoized in place and never gets dropped\n        first_render_click(number);\n    }\n\n    rsx! {\n        button {\n            onclick: move |_| click(number),\n            \"{number}\"\n        }\n    }\n}\n\n#[component]\nfn TakesSignal(sig: ReadSignal<usize>, number: usize) -> Element {\n    let first_render_sig = use_hook(move || sig);\n    if generation() > 0 {\n        // Make sure the signal is memoized in place and never gets dropped\n        println!(\"{first_render_sig}\");\n    }\n\n    rsx! {\n        button { \"{number}\" }\n    }\n}\n\n// Regression test for https://github.com/DioxusLabs/dioxus/issues/2582\n#[test]\nfn spreads_memorize_in_place() {\n    use dioxus_core::Properties;\n\n    #[derive(Props, Clone, PartialEq)]\n    struct CompProps {\n        #[props(extends = GlobalAttributes)]\n        attributes: Vec<Attribute>,\n    }\n\n    let mut props = CompProps::builder().build();\n    assert!(!props.memoize(&CompProps::builder().all(\"123\").build()));\n    assert_eq!(\n        props.attributes,\n        vec![Attribute::new(\"all\", \"123\", Some(\"style\"), false)]\n    );\n\n    assert!(!props.memoize(&CompProps::builder().width(\"123\").build()));\n    assert_eq!(\n        props.attributes,\n        vec![Attribute::new(\"width\", \"123\", Some(\"style\"), false)]\n    );\n\n    assert!(!props.memoize(&CompProps::builder().build()));\n    assert_eq!(props.attributes, vec![]);\n\n    assert!(props.memoize(&CompProps::builder().build()));\n    assert_eq!(props.attributes, vec![]);\n}\n\n// Regression test for https://github.com/DioxusLabs/dioxus/issues/2331\n#[test]\nfn cloning_read_signal_components_work() {\n    fn app() -> Element {\n        if generation() < 5 {\n            println!(\"Generating new props\");\n            needs_update();\n        }\n\n        let read_signal_rsx = rsx! {\n            TakesReadSignalNonClone { sig: NonCloneable(generation() as i32) }\n            TakesReadSignalNum { sig: generation() as i32 }\n        };\n\n        rsx! {\n            {read_signal_rsx.clone()}\n            {read_signal_rsx}\n        }\n    }\n\n    struct NonCloneable<T>(T);\n\n    #[component]\n    fn TakesReadSignalNum(sig: ReadSignal<i32>) -> Element {\n        rsx! {}\n    }\n\n    #[component]\n    fn TakesReadSignalNonClone(sig: ReadSignal<NonCloneable<i32>>) -> Element {\n        rsx! {}\n    }\n\n    set_event_converter(Box::new(dioxus::html::SerializedHtmlEventConverter));\n    let mut dom = VirtualDom::new(app);\n\n    let mutations = dom.rebuild_to_vec();\n    println!(\"{:#?}\", mutations);\n    dom.mark_dirty(ScopeId::APP);\n    for _ in 0..20 {\n        let event = Event::new(\n            Rc::new(PlatformEventData::new(Box::<SerializedMouseData>::default())) as Rc<dyn Any>,\n            true,\n        );\n        dom.runtime().handle_event(\"click\", event, ElementId(1));\n        dom.render_immediate(&mut dioxus_core::NoOpMutations);\n    }\n    dom.render_immediate(&mut dioxus_core::NoOpMutations);\n}\n\n// Regression test for https://github.com/DioxusLabs/dioxus/issues/5222\n#[tokio::test]\nasync fn optional_event_handler_diff() {\n    use dioxus_core::Properties;\n\n    #[derive(Props, Clone, PartialEq)]\n    struct CompProps {\n        callback: Option<Callback>,\n    }\n\n    let dom = VirtualDom::new(|| rsx! {});\n\n    dom.in_scope(ScopeId::APP, || {\n        // Diffing from None to Some should be different and copy the callback\n        let mut props = CompProps::builder().callback(None).build();\n        assert!(!props.memoize(&CompProps::builder().callback(|_| {}).build()));\n        assert!(props.inner.callback.is_some());\n\n        // Diffing from Some to None should be different and remove the callback\n        let mut props = CompProps::builder().callback(|_| {}).build();\n        assert!(!props.memoize(&CompProps::builder().callback(None).build()));\n        assert!(props.inner.callback.is_none());\n    });\n}\n"
  },
  {
    "path": "packages/core-types/Cargo.toml",
    "content": "[package]\nname = \"dioxus-core-types\"\nedition = \"2021\"\nversion.workspace = true\nauthors = [\"Jonathan Kelley\", \"Evan Almloff\"]\nlicense = \"MIT OR Apache-2.0\"\nrepository = \"https://github.com/DioxusLabs/dioxus/\"\nhomepage = \"https://dioxuslabs.com\"\ndescription = \"CLI Configuration for dioxus-cli\"\nkeywords = [\"dom\", \"ui\", \"gui\", \"react\", ]\n\n\n[dependencies]\n"
  },
  {
    "path": "packages/core-types/src/attributes.rs",
    "content": "\n"
  },
  {
    "path": "packages/core-types/src/bubbles.rs",
    "content": "/// Check if the event bubbles\n///\n/// todo: this should not be in this crate, but this crate is a \"root\" crate and\n/// has zero-deps, meaning it gets compiled before anything else.\n///\n/// This function being here means we can use it in the interpreter without pulling in dioxus-html,\n/// drastically shortening the crate graph and thus compile times\n///\n/// The real solution to this problem is that events need to mark themselves as \"bubbling\" or \"not bubbling\"\n/// in their definition, which gets passed as part of the mutations.\npub fn event_bubbles(evt: &str) -> bool {\n    match evt {\n        \"cancel\" => false,\n        \"copy\" => true,\n        \"cut\" => true,\n        \"paste\" => true,\n        \"compositionend\" => true,\n        \"compositionstart\" => true,\n        \"compositionupdate\" => true,\n        \"keydown\" => true,\n        \"keypress\" => true,\n        \"keyup\" => true,\n        \"focus\" => false,\n        \"focusout\" => true,\n        \"focusin\" => true,\n        \"blur\" => false,\n        \"change\" => true,\n        \"input\" => true,\n        \"invalid\" => true,\n        \"reset\" => true,\n        \"submit\" => true,\n        \"auxclick\" => true,\n        \"click\" => true,\n        \"contextmenu\" => true,\n        \"doubleclick\" => true,\n        \"dblclick\" => true,\n        \"drag\" => true,\n        \"dragend\" => true,\n        \"dragenter\" => false,\n        \"dragexit\" => false,\n        \"dragleave\" => true,\n        \"dragover\" => true,\n        \"dragstart\" => true,\n        \"drop\" => true,\n        \"mousedown\" => true,\n        \"mouseenter\" => false,\n        \"mouseleave\" => false,\n        \"mousemove\" => true,\n        \"mouseout\" => true,\n        \"scroll\" => false,\n        \"scrollend\" => false,\n        \"mouseover\" => true,\n        \"mouseup\" => true,\n        \"pointerdown\" => true,\n        \"pointermove\" => true,\n        \"pointerup\" => true,\n        \"pointercancel\" => true,\n        \"gotpointercapture\" => true,\n        \"lostpointercapture\" => true,\n        \"pointerenter\" => false,\n        \"pointerleave\" => false,\n        \"pointerover\" => true,\n        \"pointerout\" => true,\n        \"select\" => true,\n        \"touchcancel\" => true,\n        \"touchend\" => true,\n        \"touchmove\" => true,\n        \"touchstart\" => true,\n        \"wheel\" => true,\n        \"abort\" => false,\n        \"canplay\" => false,\n        \"canplaythrough\" => false,\n        \"durationchange\" => false,\n        \"emptied\" => false,\n        \"encrypted\" => true,\n        \"ended\" => false,\n        \"error\" => false,\n        \"loadeddata\" => false,\n        \"loadedmetadata\" => false,\n        \"loadstart\" => false,\n        \"load\" => false,\n        \"pause\" => false,\n        \"play\" => false,\n        \"playing\" => false,\n        \"progress\" => false,\n        \"ratechange\" => false,\n        \"resize\" => false,\n        \"seeked\" => false,\n        \"seeking\" => false,\n        \"stalled\" => false,\n        \"suspend\" => false,\n        \"timeupdate\" => false,\n        \"volumechange\" => false,\n        \"waiting\" => false,\n        \"animationstart\" => true,\n        \"animationend\" => true,\n        \"animationiteration\" => true,\n        \"transitionend\" => true,\n        \"toggle\" => false,\n        \"beforetoggle\" => false,\n        \"mounted\" => false,\n        \"visible\" => false,\n        _ => true,\n    }\n}\n"
  },
  {
    "path": "packages/core-types/src/bundled.rs",
    "content": "use std::sync::LazyLock;\n\npub fn is_bundled_app() -> bool {\n    static BUNDLED: LazyLock<bool> = LazyLock::new(|| {\n        // If the env var is set, we're bundled\n        if std::env::var(\"DIOXUS_CLI_ENABLED\").is_ok() {\n            return true;\n        }\n\n        // If the cargo manifest dir is set, we're not bundled.\n        // Generally this is only set with `cargo run`\n        if let Ok(path) = std::env::var(\"CARGO_MANIFEST_DIR\") {\n            if !path.is_empty() {\n                return false;\n            }\n        }\n\n        true\n    });\n\n    *BUNDLED\n}\n"
  },
  {
    "path": "packages/core-types/src/formatter.rs",
    "content": "use std::borrow::Cow;\n\n/// Take this type and format it into a Cow<'static, str>\n///\n/// This trait exists so libraries like manganis can implement this type for assets without depending\n/// on dioxus-core, which can be heavy in proc macros and build scripts.\n///\n/// We don't want a blanket impl for T: Display because that might conflict for the other integral data\n/// types of AttributeValue\n///\n/// Todo: we might be able to specialize without this just with Display.\npub trait DioxusFormattable {\n    fn format(&self) -> Cow<'static, str>;\n}\n"
  },
  {
    "path": "packages/core-types/src/hr_context.rs",
    "content": "pub trait HotReloadingContext {\n    fn map_attribute(\n        element_name_rust: &str,\n        attribute_name_rust: &str,\n    ) -> Option<(&'static str, Option<&'static str>)>;\n    fn map_element(element_name_rust: &str) -> Option<(&'static str, Option<&'static str>)>;\n}\n\npub struct Empty;\n\nimpl HotReloadingContext for Empty {\n    fn map_attribute(_: &str, _: &str) -> Option<(&'static str, Option<&'static str>)> {\n        None\n    }\n\n    fn map_element(_: &str) -> Option<(&'static str, Option<&'static str>)> {\n        None\n    }\n}\n"
  },
  {
    "path": "packages/core-types/src/lib.rs",
    "content": "pub mod bubbles;\npub mod bundled;\npub mod formatter;\npub mod hr_context;\n\npub use bubbles::*;\npub use bundled::*;\npub use formatter::*;\npub use hr_context::*;\n"
  },
  {
    "path": "packages/depinfo/Cargo.toml",
    "content": "[package]\nname = \"depinfo\"\nversion.workspace = true\nedition = \"2021\"\nauthors = [\"Jonathan Kelley\"]\nlicense = \"MIT OR Apache-2.0\"\nrepository = \"https://github.com/DioxusLabs/dioxus/\"\nhomepage = \"https://dioxuslabs.com\"\ndescription = \"Depinfo parser for Rust\"\nkeywords = [\"rustc\", \"cargo\", \"dep-info\" ]\n\n[dependencies]\nthiserror = { workspace = true }\n\n"
  },
  {
    "path": "packages/depinfo/README.md",
    "content": "# Rustc Dep-Info Parser\n\nThis crate parses the output of rustc's `.d` dep-info file. It is used by the hot-reloading engine and other libraries to provide higher quality dependency analysis for the user's project.\n\n## License\n\nThis project is licensed under either the [MIT license] or the [Apache-2 License].\n\n[apache-2 license]: https://github.com/DioxusLabs/dioxus/blob/master/LICENSE-APACHE\n[mit license]: https://github.com/DioxusLabs/dioxus/blob/master/LICENSE-MIT\n\nUnless you explicitly state otherwise, any contribution intentionally submitted\nfor inclusion in Dioxus by you, shall be licensed as MIT or Apache-2, without any additional\nterms or conditions.\n"
  },
  {
    "path": "packages/depinfo/src/dx.d",
    "content": "/dioxus/target/debug/dx: /dioxus/packages/autofmt/README.md /dioxus/packages/autofmt/src/buffer.rs /dioxus/packages/autofmt/src/collect_macros.rs /dioxus/packages/autofmt/src/indent.rs /dioxus/packages/autofmt/src/lib.rs /dioxus/packages/autofmt/src/prettier_please.rs /dioxus/packages/autofmt/src/writer.rs /dioxus/packages/check/README.md /dioxus/packages/check/src/check.rs /dioxus/packages/check/src/issues.rs /dioxus/packages/check/src/lib.rs /dioxus/packages/check/src/metadata.rs /dioxus/packages/cli/README.md /dioxus/packages/cli/assets/android/MainActivity.kt.hbs /dioxus/packages/cli/assets/android/gen/app/build.gradle.kts.hbs /dioxus/packages/cli/assets/android/gen/app/proguard-rules.pro /dioxus/packages/cli/assets/android/gen/app/src/main/AndroidManifest.xml.hbs /dioxus/packages/cli/assets/android/gen/app/src/main/res/drawable/ic_launcher_background.xml /dioxus/packages/cli/assets/android/gen/app/src/main/res/drawable-v24/ic_launcher_foreground.xml /dioxus/packages/cli/assets/android/gen/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml /dioxus/packages/cli/assets/android/gen/app/src/main/res/mipmap-hdpi/ic_launcher.webp /dioxus/packages/cli/assets/android/gen/app/src/main/res/mipmap-mdpi/ic_launcher.webp /dioxus/packages/cli/assets/android/gen/app/src/main/res/mipmap-xhdpi/ic_launcher.webp /dioxus/packages/cli/assets/android/gen/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp /dioxus/packages/cli/assets/android/gen/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp /dioxus/packages/cli/assets/android/gen/app/src/main/res/values/colors.xml /dioxus/packages/cli/assets/android/gen/app/src/main/res/values/strings.xml.hbs /dioxus/packages/cli/assets/android/gen/app/src/main/res/values/styles.xml /dioxus/packages/cli/assets/android/gen/build.gradle.kts /dioxus/packages/cli/assets/android/gen/gradle/wrapper/gradle-wrapper.jar /dioxus/packages/cli/assets/android/gen/gradle/wrapper/gradle-wrapper.properties /dioxus/packages/cli/assets/android/gen/gradle.properties /dioxus/packages/cli/assets/android/gen/gradlew /dioxus/packages/cli/assets/android/gen/gradlew.bat /dioxus/packages/cli/assets/android/gen/settings.gradle /dioxus/packages/cli/assets/dioxus.toml /dioxus/packages/cli/assets/ios/ios.plist.hbs /dioxus/packages/cli/assets/macos/mac.plist.hbs /dioxus/packages/cli/assets/web/index.html /dioxus/packages/cli/assets/web/loading.html /dioxus/packages/cli/assets/web/toast.html /dioxus/packages/cli/build.rs /dioxus/packages/cli/src/build/builder.rs /dioxus/packages/cli/src/build/bundle.rs /dioxus/packages/cli/src/build/mod.rs /dioxus/packages/cli/src/build/prerender.rs /dioxus/packages/cli/src/build/progress.rs /dioxus/packages/cli/src/build/request.rs /dioxus/packages/cli/src/build/templates.rs /dioxus/packages/cli/src/build/verify.rs /dioxus/packages/cli/src/build/web.rs /dioxus/packages/cli/src/bundle_utils.rs /dioxus/packages/cli/src/cli/autoformat.rs /dioxus/packages/cli/src/cli/build.rs /dioxus/packages/cli/src/cli/bundle.rs /dioxus/packages/cli/src/cli/check.rs /dioxus/packages/cli/src/cli/clean.rs /dioxus/packages/cli/src/cli/config.rs /dioxus/packages/cli/src/cli/create.rs /dioxus/packages/cli/src/cli/init.rs /dioxus/packages/cli/src/cli/link.rs /dioxus/packages/cli/src/cli/mod.rs /dioxus/packages/cli/src/cli/run.rs /dioxus/packages/cli/src/cli/serve.rs /dioxus/packages/cli/src/cli/target.rs /dioxus/packages/cli/src/cli/translate.rs /dioxus/packages/cli/src/cli/verbosity.rs /dioxus/packages/cli/src/config/app.rs /dioxus/packages/cli/src/config/bundle.rs /dioxus/packages/cli/src/config/desktop.rs /dioxus/packages/cli/src/config/dioxus_config.rs /dioxus/packages/cli/src/config/serve.rs /dioxus/packages/cli/src/config/web.rs /dioxus/packages/cli/src/config.rs /dioxus/packages/cli/src/dioxus_crate.rs /dioxus/packages/cli/src/dx_build_info.rs /dioxus/packages/cli/src/error.rs /dioxus/packages/cli/src/fastfs.rs /dioxus/packages/cli/src/filemap.rs /dioxus/packages/cli/src/logging.rs /dioxus/packages/cli/src/main.rs /dioxus/packages/cli/src/metadata.rs /dioxus/packages/cli/src/platform.rs /dioxus/packages/cli/src/rustc.rs /dioxus/packages/cli/src/serve/ansi_buffer.rs /dioxus/packages/cli/src/serve/detect.rs /dioxus/packages/cli/src/serve/handle.rs /dioxus/packages/cli/src/serve/mod.rs /dioxus/packages/cli/src/serve/output.rs /dioxus/packages/cli/src/serve/proxy.rs /dioxus/packages/cli/src/serve/runner.rs /dioxus/packages/cli/src/serve/server.rs /dioxus/packages/cli/src/serve/update.rs /dioxus/packages/cli/src/serve/watcher.rs /dioxus/packages/cli/src/settings.rs /dioxus/packages/cli/src/wasm_bindgen.rs /dioxus/packages/cli-config/src/lib.rs /dioxus/packages/cli-opt/src/css.rs /dioxus/packages/cli-opt/src/file.rs /dioxus/packages/cli-opt/src/folder.rs /dioxus/packages/cli-opt/src/image/jpg.rs /dioxus/packages/cli-opt/src/image/mod.rs /dioxus/packages/cli-opt/src/image/png.rs /dioxus/packages/cli-opt/src/js.rs /dioxus/packages/cli-opt/src/json.rs /dioxus/packages/cli-opt/src/lib.rs /dioxus/packages/config-macro/README.md /dioxus/packages/config-macro/src/lib.rs /dioxus/packages/const-serialize/README.md /dioxus/packages/const-serialize/src/const_buffers.rs /dioxus/packages/const-serialize/src/const_vec.rs /dioxus/packages/const-serialize/src/lib.rs /dioxus/packages/const-serialize-macro/src/lib.rs /dioxus/packages/core/README.md /dioxus/packages/core/docs/common_spawn_errors.md /dioxus/packages/core/docs/reactivity.md /dioxus/packages/core/src/any_props.rs /dioxus/packages/core/src/arena.rs /dioxus/packages/core/src/diff/component.rs /dioxus/packages/core/src/diff/iterator.rs /dioxus/packages/core/src/diff/mod.rs /dioxus/packages/core/src/diff/node.rs /dioxus/packages/core/src/effect.rs /dioxus/packages/core/src/error_boundary.rs /dioxus/packages/core/src/events.rs /dioxus/packages/core/src/fragment.rs /dioxus/packages/core/src/generational_box.rs /dioxus/packages/core/src/global_context.rs /dioxus/packages/core/src/hotreload_utils.rs /dioxus/packages/core/src/launch.rs /dioxus/packages/core/src/lib.rs /dioxus/packages/core/src/mutations.rs /dioxus/packages/core/src/nodes.rs /dioxus/packages/core/src/properties.rs /dioxus/packages/core/src/reactive_context.rs /dioxus/packages/core/src/render_error.rs /dioxus/packages/core/src/root_wrapper.rs /dioxus/packages/core/src/runtime.rs /dioxus/packages/core/src/scheduler.rs /dioxus/packages/core/src/scope_arena.rs /dioxus/packages/core/src/scope_context.rs /dioxus/packages/core/src/scopes.rs /dioxus/packages/core/src/suspense/component.rs /dioxus/packages/core/src/suspense/mod.rs /dioxus/packages/core/src/tasks.rs /dioxus/packages/core/src/virtual_dom.rs /dioxus/packages/core-macro/README.md /dioxus/packages/core-macro/docs/component.md /dioxus/packages/core-macro/docs/props.md /dioxus/packages/core-macro/docs/rsx.md /dioxus/packages/core-macro/src/component.rs /dioxus/packages/core-macro/src/lib.rs /dioxus/packages/core-macro/src/props/mod.rs /dioxus/packages/core-macro/src/utils.rs /dioxus/packages/core-types/src/bubbles.rs /dioxus/packages/core-types/src/bundled.rs /dioxus/packages/core-types/src/formatter.rs /dioxus/packages/core-types/src/hr_context.rs /dioxus/packages/core-types/src/lib.rs /dioxus/packages/devtools/src/lib.rs /dioxus/packages/devtools-types/src/lib.rs /dioxus/packages/dioxus-lib/README.md /dioxus/packages/dioxus-lib/src/lib.rs /dioxus/packages/document/build.rs /dioxus/packages/document/docs/eval.md /dioxus/packages/document/docs/head.md /dioxus/packages/document/src/document.rs /dioxus/packages/document/src/elements/link.rs /dioxus/packages/document/src/elements/meta.rs /dioxus/packages/document/src/elements/mod.rs /dioxus/packages/document/src/elements/script.rs /dioxus/packages/document/src/elements/style.rs /dioxus/packages/document/src/elements/stylesheet.rs /dioxus/packages/document/src/elements/title.rs /dioxus/packages/document/src/error.rs /dioxus/packages/document/src/eval.rs /dioxus/packages/document/src/js/head.js /dioxus/packages/document/src/lib.rs /dioxus/packages/document/./src/ts/eval.ts /dioxus/packages/document/./src/ts/head.ts /dioxus/packages/dx-wire-format/src/lib.rs /dioxus/packages/fullstack/README.md /dioxus/packages/fullstack/src/document/mod.rs /dioxus/packages/fullstack/src/hooks/mod.rs /dioxus/packages/fullstack/src/hooks/server_cached.rs /dioxus/packages/fullstack/src/hooks/server_future.rs /dioxus/packages/fullstack/src/html_storage/mod.rs /dioxus/packages/fullstack/src/lib.rs /dioxus/packages/generational-box/README.md /dioxus/packages/generational-box/src/entry.rs /dioxus/packages/generational-box/src/error.rs /dioxus/packages/generational-box/src/lib.rs /dioxus/packages/generational-box/src/references.rs /dioxus/packages/generational-box/src/sync.rs /dioxus/packages/generational-box/src/unsync.rs /dioxus/packages/history/src/lib.rs /dioxus/packages/history/src/memory.rs /dioxus/packages/hooks/README.md /dioxus/packages/hooks/docs/derived_state.md /dioxus/packages/hooks/docs/moving_state_around.md /dioxus/packages/hooks/docs/rules_of_hooks.md /dioxus/packages/hooks/docs/side_effects.md /dioxus/packages/hooks/docs/use_resource.md /dioxus/packages/hooks/src/lib.rs /dioxus/packages/hooks/src/use_callback.rs /dioxus/packages/hooks/src/use_context.rs /dioxus/packages/hooks/src/use_coroutine.rs /dioxus/packages/hooks/src/use_effect.rs /dioxus/packages/hooks/src/use_future.rs /dioxus/packages/hooks/src/use_hook_did_run.rs /dioxus/packages/hooks/src/use_memo.rs /dioxus/packages/hooks/src/use_on_destroy.rs /dioxus/packages/hooks/src/use_reactive.rs /dioxus/packages/hooks/src/use_resource.rs /dioxus/packages/hooks/src/use_root_context.rs /dioxus/packages/hooks/src/use_set_compare.rs /dioxus/packages/hooks/src/use_signal.rs /dioxus/packages/html/README.md /dioxus/packages/html/docs/common_event_handler_errors.md /dioxus/packages/html/docs/event_handlers.md /dioxus/packages/html/src/attribute_groups.rs /dioxus/packages/html/src/elements.rs /dioxus/packages/html/src/events/animation.rs /dioxus/packages/html/src/events/clipboard.rs /dioxus/packages/html/src/events/composition.rs /dioxus/packages/html/src/events/drag.rs /dioxus/packages/html/src/events/focus.rs /dioxus/packages/html/src/events/form.rs /dioxus/packages/html/src/events/image.rs /dioxus/packages/html/src/events/keyboard.rs /dioxus/packages/html/src/events/media.rs /dioxus/packages/html/src/events/mod.rs /dioxus/packages/html/src/events/mounted.rs /dioxus/packages/html/src/events/mouse.rs /dioxus/packages/html/src/events/pointer.rs /dioxus/packages/html/src/events/resize.rs /dioxus/packages/html/src/events/scroll.rs /dioxus/packages/html/src/events/selection.rs /dioxus/packages/html/src/events/toggle.rs /dioxus/packages/html/src/events/touch.rs /dioxus/packages/html/src/events/transition.rs /dioxus/packages/html/src/events/visible.rs /dioxus/packages/html/src/events/wheel.rs /dioxus/packages/html/src/file_data.rs /dioxus/packages/html/src/geometry.rs /dioxus/packages/html/src/input_data.rs /dioxus/packages/html/src/lib.rs /dioxus/packages/html/src/point_interaction.rs /dioxus/packages/html/src/render_template.rs /dioxus/packages/html-internal-macro/src/lib.rs /dioxus/packages/lazy-js-bundle/src/lib.rs /dioxus/packages/manganis/manganis/README.md /dioxus/packages/manganis/manganis/src/hash.rs /dioxus/packages/manganis/manganis/src/lib.rs /dioxus/packages/manganis/manganis/src/macro_helpers.rs /dioxus/packages/manganis/manganis-core/src/asset.rs /dioxus/packages/manganis/manganis-core/src/css.rs /dioxus/packages/manganis/manganis-core/src/folder.rs /dioxus/packages/manganis/manganis-core/src/hash.rs /dioxus/packages/manganis/manganis-core/src/images.rs /dioxus/packages/manganis/manganis-core/src/js.rs /dioxus/packages/manganis/manganis-core/src/lib.rs /dioxus/packages/manganis/manganis-core/src/linker.rs /dioxus/packages/manganis/manganis-core/src/options.rs /dioxus/packages/manganis/manganis-macro/README.md /dioxus/packages/manganis/manganis-macro/src/asset.rs /dioxus/packages/manganis/manganis-macro/src/lib.rs /dioxus/packages/manganis/manganis-macro/src/linker.rs /dioxus/packages/rsx/src/assign_dyn_ids.rs /dioxus/packages/rsx/src/attribute.rs /dioxus/packages/rsx/src/component.rs /dioxus/packages/rsx/src/diagnostics.rs /dioxus/packages/rsx/src/element.rs /dioxus/packages/rsx/src/expr_node.rs /dioxus/packages/rsx/src/forloop.rs /dioxus/packages/rsx/src/ifchain.rs /dioxus/packages/rsx/src/ifmt.rs /dioxus/packages/rsx/src/lib.rs /dioxus/packages/rsx/src/literal.rs /dioxus/packages/rsx/src/location.rs /dioxus/packages/rsx/src/node.rs /dioxus/packages/rsx/src/partial_closure.rs /dioxus/packages/rsx/src/raw_expr.rs /dioxus/packages/rsx/src/rsx_block.rs /dioxus/packages/rsx/src/rsx_call.rs /dioxus/packages/rsx/src/template_body.rs /dioxus/packages/rsx/src/text_node.rs /dioxus/packages/rsx/src/util.rs /dioxus/packages/rsx-hotreload/src/collect.rs /dioxus/packages/rsx-hotreload/src/diff.rs /dioxus/packages/rsx-hotreload/src/extensions.rs /dioxus/packages/rsx-hotreload/src/last_build_state.rs /dioxus/packages/rsx-hotreload/src/lib.rs /dioxus/packages/rsx-rosetta/README.md /dioxus/packages/rsx-rosetta/src/lib.rs /dioxus/packages/server-macro/src/lib.rs /dioxus/packages/signals/README.md /dioxus/packages/signals/docs/hoist/error.rs /dioxus/packages/signals/docs/hoist/fixed_list.rs /dioxus/packages/signals/docs/memo.md /dioxus/packages/signals/docs/signals.md /dioxus/packages/signals/src/copy_value.rs /dioxus/packages/signals/src/global/memo.rs /dioxus/packages/signals/src/global/mod.rs /dioxus/packages/signals/src/global/signal.rs /dioxus/packages/signals/src/impls.rs /dioxus/packages/signals/src/lib.rs /dioxus/packages/signals/src/map.rs /dioxus/packages/signals/src/memo.rs /dioxus/packages/signals/src/props.rs /dioxus/packages/signals/src/read.rs /dioxus/packages/signals/src/set_compare.rs /dioxus/packages/signals/src/signal.rs /dioxus/packages/signals/src/warnings.rs /dioxus/packages/signals/src/write.rs /dioxus/target/debug/build/dioxus-cli-90993e55e02b7cee/out/built.rs\n"
  },
  {
    "path": "packages/depinfo/src/lib.rs",
    "content": "//! Parse the output of rustc's `.d` dep-info file.\n//!\n//! Used by the hot-reloading engine and other libraries to provide higher quality dependency analysis\n//! for the user's project.\n\nuse std::path::{Path, PathBuf};\n\n#[non_exhaustive]\n#[derive(Debug, thiserror::Error)]\npub enum DepInfoParseError {\n    /// The input was malformed - maybe this `.d` format is no longer supported?\n    #[error(\"Malformed input\")]\n    MalformedInput,\n\n    /// An env var could not be escaped or parsed - this might be a bug in rustc.\n    #[error(\"Failed to parse env var name\")]\n    InvalidEnvVarName,\n}\n\n#[non_exhaustive]\n#[derive(Default, Debug, Clone, PartialEq, Eq)]\npub struct RustcDepInfo {\n    /// The list of files that the main target in the dep-info file depends on.\n    pub files: Vec<PathBuf>,\n\n    /// The list of environment variables we found that the rustc compilation\n    /// depends on.\n    ///\n    /// The first element of the pair is the name of the env var and the second\n    /// item is the value. `Some` means that the env var was set, and `None`\n    /// means that the env var wasn't actually set and the compilation depends\n    /// on it not being set.\n    pub env: Vec<(String, Option<String>)>,\n}\n\nimpl std::str::FromStr for RustcDepInfo {\n    type Err = DepInfoParseError;\n\n    fn from_str(s: &str) -> Result<Self, Self::Err> {\n        Self::new(s)\n    }\n}\n\nimpl RustcDepInfo {\n    pub fn from_file(path: &Path) -> Result<RustcDepInfo, DepInfoParseError> {\n        let contents =\n            std::fs::read_to_string(path).map_err(|_| DepInfoParseError::MalformedInput)?;\n        RustcDepInfo::new(&contents)\n    }\n\n    /// Parse the `.d` dep-info file generated by rustc.\n    pub fn new(contents: &str) -> Result<RustcDepInfo, DepInfoParseError> {\n        let mut ret = RustcDepInfo::default();\n        let mut found_deps = false;\n\n        for line in contents.lines() {\n            if let Some(rest) = line.strip_prefix(\"# env-dep:\") {\n                let mut parts = rest.splitn(2, '=');\n                let env_var = match parts.next() {\n                    Some(s) => s,\n                    None => continue,\n                };\n                let env_val = match parts.next() {\n                    Some(s) => Some(unescape_env(s)?),\n                    None => None,\n                };\n                ret.env.push((unescape_env(env_var)?, env_val));\n            } else if let Some(pos) = line.find(\": \") {\n                if found_deps {\n                    continue;\n                }\n                found_deps = true;\n                let mut deps = line[pos + 2..].split_whitespace();\n\n                while let Some(s) = deps.next() {\n                    let mut file = s.to_string();\n                    while file.ends_with('\\\\') {\n                        file.pop();\n                        file.push(' ');\n                        file.push_str(deps.next().ok_or(DepInfoParseError::MalformedInput)?);\n                    }\n                    ret.files.push(file.into());\n                }\n            }\n        }\n        return Ok(ret);\n\n        // rustc tries to fit env var names and values all on a single line, which\n        // means it needs to escape `\\r` and `\\n`. The escape syntax used is \"\\n\"\n        // which means that `\\` also needs to be escaped.\n        fn unescape_env(s: &str) -> Result<String, DepInfoParseError> {\n            let mut ret = String::with_capacity(s.len());\n            let mut chars = s.chars();\n            while let Some(c) = chars.next() {\n                if c != '\\\\' {\n                    ret.push(c);\n                    continue;\n                }\n                match chars.next() {\n                    Some('\\\\') => ret.push('\\\\'),\n                    Some('n') => ret.push('\\n'),\n                    Some('r') => ret.push('\\r'),\n                    Some(_) => return Err(DepInfoParseError::InvalidEnvVarName),\n                    None => return Err(DepInfoParseError::InvalidEnvVarName),\n                }\n            }\n            Ok(ret)\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n\n    use super::*;\n\n    #[test]\n    fn parses_from_path() {\n        let contents = include_str!(\"./dx.d\");\n        let info: RustcDepInfo = contents.parse().unwrap();\n        let answer = vec![\n            \"/dioxus/packages/autofmt/README.md\",\n            \"/dioxus/packages/autofmt/src/buffer.rs\",\n            \"/dioxus/packages/autofmt/src/collect_macros.rs\",\n            \"/dioxus/packages/autofmt/src/indent.rs\",\n            \"/dioxus/packages/autofmt/src/lib.rs\",\n            \"/dioxus/packages/autofmt/src/prettier_please.rs\",\n            \"/dioxus/packages/autofmt/src/writer.rs\",\n            \"/dioxus/packages/check/README.md\",\n            \"/dioxus/packages/check/src/check.rs\",\n            \"/dioxus/packages/check/src/issues.rs\",\n            \"/dioxus/packages/check/src/lib.rs\",\n            \"/dioxus/packages/check/src/metadata.rs\",\n            \"/dioxus/packages/cli/README.md\",\n            \"/dioxus/packages/cli/assets/android/MainActivity.kt.hbs\",\n            \"/dioxus/packages/cli/assets/android/gen/app/build.gradle.kts.hbs\",\n            \"/dioxus/packages/cli/assets/android/gen/app/proguard-rules.pro\",\n            \"/dioxus/packages/cli/assets/android/gen/app/src/main/AndroidManifest.xml.hbs\",\n            \"/dioxus/packages/cli/assets/android/gen/app/src/main/res/drawable/ic_launcher_background.xml\",\n            \"/dioxus/packages/cli/assets/android/gen/app/src/main/res/drawable-v24/ic_launcher_foreground.xml\",\n            \"/dioxus/packages/cli/assets/android/gen/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml\",\n            \"/dioxus/packages/cli/assets/android/gen/app/src/main/res/mipmap-hdpi/ic_launcher.webp\",\n            \"/dioxus/packages/cli/assets/android/gen/app/src/main/res/mipmap-mdpi/ic_launcher.webp\",\n            \"/dioxus/packages/cli/assets/android/gen/app/src/main/res/mipmap-xhdpi/ic_launcher.webp\",\n            \"/dioxus/packages/cli/assets/android/gen/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp\",\n            \"/dioxus/packages/cli/assets/android/gen/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp\",\n            \"/dioxus/packages/cli/assets/android/gen/app/src/main/res/values/colors.xml\",\n            \"/dioxus/packages/cli/assets/android/gen/app/src/main/res/values/strings.xml.hbs\",\n            \"/dioxus/packages/cli/assets/android/gen/app/src/main/res/values/styles.xml\",\n            \"/dioxus/packages/cli/assets/android/gen/build.gradle.kts\",\n            \"/dioxus/packages/cli/assets/android/gen/gradle/wrapper/gradle-wrapper.jar\",\n            \"/dioxus/packages/cli/assets/android/gen/gradle/wrapper/gradle-wrapper.properties\",\n            \"/dioxus/packages/cli/assets/android/gen/gradle.properties\",\n            \"/dioxus/packages/cli/assets/android/gen/gradlew\",\n            \"/dioxus/packages/cli/assets/android/gen/gradlew.bat\",\n            \"/dioxus/packages/cli/assets/android/gen/settings.gradle\",\n            \"/dioxus/packages/cli/assets/dioxus.toml\",\n            \"/dioxus/packages/cli/assets/ios/ios.plist.hbs\",\n            \"/dioxus/packages/cli/assets/macos/mac.plist.hbs\",\n            \"/dioxus/packages/cli/assets/web/index.html\",\n            \"/dioxus/packages/cli/assets/web/loading.html\",\n            \"/dioxus/packages/cli/assets/web/toast.html\",\n            \"/dioxus/packages/cli/build.rs\",\n            \"/dioxus/packages/cli/src/build/builder.rs\",\n            \"/dioxus/packages/cli/src/build/bundle.rs\",\n            \"/dioxus/packages/cli/src/build/mod.rs\",\n            \"/dioxus/packages/cli/src/build/prerender.rs\",\n            \"/dioxus/packages/cli/src/build/progress.rs\",\n            \"/dioxus/packages/cli/src/build/request.rs\",\n            \"/dioxus/packages/cli/src/build/templates.rs\",\n            \"/dioxus/packages/cli/src/build/verify.rs\",\n            \"/dioxus/packages/cli/src/build/web.rs\",\n            \"/dioxus/packages/cli/src/bundle_utils.rs\",\n            \"/dioxus/packages/cli/src/cli/autoformat.rs\",\n            \"/dioxus/packages/cli/src/cli/build.rs\",\n            \"/dioxus/packages/cli/src/cli/bundle.rs\",\n            \"/dioxus/packages/cli/src/cli/check.rs\",\n            \"/dioxus/packages/cli/src/cli/clean.rs\",\n            \"/dioxus/packages/cli/src/cli/config.rs\",\n            \"/dioxus/packages/cli/src/cli/create.rs\",\n            \"/dioxus/packages/cli/src/cli/init.rs\",\n            \"/dioxus/packages/cli/src/cli/link.rs\",\n            \"/dioxus/packages/cli/src/cli/mod.rs\",\n            \"/dioxus/packages/cli/src/cli/run.rs\",\n            \"/dioxus/packages/cli/src/cli/serve.rs\",\n            \"/dioxus/packages/cli/src/cli/target.rs\",\n            \"/dioxus/packages/cli/src/cli/translate.rs\",\n            \"/dioxus/packages/cli/src/cli/verbosity.rs\",\n            \"/dioxus/packages/cli/src/config/app.rs\",\n            \"/dioxus/packages/cli/src/config/bundle.rs\",\n            \"/dioxus/packages/cli/src/config/desktop.rs\",\n            \"/dioxus/packages/cli/src/config/dioxus_config.rs\",\n            \"/dioxus/packages/cli/src/config/serve.rs\",\n            \"/dioxus/packages/cli/src/config/web.rs\",\n            \"/dioxus/packages/cli/src/config.rs\",\n            \"/dioxus/packages/cli/src/dioxus_crate.rs\",\n            \"/dioxus/packages/cli/src/dx_build_info.rs\",\n            \"/dioxus/packages/cli/src/error.rs\",\n            \"/dioxus/packages/cli/src/fastfs.rs\",\n            \"/dioxus/packages/cli/src/filemap.rs\",\n            \"/dioxus/packages/cli/src/logging.rs\",\n            \"/dioxus/packages/cli/src/main.rs\",\n            \"/dioxus/packages/cli/src/metadata.rs\",\n            \"/dioxus/packages/cli/src/platform.rs\",\n            \"/dioxus/packages/cli/src/rustc.rs\",\n            \"/dioxus/packages/cli/src/serve/ansi_buffer.rs\",\n            \"/dioxus/packages/cli/src/serve/detect.rs\",\n            \"/dioxus/packages/cli/src/serve/handle.rs\",\n            \"/dioxus/packages/cli/src/serve/mod.rs\",\n            \"/dioxus/packages/cli/src/serve/output.rs\",\n            \"/dioxus/packages/cli/src/serve/proxy.rs\",\n            \"/dioxus/packages/cli/src/serve/runner.rs\",\n            \"/dioxus/packages/cli/src/serve/server.rs\",\n            \"/dioxus/packages/cli/src/serve/update.rs\",\n            \"/dioxus/packages/cli/src/serve/watcher.rs\",\n            \"/dioxus/packages/cli/src/settings.rs\",\n            \"/dioxus/packages/cli/src/wasm_bindgen.rs\",\n            \"/dioxus/packages/cli-config/src/lib.rs\",\n            \"/dioxus/packages/cli-opt/src/css.rs\",\n            \"/dioxus/packages/cli-opt/src/file.rs\",\n            \"/dioxus/packages/cli-opt/src/folder.rs\",\n            \"/dioxus/packages/cli-opt/src/image/jpg.rs\",\n            \"/dioxus/packages/cli-opt/src/image/mod.rs\",\n            \"/dioxus/packages/cli-opt/src/image/png.rs\",\n            \"/dioxus/packages/cli-opt/src/js.rs\",\n            \"/dioxus/packages/cli-opt/src/json.rs\",\n            \"/dioxus/packages/cli-opt/src/lib.rs\",\n            \"/dioxus/packages/config-macro/README.md\",\n            \"/dioxus/packages/config-macro/src/lib.rs\",\n            \"/dioxus/packages/const-serialize/README.md\",\n            \"/dioxus/packages/const-serialize/src/const_buffers.rs\",\n            \"/dioxus/packages/const-serialize/src/const_vec.rs\",\n            \"/dioxus/packages/const-serialize/src/lib.rs\",\n            \"/dioxus/packages/const-serialize-macro/src/lib.rs\",\n            \"/dioxus/packages/core/README.md\",\n            \"/dioxus/packages/core/docs/common_spawn_errors.md\",\n            \"/dioxus/packages/core/docs/reactivity.md\",\n            \"/dioxus/packages/core/src/any_props.rs\",\n            \"/dioxus/packages/core/src/arena.rs\",\n            \"/dioxus/packages/core/src/diff/component.rs\",\n            \"/dioxus/packages/core/src/diff/iterator.rs\",\n            \"/dioxus/packages/core/src/diff/mod.rs\",\n            \"/dioxus/packages/core/src/diff/node.rs\",\n            \"/dioxus/packages/core/src/effect.rs\",\n            \"/dioxus/packages/core/src/error_boundary.rs\",\n            \"/dioxus/packages/core/src/events.rs\",\n            \"/dioxus/packages/core/src/fragment.rs\",\n            \"/dioxus/packages/core/src/generational_box.rs\",\n            \"/dioxus/packages/core/src/global_context.rs\",\n            \"/dioxus/packages/core/src/hotreload_utils.rs\",\n            \"/dioxus/packages/core/src/launch.rs\",\n            \"/dioxus/packages/core/src/lib.rs\",\n            \"/dioxus/packages/core/src/mutations.rs\",\n            \"/dioxus/packages/core/src/nodes.rs\",\n            \"/dioxus/packages/core/src/properties.rs\",\n            \"/dioxus/packages/core/src/reactive_context.rs\",\n            \"/dioxus/packages/core/src/render_error.rs\",\n            \"/dioxus/packages/core/src/root_wrapper.rs\",\n            \"/dioxus/packages/core/src/runtime.rs\",\n            \"/dioxus/packages/core/src/scheduler.rs\",\n            \"/dioxus/packages/core/src/scope_arena.rs\",\n            \"/dioxus/packages/core/src/scope_context.rs\",\n            \"/dioxus/packages/core/src/scopes.rs\",\n            \"/dioxus/packages/core/src/suspense/component.rs\",\n            \"/dioxus/packages/core/src/suspense/mod.rs\",\n            \"/dioxus/packages/core/src/tasks.rs\",\n            \"/dioxus/packages/core/src/virtual_dom.rs\",\n            \"/dioxus/packages/core-macro/README.md\",\n            \"/dioxus/packages/core-macro/docs/component.md\",\n            \"/dioxus/packages/core-macro/docs/props.md\",\n            \"/dioxus/packages/core-macro/docs/rsx.md\",\n            \"/dioxus/packages/core-macro/src/component.rs\",\n            \"/dioxus/packages/core-macro/src/lib.rs\",\n            \"/dioxus/packages/core-macro/src/props/mod.rs\",\n            \"/dioxus/packages/core-macro/src/utils.rs\",\n            \"/dioxus/packages/core-types/src/bubbles.rs\",\n            \"/dioxus/packages/core-types/src/bundled.rs\",\n            \"/dioxus/packages/core-types/src/formatter.rs\",\n            \"/dioxus/packages/core-types/src/hr_context.rs\",\n            \"/dioxus/packages/core-types/src/lib.rs\",\n            \"/dioxus/packages/devtools/src/lib.rs\",\n            \"/dioxus/packages/devtools-types/src/lib.rs\",\n            \"/dioxus/packages/dioxus-lib/README.md\",\n            \"/dioxus/packages/dioxus-lib/src/lib.rs\",\n            \"/dioxus/packages/document/build.rs\",\n            \"/dioxus/packages/document/docs/eval.md\",\n            \"/dioxus/packages/document/docs/head.md\",\n            \"/dioxus/packages/document/src/document.rs\",\n            \"/dioxus/packages/document/src/elements/link.rs\",\n            \"/dioxus/packages/document/src/elements/meta.rs\",\n            \"/dioxus/packages/document/src/elements/mod.rs\",\n            \"/dioxus/packages/document/src/elements/script.rs\",\n            \"/dioxus/packages/document/src/elements/style.rs\",\n            \"/dioxus/packages/document/src/elements/stylesheet.rs\",\n            \"/dioxus/packages/document/src/elements/title.rs\",\n            \"/dioxus/packages/document/src/error.rs\",\n            \"/dioxus/packages/document/src/eval.rs\",\n            \"/dioxus/packages/document/src/js/head.js\",\n            \"/dioxus/packages/document/src/lib.rs\",\n            \"/dioxus/packages/document/./src/ts/eval.ts\",\n            \"/dioxus/packages/document/./src/ts/head.ts\",\n            \"/dioxus/packages/dx-wire-format/src/lib.rs\",\n            \"/dioxus/packages/fullstack/README.md\",\n            \"/dioxus/packages/fullstack/src/document/mod.rs\",\n            \"/dioxus/packages/fullstack/src/hooks/mod.rs\",\n            \"/dioxus/packages/fullstack/src/hooks/server_cached.rs\",\n            \"/dioxus/packages/fullstack/src/hooks/server_future.rs\",\n            \"/dioxus/packages/fullstack/src/html_storage/mod.rs\",\n            \"/dioxus/packages/fullstack/src/lib.rs\",\n            \"/dioxus/packages/generational-box/README.md\",\n            \"/dioxus/packages/generational-box/src/entry.rs\",\n            \"/dioxus/packages/generational-box/src/error.rs\",\n            \"/dioxus/packages/generational-box/src/lib.rs\",\n            \"/dioxus/packages/generational-box/src/references.rs\",\n            \"/dioxus/packages/generational-box/src/sync.rs\",\n            \"/dioxus/packages/generational-box/src/unsync.rs\",\n            \"/dioxus/packages/history/src/lib.rs\",\n            \"/dioxus/packages/history/src/memory.rs\",\n            \"/dioxus/packages/hooks/README.md\",\n            \"/dioxus/packages/hooks/docs/derived_state.md\",\n            \"/dioxus/packages/hooks/docs/moving_state_around.md\",\n            \"/dioxus/packages/hooks/docs/rules_of_hooks.md\",\n            \"/dioxus/packages/hooks/docs/side_effects.md\",\n            \"/dioxus/packages/hooks/docs/use_resource.md\",\n            \"/dioxus/packages/hooks/src/lib.rs\",\n            \"/dioxus/packages/hooks/src/use_callback.rs\",\n            \"/dioxus/packages/hooks/src/use_context.rs\",\n            \"/dioxus/packages/hooks/src/use_coroutine.rs\",\n            \"/dioxus/packages/hooks/src/use_effect.rs\",\n            \"/dioxus/packages/hooks/src/use_future.rs\",\n            \"/dioxus/packages/hooks/src/use_hook_did_run.rs\",\n            \"/dioxus/packages/hooks/src/use_memo.rs\",\n            \"/dioxus/packages/hooks/src/use_on_destroy.rs\",\n            \"/dioxus/packages/hooks/src/use_reactive.rs\",\n            \"/dioxus/packages/hooks/src/use_resource.rs\",\n            \"/dioxus/packages/hooks/src/use_root_context.rs\",\n            \"/dioxus/packages/hooks/src/use_set_compare.rs\",\n            \"/dioxus/packages/hooks/src/use_signal.rs\",\n            \"/dioxus/packages/html/README.md\",\n            \"/dioxus/packages/html/docs/common_event_handler_errors.md\",\n            \"/dioxus/packages/html/docs/event_handlers.md\",\n            \"/dioxus/packages/html/src/attribute_groups.rs\",\n            \"/dioxus/packages/html/src/elements.rs\",\n            \"/dioxus/packages/html/src/events/animation.rs\",\n            \"/dioxus/packages/html/src/events/clipboard.rs\",\n            \"/dioxus/packages/html/src/events/composition.rs\",\n            \"/dioxus/packages/html/src/events/drag.rs\",\n            \"/dioxus/packages/html/src/events/focus.rs\",\n            \"/dioxus/packages/html/src/events/form.rs\",\n            \"/dioxus/packages/html/src/events/image.rs\",\n            \"/dioxus/packages/html/src/events/keyboard.rs\",\n            \"/dioxus/packages/html/src/events/media.rs\",\n            \"/dioxus/packages/html/src/events/mod.rs\",\n            \"/dioxus/packages/html/src/events/mounted.rs\",\n            \"/dioxus/packages/html/src/events/mouse.rs\",\n            \"/dioxus/packages/html/src/events/pointer.rs\",\n            \"/dioxus/packages/html/src/events/resize.rs\",\n            \"/dioxus/packages/html/src/events/scroll.rs\",\n            \"/dioxus/packages/html/src/events/selection.rs\",\n            \"/dioxus/packages/html/src/events/toggle.rs\",\n            \"/dioxus/packages/html/src/events/touch.rs\",\n            \"/dioxus/packages/html/src/events/transition.rs\",\n            \"/dioxus/packages/html/src/events/visible.rs\",\n            \"/dioxus/packages/html/src/events/wheel.rs\",\n            \"/dioxus/packages/html/src/file_data.rs\",\n            \"/dioxus/packages/html/src/geometry.rs\",\n            \"/dioxus/packages/html/src/input_data.rs\",\n            \"/dioxus/packages/html/src/lib.rs\",\n            \"/dioxus/packages/html/src/point_interaction.rs\",\n            \"/dioxus/packages/html/src/render_template.rs\",\n            \"/dioxus/packages/html-internal-macro/src/lib.rs\",\n            \"/dioxus/packages/lazy-js-bundle/src/lib.rs\",\n            \"/dioxus/packages/manganis/manganis/README.md\",\n            \"/dioxus/packages/manganis/manganis/src/hash.rs\",\n            \"/dioxus/packages/manganis/manganis/src/lib.rs\",\n            \"/dioxus/packages/manganis/manganis/src/macro_helpers.rs\",\n            \"/dioxus/packages/manganis/manganis-core/src/asset.rs\",\n            \"/dioxus/packages/manganis/manganis-core/src/css.rs\",\n            \"/dioxus/packages/manganis/manganis-core/src/folder.rs\",\n            \"/dioxus/packages/manganis/manganis-core/src/hash.rs\",\n            \"/dioxus/packages/manganis/manganis-core/src/images.rs\",\n            \"/dioxus/packages/manganis/manganis-core/src/js.rs\",\n            \"/dioxus/packages/manganis/manganis-core/src/lib.rs\",\n            \"/dioxus/packages/manganis/manganis-core/src/linker.rs\",\n            \"/dioxus/packages/manganis/manganis-core/src/options.rs\",\n            \"/dioxus/packages/manganis/manganis-macro/README.md\",\n            \"/dioxus/packages/manganis/manganis-macro/src/asset.rs\",\n            \"/dioxus/packages/manganis/manganis-macro/src/lib.rs\",\n            \"/dioxus/packages/manganis/manganis-macro/src/linker.rs\",\n            \"/dioxus/packages/rsx/src/assign_dyn_ids.rs\",\n            \"/dioxus/packages/rsx/src/attribute.rs\",\n            \"/dioxus/packages/rsx/src/component.rs\",\n            \"/dioxus/packages/rsx/src/diagnostics.rs\",\n            \"/dioxus/packages/rsx/src/element.rs\",\n            \"/dioxus/packages/rsx/src/expr_node.rs\",\n            \"/dioxus/packages/rsx/src/forloop.rs\",\n            \"/dioxus/packages/rsx/src/ifchain.rs\",\n            \"/dioxus/packages/rsx/src/ifmt.rs\",\n            \"/dioxus/packages/rsx/src/lib.rs\",\n            \"/dioxus/packages/rsx/src/literal.rs\",\n            \"/dioxus/packages/rsx/src/location.rs\",\n            \"/dioxus/packages/rsx/src/node.rs\",\n            \"/dioxus/packages/rsx/src/partial_closure.rs\",\n            \"/dioxus/packages/rsx/src/raw_expr.rs\",\n            \"/dioxus/packages/rsx/src/rsx_block.rs\",\n            \"/dioxus/packages/rsx/src/rsx_call.rs\",\n            \"/dioxus/packages/rsx/src/template_body.rs\",\n            \"/dioxus/packages/rsx/src/text_node.rs\",\n            \"/dioxus/packages/rsx/src/util.rs\",\n            \"/dioxus/packages/rsx-hotreload/src/collect.rs\",\n            \"/dioxus/packages/rsx-hotreload/src/diff.rs\",\n            \"/dioxus/packages/rsx-hotreload/src/extensions.rs\",\n            \"/dioxus/packages/rsx-hotreload/src/last_build_state.rs\",\n            \"/dioxus/packages/rsx-hotreload/src/lib.rs\",\n            \"/dioxus/packages/rsx-rosetta/README.md\",\n            \"/dioxus/packages/rsx-rosetta/src/lib.rs\",\n            \"/dioxus/packages/server-macro/src/lib.rs\",\n            \"/dioxus/packages/signals/README.md\",\n            \"/dioxus/packages/signals/docs/hoist/error.rs\",\n            \"/dioxus/packages/signals/docs/hoist/fixed_list.rs\",\n            \"/dioxus/packages/signals/docs/memo.md\",\n            \"/dioxus/packages/signals/docs/signals.md\",\n            \"/dioxus/packages/signals/src/copy_value.rs\",\n            \"/dioxus/packages/signals/src/global/memo.rs\",\n            \"/dioxus/packages/signals/src/global/mod.rs\",\n            \"/dioxus/packages/signals/src/global/signal.rs\",\n            \"/dioxus/packages/signals/src/impls.rs\",\n            \"/dioxus/packages/signals/src/lib.rs\",\n            \"/dioxus/packages/signals/src/map.rs\",\n            \"/dioxus/packages/signals/src/memo.rs\",\n            \"/dioxus/packages/signals/src/props.rs\",\n            \"/dioxus/packages/signals/src/read.rs\",\n            \"/dioxus/packages/signals/src/set_compare.rs\",\n            \"/dioxus/packages/signals/src/signal.rs\",\n            \"/dioxus/packages/signals/src/warnings.rs\",\n            \"/dioxus/packages/signals/src/write.rs\",\n            \"/dioxus/target/debug/build/dioxus-cli-90993e55e02b7cee/out/built.rs\",\n        ];\n        assert_eq!(\n            answer.iter().map(PathBuf::from).collect::<Vec<_>>(),\n            info.files\n        );\n    }\n}\n"
  },
  {
    "path": "packages/desktop/.vscode/settings.json",
    "content": "{\n    // \"rust-analyzer.cargo.target\": \"aarch64-linux-android\"\n}\n"
  },
  {
    "path": "packages/desktop/Cargo.toml",
    "content": "[package]\nname = \"dioxus-desktop\"\nversion = { workspace = true }\nauthors = [\"Jonathan Kelley\"]\nedition = \"2021\"\ndescription = \"WebView renderer for Dioxus\"\nlicense = \"MIT OR Apache-2.0\"\nrepository = \"https://github.com/DioxusLabs/dioxus/\"\nhomepage = \"https://dioxuslabs.com/learn/0.7/getting_started\"\nkeywords = [\"dom\", \"ui\", \"gui\", \"react\"]\nrust-version = \"1.76.0\"\n\n[dependencies]\ndioxus-core = { workspace = true, features = [\"serialize\"] }\ndioxus-html = { workspace = true, features = [\"serialize\"] }\ndioxus-document = { workspace = true }\ndioxus-signals = { workspace = true, optional = true }\ndioxus-interpreter-js = { workspace = true, features = [\"binary-protocol\", \"serialize\"] }\ndioxus-cli-config = { workspace = true }\ndioxus-asset-resolver = { workspace = true, features = [\"native\"] }\ngenerational-box = { workspace = true }\ndioxus-devtools = { workspace = true, optional = true }\n\nserde = \"1.0.219\"\nserde_json = \"1.0.140\"\nthiserror = { workspace = true }\ntracing = { workspace = true }\nwry = { workspace = true, default-features = false, features = [\"os-webview\", \"protocol\", \"drag-drop\"] }\nfutures-channel = { workspace = true }\ntokio = { workspace = true, features = [\n  \"sync\",\n  \"rt-multi-thread\",\n  \"rt\",\n  \"time\",\n  \"macros\",\n  \"fs\",\n  \"io-util\",\n], optional = true }\ninfer = { workspace = true }\ndunce = { workspace = true }\nslab = { workspace = true }\nrustc-hash = { workspace = true }\ndioxus-hooks = { workspace = true }\nfutures-util = { workspace = true }\npercent-encoding = { workspace = true }\nasync-trait = { workspace = true }\ntao = { workspace = true, features = [\"rwh_05\"] }\ndioxus-history = { workspace = true }\nbase64 = { workspace = true }\nlibc = \"0.2.174\"\nrand = { workspace = true, features = [\"std_rng\"] }\nsubtle = { version = \"2.6\", features = [\"const-generics\"] }\nbytes = { workspace = true }\nwebbrowser = { workspace = true }\n\n[target.'cfg(unix)'.dependencies]\nsignal-hook = \"0.3.18\"\n\n[target.'cfg(target_os = \"linux\")'.dependencies]\nwry = { workspace = true, features = [\"os-webview\", \"protocol\", \"drag-drop\", \"linux-body\"] }\n\n[target.'cfg(any(target_os = \"windows\",target_os = \"macos\",target_os = \"linux\",target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"netbsd\", target_os = \"openbsd\"))'.dependencies]\nglobal-hotkey = \"0.7.0\"\nrfd = { version = \"0.17.2\", default-features = false, features = [\"xdg-portal\"] }\nmuda = { workspace = true }\n\n[target.'cfg(any(target_os = \"windows\",target_os = \"macos\",target_os = \"linux\"))'.dependencies]\ntray-icon = { workspace = true }\n\n[target.'cfg(target_os = \"ios\")'.dependencies]\nobjc = \"0.2.7\"\nobjc_id = \"0.1.1\"\n\n# use rustls on android\n[target.'cfg(target_os = \"android\")'.dependencies]\ntungstenite = { workspace = true, features = [\"rustls\"] }\njni = \"0.21.1\"\nndk = { version = \"0.9.0\" }\nndk-sys = { version = \"0.6.0\" }\nndk-context = { version = \"0.1.1\" }\n\n# use native tls on other platforms\n[target.'cfg(not(target_os = \"android\"))'.dependencies]\ntungstenite = { workspace = true, features = [\"native-tls\"] }\n\n[target.'cfg(target_os = \"macos\")'.dependencies]\ncocoa = \"0.26.1\"\ncore-foundation = \"0.10.1\"\nobjc = \"0.2.7\"\n\n[build-dependencies]\nlazy-js-bundle = { workspace = true }\n\n[features]\ndefault = [\"tokio_runtime\", \"transparent\", \"devtools\"]\ntokio_runtime = [\"dep:tokio\"]\nfullscreen = [\"wry/fullscreen\"]\ndevtools = [\"wry/devtools\", \"dep:dioxus-devtools\", \"dioxus-signals\"]\ntransparent = [\"wry/transparent\"]\ngnu = []\n\n[package.metadata.docs.rs]\nfeatures = [\"tokio_runtime\", \"devtools\"]\ncargo-args = [\"-Zunstable-options\", \"-Zrustdoc-scrape-examples\"]\ndefault-features = false\ntargets = [\n  \"x86_64-unknown-linux-gnu\",\n  \"x86_64-pc-windows-msvc\",\n  \"aarch64-apple-darwin\",\n  \"aarch64-apple-ios\",\n  \"aarch64-linux-android\",\n  \"armv7-linux-androideabi\",\n]\n\n[dev-dependencies]\ndioxus = { workspace = true, features = [\"desktop\"] }\nexitcode = \"1.1.2\"\nreqwest = { workspace = true, features = [\"json\"] }\nhttp-range = { version = \"0.1.5\" }\ndioxus-ssr = { workspace = true, default-features = false }\nseparator = \"0.4.1\"\n\n# These tests need to be run on the main thread, so they cannot use rust's test harness.\n[[test]]\nname = \"check_events\"\npath = \"headless_tests/events.rs\"\nharness = false\n\n[[test]]\nname = \"check_rendering\"\npath = \"headless_tests/rendering.rs\"\nharness = false\n\n[[test]]\nname = \"check_forms\"\npath = \"headless_tests/forms.rs\"\nharness = false\n\n[[test]]\nname = \"check_eval\"\npath = \"headless_tests/eval.rs\"\nharness = false\n"
  },
  {
    "path": "packages/desktop/README.md",
    "content": "# Dioxus Desktop (webview)\n\n[![Crates.io][crates-badge]][crates-url]\n[![MIT licensed][mit-badge]][mit-url]\n[![Build Status][actions-badge]][actions-url]\n[![Discord chat][discord-badge]][discord-url]\n\n[crates-badge]: https://img.shields.io/crates/v/dioxus-desktop.svg\n[crates-url]: https://crates.io/crates/dioxus-desktop\n[mit-badge]: https://img.shields.io/badge/license-MIT-blue.svg\n[mit-url]: https://github.com/dioxuslabs/dioxus/blob/main/LICENSE-MIT\n[actions-badge]: https://github.com/dioxuslabs/dioxus/actions/workflows/main.yml/badge.svg\n[actions-url]: https://github.com/dioxuslabs/dioxus/actions?query=workflow%3ACI+branch%3Amaster\n[discord-badge]: https://img.shields.io/discord/899851952891002890.svg?logo=discord&style=flat-square\n[discord-url]: https://discord.gg/XgGxMSkvUM\n\n[Website](https://dioxuslabs.com) |\n[Guides](https://dioxuslabs.com/learn/0.7/) |\n[API Docs](https://docs.rs/dioxus-desktop/latest/dioxus_desktop) |\n[Chat](https://discord.gg/XgGxMSkvUM)\n\n## Overview\n\n`dioxus-desktop` provides a webview-based desktop renderer for the Dioxus VirtualDom.\n\nThis requires that webview is installed on the target system. WebView is installed by default on macOS and iOS devices, but might not come preinstalled on Windows or Linux devices. To fix these issues, follow the [instructions in the guide](guide-url).\n\n[guide-url]: https://dioxuslabs.com/learn/0.7/getting_started\n\n## Features\n\n- Simple, one-line launch for desktop apps\n- Dioxus VirtualDom running on a native thread\n- Full HTML/CSS support via `wry` and `tao`\n- Exposed `window` and `Proxy` types from tao for direct window manipulation\n- Helpful hooks for accessing the window, WebView, and running javascript.\n\n## Contributing\n\n- Report issues on our [issue tracker](https://github.com/dioxuslabs/dioxus/issues).\n- Join the discord and ask questions!\n\n## License\n\nThis project is licensed under the [MIT license].\n\n[mit license]: https://github.com/dioxuslabs/dioxus/blob/main/LICENSE-MIT\n\nUnless you explicitly state otherwise, any contribution intentionally submitted\nfor inclusion in Dioxus by you shall be licensed as MIT without any additional\nterms or conditions.\n"
  },
  {
    "path": "packages/desktop/build.rs",
    "content": "use std::{io::Write as _, path::PathBuf};\n\nfn check_gnu() {\n    // WARN about wry support on windows gnu targets. GNU windows targets don't work well in wry currently\n    if std::env::var(\"CARGO_CFG_WINDOWS\").is_ok()\n        && std::env::var(\"CARGO_CFG_TARGET_ENV\").unwrap() == \"gnu\"\n        && !cfg!(feature = \"gnu\")\n    {\n        println!(\"cargo:warning=GNU windows targets have some limitations within Wry. Using the MSVC windows toolchain is recommended. If you would like to use continue using GNU, you can read https://github.com/wravery/webview2-rs#cross-compilation and disable this warning by adding the gnu feature to dioxus-desktop in your Cargo.toml\")\n    }\n\n    // To prepare for a release, we add extra examples to desktop for doc scraping and copy assets from the workspace to make those examples compile\n    if option_env!(\"DIOXUS_RELEASE\").is_some() {\n        // Append EXAMPLES_TOML to the cargo.toml\n        let cargo_toml = std::fs::OpenOptions::new()\n            .append(true)\n            .open(\"Cargo.toml\")\n            .unwrap();\n        let mut write = std::io::BufWriter::new(cargo_toml);\n        write.write_all(EXAMPLES_TOML.as_bytes()).unwrap();\n\n        // Copy the assets from the workspace to the examples directory\n        let crate_dir = PathBuf::from(std::env::var(\"CARGO_MANIFEST_DIR\").unwrap());\n        let workspace_dir = crate_dir.parent().unwrap().parent().unwrap();\n        let workspace_assets_dir = workspace_dir.join(\"examples\").join(\"assets\");\n        let desktop_assets_dir = PathBuf::from(\"examples\").join(\"assets\");\n        std::fs::create_dir_all(&desktop_assets_dir).unwrap();\n        // move all files from the workspace assets dir to the desktop assets dir\n        for entry in std::fs::read_dir(workspace_assets_dir).unwrap() {\n            let entry = entry.unwrap();\n            let path = entry.path();\n            if path.is_file() {\n                std::fs::copy(&path, desktop_assets_dir.join(path.file_name().unwrap())).unwrap();\n            }\n        }\n    }\n}\n\nfn compile_ts() {\n    // If any TS files change, re-run the build script\n    lazy_js_bundle::LazyTypeScriptBindings::new()\n        .with_watching(\"./src/ts\")\n        .with_binding(\"./src/ts/native_eval.ts\", \"./src/js/native_eval.js\")\n        .run();\n}\n\nfn main() {\n    check_gnu();\n    compile_ts();\n}\n\nconst EXAMPLES_TOML: &str = r#\"\n# Most of the examples live in the workspace. We include some here so that docs.rs can scrape our examples for better inline docs\n[[example]]\nname = \"video_stream\"\npath = \"../../examples/video_stream.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"suspense\"\npath = \"../../examples/suspense.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"calculator_mutable\"\npath = \"../../examples/calculator_mutable.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"custom_html\"\npath = \"../../examples/custom_html.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"custom_menu\"\npath = \"../../examples/custom_menu.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"errors\"\npath = \"../../examples/errors.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"future\"\npath = \"../../examples/future.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"hydration\"\npath = \"../../examples/hydration.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"multiwindow\"\npath = \"../../examples/multiwindow.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"overlay\"\npath = \"../../examples/overlay.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"popup\"\npath = \"../../examples/popup.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"read_size\"\npath = \"../../examples/read_size.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"shortcut\"\npath = \"../../examples/shortcut.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"streams\"\npath = \"../../examples/streams.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"window_event\"\npath = \"../../examples/window_event.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"window_focus\"\npath = \"../../examples/window_focus.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"window_zoom\"\npath = \"../../examples/window_zoom.rs\"\ndoc-scrape-examples = true\"#;\n"
  },
  {
    "path": "packages/desktop/headless_tests/eval.rs",
    "content": "use dioxus::prelude::*;\nuse dioxus_desktop::window;\nuse serde::Deserialize;\n\n#[path = \"./utils.rs\"]\nmod utils;\n\npub fn main() {\n    #[cfg(not(windows))]\n    utils::check_app_exits(app);\n}\n\nstatic EVALS_RECEIVED: GlobalSignal<usize> = Signal::global(|| 0);\nstatic EVALS_RETURNED: GlobalSignal<usize> = Signal::global(|| 0);\n\nfn app() -> Element {\n    // Double 100 values in the value\n    use_future(|| async {\n        let mut eval = document::eval(\n            r#\"for (let i = 0; i < 100; i++) {\n            let value = await dioxus.recv();\n            dioxus.send(value*2);\n        }\"#,\n        );\n        for i in 0..100 {\n            eval.send(i).unwrap();\n            let value: i32 = eval.recv().await.unwrap();\n            assert_eq!(value, i * 2);\n            EVALS_RECEIVED.with_mut(|x| *x += 1);\n        }\n    });\n\n    // Make sure returning no value resolves the future\n    use_future(|| async {\n        let eval = document::eval(r#\"return;\"#);\n\n        eval.await.unwrap();\n        EVALS_RETURNED.with_mut(|x| *x += 1);\n    });\n\n    // Return a value from the future\n    use_future(|| async {\n        let eval = document::eval(\n            r#\"\n        return [1, 2, 3];\n        \"#,\n        );\n\n        assert_eq!(\n            Vec::<i32>::deserialize(&eval.await.unwrap()).unwrap(),\n            vec![1, 2, 3]\n        );\n        EVALS_RETURNED.with_mut(|x| *x += 1);\n    });\n\n    use_memo(|| {\n        println!(\"expected 100 evals received found {}\", EVALS_RECEIVED());\n        println!(\"expected 2 eval returned found {}\", EVALS_RETURNED());\n        if EVALS_RECEIVED() == 100 && EVALS_RETURNED() == 2 {\n            window().close();\n        }\n    });\n\n    VNode::empty()\n}\n"
  },
  {
    "path": "packages/desktop/headless_tests/events.rs",
    "content": "use dioxus::html::geometry::euclid::Vector3D;\nuse dioxus::prelude::*;\nuse dioxus_desktop::DesktopContext;\n\n#[path = \"./utils.rs\"]\nmod utils;\n\npub fn main() {\n    #[cfg(not(windows))]\n    utils::check_app_exits(app);\n}\n\nstatic RECEIVED_EVENTS: GlobalSignal<usize> = Signal::global(|| 0);\n\nfn app() -> Element {\n    let desktop_context: DesktopContext = consume_context();\n\n    let received = RECEIVED_EVENTS();\n    let expected = utils::EXPECTED_EVENTS();\n\n    use_memo(move || {\n        println!(\"expecting {} events\", utils::EXPECTED_EVENTS());\n        println!(\"received {} events\", RECEIVED_EVENTS());\n    });\n\n    if expected != 0 && received == expected {\n        println!(\"all events received\");\n        desktop_context.close();\n    }\n\n    rsx! {\n        div {\n            test_mounted {}\n            test_button {}\n            test_mouse_move_div {}\n            test_mouse_click_div {}\n            test_mouse_dblclick_div {}\n            test_mouse_down_div {}\n            test_mouse_up_div {}\n            test_mouse_scroll_div {}\n            test_key_down_div {}\n            test_key_up_div {}\n            test_key_press_div {}\n            test_focus_in_div {}\n            test_focus_out_div {}\n            test_form_input {}\n            test_form_submit {}\n            test_select_multiple_options {}\n            test_unicode {}\n        }\n    }\n}\n\nfn test_mounted() -> Element {\n    use_hook(|| utils::EXPECTED_EVENTS.with_mut(|x| *x += 1));\n    let mut onmounted_triggered = use_signal(|| false);\n\n    rsx! {\n        div {\n            width: \"100px\",\n            height: \"100px\",\n            onmounted: move |evt| async move {\n                let rect = evt.get_client_rect().await.unwrap();\n                println!(\"rect: {rect:?}\");\n                assert_eq!(rect.width(), 100.0);\n                assert_eq!(rect.height(), 100.0);\n                RECEIVED_EVENTS.with_mut(|x| *x += 1);\n                // Onmounted should only be called once\n                let mut onmounted_triggered_write = onmounted_triggered.write();\n                assert!(!*onmounted_triggered_write);\n                *onmounted_triggered_write = true;\n            }\n        }\n    }\n}\n\nfn test_button() -> Element {\n    utils::mock_event(\n        \"button\",\n        r#\"new MouseEvent(\"click\", {\n            view: window,\n            bubbles: true,\n            cancelable: true,\n            button: 0,\n        })\"#,\n    );\n\n    rsx! {\n        button {\n            id: \"button\",\n            onclick: move |event| {\n                println!(\"{:?}\", event.data);\n                assert!(event.data.modifiers().is_empty());\n                assert!(event.data.held_buttons().is_empty());\n                assert_eq!(\n                    event.data.trigger_button(),\n                    Some(dioxus_html::input_data::MouseButton::Primary),\n                );\n                RECEIVED_EVENTS.with_mut(|x| *x += 1);\n            }\n        }\n    }\n}\n\nfn test_mouse_move_div() -> Element {\n    utils::mock_event(\n        \"mouse_move_div\",\n        r#\"new MouseEvent(\"mousemove\", {\n        view: window,\n        bubbles: true,\n        cancelable: true,\n        buttons: 2,\n        })\"#,\n    );\n\n    rsx! {\n        div {\n            id: \"mouse_move_div\",\n            onmousemove: move |event| {\n                println!(\"{:?}\", event.data);\n                assert!(event.data.modifiers().is_empty());\n                assert!(\n                    event\n                        .data\n                        .held_buttons()\n                        .contains(dioxus_html::input_data::MouseButton::Secondary),\n                );\n                RECEIVED_EVENTS.with_mut(|x| *x += 1);\n            }\n        }\n    }\n}\n\nfn test_mouse_click_div() -> Element {\n    utils::mock_event(\n        \"mouse_click_div\",\n        r#\"new MouseEvent(\"click\", {\n        view: window,\n        bubbles: true,\n        cancelable: true,\n        buttons: 2,\n        button: 2,\n        })\"#,\n    );\n\n    rsx! {\n        div {\n            id: \"mouse_click_div\",\n            onclick: move |event| {\n                println!(\"{:?}\", event.data);\n                assert!(event.data.modifiers().is_empty());\n                assert!(\n                    event\n                        .data\n                        .held_buttons()\n                        .contains(dioxus_html::input_data::MouseButton::Secondary),\n                );\n                assert_eq!(\n                    event.data.trigger_button(),\n                    Some(dioxus_html::input_data::MouseButton::Secondary),\n                );\n                RECEIVED_EVENTS.with_mut(|x| *x += 1);\n            }\n        }\n    }\n}\n\nfn test_mouse_dblclick_div() -> Element {\n    utils::mock_event(\n        \"mouse_dblclick_div\",\n        r#\"new MouseEvent(\"dblclick\", {\n            view: window,\n            bubbles: true,\n            cancelable: true,\n            buttons: 1|2,\n            button: 2,\n        })\"#,\n    );\n\n    rsx! {\n        div {\n            id: \"mouse_dblclick_div\",\n            ondoubleclick: move |event| {\n                println!(\"{:?}\", event.data);\n                assert!(event.data.modifiers().is_empty());\n                assert!(\n                    event\n                        .data\n                        .held_buttons()\n                        .contains(dioxus_html::input_data::MouseButton::Primary),\n                );\n                assert!(\n                    event\n                        .data\n                        .held_buttons()\n                        .contains(dioxus_html::input_data::MouseButton::Secondary),\n                );\n                assert_eq!(\n                    event.data.trigger_button(),\n                    Some(dioxus_html::input_data::MouseButton::Secondary),\n                );\n                RECEIVED_EVENTS.with_mut(|x| *x += 1);\n            }\n        }\n    }\n}\n\nfn test_mouse_down_div() -> Element {\n    utils::mock_event(\n        \"mouse_down_div\",\n        r#\"new MouseEvent(\"mousedown\", {\n            view: window,\n            bubbles: true,\n            cancelable: true,\n            buttons: 2,\n            button: 2,\n        })\"#,\n    );\n\n    rsx! {\n        div {\n            id: \"mouse_down_div\",\n            onmousedown: move |event| {\n                println!(\"{:?}\", event.data);\n                assert!(event.data.modifiers().is_empty());\n                assert!(\n                    event\n                        .data\n                        .held_buttons()\n                        .contains(dioxus_html::input_data::MouseButton::Secondary),\n                );\n                assert_eq!(\n                    event.data.trigger_button(),\n                    Some(dioxus_html::input_data::MouseButton::Secondary),\n                );\n                RECEIVED_EVENTS.with_mut(|x| *x += 1);\n            }\n        }\n    }\n}\n\nfn test_mouse_up_div() -> Element {\n    utils::mock_event(\n        \"mouse_up_div\",\n        r#\"new MouseEvent(\"mouseup\", {\n            view: window,\n            bubbles: true,\n            cancelable: true,\n            buttons: 0,\n            button: 0,\n        })\"#,\n    );\n\n    rsx! {\n        div {\n            id: \"mouse_up_div\",\n            onmouseup: move |event| {\n                println!(\"{:?}\", event.data);\n                assert!(event.data.modifiers().is_empty());\n                assert!(event.data.held_buttons().is_empty());\n                assert_eq!(\n                    event.data.trigger_button(),\n                    Some(dioxus_html::input_data::MouseButton::Primary),\n                );\n                RECEIVED_EVENTS.with_mut(|x| *x += 1);\n            }\n        }\n    }\n}\n\nfn test_mouse_scroll_div() -> Element {\n    utils::mock_event(\n        \"wheel_div\",\n        r#\"new WheelEvent(\"wheel\", {\n            view: window,\n            deltaX: 1.0,\n            deltaY: 2.0,\n            deltaZ: 3.0,\n            deltaMode: 0x00,\n            bubbles: true,\n        })\"#,\n    );\n\n    rsx! {\n        div {\n            id: \"wheel_div\",\n            width: \"100px\",\n            height: \"100px\",\n            background_color: \"red\",\n            onwheel: move |event| {\n                println!(\"{:?}\", event.data);\n                let dioxus_html::geometry::WheelDelta::Pixels(delta) = event.data.delta() else {\n                    panic!(\"Expected delta to be in pixels\")\n                };\n                assert_eq!(delta, Vector3D::new(1.0, 2.0, 3.0));\n                RECEIVED_EVENTS.with_mut(|x| *x += 1);\n            }\n        }\n    }\n}\n\nfn test_key_down_div() -> Element {\n    utils::mock_event(\n        \"key_down_div\",\n        r#\"new KeyboardEvent(\"keydown\", {\n        key: \"a\",\n        code: \"KeyA\",\n        location: 0,\n        repeat: true,\n        keyCode: 65,\n        charCode: 97,\n        char: \"a\",\n        charCode: 0,\n        altKey: false,\n        ctrlKey: false,\n        metaKey: false,\n        shiftKey: false,\n        isComposing: true,\n        which: 65,\n        bubbles: true,\n        })\"#,\n    );\n    rsx! {\n        input {\n            id: \"key_down_div\",\n            onkeydown: move |event| {\n                println!(\"{:?}\", event.data);\n                assert!(event.data.modifiers().is_empty());\n                assert_eq!(event.data.key().to_string(), \"a\");\n                assert_eq!(event.data.code().to_string(), \"KeyA\");\n                assert_eq!(event.data.location(), Location::Standard);\n                assert!(event.data.is_auto_repeating());\n                assert!(event.data.is_composing());\n                RECEIVED_EVENTS.with_mut(|x| *x += 1);\n            }\n        }\n    }\n}\nfn test_key_up_div() -> Element {\n    utils::mock_event(\n        \"key_up_div\",\n        r#\"new KeyboardEvent(\"keyup\", {\n        key: \"a\",\n        code: \"KeyA\",\n        location: 0,\n        repeat: false,\n        keyCode: 65,\n        charCode: 97,\n        char: \"a\",\n        charCode: 0,\n        altKey: false,\n        ctrlKey: false,\n        metaKey: false,\n        shiftKey: false,\n        isComposing: false,\n        which: 65,\n        bubbles: true,\n        })\"#,\n    );\n\n    rsx! {\n        input {\n            id: \"key_up_div\",\n            onkeyup: move |event| {\n                println!(\"{:?}\", event.data);\n                assert!(event.data.modifiers().is_empty());\n                assert_eq!(event.data.key().to_string(), \"a\");\n                assert_eq!(event.data.code().to_string(), \"KeyA\");\n                assert_eq!(event.data.location(), Location::Standard);\n                assert!(!event.data.is_auto_repeating());\n                assert!(!event.data.is_composing());\n                RECEIVED_EVENTS.with_mut(|x| *x += 1);\n            }\n        }\n    }\n}\nfn test_key_press_div() -> Element {\n    utils::mock_event(\n        \"key_press_div\",\n        r#\"new KeyboardEvent(\"keypress\", {\n        key: \"a\",\n        code: \"KeyA\",\n        location: 0,\n        repeat: false,\n        keyCode: 65,\n        charCode: 97,\n        char: \"a\",\n        charCode: 0,\n        altKey: false,\n        ctrlKey: false,\n        metaKey: false,\n        shiftKey: false,\n        isComposing: false,\n        which: 65,\n        bubbles: true,\n        })\"#,\n    );\n    rsx! {\n        input {\n            id: \"key_press_div\",\n            onkeypress: move |event| {\n                println!(\"{:?}\", event.data);\n                assert!(event.data.modifiers().is_empty());\n                assert_eq!(event.data.key().to_string(), \"a\");\n                assert_eq!(event.data.code().to_string(), \"KeyA\");\n                assert_eq!(event.data.location(), Location::Standard);\n                assert!(!event.data.is_auto_repeating());\n                assert!(!event.data.is_composing());\n                RECEIVED_EVENTS.with_mut(|x| *x += 1);\n            }\n        }\n    }\n}\n\nfn test_focus_in_div() -> Element {\n    utils::mock_event(\n        \"focus_in_div\",\n        r#\"new FocusEvent(\"focusin\", {bubbles: true})\"#,\n    );\n\n    rsx! {\n        input {\n            id: \"focus_in_div\",\n            onfocusin: move |event| {\n                println!(\"{:?}\", event.data);\n                RECEIVED_EVENTS.with_mut(|x| *x += 1);\n            }\n        }\n    }\n}\n\nfn test_focus_out_div() -> Element {\n    utils::mock_event(\n        \"focus_out_div\",\n        r#\"new FocusEvent(\"focusout\",{bubbles: true})\"#,\n    );\n    rsx! {\n        input {\n            id: \"focus_out_div\",\n            onfocusout: move |event| {\n                println!(\"{:?}\", event.data);\n                RECEIVED_EVENTS.with_mut(|x| *x += 1);\n            }\n        }\n    }\n}\n\nfn test_form_input() -> Element {\n    let mut values = use_signal(Vec::new);\n\n    utils::mock_event_with_extra(\n        \"form-username\",\n        r#\"new Event(\"input\", { bubbles: true, cancelable: true, composed: true })\"#,\n        r#\"element.value = \"hello\";\"#,\n    );\n\n    let set_username = move |ev: FormEvent| {\n        values.set(ev.values());\n\n        // The value of the input should match\n        assert_eq!(ev.value(), \"hello\");\n\n        // And then the value the form gives us should also match\n        values.with_mut(|x| {\n            assert_eq!(x.iter().find(|f| f.0 == \"username\").unwrap().1, \"hello\");\n            assert_eq!(x.iter().find(|f| f.0 == \"full-name\").unwrap().1, \"lorem\");\n            assert_eq!(x.iter().find(|f| f.0 == \"password\").unwrap().1, \"ipsum\");\n            assert_eq!(x.iter().find(|f| f.0 == \"color\").unwrap().1, \"red\");\n        });\n        RECEIVED_EVENTS.with_mut(|x| *x += 1);\n    };\n\n    rsx! {\n        div {\n            h1 { \"Form\" }\n            form {\n                id: \"form\",\n                oninput: move |ev| {\n                    values.set(ev.values());\n                },\n                onsubmit: move |ev| {\n                    println!(\"{ev:?}\");\n                },\n                input {\n                    r#type: \"text\",\n                    name: \"username\",\n                    id: \"form-username\",\n                    oninput: set_username\n                }\n                input { r#type: \"text\", name: \"full-name\", value: \"lorem\" }\n                input { r#type: \"password\", name: \"password\", value: \"ipsum\" }\n                input {\n                    r#type: \"radio\",\n                    name: \"color\",\n                    value: \"red\",\n                    checked: true\n                }\n                input { r#type: \"radio\", name: \"color\", value: \"blue\" }\n                button { r#type: \"submit\", value: \"Submit\", \"Submit the form\" }\n            }\n        }\n    }\n}\n\nfn test_form_submit() -> Element {\n    let mut values = use_signal(Vec::new);\n\n    utils::mock_event_with_extra(\n        \"form-submitter\",\n        r#\"new Event(\"submit\", { bubbles: true, cancelable: true, composed: true })\"#,\n        r#\"element.submit();\"#,\n    );\n\n    let set_values = move |ev: FormEvent| {\n        values.set(ev.values());\n        values.with_mut(|x| {\n            assert_eq!(x.iter().find(|f| f.0 == \"username\").unwrap().1, \"goodbye\");\n            assert_eq!(x.iter().find(|f| f.0 == \"full-name\").unwrap().1, \"lorem\");\n            assert_eq!(x.iter().find(|f| f.0 == \"password\").unwrap().1, \"ipsum\");\n            assert_eq!(x.iter().find(|f| f.0 == \"color\").unwrap().1, \"red\");\n        });\n        RECEIVED_EVENTS.with_mut(|x| *x += 1);\n    };\n\n    rsx! {\n        div {\n            h1 { \"Form\" }\n            form { id: \"form-submitter\", onsubmit: set_values,\n                input {\n                    r#type: \"text\",\n                    name: \"username\",\n                    id: \"username\",\n                    value: \"goodbye\"\n                }\n                input { r#type: \"text\", name: \"full-name\", value: \"lorem\" }\n                input { r#type: \"password\", name: \"password\", value: \"ipsum\" }\n                input {\n                    r#type: \"radio\",\n                    name: \"color\",\n                    value: \"red\",\n                    checked: true\n                }\n                input { r#type: \"radio\", name: \"color\", value: \"blue\" }\n                button { r#type: \"submit\", value: \"Submit\", \"Submit the form\" }\n            }\n        }\n    }\n}\n\nfn test_select_multiple_options() -> Element {\n    utils::mock_event_with_extra(\n        \"select-many\",\n        r#\"new Event(\"input\", { bubbles: true, cancelable: true, composed: true })\"#,\n        r#\"\n            document.getElementById('usa').selected = true;\n            document.getElementById('canada').selected = true;\n            document.getElementById('mexico').selected = false;\n        \"#,\n    );\n\n    rsx! {\n        select {\n            id: \"select-many\",\n            name: \"country\",\n            multiple: true,\n            oninput: move |ev| {\n                let values = ev.value();\n                let values = values.split(',').collect::<Vec<_>>();\n                assert_eq!(values, vec![\"usa\", \"canada\"]);\n                RECEIVED_EVENTS.with_mut(|x| *x += 1);\n            },\n            option { id: \"usa\", value: \"usa\", \"USA\" }\n            option { id: \"canada\", value: \"canada\", \"Canada\" }\n            option { id: \"mexico\", value: \"mexico\", selected: true, \"Mexico\" }\n        }\n    }\n}\n\nfn test_unicode() -> Element {\n    // emulate an oninput event with a unicode character\n    utils::mock_event_with_extra(\n        \"unicode\",\n        r#\"new InputEvent(\"input\", {\n            inputType: 'insertText',\n            bubbles: true,\n            cancelable: true,\n        })\"#,\n        r#\"\n            element.value = \"🦀\";\n        \"#,\n    );\n\n    rsx! {\n        input {\n            id: \"unicode\",\n            oninput: move |event| {\n                println!(\"{:?}\", event.data);\n                assert_eq!(event.data.value(), \"🦀\");\n                RECEIVED_EVENTS.with_mut(|x| *x += 1);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "packages/desktop/headless_tests/forms.rs",
    "content": "use dioxus::prelude::*;\nuse dioxus_desktop::DesktopContext;\nuse dioxus_document::eval;\n\n#[path = \"./utils.rs\"]\nmod utils;\n\nfn main() {\n    #[cfg(not(windows))]\n    utils::check_app_exits(check_html_renders);\n}\n\nasync fn inner_html(count: usize) -> String {\n    let html = document::eval(&format!(\n        r#\"// Wait until the element is available\n            const interval = setInterval(() => {{\n                const element = document.getElementById('div-{count}');\n                if (element) {{\n                    dioxus.send(element.innerHTML);\n                    clearInterval(interval);\n                }}\n            }}, 100);\"#\n    ))\n    .recv::<String>()\n    .await\n    .unwrap();\n\n    println!(\"html: {html}\");\n    html\n}\n\nfn check_html_renders() -> Element {\n    let mut signal = use_signal(|| 0);\n\n    use_effect(move || {\n        let signal = signal();\n        // Pass the test once the count is greater than 10\n        if signal > 10 {\n            let desktop_context: DesktopContext = consume_context();\n            desktop_context.close();\n        }\n\n        spawn(async move {\n            let raw_html = inner_html(signal).await;\n            println!(\"{raw_html}\");\n            // Make sure the html contains count is {signal}\n            assert!(raw_html.contains(&format!(\"count is {}\", signal)));\n            tokio::time::sleep(std::time::Duration::from_millis(100)).await;\n            eval(\n                r#\"let button = document.querySelector('#increment');\n                    button.dispatchEvent(new MouseEvent('click', {\n                        view: window,\n                        bubbles: true,\n                        cancelable: true,\n                        buttons: 2,\n                        button: 2,\n                    }));\"#,\n            );\n\n            // If the signal is even, also submit the form. This should not effect the count because the navigation is blocked\n            if signal % 2 == 0 {\n                tokio::time::sleep(std::time::Duration::from_millis(100)).await;\n                eval(\n                    r#\"let form = document.getElementById('form-submit');\n                        form.dispatchEvent(new Event('click', {\n                            view: window,\n                            bubbles: true,\n                            cancelable: true,\n                            buttons: 2,\n                            button: 2,\n                        }));\"#,\n                );\n            }\n        });\n    });\n\n    rsx! {\n        div {\n            id: \"div-{signal}\",\n            h1 { \"Form\" }\n            form {\n                input {\n                    r#type: \"text\",\n                    name: \"username\",\n                    id: \"username\",\n                    value: \"goodbye\"\n                }\n                input { r#type: \"text\", name: \"full-name\", value: \"lorem\" }\n                input { r#type: \"password\", name: \"password\", value: \"ipsum\" }\n                input {\n                    r#type: \"radio\",\n                    name: \"color\",\n                    value: \"red\",\n                    checked: true\n                }\n                input { r#type: \"radio\", name: \"color\", value: \"blue\" }\n                button { id: \"form-submit\", r#type: \"submit\", value: \"Submit\", \"Submit the form\" }\n            }\n\n            button {\n                id: \"increment\",\n                onclick: move |_| {\n                    signal += 1;\n                },\n                \"count is {signal}\"\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "packages/desktop/headless_tests/rendering.rs",
    "content": "use dioxus::prelude::*;\nuse dioxus_desktop::DesktopContext;\n\n#[path = \"./utils.rs\"]\nmod utils;\n\nfn main() {\n    #[cfg(not(windows))]\n    utils::check_app_exits(check_html_renders);\n}\n\nfn use_inner_html(id: &'static str) -> Option<String> {\n    let mut value = use_signal(|| None as Option<String>);\n\n    use_effect(move || {\n        spawn(async move {\n            tokio::time::sleep(std::time::Duration::from_millis(500)).await;\n\n            let res = document::eval(&format!(\n                r#\"let element = document.getElementById('{id}');\n                return element.innerHTML\"#\n            ))\n            .await\n            .unwrap();\n\n            if let Some(html) = res.as_str() {\n                println!(\"html: {html}\");\n                value.set(Some(html.to_string()));\n            }\n        });\n    });\n\n    value()\n}\n\nconst EXPECTED_HTML: &str = r#\"<div style=\"width: 100px; height: 100px; color: rgb(0, 0, 0);\" id=\"5\"><input type=\"checkbox\"><h1>text</h1><div><p>hello world</p></div></div>\"#;\n\nfn check_html_renders() -> Element {\n    let inner_html = use_inner_html(\"main_div\");\n\n    let desktop_context: DesktopContext = consume_context();\n\n    if let Some(raw_html) = inner_html {\n        println!(\"{raw_html}\");\n        let fragment = &raw_html;\n        let expected = EXPECTED_HTML;\n        assert_eq!(raw_html, EXPECTED_HTML);\n        if fragment == expected {\n            println!(\"html matches\");\n            desktop_context.close();\n        }\n    }\n\n    let dyn_value = 0;\n    let dyn_element = rsx! { div { dangerous_inner_html: \"<p>hello world</p>\" } };\n\n    rsx! {\n        div { id: \"main_div\",\n            div {\n                width: \"100px\",\n                height: \"100px\",\n                color: \"rgb({dyn_value}, {dyn_value}, {dyn_value})\",\n                id: 5,\n                input { \"type\": \"checkbox\" }\n                h1 { \"text\" }\n                {dyn_element}\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "packages/desktop/headless_tests/utils.rs",
    "content": "#![allow(unused)] // for whatever reason, the compiler is not recognizing the use of these functions\n\nuse dioxus::prelude::*;\nuse dioxus_core::Element;\n\npub fn check_app_exits(app: fn() -> Element) {\n    use dioxus_desktop::tao::window::WindowBuilder;\n    use dioxus_desktop::Config;\n    // This is a deadman's switch to ensure that the app exits\n    let should_panic = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(true));\n    let should_panic_clone = should_panic.clone();\n    std::thread::spawn(move || {\n        std::thread::sleep(std::time::Duration::from_secs(60));\n        if should_panic_clone.load(std::sync::atomic::Ordering::SeqCst) {\n            eprintln!(\"App did not exit in time\");\n            std::process::exit(exitcode::SOFTWARE);\n        }\n    });\n\n    dioxus::LaunchBuilder::desktop()\n        .with_cfg(Config::new().with_window(WindowBuilder::new().with_visible(false)))\n        .launch(app);\n\n    // Stop deadman's switch\n    should_panic.store(false, std::sync::atomic::Ordering::SeqCst);\n}\n\npub static EXPECTED_EVENTS: GlobalSignal<usize> = Signal::global(|| 0);\n\npub fn mock_event(id: &'static str, value: &'static str) {\n    mock_event_with_extra(id, value, \"\");\n}\n\npub fn mock_event_with_extra(id: &'static str, value: &'static str, extra: &'static str) {\n    use_hook(move || {\n        EXPECTED_EVENTS.with_mut(|x| *x += 1);\n\n        spawn(async move {\n            // We need to wait for edits to be applied before we can send the event\n            // Sometimes (windows...) this takes a while\n            // we should really be running this check when mounted\n            tokio::time::sleep(std::time::Duration::from_millis(10000)).await;\n\n            let js = format!(\n                r#\"\n                let event = {value};\n                let element = document.getElementById('{id}');\n                {extra}\n                element.dispatchEvent(event);\n                \"#\n            );\n\n            document::eval(&js).await.unwrap();\n        });\n    })\n}\n"
  },
  {
    "path": "packages/desktop/src/android_sync_lock.rs",
    "content": "/// This is a hack to get around the fact that wry is currently not thread safe on android\n///\n/// We want to acquire this mutex before doing anything with the virtualdom directly\n#[cfg(target_os = \"android\")]\npub fn android_runtime_lock() -> std::sync::MutexGuard<'static, ()> {\n    use std::sync::{Mutex, OnceLock};\n\n    static RUNTIME_LOCK: OnceLock<Mutex<()>> = OnceLock::new();\n\n    RUNTIME_LOCK.get_or_init(|| Mutex::new(())).lock().unwrap()\n}\n"
  },
  {
    "path": "packages/desktop/src/app.rs",
    "content": "use crate::{\n    config::{Config, WindowCloseBehaviour},\n    edits::EditWebsocket,\n    event_handlers::WindowEventHandlers,\n    ipc::{IpcMessage, UserWindowEvent},\n    query::QueryResult,\n    shortcut::ShortcutRegistry,\n    webview::{PendingWebview, WebviewInstance},\n};\nuse dioxus_core::VirtualDom;\nuse std::{\n    cell::{Cell, RefCell},\n    collections::HashMap,\n    rc::Rc,\n    time::Duration,\n};\nuse tao::{\n    dpi::PhysicalSize,\n    event::Event,\n    event_loop::{ControlFlow, EventLoop, EventLoopBuilder, EventLoopProxy, EventLoopWindowTarget},\n    window::WindowId,\n};\n\n/// The single top-level object that manages all the running windows, assets, shortcuts, etc\npub(crate) struct App {\n    // move the props into a cell so we can pop it out later to create the first window\n    // iOS panics if we create a window before the event loop is started, so we toss them into a cell\n    pub(crate) unmounted_dom: Cell<Option<VirtualDom>>,\n    pub(crate) cfg: Cell<Option<Config>>,\n\n    // Stuff we need mutable access to\n    pub(crate) control_flow: ControlFlow,\n    pub(crate) is_visible_before_start: bool,\n    pub(crate) exit_on_last_window_close: bool,\n    pub(crate) disable_dma_buf_on_wayland: bool,\n    pub(crate) webviews: HashMap<WindowId, WebviewInstance>,\n    pub(crate) float_all: bool,\n    pub(crate) show_devtools: bool,\n\n    /// This single blob of state is shared between all the windows so they have access to the runtime state\n    ///\n    /// This includes stuff like the event handlers, shortcuts, etc as well as ways to modify *other* windows\n    pub(crate) shared: Rc<SharedContext>,\n}\n\n/// A bundle of state shared between all the windows, providing a way for us to communicate with running webview.\npub(crate) struct SharedContext {\n    pub(crate) event_handlers: WindowEventHandlers,\n    pub(crate) pending_webviews: RefCell<Vec<PendingWebview>>,\n    pub(crate) shortcut_manager: ShortcutRegistry,\n    pub(crate) proxy: EventLoopProxy<UserWindowEvent>,\n    pub(crate) target: EventLoopWindowTarget<UserWindowEvent>,\n    pub(crate) websocket: EditWebsocket,\n}\n\nimpl App {\n    pub fn new(mut cfg: Config, virtual_dom: VirtualDom) -> (EventLoop<UserWindowEvent>, Self) {\n        let event_loop = cfg\n            .event_loop\n            .take()\n            .unwrap_or_else(|| EventLoopBuilder::<UserWindowEvent>::with_user_event().build());\n\n        let app = Self {\n            exit_on_last_window_close: cfg.exit_on_last_window_close,\n            disable_dma_buf_on_wayland: cfg.disable_dma_buf_on_wayland,\n            is_visible_before_start: true,\n            webviews: HashMap::new(),\n            control_flow: ControlFlow::Wait,\n            unmounted_dom: Cell::new(Some(virtual_dom)),\n            float_all: false,\n            show_devtools: false,\n            cfg: Cell::new(Some(cfg)),\n            shared: Rc::new(SharedContext {\n                event_handlers: WindowEventHandlers::default(),\n                pending_webviews: Default::default(),\n                shortcut_manager: ShortcutRegistry::new(),\n                proxy: event_loop.create_proxy(),\n                target: event_loop.clone(),\n                websocket: EditWebsocket::start(),\n            }),\n        };\n\n        // Set the event converter\n        dioxus_html::set_event_converter(Box::new(crate::events::SerializedHtmlEventConverter));\n\n        // Wire up the global hotkey handler\n        #[cfg(any(target_os = \"windows\", target_os = \"linux\", target_os = \"macos\"))]\n        app.set_global_hotkey_handler();\n\n        // Wire up the menubar receiver - this way any component can key into the menubar actions\n        #[cfg(any(target_os = \"windows\", target_os = \"linux\", target_os = \"macos\"))]\n        app.set_menubar_receiver();\n\n        // Wire up the tray icon receiver - this way any component can key into the menubar actions\n        #[cfg(any(target_os = \"windows\", target_os = \"linux\", target_os = \"macos\"))]\n        app.set_tray_icon_receiver();\n\n        // Allow hotreloading to work - but only in debug mode\n        #[cfg(all(feature = \"devtools\", debug_assertions))]\n        app.connect_hotreload();\n\n        #[cfg(debug_assertions)]\n        #[cfg(any(target_os = \"windows\", target_os = \"linux\", target_os = \"macos\"))]\n        app.connect_preserve_window_state_handler();\n\n        // Make sure to disable DMA buffer rendering on Linux Wayland sessions\n        app.disable_dma_buf();\n\n        (event_loop, app)\n    }\n\n    pub fn tick(&mut self, window_event: &Event<'_, UserWindowEvent>) {\n        self.control_flow = ControlFlow::Wait;\n        self.shared\n            .event_handlers\n            .apply_event(window_event, &self.shared.target);\n    }\n\n    #[cfg(any(target_os = \"windows\", target_os = \"linux\", target_os = \"macos\"))]\n    pub fn handle_global_hotkey(&self, event: global_hotkey::GlobalHotKeyEvent) {\n        self.shared.shortcut_manager.call_handlers(event);\n    }\n\n    #[cfg(any(target_os = \"windows\", target_os = \"linux\", target_os = \"macos\"))]\n    pub fn handle_menu_event(&mut self, event: muda::MenuEvent) {\n        match event.id().0.as_str() {\n            \"dioxus-float-top\" => {\n                for webview in self.webviews.values() {\n                    webview\n                        .desktop_context\n                        .window\n                        .set_always_on_top(self.float_all);\n                }\n                self.float_all = !self.float_all;\n            }\n            \"dioxus-toggle-dev-tools\" => {\n                self.show_devtools = !self.show_devtools;\n                for webview in self.webviews.values() {\n                    let wv = &webview.desktop_context.webview;\n                    if self.show_devtools {\n                        wv.open_devtools();\n                    } else {\n                        wv.close_devtools();\n                    }\n                }\n            }\n            _ => (),\n        }\n    }\n    #[cfg(any(target_os = \"windows\", target_os = \"linux\", target_os = \"macos\"))]\n    pub fn handle_tray_menu_event(&mut self, event: tray_icon::menu::MenuEvent) {\n        _ = event;\n    }\n\n    #[cfg(any(target_os = \"windows\", target_os = \"linux\", target_os = \"macos\"))]\n    pub fn handle_tray_icon_event(&mut self, event: tray_icon::TrayIconEvent) {\n        if let tray_icon::TrayIconEvent::Click {\n            id: _,\n            position: _,\n            rect: _,\n            button,\n            button_state: _,\n        } = event\n        {\n            if button == tray_icon::MouseButton::Left {\n                for webview in self.webviews.values() {\n                    webview.desktop_context.window.set_visible(true);\n                    webview.desktop_context.window.set_focus();\n                }\n            }\n        }\n    }\n\n    #[cfg(all(feature = \"devtools\", debug_assertions))]\n    pub fn connect_hotreload(&self) {\n        let proxy = self.shared.proxy.clone();\n        dioxus_devtools::connect(move |msg| {\n            _ = proxy.send_event(UserWindowEvent::HotReloadEvent(msg));\n        })\n    }\n\n    pub fn handle_new_window(&mut self) {\n        for pending_webview in self.shared.pending_webviews.borrow_mut().drain(..) {\n            let window = pending_webview.create_window(&self.shared);\n            let id = window.desktop_context.window.id();\n            self.webviews.insert(id, window);\n            _ = self.shared.proxy.send_event(UserWindowEvent::Poll(id));\n        }\n    }\n\n    pub fn handle_close_requested(&mut self, id: WindowId) {\n        let Some(window) = self.webviews.get(&id) else {\n            // If the window is not found, we can just return\n            return;\n        };\n\n        match window.desktop_context.close_behaviour.get() {\n            // If the window is just set to hide when closed, we can just hide it\n            WindowCloseBehaviour::WindowHides => {\n                window.desktop_context.window.set_visible(false);\n            }\n\n            // If the window is set to close, we can remove it from the list of webviews\n            // If the app is set to exit when the last window closes, we should also exit the app\n            WindowCloseBehaviour::WindowCloses => {\n                #[cfg(debug_assertions)]\n                self.persist_window_state();\n\n                self.webviews.remove(&id);\n\n                if self.exit_on_last_window_close && self.webviews.is_empty() {\n                    self.control_flow = ControlFlow::Exit\n                }\n            }\n        };\n    }\n\n    pub fn window_destroyed(&mut self, id: WindowId) {\n        self.webviews.remove(&id);\n\n        if self.exit_on_last_window_close && self.webviews.is_empty() {\n            self.control_flow = ControlFlow::Exit\n        }\n    }\n\n    pub fn resize_window(&self, id: WindowId, size: PhysicalSize<u32>) {\n        // TODO: the app layer should avoid directly manipulating the webview webview instance internals.\n        // Window creation and modification is the responsibility of the webview instance so it makes sense to\n        // encapsulate that there.\n        if let Some(webview) = self.webviews.get(&id) {\n            use wry::Rect;\n\n            _ = webview.desktop_context.webview.set_bounds(Rect {\n                position: wry::dpi::Position::Logical(wry::dpi::LogicalPosition::new(0.0, 0.0)),\n                size: wry::dpi::Size::Physical(wry::dpi::PhysicalSize::new(\n                    size.width,\n                    size.height,\n                )),\n            });\n        }\n    }\n\n    pub fn handle_start_cause_init(&mut self) {\n        let virtual_dom = self\n            .unmounted_dom\n            .take()\n            .expect(\"Virtualdom should be set before initialization\");\n        #[allow(unused_mut)]\n        let mut cfg = self\n            .cfg\n            .take()\n            .expect(\"Config should be set before initialization\");\n\n        self.is_visible_before_start = cfg.window.window.visible;\n        #[cfg(not(target_os = \"linux\"))]\n        {\n            cfg.window = cfg.window.with_visible(false);\n        }\n        let explicit_window_size = cfg.window.window.inner_size;\n        let explicit_window_position = cfg.window.window.position;\n\n        let webview = WebviewInstance::new(cfg, virtual_dom, self.shared.clone());\n\n        // And then attempt to resume from state\n        self.resume_from_state(&webview, explicit_window_size, explicit_window_position);\n\n        let id = webview.desktop_context.window.id();\n        self.webviews.insert(id, webview);\n    }\n\n    pub fn handle_browser_open(&mut self, msg: IpcMessage) {\n        if let Some(temp) = msg.params().as_object() {\n            if temp.contains_key(\"href\") {\n                if let Some(href) = temp.get(\"href\").and_then(|v| v.as_str()) {\n                    if let Err(err) = webbrowser::open(href) {\n                        tracing::error!(\"Failed to open URL: {}\", err);\n                    }\n                }\n            }\n        }\n    }\n\n    /// The webview is finally loaded\n    ///\n    /// Let's rebuild it and then start polling it\n    pub fn handle_initialize_msg(&mut self, id: WindowId) {\n        let view = self.webviews.get_mut(&id).unwrap();\n\n        view.edits\n            .wry_queue\n            .with_mutation_state_mut(|f| view.dom.rebuild(f));\n\n        view.edits.wry_queue.send_edits();\n\n        #[cfg(not(target_os = \"linux\"))]\n        {\n            view.desktop_context\n                .window\n                .set_visible(self.is_visible_before_start);\n        }\n\n        _ = self.shared.proxy.send_event(UserWindowEvent::Poll(id));\n    }\n\n    pub fn handle_query_msg(&mut self, msg: IpcMessage, id: WindowId) {\n        let Ok(result) = serde_json::from_value::<QueryResult>(msg.params()) else {\n            return;\n        };\n\n        let Some(view) = self.webviews.get(&id) else {\n            return;\n        };\n\n        view.desktop_context.query.send(result);\n    }\n\n    #[cfg(all(feature = \"devtools\", debug_assertions))]\n    pub fn handle_hot_reload_msg(&mut self, msg: dioxus_devtools::DevserverMsg) {\n        use std::time::Duration;\n\n        use dioxus_devtools::DevserverMsg;\n\n        // Amount of time that toats should be displayed.\n        const TOAST_TIMEOUT: Duration = Duration::from_secs(2);\n        const TOAST_TIMEOUT_LONG: Duration = Duration::from_secs(3600); // Duration::MAX is too long for JS.\n\n        match msg {\n            DevserverMsg::HotReload(hr_msg) => {\n                for webview in self.webviews.values_mut() {\n                    {\n                        // This is a place where wry says it's threadsafe but it's actually not.\n                        // If we're patching the app, we want to make sure it's not going to progress in the interim.\n                        #[cfg(target_os = \"android\")]\n                        let _lock = crate::android_sync_lock::android_runtime_lock();\n                        dioxus_devtools::apply_changes(&webview.dom, &hr_msg);\n                    }\n\n                    webview.poll_vdom();\n                }\n\n                if !hr_msg.assets.is_empty() {\n                    for webview in self.webviews.values_mut() {\n                        webview.kick_stylsheets();\n                    }\n                }\n\n                if hr_msg.jump_table.is_some()\n                    && hr_msg.for_build_id == Some(dioxus_cli_config::build_id())\n                {\n                    self.send_toast_to_all(\n                        \"Hot-patch success!\",\n                        &format!(\"App successfully patched in {} ms\", hr_msg.ms_elapsed),\n                        \"success\",\n                        TOAST_TIMEOUT,\n                        false,\n                    );\n                }\n            }\n            DevserverMsg::FullReloadCommand => {\n                self.send_toast_to_all(\n                    \"Successfully rebuilt.\",\n                    \"Your app was rebuilt successfully and without error.\",\n                    \"success\",\n                    TOAST_TIMEOUT,\n                    true,\n                );\n            }\n            DevserverMsg::FullReloadStart => self.send_toast_to_all(\n                \"Your app is being rebuilt.\",\n                \"A non-hot-reloadable change occurred and we must rebuild.\",\n                \"info\",\n                TOAST_TIMEOUT_LONG,\n                false,\n            ),\n            DevserverMsg::FullReloadFailed => self.send_toast_to_all(\n                \"Oops! The build failed.\",\n                \"We tried to rebuild your app, but something went wrong.\",\n                \"error\",\n                TOAST_TIMEOUT_LONG,\n                false,\n            ),\n            DevserverMsg::HotPatchStart => self.send_toast_to_all(\n                \"Hot-patching app...\",\n                \"Hot-patching modified Rust code.\",\n                \"info\",\n                TOAST_TIMEOUT_LONG,\n                false,\n            ),\n            DevserverMsg::Shutdown => {\n                self.control_flow = ControlFlow::Exit;\n            }\n            _ => {}\n        }\n    }\n\n    #[cfg(all(feature = \"devtools\", debug_assertions))]\n    fn send_toast_to_all(\n        &self,\n        header_text: &str,\n        message: &str,\n        level: &str,\n        duration: Duration,\n        after_reload: bool,\n    ) {\n        for webview in self.webviews.values() {\n            webview.show_toast(header_text, message, level, duration, after_reload);\n        }\n    }\n\n    /// Poll the virtualdom until it's pending\n    ///\n    /// The waker we give it is connected to the event loop, so it will wake up the event loop when it's ready to be polled again\n    ///\n    /// All IO is done on the tokio runtime we started earlier\n    pub fn poll_vdom(&mut self, id: WindowId) {\n        let Some(view) = self.webviews.get_mut(&id) else {\n            return;\n        };\n\n        view.poll_vdom();\n    }\n\n    #[cfg(any(target_os = \"windows\", target_os = \"linux\", target_os = \"macos\"))]\n    fn set_global_hotkey_handler(&self) {\n        let receiver = self.shared.proxy.clone();\n\n        // The event loop becomes the hotkey receiver\n        // This means we don't need to poll the receiver on every tick - we just get the events as they come in\n        // This is a bit more efficient than the previous implementation, but if someone else sets a handler, the\n        // receiver will become inert.\n        global_hotkey::GlobalHotKeyEvent::set_event_handler(Some(move |t| {\n            // todo: should we unset the event handler when the app shuts down?\n            _ = receiver.send_event(UserWindowEvent::GlobalHotKeyEvent(t));\n        }));\n    }\n\n    #[cfg(any(target_os = \"windows\", target_os = \"linux\", target_os = \"macos\"))]\n    fn set_menubar_receiver(&self) {\n        let receiver = self.shared.proxy.clone();\n\n        // The event loop becomes the menu receiver\n        // This means we don't need to poll the receiver on every tick - we just get the events as they come in\n        // This is a bit more efficient than the previous implementation, but if someone else sets a handler, the\n        // receiver will become inert.\n        muda::MenuEvent::set_event_handler(Some(move |t| {\n            // todo: should we unset the event handler when the app shuts down?\n            _ = receiver.send_event(UserWindowEvent::MudaMenuEvent(t));\n        }));\n    }\n\n    #[cfg(any(target_os = \"windows\", target_os = \"linux\", target_os = \"macos\"))]\n    fn set_tray_icon_receiver(&self) {\n        let receiver = self.shared.proxy.clone();\n\n        // The event loop becomes the menu receiver\n        // This means we don't need to poll the receiver on every tick - we just get the events as they come in\n        // This is a bit more efficient than the previous implementation, but if someone else sets a handler, the\n        // receiver will become inert.\n        tray_icon::TrayIconEvent::set_event_handler(Some(move |t| {\n            // todo: should we unset the event handler when the app shuts down?\n            _ = receiver.send_event(UserWindowEvent::TrayIconEvent(t));\n        }));\n\n        // for whatever reason they had to make it separate\n        let receiver = self.shared.proxy.clone();\n        tray_icon::menu::MenuEvent::set_event_handler(Some(move |t| {\n            // todo: should we unset the event handler when the app shuts down?\n            _ = receiver.send_event(UserWindowEvent::TrayMenuEvent(t));\n        }));\n    }\n\n    /// Do our best to preserve state about the window when the event loop is destroyed\n    ///\n    /// This will attempt to save the window position, size, and monitor into the environment before\n    /// closing. This way, when the app is restarted, it can attempt to restore the window to the same\n    /// position and size it was in before, making a better DX.\n    pub(crate) fn handle_loop_destroyed(&self) {\n        #[cfg(debug_assertions)]\n        self.persist_window_state();\n    }\n\n    #[cfg(debug_assertions)]\n    fn persist_window_state(&self) {\n        if let Some(webview) = self.webviews.values().next() {\n            let window = &webview.desktop_context.window;\n\n            let Some(monitor) = window.current_monitor() else {\n                return;\n            };\n\n            let Ok(position) = window.outer_position() else {\n                return;\n            };\n            let (x, y) = if cfg!(target_os = \"macos\") {\n                let position = position.to_logical::<i32>(window.scale_factor());\n                (position.x, position.y)\n            } else {\n                (position.x, position.y)\n            };\n\n            let (width, height) = if cfg!(target_os = \"macos\") {\n                let size = window.outer_size();\n                let size = size.to_logical::<u32>(window.scale_factor());\n                // This is to work around a bug in how tao handles inner_size on macOS\n                // We *want* to use inner_size, but that's currently broken, so we use outer_size instead and then an adjustment\n                //\n                // https://github.com/tauri-apps/tao/issues/889\n                let adjustment = if window.is_decorated() { 28 } else { 0 };\n                (size.width, size.height.saturating_sub(adjustment))\n            } else {\n                let size = window.inner_size();\n                (size.width, size.height)\n            };\n\n            let Some(monitor_name) = monitor.name() else {\n                return;\n            };\n\n            let state = PreservedWindowState {\n                x,\n                y,\n                width: width.max(200),\n                height: height.max(200),\n                monitor: monitor_name.to_string(),\n            };\n\n            // Yes... I know... we're loading a file that might not be ours... but it's a debug feature\n            if let Ok(state) = serde_json::to_string(&state) {\n                _ = std::fs::write(restore_file(), state);\n            }\n        }\n    }\n\n    // Write this to the target dir so we can pick back up\n    fn resume_from_state(\n        &mut self,\n        webview: &WebviewInstance,\n        explicit_inner_size: Option<tao::dpi::Size>,\n        explicit_window_position: Option<tao::dpi::Position>,\n    ) {\n        // We only want to do this on desktop\n        if cfg!(target_os = \"android\") || cfg!(target_os = \"ios\") {\n            return;\n        }\n\n        // We only want to do this in debug mode\n        if !cfg!(debug_assertions) {\n            return;\n        }\n\n        if let Ok(state) = std::fs::read_to_string(restore_file()) {\n            if let Ok(state) = serde_json::from_str::<PreservedWindowState>(&state) {\n                let window = &webview.desktop_context.window;\n                let position = (state.x, state.y);\n                let size = (state.width, state.height);\n\n                // Only set the outer position if it wasn't explicitly set\n                if explicit_window_position.is_none() {\n                    if cfg!(target_os = \"macos\") {\n                        window.set_outer_position(tao::dpi::LogicalPosition::new(\n                            position.0, position.1,\n                        ));\n                    } else {\n                        window.set_outer_position(tao::dpi::PhysicalPosition::new(\n                            position.0, position.1,\n                        ));\n                    }\n                }\n\n                // Only set the inner size if it wasn't explicitly set\n                if explicit_inner_size.is_none() {\n                    if cfg!(target_os = \"macos\") {\n                        window.set_inner_size(tao::dpi::LogicalSize::new(size.0, size.1));\n                    } else {\n                        window.set_inner_size(tao::dpi::PhysicalSize::new(size.0, size.1));\n                    }\n                }\n            }\n        }\n    }\n\n    /// Wire up a receiver to sigkill that lets us preserve the window state\n    /// Whenever sigkill is sent, we shut down the app and save the window state\n    #[cfg(debug_assertions)]\n    fn connect_preserve_window_state_handler(&self) {\n        // TODO: make this work on windows\n        #[cfg(unix)]\n        {\n            // Wire up the trap\n            let target = self.shared.proxy.clone();\n            std::thread::spawn(move || {\n                use signal_hook::consts::{SIGINT, SIGTERM};\n                let sigkill = signal_hook::iterator::Signals::new([SIGTERM, SIGINT]);\n                if let Ok(mut sigkill) = sigkill {\n                    for _ in sigkill.forever() {\n                        if target.send_event(UserWindowEvent::Shutdown).is_err() {\n                            std::process::exit(0);\n                        }\n\n                        // give it a moment for the event to be processed\n                        std::thread::sleep(std::time::Duration::from_millis(100));\n                    }\n                }\n            });\n        }\n    }\n\n    /// Disable DMA buffer rendering on Linux Wayland sessions to avoid bugs with WebKitGTK\n    fn disable_dma_buf(&self) {\n        if cfg!(target_os = \"linux\") && self.disable_dma_buf_on_wayland {\n            static INIT: std::sync::Once = std::sync::Once::new();\n            INIT.call_once(|| {\n                if std::path::Path::new(\"/dev/dri\").exists()\n                    && std::env::var(\"XDG_SESSION_TYPE\").unwrap_or_default() == \"wayland\"\n                {\n                    // Gnome Webkit is currently buggy under Wayland and KDE, so we will run it with XWayland mode.\n                    // See: https://github.com/DioxusLabs/dioxus/issues/3667\n                    unsafe {\n                        // Disable explicit sync for NVIDIA drivers on Linux when using Way\n                        std::env::set_var(\"WEBKIT_DISABLE_DMABUF_RENDERER\", \"1\");\n                    }\n                }\n                unsafe {\n                    std::env::set_var(\"GDK_BACKEND\", \"x11\");\n                }\n            });\n        }\n    }\n}\n\n#[derive(Debug, serde::Serialize, serde::Deserialize)]\nstruct PreservedWindowState {\n    x: i32,\n    y: i32,\n    width: u32,\n    height: u32,\n    monitor: String,\n}\n\n/// Return the location of a tempfile with our window state in it such that we can restore it later\nfn restore_file() -> std::path::PathBuf {\n    let dir = dioxus_cli_config::session_cache_dir().unwrap_or_else(std::env::temp_dir);\n    dir.join(\"window-state.json\")\n}\n"
  },
  {
    "path": "packages/desktop/src/assets/dev.index.html",
    "content": "<!DOCTYPE html>\n<html>\n    <head>\n        <title>Dioxus app</title>\n        <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no\">\n        <style>\n            /* Inter Font */\n            /* @import url('https://fonts.googleapis.com/css2?family=Inter:wght@100..900&display=swap') layer; */\n\n            #dx-toast-template {\n                display: none;\n                visibility: hidden;\n            }\n\n            .dx-toast {\n                position: absolute;\n                top: 10px;\n                right: 0;\n                padding-right: 10px;\n                user-select: none;\n                /* transition: transform 0.2s ease; */\n                z-index: 2147483647;\n            }\n\n            .dx-toast .dx-toast-inner {\n                /* transition: right 0.2s ease-out; */\n                position: fixed;\n\n                background-color: #181B20;\n                color: #ffffff;\n                font-family: \"Inter\", sans-serif;\n\n                display: grid;\n                grid-template-columns: auto auto;\n                max-width: 400px;\n                min-height: 56px;\n                border-radius: 5px;\n            }\n\n            .dx-toast .dx-toast-inner {\n                cursor: pointer;\n                margin-right: 10px;\n            }\n\n            .dx-toast .dx-toast-level-bar-container {\n                height: 100%;\n                width: 6px;\n            }\n\n            .dx-toast .dx-toast-level-bar-container .dx-toast-level-bar {\n                width: 100%;\n                height: 100%;\n                border-radius: 5px 0px 0px 5px;\n            }\n\n            .dx-toast .dx-toast-content {\n                padding: 8px;\n            }\n\n            .dx-toast .dx-toast-header {\n                display: flex;\n                flex-direction: row;\n                justify-content: start;\n                align-items: end;\n                margin-bottom: 10px;\n            }\n\n            .dx-toast .dx-toast-header>svg {\n                height: 18px;\n                margin-right: 5px;\n            }\n\n            .dx-toast .dx-toast-header .dx-toast-header-text {\n                font-size: 14px;\n                font-weight: 700;\n                padding: 0;\n                margin: 0;\n            }\n\n            .dx-toast .dx-toast-msg {\n                font-size: 11px;\n                font-weight: 400;\n                padding: 0;\n                margin: 0;\n            }\n\n            .dx-toast-level-bar.info {\n                background-color: #428EFF;\n            }\n\n            .dx-toast-level-bar.success {\n                background-color: #42FF65;\n            }\n\n            .dx-toast-level-bar.error {\n                background-color: #FF4242;\n            }\n        </style>\n        <script>\n            const STORAGE_KEY = \"SCHEDULED-DX-TOAST\";\n            let currentTimeout = null;\n            let currentToastId = 0;\n\n            // Show a toast, removing the previous one.\n            function showDXToast(headerText, message, progressLevel, durationMs) {\n                const decor = document.getElementById(\"__dx-toast-decor\");\n                const text = document.getElementById(\"__dx-toast-text\");\n                const msg = document.getElementById(\"__dx-toast-msg\");\n                const inner = document.getElementById(\"__dx-toast-inner\");\n                const toast = document.getElementById(\"__dx-toast\");\n\n                if (decor) decor.className = `dx-toast-level-bar ${progressLevel}`;\n                if (text) text.innerText = headerText;\n                if (msg) msg.innerText = message;\n                if (inner) inner.style.right = \"0\";\n                if (toast) {\n                    toast.removeAttribute(\"aria-hidden\");\n                    toast.addEventListener(\"click\", closeDXToast);\n                }\n\n\n                // Wait a bit of time so animation plays correctly.\n                setTimeout(\n                    () => {\n                        let ourToastId = currentToastId;\n                        currentTimeout = setTimeout(() => {\n                            if (ourToastId == currentToastId) {\n                                closeDXToast();\n                            }\n                        }, durationMs);\n                    },\n                    100\n                );\n\n                currentToastId += 1;\n            }\n\n            // Schedule a toast to be displayed after reload.\n            function scheduleDXToast(headerText, message, level, durationMs) {\n                let data = {\n                    headerText,\n                    message,\n                    level,\n                    durationMs,\n                };\n\n                let jsonData = JSON.stringify(data);\n                sessionStorage.setItem(STORAGE_KEY, jsonData);\n            }\n\n            // Close the current toast.\n            function closeDXToast() {\n                document.getElementById(\"__dx-toast-inner\").style.right = \"-1000px\";\n                document.getElementById(\"__dx-toast\").setAttribute(\"aria-hidden\", \"true\");\n                clearTimeout(currentTimeout);\n            }\n\n            // Handle any scheduled toasts after reload.\n            let potentialData = sessionStorage.getItem(STORAGE_KEY);\n            if (potentialData) {\n                sessionStorage.removeItem(STORAGE_KEY);\n                let data = JSON.parse(potentialData);\n                showDXToast(data.headerText, data.message, data.level, data.durationMs);\n            }\n\n            window.scheduleDXToast = scheduleDXToast;\n            window.showDXToast = showDXToast;\n            window.closeDXToast = closeDXToast;\n        </script>\n        <!-- CUSTOM HEAD -->\n    </head>\n    <body>\n        <div id=\"__dx-toast\" class=\"dx-toast\" aria-hidden=\"true\">\n            <div id=\"__dx-toast-inner\" class=\"dx-toast-inner\" style=\"right:-1000px;\">\n                <div class=\"dx-toast-level-bar-container\">\n                    <div id=\"__dx-toast-decor\" class=\"dx-toast-level-bar __info\"></div>\n                </div>\n                <div class=\"dx-toast-content\">\n                    <div class=\"dx-toast-header\">\n                        <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 32 32\" preserveAspectRatio=\"none\">\n                            <path d=\"M22.158 1.783c0 3.077-.851 5.482-2.215 7.377s-3.32 3.557-5.447 5.33-4.425 3.657-6.252 6.195-3.102 5.515-3.102 9.532h4.699c0-3.077.853-5.377 2.217-7.272s3.32-3.557 5.447-5.33 4.425-3.657 6.252-6.195 3.102-5.62 3.102-9.637z\" fill=\"#e96020\"/>\n                            <path d=\"M9.531 25.927c-.635 0-1.021.515-1.02 1.15s.385 1.151 1.02 1.15H22.47a1.151 1.151 0 1 0 0-2.301zm1.361-4.076c-.608 0-.954.558-.953 1.166s.346 1.035.953 1.035h10.217a1.101 1.101 0 1 0 0-2.201zm0-13.594a1.101 1.101 0 1 0 0 2.201h10.217c.607 0 .953-.598.953-1.205s-.345-.996-.953-.996zM9.531 4.021A1.15 1.15 0 0 0 8.38 5.17a1.15 1.15 0 0 0 1.15 1.15h12.94c.635 0 1.021-.498 1.02-1.133s-.386-1.166-1.02-1.166z\" fill=\"#2d323b\"/>\n                            <path d=\"M5.142 1.783c0 4.016 1.275 7.099 3.102 9.637s4.125 4.422 6.252 6.195 4.083 3.656 5.447 5.551 2.215 3.974 2.215 7.051h4.701c0-4.016-1.275-7.038-3.102-9.576s-4.125-4.422-6.252-6.195-4.083-3.435-5.447-5.33S9.841 4.86 9.841 1.783z\" fill=\"#00a8d6\"/>\n                        </svg>\n                        <h3 id=\"__dx-toast-text\" class=\"dx-toast-header-text\">Your app is being rebuilt.</h3>\n                    </div>\n                    <p id=\"__dx-toast-msg\" class=\"dx-toast-msg\">A non-hot-reloadable change occurred and we must rebuild.</p>\n                </div>\n            </div>\n        </div>\n        <div id=\"main\"></div>\n        <!-- MODULE LOADER -->\n    </body>\n</html>\n"
  },
  {
    "path": "packages/desktop/src/assets/prod.index.html",
    "content": "<!DOCTYPE html>\n<html>\n    <head>\n        <title>Dioxus app</title>\n        <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no\">\n        <!-- CUSTOM HEAD -->\n    </head>\n    <body>\n        <div id=\"main\"></div>\n        <!-- MODULE LOADER -->\n    </body>\n</html>\n"
  },
  {
    "path": "packages/desktop/src/assets.rs",
    "content": "use dioxus_core::Callback;\nuse rustc_hash::FxHashMap;\nuse std::{cell::RefCell, rc::Rc};\nuse wry::{http::Request, RequestAsyncResponder};\n\n/// A request for an asset within dioxus-desktop.\npub type AssetRequest = Request<Vec<u8>>;\n\npub struct AssetHandler {\n    f: Callback<(AssetRequest, RequestAsyncResponder)>,\n}\n\n#[derive(Clone)]\npub struct AssetHandlerRegistry {\n    handlers: Rc<RefCell<FxHashMap<String, AssetHandler>>>,\n}\n\nimpl AssetHandlerRegistry {\n    pub fn new() -> Self {\n        AssetHandlerRegistry {\n            handlers: Default::default(),\n        }\n    }\n\n    pub fn has_handler(&self, name: &str) -> bool {\n        self.handlers.borrow().contains_key(name)\n    }\n\n    pub fn handle_request(\n        &self,\n        name: &str,\n        request: AssetRequest,\n        responder: RequestAsyncResponder,\n    ) {\n        if let Some(handler) = self.handlers.borrow().get(name) {\n            // Avoid handler being already borrowed on android\n            #[cfg(target_os = \"android\")]\n            let _lock = crate::android_sync_lock::android_runtime_lock();\n\n            // And run the handler in the scope of the component that created it\n            handler.f.call((request, responder));\n        }\n    }\n\n    pub fn register_handler(\n        &self,\n        name: String,\n        f: Callback<(AssetRequest, RequestAsyncResponder)>,\n    ) {\n        self.handlers.borrow_mut().insert(name, AssetHandler { f });\n    }\n\n    pub fn remove_handler(&self, name: &str) -> Option<AssetHandler> {\n        self.handlers.borrow_mut().remove(name)\n    }\n}\n"
  },
  {
    "path": "packages/desktop/src/config.rs",
    "content": "use dioxus_core::{LaunchConfig, VirtualDom};\nuse std::path::PathBuf;\nuse std::{borrow::Cow, sync::Arc};\nuse tao::window::{Icon, WindowBuilder};\nuse tao::{\n    event_loop::{EventLoop, EventLoopWindowTarget},\n    window::Window,\n};\nuse wry::http::{Request as HttpRequest, Response as HttpResponse};\nuse wry::{RequestAsyncResponder, WebViewId};\n\nuse crate::ipc::UserWindowEvent;\nuse crate::menubar::{default_menu_bar, DioxusMenu};\n\ntype CustomEventHandler = Box<\n    dyn 'static\n        + for<'a> FnMut(\n            &tao::event::Event<'a, UserWindowEvent>,\n            &EventLoopWindowTarget<UserWindowEvent>,\n        ),\n>;\n\n/// The closing behaviour of specific application window.\n#[derive(Debug, Copy, Clone, Eq, PartialEq)]\n#[non_exhaustive]\npub enum WindowCloseBehaviour {\n    /// Window will hide instead of closing\n    WindowHides,\n\n    /// Window will close\n    WindowCloses,\n}\n\n/// The state of the menu builder. We need to keep track of if the state is default\n/// so we only swap out the default menu bar when decorations are disabled\npub(crate) enum MenuBuilderState {\n    Unset,\n    Set(Option<DioxusMenu>),\n}\n\nimpl From<MenuBuilderState> for Option<DioxusMenu> {\n    fn from(val: MenuBuilderState) -> Self {\n        match val {\n            MenuBuilderState::Unset => Some(default_menu_bar()),\n            MenuBuilderState::Set(menu) => menu,\n        }\n    }\n}\n\n/// The configuration for the desktop application.\npub struct Config {\n    pub(crate) event_loop: Option<EventLoop<UserWindowEvent>>,\n    pub(crate) window: WindowBuilder,\n    pub(crate) as_child_window: bool,\n    pub(crate) menu: MenuBuilderState,\n    pub(crate) protocols: Vec<WryProtocol>,\n    pub(crate) asynchronous_protocols: Vec<AsyncWryProtocol>,\n    pub(crate) pre_rendered: Option<String>,\n    pub(crate) disable_context_menu: bool,\n    pub(crate) resource_dir: Option<PathBuf>,\n    pub(crate) data_dir: Option<PathBuf>,\n    pub(crate) custom_head: Option<String>,\n    pub(crate) custom_index: Option<String>,\n    pub(crate) root_name: String,\n    pub(crate) background_color: Option<(u8, u8, u8, u8)>,\n    pub(crate) exit_on_last_window_close: bool,\n    pub(crate) window_close_behavior: WindowCloseBehaviour,\n    pub(crate) custom_event_handler: Option<CustomEventHandler>,\n    pub(crate) disable_file_drop_handler: bool,\n    pub(crate) disable_dma_buf_on_wayland: bool,\n    pub(crate) additional_windows_args: Option<String>,\n\n    #[allow(clippy::type_complexity)]\n    pub(crate) on_window: Option<Box<dyn FnMut(Arc<Window>, &mut VirtualDom) + 'static>>,\n}\n\nimpl LaunchConfig for Config {}\n\npub(crate) type WryProtocol = (\n    String,\n    Box<dyn Fn(WebViewId, HttpRequest<Vec<u8>>) -> HttpResponse<Cow<'static, [u8]>> + 'static>,\n);\n\npub(crate) type AsyncWryProtocol = (\n    String,\n    Box<dyn Fn(WebViewId, HttpRequest<Vec<u8>>, RequestAsyncResponder) + 'static>,\n);\n\nimpl Config {\n    /// Initializes a new `WindowBuilder` with default values.\n    #[inline]\n    pub fn new() -> Self {\n        let mut window: WindowBuilder = WindowBuilder::new()\n            .with_title(dioxus_cli_config::app_title().unwrap_or_else(|| \"Dioxus App\".to_string()));\n\n        // During development we want the window to be on top so we can see it while we work\n        let always_on_top = dioxus_cli_config::always_on_top().unwrap_or(true);\n\n        if cfg!(debug_assertions) {\n            window = window.with_always_on_top(always_on_top);\n        }\n\n        Self {\n            window,\n            as_child_window: false,\n            event_loop: None,\n            menu: MenuBuilderState::Unset,\n            protocols: Vec::new(),\n            asynchronous_protocols: Vec::new(),\n            pre_rendered: None,\n            disable_context_menu: !cfg!(debug_assertions),\n            resource_dir: None,\n            data_dir: None,\n            custom_head: None,\n            custom_index: None,\n            root_name: \"main\".to_string(),\n            background_color: None,\n            exit_on_last_window_close: true,\n            window_close_behavior: WindowCloseBehaviour::WindowCloses,\n            custom_event_handler: None,\n            disable_file_drop_handler: false,\n            disable_dma_buf_on_wayland: true,\n            on_window: None,\n            additional_windows_args: None,\n        }\n    }\n\n    /// set the directory from which assets will be searched in release mode\n    pub fn with_resource_directory(mut self, path: impl Into<PathBuf>) -> Self {\n        self.resource_dir = Some(path.into());\n        self\n    }\n\n    /// set the directory where data will be stored in release mode.\n    ///\n    /// > Note: This **must** be set when bundling on Windows.\n    pub fn with_data_directory(mut self, path: impl Into<PathBuf>) -> Self {\n        self.data_dir = Some(path.into());\n        self\n    }\n\n    /// Set whether or not the right-click context menu should be disabled.\n    pub fn with_disable_context_menu(mut self, disable: bool) -> Self {\n        self.disable_context_menu = disable;\n        self\n    }\n\n    /// Set whether or not the file drop handler should be disabled.\n    /// On Windows the drop handler must be disabled for HTML drag and drop APIs to work.\n    pub fn with_disable_drag_drop_handler(mut self, disable: bool) -> Self {\n        self.disable_file_drop_handler = disable;\n        self\n    }\n\n    /// Set the pre-rendered HTML content\n    pub fn with_prerendered(mut self, content: String) -> Self {\n        self.pre_rendered = Some(content);\n        self\n    }\n\n    /// Set the event loop to be used\n    pub fn with_event_loop(mut self, event_loop: EventLoop<UserWindowEvent>) -> Self {\n        self.event_loop = Some(event_loop);\n        self\n    }\n\n    /// Set the configuration for the window.\n    pub fn with_window(mut self, window: WindowBuilder) -> Self {\n        // We need to do a swap because the window builder only takes itself as muy self\n        self.window = window;\n        // If the decorations are off for the window, remove the menu as well\n        if !self.window.window.decorations && matches!(self.menu, MenuBuilderState::Unset) {\n            self.menu = MenuBuilderState::Set(None);\n        }\n        self\n    }\n\n    /// Set the window as child\n    pub fn with_as_child_window(mut self) -> Self {\n        self.as_child_window = true;\n        self\n    }\n\n    /// When the last window is closed, the application will exit.\n    ///\n    /// This is the default behaviour.\n    ///\n    /// If the last window is hidden, the application will not exit.\n    pub fn with_exits_when_last_window_closes(mut self, exit: bool) -> Self {\n        self.exit_on_last_window_close = exit;\n        self\n    }\n\n    /// Sets the behaviour of the application when the last window is closed.\n    pub fn with_close_behaviour(mut self, behaviour: WindowCloseBehaviour) -> Self {\n        self.window_close_behavior = behaviour;\n        self\n    }\n\n    /// Sets a custom callback to run whenever the event pool receives an event.\n    pub fn with_custom_event_handler(\n        mut self,\n        f: impl FnMut(&tao::event::Event<'_, UserWindowEvent>, &EventLoopWindowTarget<UserWindowEvent>)\n            + 'static,\n    ) -> Self {\n        self.custom_event_handler = Some(Box::new(f));\n        self\n    }\n\n    /// Set a custom protocol\n    pub fn with_custom_protocol<F>(mut self, name: impl ToString, handler: F) -> Self\n    where\n        F: Fn(WebViewId, HttpRequest<Vec<u8>>) -> HttpResponse<Cow<'static, [u8]>> + 'static,\n    {\n        self.protocols.push((name.to_string(), Box::new(handler)));\n        self\n    }\n\n    /// Set an asynchronous custom protocol\n    ///\n    /// **Example Usage**\n    /// ```rust\n    /// # use wry::http::response::Response as HTTPResponse;\n    /// # use std::borrow::Cow;\n    /// # use dioxus_desktop::Config;\n    /// #\n    /// # fn main() {\n    /// let cfg = Config::new()\n    ///     .with_asynchronous_custom_protocol(\"asset\", |_webview_id, request, responder| {\n    ///         tokio::spawn(async move {\n    ///             responder.respond(\n    ///                 HTTPResponse::builder()\n    ///                     .status(404)\n    ///                     .body(Cow::Borrowed(\"404 - Not Found\".as_bytes()))\n    ///                     .unwrap()\n    ///             );\n    ///         });\n    ///     });\n    /// # }\n    /// ```\n    /// note a key difference between Dioxus and Wry, the protocol name doesn't explicitly need to be a\n    /// [`String`], but needs to implement [`ToString`].\n    ///\n    /// See [`wry`](wry::WebViewBuilder::with_asynchronous_custom_protocol) for more details on implementation\n    pub fn with_asynchronous_custom_protocol<F>(mut self, name: impl ToString, handler: F) -> Self\n    where\n        F: Fn(WebViewId, HttpRequest<Vec<u8>>, RequestAsyncResponder) + 'static,\n    {\n        self.asynchronous_protocols\n            .push((name.to_string(), Box::new(handler)));\n        self\n    }\n\n    /// Set a custom icon for this application\n    pub fn with_icon(mut self, icon: Icon) -> Self {\n        self.window.window.window_icon = Some(icon);\n        self\n    }\n\n    /// Inject additional content into the document's HEAD.\n    ///\n    /// This is useful for loading CSS libraries, JS libraries, etc.\n    pub fn with_custom_head(mut self, head: String) -> Self {\n        self.custom_head = Some(head);\n        self\n    }\n\n    /// Use a custom index.html instead of the default Dioxus one.\n    ///\n    /// Make sure your index.html is valid HTML.\n    ///\n    /// Dioxus injects some loader code into the closing body tag. Your document\n    /// must include a body element!\n    pub fn with_custom_index(mut self, index: String) -> Self {\n        self.custom_index = Some(index);\n        self\n    }\n\n    /// Set the name of the element that Dioxus will use as the root.\n    ///\n    /// This is akin to calling React.render() on the element with the specified name.\n    pub fn with_root_name(mut self, name: impl Into<String>) -> Self {\n        self.root_name = name.into();\n        self\n    }\n\n    /// Sets the background color of the WebView.\n    /// This will be set before the HTML is rendered and can be used to prevent flashing when the page loads.\n    /// Accepts a color in RGBA format\n    pub fn with_background_color(mut self, color: (u8, u8, u8, u8)) -> Self {\n        self.background_color = Some(color);\n        self\n    }\n\n    /// Sets the menu the window will use. This will override the default menu bar.\n    ///\n    /// > Note: Menu will be hidden if\n    /// > [`with_decorations`](tao::window::WindowBuilder::with_decorations)\n    /// > is set to false and passed into [`with_window`](Config::with_window)\n    #[allow(unused)]\n    pub fn with_menu(mut self, menu: impl Into<Option<DioxusMenu>>) -> Self {\n        #[cfg(not(any(target_os = \"ios\", target_os = \"android\")))]\n        {\n            if self.window.window.decorations {\n                self.menu = MenuBuilderState::Set(menu.into())\n            }\n        }\n        self\n    }\n\n    /// Allows modifying the window and virtual dom right after they are built, but before the webview is created.\n    ///\n    /// This is important for z-ordering textures in child windows. Note that this callback runs on\n    /// every window creation, so it's up to you to\n    pub fn with_on_window(mut self, f: impl FnMut(Arc<Window>, &mut VirtualDom) + 'static) -> Self {\n        self.on_window = Some(Box::new(f));\n        self\n    }\n\n    /// Set whether or not DMA-BUF usage should be disabled on Wayland.\n    ///\n    /// Defaults to true to avoid issues on some systems. If you want to enable DMA-BUF usage, set this to false.\n    /// See <https://github.com/DioxusLabs/dioxus/issues/4528#issuecomment-3476430611>\n    pub fn with_disable_dma_buf_on_wayland(mut self, disable: bool) -> Self {\n        self.disable_dma_buf_on_wayland = disable;\n        self\n    }\n\n    /// Add additional windows only launch arguments for webview2\n    pub fn with_windows_browser_args(mut self, additional_args: impl ToString) -> Self {\n        self.additional_windows_args = Some(additional_args.to_string());\n        self\n    }\n}\n\nimpl Default for Config {\n    fn default() -> Self {\n        Self::new()\n    }\n}\n\n// dirty trick, avoid introducing `image` at runtime\n// TODO: use serde when `Icon` impl serde\n//\n// This function should only be enabled when generating new icons.\n//\n// #[test]\n// #[ignore]\n// fn prepare_default_icon() {\n//     use image::io::Reader as ImageReader;\n//     use image::ImageFormat;\n//     use std::fs::File;\n//     use std::io::Cursor;\n//     use std::io::Write;\n//     use std::path::PathBuf;\n//     let png: &[u8] = include_bytes!(\"default_icon.png\");\n//     let mut reader = ImageReader::new(Cursor::new(png));\n//     reader.set_format(ImageFormat::Png);\n//     let icon = reader.decode().unwrap();\n//     let bin = PathBuf::from(file!())\n//         .parent()\n//         .unwrap()\n//         .join(\"default_icon.bin\");\n//     println!(\"{:?}\", bin);\n//     let mut file = File::create(bin).unwrap();\n//     file.write_all(icon.as_bytes()).unwrap();\n//     println!(\"({}, {})\", icon.width(), icon.height())\n// }\n"
  },
  {
    "path": "packages/desktop/src/desktop_context.rs",
    "content": "use crate::{\n    app::SharedContext,\n    assets::AssetHandlerRegistry,\n    file_upload::NativeFileHover,\n    ipc::UserWindowEvent,\n    query::QueryEngine,\n    shortcut::{HotKey, HotKeyState, ShortcutHandle, ShortcutRegistryError},\n    webview::PendingWebview,\n    AssetRequest, Config, WindowCloseBehaviour, WryEventHandler,\n};\nuse dioxus_core::{Callback, VirtualDom};\nuse std::{\n    cell::Cell,\n    future::{Future, IntoFuture},\n    pin::Pin,\n    rc::{Rc, Weak},\n    sync::Arc,\n};\nuse tao::{\n    event::Event,\n    event_loop::EventLoopWindowTarget,\n    window::{Fullscreen as WryFullscreen, Window, WindowId},\n};\nuse wry::{RequestAsyncResponder, WebView};\n\n#[cfg(target_os = \"ios\")]\nuse tao::platform::ios::WindowExtIOS;\n\n/// Get an imperative handle to the current window without using a hook\n///\n/// ## Panics\n///\n/// This function will panic if it is called outside of the context of a Dioxus App.\npub fn window() -> DesktopContext {\n    dioxus_core::consume_context()\n}\n\n/// A handle to the [`DesktopService`] that can be passed around.\npub type DesktopContext = Rc<DesktopService>;\n\n/// A weak handle to the [`DesktopService`] to ensure safe passing.\n/// The problem without this is that the tao window is never dropped and therefore cannot be closed.\n/// This was due to the Rc that had still references because of multiple copies when creating a webview.\npub type WeakDesktopContext = Weak<DesktopService>;\n\n/// An imperative interface to the current window.\n///\n/// To get a handle to the current window, use the [`window`] function.\n///\n///\n/// # Example\n///\n/// you can use `cx.consume_context::<DesktopContext>` to get this context\n///\n/// ```rust, ignore\n///     let desktop = cx.consume_context::<DesktopContext>().unwrap();\n/// ```\npub struct DesktopService {\n    /// The wry/tao proxy to the current window\n    pub webview: WebView,\n\n    /// The tao window itself\n    pub window: Arc<Window>,\n\n    pub(crate) shared: Rc<SharedContext>,\n\n    /// The receiver for queries about the current window\n    pub(super) query: QueryEngine,\n    pub(crate) asset_handlers: AssetHandlerRegistry,\n    pub(crate) file_hover: NativeFileHover,\n    pub(crate) close_behaviour: Rc<Cell<WindowCloseBehaviour>>,\n\n    #[cfg(target_os = \"ios\")]\n    pub(crate) views: Rc<std::cell::RefCell<Vec<*mut objc::runtime::Object>>>,\n}\n\n/// A smart pointer to the current window.\nimpl std::ops::Deref for DesktopService {\n    type Target = Window;\n\n    fn deref(&self) -> &Self::Target {\n        &self.window\n    }\n}\n\nimpl DesktopService {\n    pub(crate) fn new(\n        webview: WebView,\n        window: Arc<Window>,\n        shared: Rc<SharedContext>,\n        asset_handlers: AssetHandlerRegistry,\n        file_hover: NativeFileHover,\n        close_behaviour: WindowCloseBehaviour,\n    ) -> Self {\n        Self {\n            window,\n            webview,\n            shared,\n            asset_handlers,\n            file_hover,\n            close_behaviour: Rc::new(Cell::new(close_behaviour)),\n            query: Default::default(),\n            #[cfg(target_os = \"ios\")]\n            views: Default::default(),\n        }\n    }\n\n    /// Start the creation of a new window using the props and window builder\n    ///\n    /// Returns a future that resolves to the webview handle for the new window. You can use this\n    /// to control other windows from the current window once the new window is created.\n    ///\n    /// Be careful to not create a cycle of windows, or you might leak memory.\n    ///\n    /// # Example\n    ///\n    /// ```rust, no_run\n    /// use dioxus::prelude::*;\n    /// fn popup() -> Element {\n    ///     rsx! {\n    ///         div { \"This is a popup window!\" }\n    ///     }\n    /// }\n    ///\n    /// # async fn app() {\n    /// // Create a new window with a component that will be rendered in the new window.\n    /// let dom = VirtualDom::new(popup);\n    /// // Create and wait for the window\n    /// let window = dioxus::desktop::window().new_window(dom, Default::default()).await;\n    /// // Fullscreen the new window\n    /// window.set_fullscreen(true);\n    /// # }\n    /// ```\n    // Note: This method is asynchronous because webview2 does not support creating a new window from\n    // inside of an existing webview callback. Dioxus runs event handlers synchronously inside of a webview\n    // callback. See [this page](https://learn.microsoft.com/en-us/microsoft-edge/webview2/concepts/threading-model#reentrancy) for more information.\n    //\n    // Related issues:\n    // - https://github.com/tauri-apps/wry/issues/583\n    // - https://github.com/DioxusLabs/dioxus/issues/3080\n    pub fn new_window(&self, dom: VirtualDom, cfg: Config) -> PendingDesktopContext {\n        let (window, context) = PendingWebview::new(dom, cfg);\n\n        self.shared\n            .proxy\n            .send_event(UserWindowEvent::NewWindow)\n            .unwrap();\n\n        self.shared.pending_webviews.borrow_mut().push(window);\n\n        context\n    }\n\n    /// trigger the drag-window event\n    ///\n    /// Moves the window with the left mouse button until the button is released.\n    ///\n    /// you need use it in `onmousedown` event:\n    /// ```rust, ignore\n    /// onmousedown: move |_| { desktop.drag_window(); }\n    /// ```\n    pub fn drag(&self) {\n        if self.window.fullscreen().is_none() {\n            _ = self.window.drag_window();\n        }\n    }\n\n    /// Toggle whether the window is maximized or not\n    pub fn toggle_maximized(&self) {\n        self.window.set_maximized(!self.window.is_maximized())\n    }\n\n    /// Set the close behavior of this window\n    ///\n    /// By default, windows close when the user clicks the close button.\n    /// If this is set to `WindowCloseBehaviour::WindowHides`, the window will hide instead of closing.\n    pub fn set_close_behavior(&self, behaviour: WindowCloseBehaviour) {\n        self.close_behaviour.set(behaviour);\n    }\n\n    /// Close this window\n    pub fn close(&self) {\n        let _ = self\n            .shared\n            .proxy\n            .send_event(UserWindowEvent::CloseWindow(self.id()));\n    }\n\n    /// Close a particular window, given its ID\n    pub fn close_window(&self, id: WindowId) {\n        let _ = self\n            .shared\n            .proxy\n            .send_event(UserWindowEvent::CloseWindow(id));\n    }\n\n    /// change window to fullscreen\n    pub fn set_fullscreen(&self, fullscreen: bool) {\n        if let Some(handle) = &self.window.current_monitor() {\n            self.window.set_fullscreen(\n                fullscreen.then_some(WryFullscreen::Borderless(Some(handle.clone()))),\n            );\n        }\n    }\n\n    /// launch print modal\n    pub fn print(&self) {\n        if let Err(e) = self.webview.print() {\n            tracing::warn!(\"Open print modal failed: {e}\");\n        }\n    }\n\n    /// Set the zoom level of the webview\n    pub fn set_zoom_level(&self, level: f64) {\n        if let Err(e) = self.webview.zoom(level) {\n            tracing::warn!(\"Set webview zoom failed: {e}\");\n        }\n    }\n\n    /// opens DevTool window\n    pub fn devtool(&self) {\n        #[cfg(debug_assertions)]\n        self.webview.open_devtools();\n\n        #[cfg(not(debug_assertions))]\n        tracing::warn!(\"Devtools are disabled in release builds\");\n    }\n\n    /// Create a wry event handler that listens for wry events.\n    /// This event handler is scoped to the currently active window and will only receive events that are either global or related to the current window.\n    ///\n    /// The id this function returns can be used to remove the event handler with [`Self::remove_wry_event_handler`]\n    pub fn create_wry_event_handler(\n        &self,\n        handler: impl FnMut(&Event<UserWindowEvent>, &EventLoopWindowTarget<UserWindowEvent>) + 'static,\n    ) -> WryEventHandler {\n        self.shared.event_handlers.add(self.window.id(), handler)\n    }\n\n    /// Remove a wry event handler created with [`Self::create_wry_event_handler`]\n    pub fn remove_wry_event_handler(&self, id: WryEventHandler) {\n        self.shared.event_handlers.remove(id)\n    }\n\n    /// Create a global shortcut\n    ///\n    /// Linux: Only works on x11. See [this issue](https://github.com/tauri-apps/tao/issues/331) for more information.\n    pub fn create_shortcut(\n        &self,\n        hotkey: HotKey,\n        callback: impl FnMut(HotKeyState) + 'static,\n    ) -> Result<ShortcutHandle, ShortcutRegistryError> {\n        self.shared\n            .shortcut_manager\n            .add_shortcut(hotkey, Box::new(callback))\n    }\n\n    /// Remove a global shortcut\n    pub fn remove_shortcut(&self, id: ShortcutHandle) {\n        self.shared.shortcut_manager.remove_shortcut(id)\n    }\n\n    /// Remove all global shortcuts\n    pub fn remove_all_shortcuts(&self) {\n        self.shared.shortcut_manager.remove_all()\n    }\n\n    /// Provide a callback to handle asset loading yourself.\n    /// If the ScopeId isn't provided, defaults to a global handler.\n    /// Note that the handler is namespaced by name, not ScopeId.\n    ///\n    /// When the component is dropped, the handler is removed.\n    ///\n    /// See [`crate::use_asset_handler`] for a convenient hook.\n    pub fn register_asset_handler(\n        &self,\n        name: String,\n        handler: impl Fn(AssetRequest, RequestAsyncResponder) + 'static,\n    ) {\n        self.asset_handlers\n            .register_handler(name, Callback::new(move |(req, resp)| handler(req, resp)))\n    }\n\n    /// Removes an asset handler by its identifier.\n    ///\n    /// Returns `None` if the handler did not exist.\n    pub fn remove_asset_handler(&self, name: &str) -> Option<()> {\n        self.asset_handlers.remove_handler(name).map(|_| ())\n    }\n\n    /// Push an objc view to the window\n    #[cfg(target_os = \"ios\")]\n    pub fn push_view(&self, view: objc_id::ShareId<objc::runtime::Object>) {\n        let window = &self.window;\n\n        unsafe {\n            use objc::runtime::Object;\n            use objc::*;\n            assert!(is_main_thread());\n            let ui_view = window.ui_view() as *mut Object;\n            let ui_view_frame: *mut Object = msg_send![ui_view, frame];\n            let _: () = msg_send![view, setFrame: ui_view_frame];\n            let _: () = msg_send![view, setAutoresizingMask: 31];\n\n            let ui_view_controller = window.ui_view_controller() as *mut Object;\n            let _: () = msg_send![ui_view_controller, setView: view];\n            self.views.borrow_mut().push(ui_view);\n        }\n    }\n\n    /// Pop an objc view from the window\n    #[cfg(target_os = \"ios\")]\n    pub fn pop_view(&self) {\n        let window = &self.window;\n\n        unsafe {\n            use objc::runtime::Object;\n            use objc::*;\n            assert!(is_main_thread());\n            if let Some(view) = self.views.borrow_mut().pop() {\n                let ui_view_controller = window.ui_view_controller() as *mut Object;\n                let _: () = msg_send![ui_view_controller, setView: view];\n            }\n        }\n    }\n}\n\n#[cfg(target_os = \"ios\")]\nfn is_main_thread() -> bool {\n    use objc::runtime::{Class, BOOL, NO};\n    use objc::*;\n\n    let cls = Class::get(\"NSThread\").unwrap();\n    let result: BOOL = unsafe { msg_send![cls, isMainThread] };\n    result != NO\n}\n\n/// A [`DesktopContext`] that is pending creation.\n///\n/// # Example\n/// ```rust, no_run\n/// # use dioxus::prelude::*;\n/// # async fn app() {\n/// // Create a new window with a component that will be rendered in the new window.\n/// let dom = VirtualDom::new(|| rsx!{ \"popup!\" });\n///\n/// // Create a new window asynchronously\n/// let pending_context = dioxus::desktop::window().new_window(dom, Default::default());\n///\n/// // Wait for the context to be created\n/// let window = pending_context.await;\n///\n/// // Now control the window\n/// window.set_fullscreen(true);\n/// # }\n/// ```\npub struct PendingDesktopContext {\n    pub(crate) receiver: futures_channel::oneshot::Receiver<DesktopContext>,\n}\n\nimpl PendingDesktopContext {\n    /// Resolve the pending context into a [`DesktopContext`].\n    pub async fn resolve(self) -> DesktopContext {\n        self.try_resolve()\n            .await\n            .expect(\"Failed to resolve pending desktop context\")\n    }\n\n    /// Try to resolve the pending context into a [`DesktopContext`].\n    pub async fn try_resolve(self) -> Result<DesktopContext, futures_channel::oneshot::Canceled> {\n        self.receiver.await\n    }\n}\n\nimpl IntoFuture for PendingDesktopContext {\n    type Output = DesktopContext;\n\n    type IntoFuture = Pin<Box<dyn Future<Output = Self::Output>>>;\n\n    fn into_future(self) -> Self::IntoFuture {\n        Box::pin(self.resolve())\n    }\n}\n"
  },
  {
    "path": "packages/desktop/src/document.rs",
    "content": "use crate::{query::Query, DesktopContext, WeakDesktopContext};\nuse dioxus_core::queue_effect;\nuse dioxus_document::{\n    create_element_in_head, Document, Eval, EvalError, Evaluator, LinkProps, MetaProps,\n    ScriptProps, StyleProps,\n};\n\nuse generational_box::{AnyStorage, GenerationalBox, UnsyncStorage};\n\n/// Code for the Dioxus channel used to communicate between the dioxus and javascript code\npub const NATIVE_EVAL_JS: &str = include_str!(\"./js/native_eval.js\");\n\n/// Represents the desktop-target's provider of evaluators.\n#[derive(Clone)]\npub struct DesktopDocument {\n    pub(crate) desktop_ctx: WeakDesktopContext,\n}\n\nimpl DesktopDocument {\n    pub fn new(desktop_ctx: DesktopContext) -> Self {\n        let desktop_ctx = std::rc::Rc::downgrade(&desktop_ctx);\n        Self { desktop_ctx }\n    }\n}\n\nimpl Document for DesktopDocument {\n    fn eval(&self, js: String) -> Eval {\n        Eval::new(DesktopEvaluator::create(\n            self.desktop_ctx\n                .upgrade()\n                .expect(\"Window to exist when document is alive\"),\n            js,\n        ))\n    }\n\n    fn set_title(&self, title: String) {\n        if let Some(ctx) = self.desktop_ctx.upgrade() {\n            ctx.set_title(&title);\n        }\n    }\n\n    /// Create a new meta tag in the head\n    fn create_meta(&self, props: MetaProps) {\n        let myself = self.clone();\n        queue_effect(move || {\n            myself.eval(create_element_in_head(\"meta\", &props.attributes(), None));\n        });\n    }\n\n    /// Create a new script tag in the head\n    fn create_script(&self, props: ScriptProps) {\n        let myself = self.clone();\n        queue_effect(move || {\n            myself.eval(create_element_in_head(\n                \"script\",\n                &props.attributes(),\n                props.script_contents().ok(),\n            ));\n        });\n    }\n\n    /// Create a new style tag in the head\n    fn create_style(&self, props: StyleProps) {\n        let myself = self.clone();\n        queue_effect(move || {\n            myself.eval(create_element_in_head(\n                \"style\",\n                &props.attributes(),\n                props.style_contents().ok(),\n            ));\n        });\n    }\n\n    /// Create a new link tag in the head\n    fn create_link(&self, props: LinkProps) {\n        let myself = self.clone();\n        queue_effect(move || {\n            myself.eval(create_element_in_head(\"link\", &props.attributes(), None));\n        });\n    }\n}\n\n/// Represents a desktop-target's JavaScript evaluator.\npub(crate) struct DesktopEvaluator {\n    query: Query<serde_json::Value>,\n}\n\nimpl DesktopEvaluator {\n    /// Creates a new evaluator for desktop-based targets.\n    pub fn create(desktop_ctx: DesktopContext, js: String) -> GenerationalBox<Box<dyn Evaluator>> {\n        let query = desktop_ctx.query.new_query(&js, desktop_ctx.clone());\n\n        // We create a generational box that is owned by the query slot so that when we drop the query slot, the generational box is also dropped.\n        let owner = UnsyncStorage::owner();\n        let query_id = query.id;\n        let query = owner.insert(Box::new(DesktopEvaluator { query }) as Box<dyn Evaluator>);\n        desktop_ctx.query.active_requests.slab.borrow_mut()[query_id].owner = Some(owner);\n\n        query\n    }\n}\n\nimpl Evaluator for DesktopEvaluator {\n    fn poll_join(\n        &mut self,\n        cx: &mut std::task::Context<'_>,\n    ) -> std::task::Poll<Result<serde_json::Value, EvalError>> {\n        self.query\n            .poll_result(cx)\n            .map_err(|e| EvalError::Communication(e.to_string()))\n    }\n\n    /// Sends a message to the evaluated JavaScript.\n    fn send(&self, data: serde_json::Value) -> Result<(), EvalError> {\n        if let Err(e) = self.query.send(data) {\n            return Err(EvalError::Communication(e.to_string()));\n        }\n        Ok(())\n    }\n\n    /// Gets an UnboundedReceiver to receive messages from the evaluated JavaScript.\n    fn poll_recv(\n        &mut self,\n        cx: &mut std::task::Context<'_>,\n    ) -> std::task::Poll<Result<serde_json::Value, EvalError>> {\n        self.query\n            .poll_recv(cx)\n            .map_err(|e| EvalError::Communication(e.to_string()))\n    }\n}\n"
  },
  {
    "path": "packages/desktop/src/edits.rs",
    "content": "//! The internal edit queue facilitating native <-> webview communication.\n//!\n//! Originally, we used long-polling on the wry custom protocol to send edits to the webview.\n//! Due to bugs in wry on android, we switched to a websocket connection that the webview connects to.\n//! We use the sledgehammer crate to build batches of edits and send them through the websocket to\n//! the webview.\n//!\n//! Using a websocket lets us send binary data to the webview quite efficiently and does encounter\n//! many of the issues with regular request/response protocols. Note that the websocket max frame\n//! size is quite large (9.22 exabytes), so we can have very large batches without issue.\n//!\n//! Using websockets does mean we need to handle security and content security policies ourselves.\n//! The code here generates a random key that the webview must use to connect to the websocket.\n//! We use the initialization script API to setup the websocket connection without leaking the key\n//! to the webview itself in case there's untrusted content in the webview.\n//!\n//! Some operating systems (like iOS) will kill the websocket connection when the device goes to sleep.\n//! If this happens, we will automatically switch to a new port and notify the webview of the new location\n//! and key. The webview will then reconnect to the new port and continue receiving edits.\n\nuse dioxus_interpreter_js::MutationState;\nuse futures_channel::oneshot;\nuse futures_util::FutureExt;\nuse rand::{RngCore, SeedableRng};\nuse std::cell::RefCell;\nuse std::collections::{HashMap, VecDeque};\nuse std::future::Future;\nuse std::net::{TcpListener, TcpStream};\nuse std::pin::Pin;\nuse std::rc::Rc;\nuse std::sync::atomic::AtomicU32;\nuse std::sync::Mutex;\nuse std::{\n    net::IpAddr,\n    sync::{Arc, RwLock},\n};\nuse tokio::sync::Notify;\n\n/// This handles communication between the requests that the webview makes and the interpreter.\n#[derive(Clone)]\npub(crate) struct WryQueue {\n    inner: Rc<RefCell<WryQueueInner>>,\n}\n\nimpl WryQueue {\n    pub(crate) fn with_mutation_state_mut<O: 'static>(\n        &self,\n        callback: impl FnOnce(&mut MutationState) -> O,\n    ) -> O {\n        let mut inner = self.inner.borrow_mut();\n        callback(&mut inner.mutation_state)\n    }\n\n    /// Send a list of mutations to the webview\n    pub(crate) fn send_edits(&self) {\n        let mut myself = self.inner.borrow_mut();\n        let webview_id = myself.location.webview_id;\n        let serialized_edits = myself.mutation_state.export_memory();\n        let receiver = myself.websocket.send_edits(webview_id, serialized_edits);\n        myself.edits_in_progress = Some(receiver);\n    }\n\n    /// Wait until all pending edits have been rendered in the webview\n    pub(crate) fn poll_edits_flushed(\n        &self,\n        cx: &mut std::task::Context<'_>,\n    ) -> std::task::Poll<()> {\n        let mut self_mut = self.inner.borrow_mut();\n        if let Some(receiver) = self_mut.edits_in_progress.as_mut() {\n            receiver.poll_unpin(cx).map(|_| ())\n        } else {\n            std::task::Poll::Ready(())\n        }\n    }\n\n    /// Check if there is a new location for the websocket edits server.\n    pub(crate) fn poll_new_edits_location(\n        &self,\n        cx: &mut std::task::Context<'_>,\n    ) -> std::task::Poll<()> {\n        let mut self_mut = self.inner.borrow_mut();\n        let poll = self_mut\n            .server_location_changed_future\n            .as_mut()\n            .poll_unpin(cx);\n        if poll.is_ready() {\n            // If the future is ready, we need to reset it to wait for the next change\n            self_mut.server_location_changed_future =\n                owned_notify_future(self_mut.server_location_changed.clone());\n        }\n        poll\n    }\n\n    /// Get the websocket path that the webview should connect to in order to receive edits\n    pub(crate) fn edits_path(&self) -> String {\n        let WebviewWebsocketLocation {\n            webview_id, server, ..\n        } = &self.inner.borrow().location;\n        let server = server.lock().unwrap();\n        let port = server.port;\n        let key = &server.client_key;\n        let key_hex = encode_key_string(key);\n        format!(\"ws://127.0.0.1:{port}/{webview_id}/{key_hex}\")\n    }\n\n    /// Get the key the client should expect from the server when connecting to the websocket.\n    pub(crate) fn required_server_key(&self) -> String {\n        let server = &self.inner.borrow().location.server;\n        let server = server.lock().unwrap();\n        encode_key_string(&server.server_key)\n    }\n}\n\npub(crate) struct WryQueueInner {\n    location: WebviewWebsocketLocation,\n    websocket: EditWebsocket,\n    // If this webview is currently waiting for an edit to be flushed. We don't run the virtual dom while this is true to avoid running effects before the dom has been updated\n    edits_in_progress: Option<oneshot::Receiver<()>>,\n    // The socket may be killed by the OS while running. If it does, this channel will receive the new server location\n    server_location_changed: Arc<Notify>,\n    server_location_changed_future: Pin<Box<dyn Future<Output = ()>>>,\n    mutation_state: MutationState,\n}\n\n/// The location of a webview websocket connection. This is used to identify the webview and the port it is connected to.\n#[derive(Clone)]\npub(crate) struct WebviewWebsocketLocation {\n    /// The id of the webview that this websocket is connected to\n    webview_id: u32,\n    server: Arc<Mutex<ServerLocation>>,\n}\n\n#[derive(Clone, Copy, PartialEq, Eq, Hash)]\npub(crate) struct ServerLocation {\n    /// The port the websocket is on\n    port: u16,\n    /// A key that every websocket connection that originates from this application will use to identify itself.\n    /// We use this to make sure no external applications can connect to our websocket and receive UI updates.\n    client_key: [u8; KEY_SIZE],\n    /// The key that the server must respond with for the client to connect to the websocket\n    server_key: [u8; KEY_SIZE],\n}\n\n/// Start a new server on an available port on localhost. Return the server location and the TCP listener that is bound to the port.\npub(crate) fn start_server() -> (ServerLocation, TcpListener) {\n    let client_key = create_secure_key();\n    let server_key = create_secure_key();\n    let server = TcpListener::bind((IpAddr::from([127, 0, 0, 1]), 0))\n        .expect(\"Failed to bind local TCP listener for edit socket\");\n    let port = server.local_addr().unwrap().port();\n    let location = ServerLocation {\n        port,\n        client_key,\n        server_key,\n    };\n    (location, server)\n}\n\n/// The websocket listener that the webview will connect to in order to receive edits and send requests. There\n/// is only one websocket listener per application even if there are multiple windows so we don't use all the\n/// open ports.\n#[derive(Clone)]\npub(crate) struct EditWebsocket {\n    current_location: Arc<Mutex<ServerLocation>>,\n    max_webview_id: Arc<AtomicU32>,\n    connections: Arc<RwLock<HashMap<u32, WebviewConnectionState>>>,\n    server_location: Arc<Notify>,\n}\n\nimpl EditWebsocket {\n    pub(crate) fn start() -> Self {\n        let connections = Arc::new(RwLock::new(HashMap::new()));\n\n        let notify = Arc::new(Notify::new());\n        let (location, server) = start_server();\n        let current_location = Arc::new(Mutex::new(location));\n\n        let connections_ = connections.clone();\n        let current_location_ = current_location.clone();\n        let notify_ = notify.clone();\n        std::thread::spawn(move || {\n            Self::accept_loop(notify_, server, current_location_, connections_)\n        });\n\n        Self {\n            connections,\n            max_webview_id: Default::default(),\n            current_location,\n            server_location: notify,\n        }\n    }\n\n    /// Accepts incoming websocket connections and handles them in a loop.\n    ///\n    /// New sockets are accepted and then put in to a new thread to handle the connection.\n    /// This is implemented using traditional sync code to allow us to be independent of the async runtime.\n    fn accept_loop(\n        notify: Arc<Notify>,\n        mut server: TcpListener,\n        current_location: Arc<Mutex<ServerLocation>>,\n        connections: Arc<RwLock<HashMap<u32, WebviewConnectionState>>>,\n    ) {\n        loop {\n            // Accept connections until we hit an error\n            while let Ok((stream, _)) = server.accept() {\n                Self::handle_connection(stream, current_location.clone(), connections.clone());\n            }\n\n            // Switch ports and reconnect on a different port if the server is killed by the OS. This\n            // will happen if an IOS device goes to sleep\n            //\n            // For security, it is important that the keys are also regenerated when the server is restarted.\n            // The client may try to reconnect to the old port that is now being used by an attacker who steals the client\n            // key and uses it to read the edits from the new port.\n            let (location, new_server) = start_server();\n            notify.notify_waiters();\n            *current_location.lock().unwrap() = location;\n            server = new_server;\n        }\n    }\n\n    fn handle_connection(\n        stream: TcpStream,\n        server_location: Arc<Mutex<ServerLocation>>,\n        connections: Arc<RwLock<HashMap<u32, WebviewConnectionState>>>,\n    ) {\n        use tungstenite::handshake::server::{Request, Response};\n\n        let current_server_location = { *server_location.lock().unwrap() };\n        let hex_encoded_client_key = encode_key_string(&current_server_location.client_key);\n        let hex_encoded_server_key = encode_key_string(&current_server_location.server_key);\n        let mut location = None;\n\n        let on_request = |req: &Request, res| {\n            // Try to parse the webview id and key from the path\n            let path = req.uri().path();\n\n            // The path should have two parts `/webview_id/key`\n            let mut segments = path.trim_matches('/').split('/');\n            let webview_id = segments\n                .next()\n                .and_then(|s| s.parse::<u32>().ok())\n                .ok_or_else(|| {\n                    Response::builder()\n                        .status(400)\n                        .body(Some(\"Bad Request: Invalid webview ID\".to_string()))\n                        .unwrap()\n                })?;\n            let key = segments.next().ok_or_else(|| {\n                Response::builder()\n                    .status(400)\n                    .body(Some(\"Bad Request: Missing key\".to_string()))\n                    .unwrap()\n            })?;\n\n            // Make sure the key matches the expected key.\n            // VERY IMPORTANT: We cannot use normal string comparison here because it reveals information\n            // about the key based on timing information. Instead we use a constant time comparison method.\n            let key_matches: bool =\n                subtle::ConstantTimeEq::ct_eq(hex_encoded_client_key.as_ref(), key.as_bytes())\n                    .into();\n            if !key_matches {\n                return Err(Response::builder()\n                    .status(403)\n                    .body(Some(\"Forbidden: Invalid key\".to_string()))\n                    .unwrap());\n            }\n\n            location = Some(WebviewWebsocketLocation {\n                webview_id,\n                server: server_location,\n            });\n\n            Ok(res)\n        };\n\n        // Accept the websocket connection while reading the path and setting the location\n        let mut websocket = match tungstenite::accept_hdr(stream, on_request) {\n            Ok(ws) => ws,\n            Err(e) => {\n                tracing::error!(\"Error accepting websocket connection: {}\", e);\n                return;\n            }\n        };\n\n        // Immediately send the key to authenticate the server\n        websocket\n            .send(tungstenite::Message::Text(hex_encoded_server_key.into()))\n            .unwrap();\n\n        let location = match location {\n            Some(loc) => loc,\n            None => {\n                tracing::error!(\"WebSocket connection without a valid webview ID\");\n                return;\n            }\n        };\n\n        // Handle the websocket connection in a separate thread\n        let (edits_outgoing, edits_incoming_rx) = std::sync::mpsc::channel::<MsgPair>();\n\n        let connections_ = connections.clone();\n        // Spawn a task to handle the websocket connection\n        std::thread::spawn(move || {\n            let mut queued_message = None;\n            // Wait until there are edits ready to send\n            'connection: while let Ok(msg) = edits_incoming_rx.recv() {\n                let data = msg.edits.clone();\n                queued_message = Some(msg);\n                // Send the edits to the webview\n                if let Err(e) = websocket.send(tungstenite::Message::Binary(data.into())) {\n                    tracing::error!(\"Error sending edits to webview: {}\", e);\n                    break 'connection;\n                }\n\n                // Wait for the webview to apply the edits\n                while let Ok(ws_msg) = websocket.read() {\n                    match ws_msg {\n                        // We expect the webview to send a binary message when it has applied the edits\n                        // This is a signal that we can continue processing\n                        tungstenite::Message::Binary(_) => break,\n                        // If the websocket closes, switch back to the pending state and\n                        // re-queue the edits that haven't been acknowledged yet\n                        tungstenite::Message::Close(_) => {\n                            break 'connection;\n                        }\n                        _ => {}\n                    }\n                }\n\n                let msg = queued_message.take().expect(\"Message should be set here\");\n\n                // Notify that the edits have been applied\n                if msg.response.send(()).is_err() {\n                    tracing::error!(\"Error sending edits applied notification\");\n                }\n            }\n            tracing::trace!(\"Webview {} closed the connection\", location.webview_id);\n            let mut connection = WebviewConnectionState::default();\n            if let Some(msg) = queued_message {\n                connection.add_message_pair(msg);\n            }\n            connections_\n                .write()\n                .unwrap()\n                .insert(location.webview_id, connection);\n        });\n\n        let mut connections = connections.write().unwrap();\n        match connections.remove(&location.webview_id) {\n            // If there are pending edits, send them to the new connection\n            Some(WebviewConnectionState::Pending { mut pending }) => {\n                while let Some(pair) = pending.pop_front() {\n                    _ = edits_outgoing.send(pair);\n                }\n            }\n\n            // If the webview was already connected, never send edits from the old connection to\n            // the new connection. This should never happen\n            Some(WebviewConnectionState::Connected { .. }) => {\n                tracing::error!(\n                    \"Webview {} was already connected. Rejecting new connection.\",\n                    location.webview_id\n                );\n                return;\n            }\n\n            None => {}\n        }\n\n        connections.insert(\n            location.webview_id,\n            WebviewConnectionState::Connected { edits_outgoing },\n        );\n    }\n\n    pub(crate) fn create_queue(&self) -> WryQueue {\n        let webview_id = self\n            .max_webview_id\n            .fetch_add(1, std::sync::atomic::Ordering::SeqCst);\n        let server = self.current_location.clone();\n        let server_location = self.server_location.clone();\n        WryQueue {\n            inner: Rc::new(RefCell::new(WryQueueInner {\n                server_location_changed: server_location.clone(),\n                server_location_changed_future: owned_notify_future(server_location),\n                location: WebviewWebsocketLocation { webview_id, server },\n                websocket: self.clone(),\n                edits_in_progress: None,\n                mutation_state: MutationState::default(),\n            })),\n        }\n    }\n\n    fn send_edits(&mut self, webview: u32, edits: Vec<u8>) -> oneshot::Receiver<()> {\n        let mut connections_mut = self.connections.write().unwrap();\n        let connection = connections_mut.entry(webview).or_default();\n        connection.add_message(edits)\n    }\n}\n\n/// The state of a webview websocket connection. This may be pending while the webview is booting.\n/// If it is, we queue up edits until the webview is ready to receive them.\nenum WebviewConnectionState {\n    Pending {\n        pending: VecDeque<MsgPair>,\n    },\n    Connected {\n        edits_outgoing: std::sync::mpsc::Sender<MsgPair>,\n    },\n}\n\nimpl Default for WebviewConnectionState {\n    fn default() -> Self {\n        WebviewConnectionState::Pending {\n            pending: VecDeque::new(),\n        }\n    }\n}\n\nimpl WebviewConnectionState {\n    /// Add a message to the active connection or queue and return a receiver that will be resolved\n    /// when the webview has applied the edits.\n    fn add_message(&mut self, edits: Vec<u8>) -> oneshot::Receiver<()> {\n        let (response_sender, response_receiver) = oneshot::channel();\n        let pair = MsgPair {\n            edits,\n            response: response_sender,\n        };\n        self.add_message_pair(pair);\n        response_receiver\n    }\n\n    /// Add a message pair to the connection state. The receiver in the message pair will be resolved\n    /// when the webview has applied the edits.\n    fn add_message_pair(&mut self, pair: MsgPair) {\n        match self {\n            WebviewConnectionState::Pending { pending: queue } => {\n                queue.push_back(pair);\n            }\n            WebviewConnectionState::Connected { edits_outgoing } => {\n                _ = edits_outgoing.send(pair);\n            }\n        }\n    }\n}\n\nstruct MsgPair {\n    edits: Vec<u8>,\n    response: oneshot::Sender<()>,\n}\n\nconst KEY_SIZE: usize = 256;\ntype EncodedKey = [u8; KEY_SIZE];\n\n/// Base64 encode the key to a string to be used in the websocket URL.\nfn encode_key_string(key: &EncodedKey) -> String {\n    base64::Engine::encode(&base64::engine::general_purpose::URL_SAFE, key)\n}\n\n/// Create a secure key for the websocket connection.\n/// Returns the key as a byte array and a hex-encoded string representation of the key.\nfn create_secure_key() -> EncodedKey {\n    // Helper function to assert that the RNG is a CryptoRng - make sure we use a secure RNG\n    fn assert_crypto_random<R: rand::CryptoRng>(val: R) -> R {\n        val\n    }\n\n    let mut secure_rng = assert_crypto_random(rand::rngs::StdRng::from_os_rng());\n    let mut expected_key: EncodedKey = [0u8; KEY_SIZE];\n    secure_rng.fill_bytes(&mut expected_key);\n    expected_key\n}\n\n#[test]\nfn test_key_encoding_length() {\n    let mut rand = rand::rngs::StdRng::from_os_rng();\n    for _ in 0..100 {\n        let mut key: EncodedKey = [0u8; KEY_SIZE];\n        rand.fill_bytes(&mut key);\n        let encoded = encode_key_string(&key);\n        // The encoded key length should be the same regardless of the value of the key\n        assert_eq!(encoded.len(), 344);\n    }\n}\n\n// Take an Arc<Notify> and create a future that waits for the notify to be triggered.\nfn owned_notify_future(notify: Arc<Notify>) -> Pin<Box<dyn Future<Output = ()>>> {\n    let mut notify_owned = Box::pin(async move {\n        let notified = notify.notified();\n\n        // The future should be after this statement once it is polled bellow\n        tokio::task::yield_now().await;\n        notified.await;\n    });\n\n    // Start tracking notify before the output future is polled\n    _ = (&mut notify_owned).now_or_never();\n    notify_owned\n}\n"
  },
  {
    "path": "packages/desktop/src/element.rs",
    "content": "use std::rc::Rc;\n\nuse dioxus_core::ElementId;\nuse dioxus_html::{\n    geometry::{PixelsRect, PixelsSize, PixelsVector2D},\n    MountedResult, RenderedElementBacking,\n};\n\nuse crate::{desktop_context::DesktopContext, query::QueryEngine, WeakDesktopContext};\n\n#[derive(Clone)]\n/// A mounted element passed to onmounted events\npub struct DesktopElement {\n    id: ElementId,\n    webview: WeakDesktopContext,\n    query: QueryEngine,\n}\n\nimpl DesktopElement {\n    pub(crate) fn new(id: ElementId, webview: DesktopContext, query: QueryEngine) -> Self {\n        let webview = Rc::downgrade(&webview);\n        Self { id, webview, query }\n    }\n}\n\nmacro_rules! scripted_getter {\n    ($meth_name:ident, $script:literal, $output_type:path) => {\n        fn $meth_name(\n            &self,\n        ) -> std::pin::Pin<\n            Box<dyn futures_util::Future<Output = dioxus_html::MountedResult<$output_type>>>,\n        > {\n            let script = format!($script, id = self.id.0);\n            let webview = self\n                .webview\n                .upgrade()\n                .expect(\"Webview should be alive if the element is being queried\");\n            let fut = self\n                .query\n                .new_query::<Option<$output_type>>(&script, webview)\n                .resolve();\n            Box::pin(async move {\n                match fut.await {\n                    Ok(Some(res)) => Ok(res),\n                    Ok(None) => MountedResult::Err(dioxus_html::MountedError::OperationFailed(\n                        Box::new(DesktopQueryError::FailedToQuery),\n                    )),\n                    Err(err) => MountedResult::Err(dioxus_html::MountedError::OperationFailed(\n                        Box::new(err),\n                    )),\n                }\n            })\n        }\n    };\n}\n\nimpl RenderedElementBacking for DesktopElement {\n    fn as_any(&self) -> &dyn std::any::Any {\n        self\n    }\n\n    scripted_getter!(\n        get_scroll_offset,\n        \"return [window.interpreter.getScrollLeft({id}), window.interpreter.getScrollTop({id})]\",\n        PixelsVector2D\n    );\n\n    scripted_getter!(\n        get_scroll_size,\n        \"return [window.interpreter.getScrollWidth({id}), window.interpreter.getScrollHeight({id})]\",\n        PixelsSize\n    );\n\n    scripted_getter!(\n        get_client_rect,\n        \"return window.interpreter.getClientRect({id});\",\n        PixelsRect\n    );\n\n    fn scroll_to(\n        &self,\n        options: dioxus_html::ScrollToOptions,\n    ) -> std::pin::Pin<Box<dyn futures_util::Future<Output = dioxus_html::MountedResult<()>>>> {\n        let script = format!(\n            \"return window.interpreter.scrollTo({}, {});\",\n            self.id.0,\n            serde_json::to_string(&options).expect(\"Failed to serialize ScrollToOptions\")\n        );\n        let webview = self\n            .webview\n            .upgrade()\n            .expect(\"Webview should be alive if the element is being queried\");\n        let fut = self.query.new_query::<bool>(&script, webview).resolve();\n        Box::pin(async move {\n            match fut.await {\n                Ok(true) => Ok(()),\n                Ok(false) => MountedResult::Err(dioxus_html::MountedError::OperationFailed(\n                    Box::new(DesktopQueryError::FailedToQuery),\n                )),\n                Err(err) => {\n                    MountedResult::Err(dioxus_html::MountedError::OperationFailed(Box::new(err)))\n                }\n            }\n        })\n    }\n\n    fn scroll(\n        &self,\n        coordinates: PixelsVector2D,\n        behavior: dioxus_html::ScrollBehavior,\n    ) -> std::pin::Pin<Box<dyn std::future::Future<Output = MountedResult<()>>>> {\n        let script = format!(\n            \"return window.interpreter.scroll({}, {}, {}, {});\",\n            self.id.0,\n            coordinates.x,\n            coordinates.y,\n            serde_json::to_string(&behavior).expect(\"Failed to serialize ScrollBehavior\")\n        );\n        let webview = self\n            .webview\n            .upgrade()\n            .expect(\"Webview should be alive if the element is being queried\");\n        let fut = self.query.new_query::<bool>(&script, webview).resolve();\n        Box::pin(async move {\n            match fut.await {\n                Ok(true) => Ok(()),\n                Ok(false) => MountedResult::Err(dioxus_html::MountedError::OperationFailed(\n                    Box::new(DesktopQueryError::FailedToQuery),\n                )),\n                Err(err) => {\n                    MountedResult::Err(dioxus_html::MountedError::OperationFailed(Box::new(err)))\n                }\n            }\n        })\n    }\n\n    fn set_focus(\n        &self,\n        focus: bool,\n    ) -> std::pin::Pin<Box<dyn futures_util::Future<Output = dioxus_html::MountedResult<()>>>> {\n        let script = format!(\n            \"return window.interpreter.setFocus({}, {});\",\n            self.id.0, focus\n        );\n        let webview = self\n            .webview\n            .upgrade()\n            .expect(\"Webview should be alive if the element is being queried\");\n        let fut = self.query.new_query::<bool>(&script, webview).resolve();\n\n        Box::pin(async move {\n            match fut.await {\n                Ok(true) => Ok(()),\n                Ok(false) => MountedResult::Err(dioxus_html::MountedError::OperationFailed(\n                    Box::new(DesktopQueryError::FailedToQuery),\n                )),\n                Err(err) => {\n                    MountedResult::Err(dioxus_html::MountedError::OperationFailed(Box::new(err)))\n                }\n            }\n        })\n    }\n}\n\n#[derive(Debug)]\nenum DesktopQueryError {\n    FailedToQuery,\n}\n\nimpl std::fmt::Display for DesktopQueryError {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        match self {\n            DesktopQueryError::FailedToQuery => write!(f, \"Failed to query the element\"),\n        }\n    }\n}\n\nimpl std::error::Error for DesktopQueryError {}\n"
  },
  {
    "path": "packages/desktop/src/event_handlers.rs",
    "content": "use crate::{ipc::UserWindowEvent, window};\nuse slab::Slab;\nuse std::cell::RefCell;\nuse tao::{event::Event, event_loop::EventLoopWindowTarget, window::WindowId};\n\n/// The unique identifier of a window event handler. This can be used to later remove the handler.\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]\npub struct WryEventHandler(pub(crate) usize);\n\nimpl WryEventHandler {\n    /// Unregister this event handler from the window\n    pub fn remove(&self) {\n        window().shared.event_handlers.remove(*self)\n    }\n}\n\n#[derive(Default)]\npub struct WindowEventHandlers {\n    handlers: RefCell<Slab<WryWindowEventHandlerInner>>,\n}\n\nstruct WryWindowEventHandlerInner {\n    window_id: WindowId,\n\n    #[allow(clippy::type_complexity)]\n    handler:\n        Box<dyn FnMut(&Event<UserWindowEvent>, &EventLoopWindowTarget<UserWindowEvent>) + 'static>,\n}\n\nimpl WindowEventHandlers {\n    pub(crate) fn add(\n        &self,\n        window_id: WindowId,\n        handler: impl FnMut(&Event<UserWindowEvent>, &EventLoopWindowTarget<UserWindowEvent>) + 'static,\n    ) -> WryEventHandler {\n        WryEventHandler(\n            self.handlers\n                .borrow_mut()\n                .insert(WryWindowEventHandlerInner {\n                    window_id,\n                    handler: Box::new(handler),\n                }),\n        )\n    }\n\n    pub(crate) fn remove(&self, id: WryEventHandler) {\n        self.handlers.borrow_mut().try_remove(id.0);\n    }\n\n    pub fn apply_event(\n        &self,\n        event: &Event<UserWindowEvent>,\n        target: &EventLoopWindowTarget<UserWindowEvent>,\n    ) {\n        for (_, handler) in self.handlers.borrow_mut().iter_mut() {\n            // Avoid interacting with the runtime while something else is using it\n            #[cfg(target_os = \"android\")]\n            let _lock = crate::android_sync_lock::android_runtime_lock();\n\n            // if this event does not apply to the window this listener cares about, continue\n            if let Event::WindowEvent { window_id, .. } = event {\n                if *window_id != handler.window_id {\n                    continue;\n                }\n            }\n\n            (handler.handler)(event, target)\n        }\n    }\n}\n"
  },
  {
    "path": "packages/desktop/src/events.rs",
    "content": "//! Convert a serialized event to an event trigger\n\nuse crate::{\n    element::DesktopElement,\n    file_upload::{DesktopFileDragEvent, DesktopFormData},\n};\nuse dioxus_html::*;\n\npub(crate) struct SerializedHtmlEventConverter;\n\nimpl HtmlEventConverter for SerializedHtmlEventConverter {\n    /// This impl is special because it relies on the renderer to convert from serialized data to native data\n    /// as an intermediate step.\n    fn convert_drag_data(&self, event: &PlatformEventData) -> DragData {\n        event\n            .downcast::<DesktopFileDragEvent>()\n            .cloned()\n            .unwrap()\n            .into()\n    }\n\n    /// This impl is special because it relies on the renderer to convert from serialized data to native data\n    /// as an intermediate step.\n    fn convert_form_data(&self, event: &PlatformEventData) -> FormData {\n        event.downcast::<DesktopFormData>().cloned().unwrap().into()\n    }\n\n    /// This impl is special because it leverages the `DesktopElement` ref between the virtualdom and webview\n    fn convert_mounted_data(&self, event: &PlatformEventData) -> MountedData {\n        event.downcast::<DesktopElement>().cloned().unwrap().into()\n    }\n\n    fn convert_animation_data(&self, event: &PlatformEventData) -> AnimationData {\n        event\n            .downcast::<SerializedAnimationData>()\n            .cloned()\n            .unwrap()\n            .into()\n    }\n\n    fn convert_cancel_data(&self, event: &PlatformEventData) -> CancelData {\n        event\n            .downcast::<SerializedCancelData>()\n            .cloned()\n            .unwrap()\n            .into()\n    }\n\n    fn convert_clipboard_data(&self, event: &PlatformEventData) -> ClipboardData {\n        event\n            .downcast::<SerializedClipboardData>()\n            .cloned()\n            .unwrap()\n            .into()\n    }\n\n    fn convert_composition_data(&self, event: &PlatformEventData) -> CompositionData {\n        event\n            .downcast::<SerializedCompositionData>()\n            .cloned()\n            .unwrap()\n            .into()\n    }\n\n    fn convert_focus_data(&self, event: &PlatformEventData) -> FocusData {\n        event\n            .downcast::<SerializedFocusData>()\n            .cloned()\n            .unwrap()\n            .into()\n    }\n\n    fn convert_image_data(&self, event: &PlatformEventData) -> ImageData {\n        event\n            .downcast::<SerializedImageData>()\n            .cloned()\n            .unwrap()\n            .into()\n    }\n\n    fn convert_keyboard_data(&self, event: &PlatformEventData) -> KeyboardData {\n        event\n            .downcast::<SerializedKeyboardData>()\n            .cloned()\n            .unwrap()\n            .into()\n    }\n\n    fn convert_media_data(&self, event: &PlatformEventData) -> MediaData {\n        event\n            .downcast::<SerializedMediaData>()\n            .cloned()\n            .unwrap()\n            .into()\n    }\n\n    fn convert_mouse_data(&self, event: &PlatformEventData) -> MouseData {\n        event\n            .downcast::<SerializedMouseData>()\n            .cloned()\n            .unwrap()\n            .into()\n    }\n\n    fn convert_pointer_data(&self, event: &PlatformEventData) -> PointerData {\n        event\n            .downcast::<SerializedPointerData>()\n            .cloned()\n            .unwrap()\n            .into()\n    }\n\n    fn convert_resize_data(&self, event: &PlatformEventData) -> ResizeData {\n        event\n            .downcast::<SerializedResizeData>()\n            .cloned()\n            .unwrap()\n            .into()\n    }\n\n    fn convert_scroll_data(&self, event: &PlatformEventData) -> ScrollData {\n        event\n            .downcast::<SerializedScrollData>()\n            .cloned()\n            .unwrap()\n            .into()\n    }\n\n    fn convert_selection_data(&self, event: &PlatformEventData) -> SelectionData {\n        event\n            .downcast::<SerializedSelectionData>()\n            .cloned()\n            .unwrap()\n            .into()\n    }\n\n    fn convert_toggle_data(&self, event: &PlatformEventData) -> ToggleData {\n        event\n            .downcast::<SerializedToggleData>()\n            .cloned()\n            .unwrap()\n            .into()\n    }\n\n    fn convert_touch_data(&self, event: &PlatformEventData) -> TouchData {\n        event\n            .downcast::<SerializedTouchData>()\n            .cloned()\n            .unwrap()\n            .into()\n    }\n\n    fn convert_transition_data(&self, event: &PlatformEventData) -> TransitionData {\n        event\n            .downcast::<SerializedTransitionData>()\n            .cloned()\n            .unwrap()\n            .into()\n    }\n\n    fn convert_visible_data(&self, event: &PlatformEventData) -> VisibleData {\n        event\n            .downcast::<SerializedVisibleData>()\n            .cloned()\n            .unwrap()\n            .into()\n    }\n\n    fn convert_wheel_data(&self, event: &PlatformEventData) -> WheelData {\n        event\n            .downcast::<SerializedWheelData>()\n            .cloned()\n            .unwrap()\n            .into()\n    }\n}\n"
  },
  {
    "path": "packages/desktop/src/file_upload.rs",
    "content": "#![allow(unused)]\n\nuse std::{any::Any, collections::HashMap};\n\n#[cfg(feature = \"tokio_runtime\")]\nuse tokio::{fs::File, io::AsyncReadExt};\n\nuse dioxus_html::{\n    geometry::{ClientPoint, Coordinates, ElementPoint, PagePoint, ScreenPoint},\n    input_data::{MouseButton, MouseButtonSet},\n    point_interaction::{\n        InteractionElementOffset, InteractionLocation, ModifiersInteraction, PointerInteraction,\n    },\n    FileData, FormValue, HasDataTransferData, HasDragData, HasFileData, HasFormData, HasMouseData,\n    NativeFileData, SerializedDataTransfer, SerializedFormData, SerializedFormObject,\n    SerializedMouseData, SerializedPointInteraction,\n};\n\nuse serde::{Deserialize, Serialize};\nuse std::{\n    cell::{Cell, RefCell},\n    path::PathBuf,\n    rc::Rc,\n    str::FromStr,\n    sync::Arc,\n};\nuse wry::DragDropEvent;\n\n#[derive(Debug, Deserialize, Serialize)]\npub(crate) struct FileDialogRequest {\n    #[serde(default)]\n    accept: Option<String>,\n    multiple: bool,\n    directory: bool,\n    pub event: String,\n    pub target: usize,\n    pub bubbles: bool,\n    pub target_name: String,\n    pub values: Vec<SerializedFormObject>,\n}\n\n#[allow(unused)]\nimpl FileDialogRequest {\n    #[cfg(not(any(\n        target_os = \"windows\",\n        target_os = \"macos\",\n        target_os = \"linux\",\n        target_os = \"dragonfly\",\n        target_os = \"freebsd\",\n        target_os = \"netbsd\",\n        target_os = \"openbsd\"\n    )))]\n    pub(crate) fn get_file_event(&self) -> Vec<PathBuf> {\n        vec![]\n    }\n\n    #[cfg(not(any(\n        target_os = \"windows\",\n        target_os = \"macos\",\n        target_os = \"linux\",\n        target_os = \"dragonfly\",\n        target_os = \"freebsd\",\n        target_os = \"netbsd\",\n        target_os = \"openbsd\"\n    )))]\n    pub(crate) async fn get_file_event_async(&self) -> Vec<PathBuf> {\n        vec![]\n    }\n\n    #[cfg(any(\n        target_os = \"windows\",\n        target_os = \"macos\",\n        target_os = \"linux\",\n        target_os = \"dragonfly\",\n        target_os = \"freebsd\",\n        target_os = \"netbsd\",\n        target_os = \"openbsd\"\n    ))]\n    pub(crate) fn get_file_event_sync(&self) -> Vec<PathBuf> {\n        let dialog = rfd::FileDialog::new();\n        if self.directory {\n            self.get_file_event_for_folder(dialog)\n        } else {\n            self.get_file_event_for_file(dialog)\n        }\n    }\n\n    #[cfg(any(\n        target_os = \"windows\",\n        target_os = \"macos\",\n        target_os = \"linux\",\n        target_os = \"dragonfly\",\n        target_os = \"freebsd\",\n        target_os = \"netbsd\",\n        target_os = \"openbsd\"\n    ))]\n    pub(crate) async fn get_file_event_async(&self) -> Vec<PathBuf> {\n        let mut dialog = rfd::AsyncFileDialog::new();\n\n        if self.directory {\n            if self.multiple {\n                dialog\n                    .pick_folders()\n                    .await\n                    .into_iter()\n                    .flatten()\n                    .map(|f| f.path().to_path_buf())\n                    .collect()\n            } else {\n                dialog\n                    .pick_folder()\n                    .await\n                    .into_iter()\n                    .map(|f| f.path().to_path_buf())\n                    .collect()\n            }\n        } else {\n            let filters: Vec<_> = self\n                .accept\n                .as_deref()\n                .unwrap_or(\".*\")\n                .split(',')\n                .filter_map(|s| Filters::from_str(s.trim()).ok())\n                .collect();\n\n            let file_extensions: Vec<_> = filters\n                .iter()\n                .flat_map(|f| f.as_extensions().into_iter())\n                .collect();\n\n            let filter_name = file_extensions\n                .iter()\n                .map(|extension| format!(\"*.{extension}\"))\n                .collect::<Vec<_>>()\n                .join(\", \");\n\n            dialog = dialog.add_filter(filter_name, file_extensions.as_slice());\n\n            let files: Vec<_> = if self.multiple {\n                dialog\n                    .pick_files()\n                    .await\n                    .into_iter()\n                    .flatten()\n                    .map(|f| f.path().to_path_buf())\n                    .collect()\n            } else {\n                dialog\n                    .pick_file()\n                    .await\n                    .into_iter()\n                    .map(|f| f.path().to_path_buf())\n                    .collect()\n            };\n\n            files\n        }\n    }\n\n    #[cfg(any(\n        target_os = \"windows\",\n        target_os = \"macos\",\n        target_os = \"linux\",\n        target_os = \"dragonfly\",\n        target_os = \"freebsd\",\n        target_os = \"netbsd\",\n        target_os = \"openbsd\"\n    ))]\n    fn get_file_event_for_file(&self, mut dialog: rfd::FileDialog) -> Vec<PathBuf> {\n        let filters: Vec<_> = self\n            .accept\n            .as_deref()\n            .unwrap_or(\".*\")\n            .split(',')\n            .filter_map(|s| Filters::from_str(s.trim()).ok())\n            .collect();\n\n        let file_extensions: Vec<_> = filters\n            .iter()\n            .flat_map(|f| f.as_extensions().into_iter())\n            .collect();\n\n        let filter_name = file_extensions\n            .iter()\n            .map(|extension| format!(\"*.{extension}\"))\n            .collect::<Vec<_>>()\n            .join(\", \");\n\n        dialog = dialog.add_filter(filter_name, file_extensions.as_slice());\n\n        let files: Vec<_> = if self.multiple {\n            dialog.pick_files().into_iter().flatten().collect()\n        } else {\n            dialog.pick_file().into_iter().collect()\n        };\n\n        files\n    }\n\n    #[cfg(any(\n        target_os = \"windows\",\n        target_os = \"macos\",\n        target_os = \"linux\",\n        target_os = \"dragonfly\",\n        target_os = \"freebsd\",\n        target_os = \"netbsd\",\n        target_os = \"openbsd\"\n    ))]\n    fn get_file_event_for_folder(&self, dialog: rfd::FileDialog) -> Vec<PathBuf> {\n        if self.multiple {\n            dialog.pick_folders().into_iter().flatten().collect()\n        } else {\n            dialog.pick_folder().into_iter().collect()\n        }\n    }\n}\n\nenum Filters {\n    Extension(String),\n    Mime(String),\n    Audio,\n    Video,\n    Image,\n}\n\nimpl Filters {\n    fn as_extensions(&self) -> Vec<&str> {\n        match self {\n            Filters::Extension(extension) => vec![extension.as_str()],\n            Filters::Mime(_) => vec![],\n            Filters::Audio => vec![\"mp3\", \"wav\", \"ogg\"],\n            Filters::Video => vec![\"mp4\", \"webm\"],\n            Filters::Image => vec![\"png\", \"jpg\", \"jpeg\", \"gif\", \"webp\"],\n        }\n    }\n}\n\nimpl FromStr for Filters {\n    type Err = String;\n\n    fn from_str(s: &str) -> Result<Self, Self::Err> {\n        if let Some(extension) = s.strip_prefix('.') {\n            Ok(Filters::Extension(extension.to_string()))\n        } else {\n            match s {\n                \"audio/*\" => Ok(Filters::Audio),\n                \"video/*\" => Ok(Filters::Video),\n                \"image/*\" => Ok(Filters::Image),\n                _ => Ok(Filters::Mime(s.to_string())),\n            }\n        }\n    }\n}\n\n#[derive(Clone)]\npub(crate) struct DesktopFormData {\n    pub value: String,\n    pub valid: bool,\n    pub values: Vec<(String, FormValue)>,\n}\n\nimpl DesktopFormData {\n    pub fn new(values: Vec<(String, FormValue)>) -> Self {\n        Self {\n            value: String::new(),\n            valid: true,\n            values,\n        }\n    }\n}\n\nimpl HasFileData for DesktopFormData {\n    fn files(&self) -> Vec<FileData> {\n        self.values\n            .iter()\n            .filter_map(|(_, v)| {\n                if let FormValue::File(Some(f)) = v {\n                    Some(f.clone())\n                } else {\n                    None\n                }\n            })\n            .collect()\n    }\n}\n\nimpl HasFormData for DesktopFormData {\n    fn as_any(&self) -> &dyn std::any::Any {\n        self\n    }\n\n    fn value(&self) -> String {\n        self.value.clone()\n    }\n\n    fn valid(&self) -> bool {\n        self.valid\n    }\n\n    fn values(&self) -> Vec<(String, FormValue)> {\n        self.values.clone()\n    }\n}\n\n#[derive(Default, Clone)]\npub struct NativeFileHover {\n    event: Rc<RefCell<Option<DragDropEvent>>>,\n    paths: Rc<RefCell<Vec<PathBuf>>>,\n}\nimpl NativeFileHover {\n    pub fn set(&self, event: DragDropEvent) {\n        match event {\n            DragDropEvent::Enter { ref paths, .. } => self.paths.borrow_mut().clone_from(paths),\n            DragDropEvent::Drop { ref paths, .. } => self.paths.borrow_mut().clone_from(paths),\n            _ => {}\n        }\n        self.event.borrow_mut().replace(event);\n    }\n\n    pub fn current(&self) -> Option<DragDropEvent> {\n        self.event.borrow_mut().clone()\n    }\n\n    pub fn current_paths(&self) -> Vec<PathBuf> {\n        self.paths.borrow_mut().clone()\n    }\n}\n\n#[derive(Clone)]\npub(crate) struct DesktopFileDragEvent {\n    pub mouse: SerializedPointInteraction,\n    pub data_transfer: SerializedDataTransfer,\n    pub files: Vec<PathBuf>,\n}\n\nimpl HasFileData for DesktopFileDragEvent {\n    fn files(&self) -> Vec<FileData> {\n        self.files\n            .iter()\n            .cloned()\n            .map(|f| FileData::new(DesktopFileData(f)))\n            .collect()\n    }\n}\n\nimpl HasDataTransferData for DesktopFileDragEvent {\n    fn data_transfer(&self) -> dioxus_html::DataTransfer {\n        dioxus_html::DataTransfer::new(self.data_transfer.clone())\n    }\n}\n\nimpl HasDragData for DesktopFileDragEvent {\n    fn as_any(&self) -> &dyn std::any::Any {\n        self\n    }\n}\n\nimpl HasMouseData for DesktopFileDragEvent {\n    fn as_any(&self) -> &dyn std::any::Any {\n        self\n    }\n}\n\nimpl InteractionLocation for DesktopFileDragEvent {\n    fn client_coordinates(&self) -> ClientPoint {\n        self.mouse.client_coordinates()\n    }\n\n    fn page_coordinates(&self) -> PagePoint {\n        self.mouse.page_coordinates()\n    }\n\n    fn screen_coordinates(&self) -> ScreenPoint {\n        self.mouse.screen_coordinates()\n    }\n}\n\nimpl InteractionElementOffset for DesktopFileDragEvent {\n    fn element_coordinates(&self) -> ElementPoint {\n        self.mouse.element_coordinates()\n    }\n\n    fn coordinates(&self) -> Coordinates {\n        self.mouse.coordinates()\n    }\n}\n\nimpl ModifiersInteraction for DesktopFileDragEvent {\n    fn modifiers(&self) -> dioxus_html::Modifiers {\n        self.mouse.modifiers()\n    }\n}\n\nimpl PointerInteraction for DesktopFileDragEvent {\n    fn held_buttons(&self) -> MouseButtonSet {\n        self.mouse.held_buttons()\n    }\n\n    fn trigger_button(&self) -> Option<MouseButton> {\n        self.mouse.trigger_button()\n    }\n}\n\n#[derive(Clone)]\npub struct DesktopFileData(pub(crate) PathBuf);\n\nimpl NativeFileData for DesktopFileData {\n    fn name(&self) -> String {\n        self.0.file_name().unwrap().to_string_lossy().into_owned()\n    }\n\n    fn size(&self) -> u64 {\n        std::fs::metadata(&self.0).map(|m| m.len()).unwrap_or(0)\n    }\n\n    fn last_modified(&self) -> u64 {\n        std::fs::metadata(&self.0)\n            .and_then(|m| m.modified())\n            .ok()\n            .and_then(|time| time.duration_since(std::time::UNIX_EPOCH).ok())\n            .map(|duration| duration.as_secs())\n            .unwrap_or(0)\n    }\n\n    fn read_bytes(\n        &self,\n    ) -> std::pin::Pin<\n        Box<\n            dyn std::future::Future<Output = Result<bytes::Bytes, dioxus_core::CapturedError>>\n                + 'static,\n        >,\n    > {\n        let path = self.0.clone();\n        Box::pin(async move { Ok(bytes::Bytes::from(std::fs::read(&path)?)) })\n    }\n\n    fn read_string(\n        &self,\n    ) -> std::pin::Pin<\n        Box<dyn std::future::Future<Output = Result<String, dioxus_core::CapturedError>> + 'static>,\n    > {\n        let path = self.0.clone();\n        Box::pin(async move { Ok(std::fs::read_to_string(&path)?) })\n    }\n\n    fn inner(&self) -> &dyn std::any::Any {\n        &self.0\n    }\n\n    fn path(&self) -> PathBuf {\n        self.0.clone()\n    }\n\n    fn byte_stream(\n        &self,\n    ) -> std::pin::Pin<\n        Box<\n            dyn futures_util::Stream<Item = Result<bytes::Bytes, dioxus_core::CapturedError>>\n                + 'static\n                + Send,\n        >,\n    > {\n        let path = self.0.clone();\n        Box::pin(futures_util::stream::once(async move {\n            Ok(bytes::Bytes::from(std::fs::read(&path)?))\n        }))\n    }\n\n    fn content_type(&self) -> Option<String> {\n        Some(\n            dioxus_asset_resolver::native::get_mime_from_ext(\n                self.0.extension().and_then(|ext| ext.to_str()),\n            )\n            .to_string(),\n        )\n    }\n}\n\npub struct DesktopDataTransfer {}\n"
  },
  {
    "path": "packages/desktop/src/hooks.rs",
    "content": "use std::rc::Rc;\n\nuse crate::{\n    assets::*, ipc::UserWindowEvent, shortcut::IntoAccelerator, window, DesktopContext,\n    HotKeyState, ShortcutHandle, ShortcutRegistryError, WryEventHandler,\n};\nuse dioxus_core::{consume_context, use_hook, use_hook_with_cleanup, Runtime};\n\nuse dioxus_hooks::use_callback;\nuse tao::{event::Event, event_loop::EventLoopWindowTarget};\nuse wry::RequestAsyncResponder;\n\n/// Get an imperative handle to the current window\npub fn use_window() -> DesktopContext {\n    use_hook(consume_context::<DesktopContext>)\n}\n\n/// Register an event handler that runs when a wry event is processed.\npub fn use_wry_event_handler(\n    mut handler: impl FnMut(&Event<UserWindowEvent>, &EventLoopWindowTarget<UserWindowEvent>) + 'static,\n) -> WryEventHandler {\n    use dioxus_core::current_scope_id;\n\n    // Capture the current runtime and scope ID.\n    let runtime = Runtime::current();\n    let scope_id = current_scope_id();\n\n    use_hook_with_cleanup(\n        move || {\n            window().create_wry_event_handler(move |event, target| {\n                runtime.in_scope(scope_id, || handler(event, target))\n            })\n        },\n        move |handler| handler.remove(),\n    )\n}\n\n/// Register an event handler that runs when a muda event is processed.\n#[cfg_attr(\n    docsrs,\n    doc(cfg(any(target_os = \"windows\", target_os = \"linux\", target_os = \"macos\")))\n)]\n#[cfg(any(target_os = \"windows\", target_os = \"linux\", target_os = \"macos\"))]\npub fn use_muda_event_handler(\n    mut handler: impl FnMut(&muda::MenuEvent) + 'static,\n) -> WryEventHandler {\n    use_wry_event_handler(move |event, _| {\n        if let Event::UserEvent(UserWindowEvent::MudaMenuEvent(event)) = event {\n            handler(event);\n        }\n    })\n}\n\n/// Register an event handler that runs when a tray icon menu event is processed.\n#[cfg_attr(\n    docsrs,\n    doc(cfg(any(target_os = \"windows\", target_os = \"linux\", target_os = \"macos\")))\n)]\n#[cfg(any(target_os = \"windows\", target_os = \"linux\", target_os = \"macos\"))]\npub fn use_tray_menu_event_handler(\n    mut handler: impl FnMut(&tray_icon::menu::MenuEvent) + 'static,\n) -> WryEventHandler {\n    use_wry_event_handler(move |event, _| {\n        if let Event::UserEvent(UserWindowEvent::TrayMenuEvent(event)) = event {\n            handler(event);\n        }\n    })\n}\n\n/// Register an event handler that runs when a tray icon event is processed.\n/// This is only for tray icon and not it's menus.\n/// If you want to register tray icon menus handler use `use_tray_menu_event_handler` instead.\n#[cfg_attr(\n    docsrs,\n    doc(cfg(any(target_os = \"windows\", target_os = \"linux\", target_os = \"macos\")))\n)]\n#[cfg(any(target_os = \"windows\", target_os = \"linux\", target_os = \"macos\"))]\npub fn use_tray_icon_event_handler(\n    mut handler: impl FnMut(&tray_icon::TrayIconEvent) + 'static,\n) -> WryEventHandler {\n    use_wry_event_handler(move |event, _| {\n        if let Event::UserEvent(UserWindowEvent::TrayIconEvent(event)) = event {\n            handler(event);\n        }\n    })\n}\n\n/// Provide a callback to handle asset loading yourself.\n///\n/// The callback takes a path as requested by the web view, and it should return `Some(response)`\n/// if you want to load the asset, and `None` if you want to fallback on the default behavior.\npub fn use_asset_handler(\n    name: &str,\n    mut handler: impl FnMut(AssetRequest, RequestAsyncResponder) + 'static,\n) {\n    // wrap the user's handler in something that keeps it up to date\n    let cb = use_callback(move |(asset, responder)| handler(asset, responder));\n\n    use_hook_with_cleanup(\n        || {\n            crate::window()\n                .asset_handlers\n                .register_handler(name.to_string(), cb);\n\n            Rc::new(name.to_string())\n        },\n        move |name| {\n            _ = crate::window().asset_handlers.remove_handler(name.as_ref());\n        },\n    );\n}\n\n/// Get a closure that executes any JavaScript in the WebView context.\npub fn use_global_shortcut(\n    accelerator: impl IntoAccelerator,\n    handler: impl FnMut(HotKeyState) + 'static,\n) -> Result<ShortcutHandle, ShortcutRegistryError> {\n    // wrap the user's handler in something that keeps it up to date\n    let cb = use_callback(handler);\n\n    use_hook_with_cleanup(\n        #[allow(clippy::redundant_closure)]\n        move || window().create_shortcut(accelerator.accelerator(), move |state| cb(state)),\n        |handle| {\n            if let Ok(handle) = handle {\n                handle.remove();\n            }\n        },\n    )\n}\n"
  },
  {
    "path": "packages/desktop/src/ipc.rs",
    "content": "use serde::{Deserialize, Serialize};\nuse tao::window::WindowId;\n\n#[non_exhaustive]\n#[derive(Debug, Clone)]\npub enum UserWindowEvent {\n    /// A global hotkey event\n    #[cfg(any(target_os = \"windows\", target_os = \"linux\", target_os = \"macos\"))]\n    GlobalHotKeyEvent(global_hotkey::GlobalHotKeyEvent),\n\n    #[cfg(any(target_os = \"windows\", target_os = \"linux\", target_os = \"macos\"))]\n    MudaMenuEvent(muda::MenuEvent),\n\n    #[cfg(any(target_os = \"windows\", target_os = \"linux\", target_os = \"macos\"))]\n    TrayIconEvent(tray_icon::TrayIconEvent),\n\n    #[cfg(any(target_os = \"windows\", target_os = \"linux\", target_os = \"macos\"))]\n    TrayMenuEvent(tray_icon::menu::MenuEvent),\n\n    /// Poll the virtualdom\n    Poll(WindowId),\n\n    /// Handle an ipc message eminating from the window.postMessage of a given webview\n    Ipc {\n        id: WindowId,\n        msg: IpcMessage,\n    },\n\n    /// Handle a hotreload event, basically telling us to update our templates\n    #[cfg(all(feature = \"devtools\", debug_assertions))]\n    HotReloadEvent(dioxus_devtools::DevserverMsg),\n\n    // Windows-only drag-n-drop fix events.\n    WindowsDragDrop(WindowId),\n    WindowsDragOver(WindowId, i32, i32),\n    WindowsDragLeave(WindowId),\n\n    /// Create a new window\n    NewWindow,\n\n    /// Close a given window (could be any window!)\n    CloseWindow(WindowId),\n\n    /// Gracefully shutdown the entire app\n    Shutdown,\n}\n\n/// A message struct that manages the communication between the webview and the eventloop code\n///\n/// This needs to be serializable across the JS boundary, so the method names and structs are sensitive.\n#[derive(Deserialize, Serialize, Debug, Clone)]\npub struct IpcMessage {\n    method: String,\n    params: serde_json::Value,\n}\n\n/// A set of known messages that we need to respond to\n#[derive(Deserialize, Serialize, Debug, Clone)]\npub enum IpcMethod<'a> {\n    UserEvent,\n    Query,\n    BrowserOpen,\n    Initialize,\n    Other(&'a str),\n}\n\nimpl IpcMessage {\n    pub(crate) fn method(&self) -> IpcMethod<'_> {\n        match self.method.as_str() {\n            \"user_event\" => IpcMethod::UserEvent,\n            \"query\" => IpcMethod::Query,\n            \"browser_open\" => IpcMethod::BrowserOpen,\n            \"initialize\" => IpcMethod::Initialize,\n            _ => IpcMethod::Other(&self.method),\n        }\n    }\n\n    pub(crate) fn params(self) -> serde_json::Value {\n        self.params\n    }\n}\n"
  },
  {
    "path": "packages/desktop/src/js/hash.txt",
    "content": "[16018260687765471498]"
  },
  {
    "path": "packages/desktop/src/js/native_eval.js",
    "content": "class Channel{pending;waiting;constructor(){this.pending=[],this.waiting=[]}send(data){if(this.waiting.length>0){this.waiting.shift()(data);return}this.pending.push(data)}async recv(){return new Promise((resolve,_reject)=>{if(this.pending.length>0){resolve(this.pending.shift());return}this.waiting.push(resolve)})}}class WeakDioxusChannel{inner;constructor(channel){this.inner=new WeakRef(channel)}rustSend(data){let channel=this.inner.deref();if(channel)channel.rustSend(data)}async rustRecv(){let channel=this.inner.deref();if(channel)return await channel.rustRecv()}}class DioxusChannel{weak(){return new WeakDioxusChannel(this)}}class QueryParams{id;data;constructor(id,method,data){this.id=id,this.data={method,data}}}window.__msg_queues=window.__msg_queues||[];window.finalizationRegistry=window.finalizationRegistry||new FinalizationRegistry(({id})=>{window.ipc.postMessage(JSON.stringify({method:\"query\",params:new QueryParams(id,\"drop\")}))});window.getQuery=function(request_id){return window.__msg_queues[request_id]};window.createQuery=function(request_id){return new NativeDioxusChannel(request_id)};class NativeDioxusChannel extends DioxusChannel{rust_to_js;request_id;constructor(request_id){super();this.rust_to_js=new Channel,this.request_id=request_id,window.__msg_queues[request_id]=this,window.finalizationRegistry.register(this,{id:request_id})}async recv(){return await this.rust_to_js.recv()}send(data){window.ipc.postMessage(JSON.stringify({method:\"query\",params:new QueryParams(this.request_id,\"send\",data)}))}rustSend(data){this.rust_to_js.send(data)}async rustRecv(){}close(){window.__msg_queues[this.request_id]=null}}export{NativeDioxusChannel};\n"
  },
  {
    "path": "packages/desktop/src/launch.rs",
    "content": "use crate::Config;\nuse crate::{\n    app::App,\n    ipc::{IpcMethod, UserWindowEvent},\n};\nuse dioxus_core::*;\nuse dioxus_document::eval;\nuse std::any::Any;\nuse tao::event::{Event, StartCause, WindowEvent};\n\n/// Launch the WebView and run the event loop, with configuration and root props.\n///\n/// This will block the main thread, and *must* be spawned on the main thread. This function does not assume any runtime\n/// and is equivalent to calling launch_with_props with the tokio feature disabled.\npub fn launch_virtual_dom_blocking(virtual_dom: VirtualDom, mut desktop_config: Config) -> ! {\n    let mut custom_event_handler = desktop_config.custom_event_handler.take();\n    let (event_loop, mut app) = App::new(desktop_config, virtual_dom);\n\n    event_loop.run(move |window_event, event_loop, control_flow| {\n        // Set the control flow and check if any events need to be handled in the app itself\n        app.tick(&window_event);\n\n        if let Some(ref mut f) = custom_event_handler {\n            f(&window_event, event_loop)\n        }\n\n        match window_event {\n            Event::NewEvents(StartCause::Init) => app.handle_start_cause_init(),\n            Event::LoopDestroyed => app.handle_loop_destroyed(),\n            Event::WindowEvent {\n                event, window_id, ..\n            } => match event {\n                WindowEvent::CloseRequested => app.handle_close_requested(window_id),\n                WindowEvent::Destroyed { .. } => app.window_destroyed(window_id),\n                WindowEvent::Resized(new_size) => app.resize_window(window_id, new_size),\n                _ => {}\n            },\n\n            Event::UserEvent(event) => match event {\n                UserWindowEvent::Poll(id) => app.poll_vdom(id),\n                UserWindowEvent::NewWindow => app.handle_new_window(),\n                UserWindowEvent::CloseWindow(id) => app.handle_close_requested(id),\n                UserWindowEvent::Shutdown => app.control_flow = tao::event_loop::ControlFlow::Exit,\n\n                #[cfg(any(target_os = \"windows\", target_os = \"linux\", target_os = \"macos\"))]\n                UserWindowEvent::GlobalHotKeyEvent(evnt) => app.handle_global_hotkey(evnt),\n\n                #[cfg(any(target_os = \"windows\", target_os = \"linux\", target_os = \"macos\"))]\n                UserWindowEvent::MudaMenuEvent(evnt) => app.handle_menu_event(evnt),\n\n                #[cfg(any(target_os = \"windows\", target_os = \"linux\", target_os = \"macos\"))]\n                UserWindowEvent::TrayMenuEvent(evnt) => app.handle_tray_menu_event(evnt),\n\n                #[cfg(any(target_os = \"windows\", target_os = \"linux\", target_os = \"macos\"))]\n                UserWindowEvent::TrayIconEvent(evnt) => app.handle_tray_icon_event(evnt),\n\n                #[cfg(all(feature = \"devtools\", debug_assertions))]\n                UserWindowEvent::HotReloadEvent(msg) => app.handle_hot_reload_msg(msg),\n\n                // Windows-only drag-n-drop fix events. We need to call the interpreter drag-n-drop code.\n                UserWindowEvent::WindowsDragDrop(id) => {\n                    if let Some(webview) = app.webviews.get(&id) {\n                        webview.dom.in_scope(ScopeId::ROOT, || {\n                            eval(\"window.interpreter.handleWindowsDragDrop();\");\n                        });\n                    }\n                }\n                UserWindowEvent::WindowsDragLeave(id) => {\n                    if let Some(webview) = app.webviews.get(&id) {\n                        webview.dom.in_scope(ScopeId::ROOT, || {\n                            eval(\"window.interpreter.handleWindowsDragLeave();\");\n                        });\n                    }\n                }\n                UserWindowEvent::WindowsDragOver(id, x_pos, y_pos) => {\n                    if let Some(webview) = app.webviews.get(&id) {\n                        webview.dom.in_scope(ScopeId::ROOT, || {\n                            let e = eval(\n                                r#\"\n                                    const xPos = await dioxus.recv();\n                                    const yPos = await dioxus.recv();\n                                    window.interpreter.handleWindowsDragOver(xPos, yPos)\n                                    \"#,\n                            );\n\n                            _ = e.send(x_pos);\n                            _ = e.send(y_pos);\n                        });\n                    }\n                }\n\n                UserWindowEvent::Ipc { id, msg } => match msg.method() {\n                    IpcMethod::Initialize => app.handle_initialize_msg(id),\n                    IpcMethod::UserEvent => {}\n                    IpcMethod::Query => app.handle_query_msg(msg, id),\n                    IpcMethod::BrowserOpen => app.handle_browser_open(msg),\n                    IpcMethod::Other(_) => {}\n                },\n            },\n            _ => {}\n        }\n\n        *control_flow = app.control_flow;\n    })\n}\n\n/// Launches the WebView and runs the event loop, with configuration and root props.\npub fn launch_virtual_dom(virtual_dom: VirtualDom, desktop_config: Config) -> ! {\n    #[cfg(feature = \"tokio_runtime\")]\n    {\n        if let std::result::Result::Ok(handle) = tokio::runtime::Handle::try_current() {\n            assert_ne!(\n                handle.runtime_flavor(),\n                tokio::runtime::RuntimeFlavor::CurrentThread,\n                \"The tokio current-thread runtime does not work with dioxus event handling\"\n            );\n            launch_virtual_dom_blocking(virtual_dom, desktop_config);\n        } else {\n            tokio::runtime::Builder::new_multi_thread()\n                .enable_all()\n                .build()\n                .unwrap()\n                .block_on(tokio::task::unconstrained(async move {\n                    launch_virtual_dom_blocking(virtual_dom, desktop_config)\n                }));\n\n            unreachable!(\"The desktop launch function will never exit\")\n        }\n    }\n\n    #[cfg(not(feature = \"tokio_runtime\"))]\n    {\n        launch_virtual_dom_blocking(virtual_dom, desktop_config);\n    }\n}\n\n/// Launches the WebView and runs the event loop, with configuration and root props.\npub fn launch(\n    root: fn() -> Element,\n    contexts: Vec<Box<dyn Fn() -> Box<dyn Any> + Send + Sync>>,\n    platform_config: Vec<Box<dyn Any>>,\n) -> ! {\n    let mut virtual_dom = VirtualDom::new(root);\n\n    for context in contexts {\n        virtual_dom.insert_any_root_context(context());\n    }\n\n    let platform_config = *platform_config\n        .into_iter()\n        .find_map(|cfg| cfg.downcast::<Config>().ok())\n        .unwrap_or_default();\n\n    launch_virtual_dom(virtual_dom, platform_config)\n}\n"
  },
  {
    "path": "packages/desktop/src/lib.rs",
    "content": "#![doc = include_str!(\"readme.md\")]\n#![doc(html_logo_url = \"https://avatars.githubusercontent.com/u/79236386\")]\n#![doc(html_favicon_url = \"https://avatars.githubusercontent.com/u/79236386\")]\n#![deny(missing_docs)]\n#![cfg_attr(docsrs, feature(doc_cfg))]\n\nmod android_sync_lock;\nmod app;\nmod assets;\nmod config;\nmod desktop_context;\nmod document;\nmod edits;\nmod element;\nmod event_handlers;\nmod events;\nmod file_upload;\nmod hooks;\nmod ipc;\nmod menubar;\nmod mobile;\nmod protocol;\nmod query;\nmod shortcut;\nmod waker;\nmod webview;\n\n// mobile shortcut is only supported on mobile platforms\n#[cfg(any(target_os = \"ios\", target_os = \"android\"))]\nmod mobile_shortcut;\n\n/// The main entrypoint for this crate\npub mod launch;\n\n// Reexport tao and wry, might want to re-export other important things\npub use tao;\npub use tao::dpi::{LogicalPosition, LogicalSize};\npub use tao::event::WindowEvent;\npub use tao::window::WindowBuilder;\npub use wry;\n// Reexport muda only if we are on desktop platforms that support menus\n#[cfg(not(any(target_os = \"ios\", target_os = \"android\")))]\npub use muda;\n\n// Tray icon\n#[cfg(any(target_os = \"windows\", target_os = \"linux\", target_os = \"macos\"))]\npub mod trayicon;\n\n// Public exports\npub use assets::AssetRequest;\npub use config::{Config, WindowCloseBehaviour};\npub use desktop_context::{\n    window, DesktopContext, DesktopService, PendingDesktopContext, WeakDesktopContext,\n};\npub use event_handlers::WryEventHandler;\npub use hooks::*;\npub use shortcut::{HotKeyState, ShortcutHandle, ShortcutRegistryError};\npub use wry::RequestAsyncResponder;\n"
  },
  {
    "path": "packages/desktop/src/menubar.rs",
    "content": "use tao::window::Window;\n\n#[cfg(not(any(target_os = \"ios\", target_os = \"android\")))]\npub type DioxusMenu = muda::Menu;\n#[cfg(any(target_os = \"ios\", target_os = \"android\"))]\npub type DioxusMenu = ();\n\n/// Initializes the menu bar for the window.\n#[allow(unused)]\npub fn init_menu_bar(menu: &DioxusMenu, window: &Window) {\n    #[cfg(not(any(target_os = \"ios\", target_os = \"android\")))]\n    {\n        desktop_platforms::init_menu_bar(menu, window);\n    }\n}\n\n/// Creates a standard menu bar depending on the users platform. It may be used as a starting point\n/// to further customize the menu bar and pass it to a [`WindowBuilder`](tao::window::WindowBuilder).\n/// > Note: The default menu bar enables macOS shortcuts like cut/copy/paste.\n/// > The menu bar differs per platform because of constraints introduced\n/// > by [`MenuItem`](muda::MenuItem).\n#[allow(unused)]\npub fn default_menu_bar() -> DioxusMenu {\n    #[cfg(not(any(target_os = \"ios\", target_os = \"android\")))]\n    {\n        desktop_platforms::default_menu_bar()\n    }\n}\n\n#[cfg(not(any(target_os = \"ios\", target_os = \"android\")))]\nmod desktop_platforms {\n    use super::*;\n    use muda::{Menu, MenuItem, PredefinedMenuItem, Submenu};\n\n    #[allow(unused)]\n    pub fn init_menu_bar(menu: &Menu, window: &Window) {\n        #[cfg(target_os = \"windows\")]\n        unsafe {\n            use tao::platform::windows::WindowExtWindows;\n            menu.init_for_hwnd(window.hwnd());\n        }\n\n        #[cfg(target_os = \"linux\")]\n        {\n            use tao::platform::unix::WindowExtUnix;\n            menu.init_for_gtk_window(window.gtk_window(), window.default_vbox())\n                .unwrap();\n        }\n\n        #[cfg(target_os = \"macos\")]\n        {\n            use tao::platform::macos::WindowExtMacOS;\n            menu.init_for_nsapp();\n        }\n    }\n\n    pub fn default_menu_bar() -> Menu {\n        let menu = Menu::new();\n        // since it is uncommon on windows to have an \"application menu\"\n        // we add a \"window\" menu to be more consistent across platforms with the standard menu\n        let window_menu = Submenu::new(\"Window\", true);\n        window_menu\n            .append_items(&[\n                &PredefinedMenuItem::fullscreen(None),\n                &PredefinedMenuItem::separator(),\n                &PredefinedMenuItem::hide(None),\n                &PredefinedMenuItem::hide_others(None),\n                &PredefinedMenuItem::show_all(None),\n                &PredefinedMenuItem::maximize(None),\n                &PredefinedMenuItem::minimize(None),\n                &PredefinedMenuItem::close_window(None),\n                &PredefinedMenuItem::separator(),\n                &PredefinedMenuItem::quit(None),\n            ])\n            .unwrap();\n\n        let edit_menu = Submenu::new(\"Edit\", true);\n        edit_menu\n            .append_items(&[\n                &PredefinedMenuItem::undo(None),\n                &PredefinedMenuItem::redo(None),\n                &PredefinedMenuItem::separator(),\n                &PredefinedMenuItem::cut(None),\n                &PredefinedMenuItem::copy(None),\n                &PredefinedMenuItem::paste(None),\n                &PredefinedMenuItem::separator(),\n                &PredefinedMenuItem::select_all(None),\n            ])\n            .unwrap();\n\n        menu.append_items(&[&window_menu, &edit_menu]).unwrap();\n\n        if cfg!(debug_assertions) {\n            let help_menu = Submenu::new(\"Help\", true);\n\n            help_menu\n                .append_items(&[&MenuItem::with_id(\n                    \"dioxus-toggle-dev-tools\",\n                    \"Toggle Developer Tools\",\n                    true,\n                    None,\n                )])\n                .unwrap();\n\n            // By default we float the window on top in dev mode, but let the user disable it\n            help_menu\n                .append_items(&[&MenuItem::with_id(\n                    \"dioxus-float-top\",\n                    \"Float on Top (dev mode only)\",\n                    true,\n                    None,\n                )])\n                .unwrap();\n\n            _ = menu.append_items(&[&help_menu]);\n\n            #[cfg(target_os = \"macos\")]\n            {\n                help_menu.set_as_help_menu_for_nsapp();\n            }\n        }\n\n        #[cfg(target_os = \"macos\")]\n        {\n            window_menu.set_as_windows_menu_for_nsapp();\n        }\n\n        menu\n    }\n}\n"
  },
  {
    "path": "packages/desktop/src/mobile.rs",
    "content": "/// Expose the `Java_dev_dioxus_main_WryActivity_create` function to the JNI layer.\n/// We hardcode these to have a single trampoline for host Java code to call into.\n///\n/// This saves us from having to plumb the top-level package name all the way down into\n/// this file. This is better for modularity (ie just call dioxus' main to run the app) as\n/// well as cache thrashing since this crate doesn't rely on external env vars.\n///\n/// The CLI is expecting to find `dev.dioxus.main` in the final library. If you find a need to\n/// change this, you'll need to change the CLI as well.\n#[cfg(target_os = \"android\")]\n#[no_mangle]\n#[inline(never)]\npub extern \"C\" fn start_app() {\n    use crate::Config;\n    use dioxus_core::{Element, VirtualDom};\n    use std::any::Any;\n\n    tao::android_binding!(dev_dioxus, main, WryActivity, wry::android_setup, root, tao);\n    wry::android_binding!(dev_dioxus, main, wry);\n\n    #[cfg(target_os = \"android\")]\n    fn root() {\n        fn stop_unwind<F: FnOnce() -> T, T>(f: F) -> T {\n            match std::panic::catch_unwind(std::panic::AssertUnwindSafe(f)) {\n                Ok(t) => t,\n                Err(err) => {\n                    eprintln!(\"attempt to unwind out of `rust` with err: {:?}\", err);\n                    std::process::abort()\n                }\n            }\n        }\n\n        stop_unwind(|| unsafe {\n            let mut main_fn_ptr = libc::dlsym(libc::RTLD_DEFAULT, b\"main\\0\".as_ptr() as _);\n\n            if main_fn_ptr.is_null() {\n                main_fn_ptr = libc::dlsym(libc::RTLD_DEFAULT, b\"_main\\0\".as_ptr() as _);\n            }\n\n            if main_fn_ptr.is_null() {\n                panic!(\"Failed to find main symbol\");\n            }\n\n            // Set the env vars that rust code might expect, passed off to us by the android app\n            // Doing this before main emulates the behavior of a regular executable\n            if cfg!(target_os = \"android\") && cfg!(debug_assertions) {\n                // Load the env file from the session cache if we're in debug mode and on android\n                //\n                // This is a slightly hacky way of being able to use std::env::var code in android apps without\n                // going through their custom java-based system.\n                let env_file = dioxus_cli_config::android_session_cache_dir().join(\".env\");\n                if let Ok(env_file) = std::fs::read_to_string(&env_file) {\n                    for line in env_file.lines() {\n                        if let Some((key, value)) = line.trim().split_once('=') {\n                            std::env::set_var(key, value);\n                        }\n                    }\n                }\n            }\n\n            let main_fn: extern \"C\" fn() = std::mem::transmute(main_fn_ptr);\n            main_fn();\n        });\n    }\n}\n"
  },
  {
    "path": "packages/desktop/src/mobile_shortcut.rs",
    "content": "#![allow(unused)]\n\nuse super::*;\nuse std::str::FromStr;\nuse tao::event_loop::EventLoopWindowTarget;\n\nuse dioxus_html::input_data::keyboard_types::Modifiers;\n\n#[derive(Clone, Debug)]\npub struct Accelerator;\n\n#[derive(Clone, Copy)]\npub struct HotKey;\n\nimpl HotKey {\n    pub fn new(mods: Option<Modifiers>, key: Code) -> Self {\n        Self\n    }\n\n    pub fn id(&self) -> u32 {\n        0\n    }\n}\n\nimpl FromStr for HotKey {\n    type Err = ();\n\n    fn from_str(s: &str) -> Result<Self, Self::Err> {\n        Ok(HotKey)\n    }\n}\n\npub struct GlobalHotKeyManager();\n\nimpl GlobalHotKeyManager {\n    pub fn new() -> Result<Self, HotkeyError> {\n        Ok(Self())\n    }\n\n    pub fn register(&self, accelerator: HotKey) -> Result<HotKey, HotkeyError> {\n        Ok(HotKey)\n    }\n\n    pub fn unregister(&self, id: HotKey) -> Result<(), HotkeyError> {\n        Ok(())\n    }\n\n    pub fn unregister_all(&self, _: &[HotKey]) -> Result<(), HotkeyError> {\n        Ok(())\n    }\n}\n\nuse std::{error, fmt};\n\n/// An error whose cause the `ShortcutManager` to fail.\n#[non_exhaustive]\n#[derive(Debug)]\npub enum HotkeyError {\n    AcceleratorAlreadyRegistered(Accelerator),\n    AcceleratorNotRegistered(Accelerator),\n    HotKeyParseError(String),\n}\n\nimpl error::Error for HotkeyError {}\nimpl fmt::Display for HotkeyError {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {\n        match self {\n            HotkeyError::AcceleratorAlreadyRegistered(e) => {\n                f.pad(&format!(\"hotkey already registered: {:?}\", e))\n            }\n            HotkeyError::AcceleratorNotRegistered(e) => {\n                f.pad(&format!(\"hotkey not registered: {:?}\", e))\n            }\n            HotkeyError::HotKeyParseError(e) => e.fmt(f),\n        }\n    }\n}\n\npub struct GlobalHotKeyEvent {\n    pub id: u32,\n    pub state: HotKeyState,\n}\n\n/// Describes the state of the hotkey.\n#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]\npub enum HotKeyState {\n    /// The hotkey is pressed.\n    Pressed,\n    /// The hotkey is released.\n    Released,\n}\n\npub(crate) type Code = dioxus_html::input_data::keyboard_types::Code;\n"
  },
  {
    "path": "packages/desktop/src/protocol.rs",
    "content": "use std::path::PathBuf;\n\nuse crate::{assets::*, webview::WebviewEdits};\nuse crate::{document::NATIVE_EVAL_JS, file_upload::FileDialogRequest};\nuse base64::prelude::BASE64_STANDARD;\nuse dioxus_core::AnyhowContext;\nuse dioxus_html::{SerializedFileData, SerializedFormObject};\nuse dioxus_interpreter_js::unified_bindings::SLEDGEHAMMER_JS;\nuse dioxus_interpreter_js::NATIVE_JS;\nuse wry::{\n    http::{status::StatusCode, Request, Response},\n    RequestAsyncResponder,\n};\n\n#[cfg(target_os = \"android\")]\nconst BASE_URI: &str = \"https://dioxus.index.html/\";\n\n#[cfg(target_os = \"windows\")]\nconst BASE_URI: &str = \"http://dioxus.index.html/\";\n\n#[cfg(not(any(target_os = \"android\", target_os = \"windows\")))]\nconst BASE_URI: &str = \"dioxus://index.html/\";\n\n#[cfg(debug_assertions)]\nstatic DEFAULT_INDEX: &str = include_str!(\"./assets/dev.index.html\");\n\n#[cfg(not(debug_assertions))]\nstatic DEFAULT_INDEX: &str = include_str!(\"./assets/prod.index.html\");\n\n#[allow(clippy::too_many_arguments)] // just for now, should fix this eventually\n/// Handle a request from the webview\n///\n/// - Tries to stream edits if they're requested.\n/// - If that doesn't match, tries a user provided asset handler\n/// - If that doesn't match, tries to serve a file from the filesystem\npub(super) fn desktop_handler(\n    request: Request<Vec<u8>>,\n    asset_handlers: AssetHandlerRegistry,\n    responder: RequestAsyncResponder,\n    edit_state: &WebviewEdits,\n    custom_head: Option<String>,\n    custom_index: Option<String>,\n    root_name: &str,\n    headless: bool,\n) {\n    // Try to serve the index file first\n    if let Some(index_bytes) = index_request(\n        &request,\n        custom_head,\n        custom_index,\n        root_name,\n        headless,\n        edit_state,\n    ) {\n        return responder.respond(index_bytes);\n    }\n\n    // If the request is asking for edits (ie binary protocol streaming), do that\n    let trimmed_uri = request.uri().path().trim_matches('/');\n\n    // If the request is asking for an event response, do that\n    if trimmed_uri == \"__events\" {\n        return edit_state.handle_event(request, responder);\n    }\n\n    // If the request is asking for a file dialog, handle that, returning the list of files selected\n    if trimmed_uri == \"__file_dialog\" {\n        if let Err(err) = file_dialog_responder_sync(request, responder) {\n            tracing::error!(\"Failed to handle file dialog request: {err:?}\");\n        }\n\n        return;\n    }\n\n    // todo: we want to move the custom assets onto a different protocol or something\n    if let Some(name) = request.uri().path().split('/').nth(1) {\n        if asset_handlers.has_handler(name) {\n            let _name = name.to_string();\n            return asset_handlers.handle_request(&_name, request, responder);\n        }\n    }\n\n    match dioxus_asset_resolver::native::serve_asset(request.uri().path()) {\n        Ok(res) => responder.respond(res),\n        Err(_e) => responder.respond(\n            Response::builder()\n                .status(StatusCode::INTERNAL_SERVER_ERROR)\n                .body(String::from(\"Failed to serve asset\").into_bytes())\n                .unwrap(),\n        ),\n    }\n}\n\n/// Build the index.html file we use for bootstrapping a new app\n///\n/// We use wry/webview by building a special index.html that forms a bridge between the webview and your rust code\n///\n/// This is similar to tauri, except we give more power to your rust code and less power to your frontend code.\n/// This lets us skip a build/bundle step - your code just works - but limits how your Rust code can actually\n/// mess with UI elements. We make this decision since other renderers like LiveView are very separate and can\n/// never properly bridge the gap. Eventually of course, the idea is to build a custom CSS/HTML renderer where you\n/// *do* have native control over elements, but that still won't work with liveview.\nfn index_request(\n    request: &Request<Vec<u8>>,\n    custom_head: Option<String>,\n    custom_index: Option<String>,\n    root_name: &str,\n    headless: bool,\n    edit_state: &WebviewEdits,\n) -> Option<Response<Vec<u8>>> {\n    // If the request is for the root, we'll serve the index.html file.\n    if request.uri().path() != \"/\" {\n        return None;\n    }\n\n    // Load a custom index file if provided\n    let mut index = custom_index.unwrap_or_else(|| DEFAULT_INDEX.to_string());\n\n    // Insert a custom head if provided\n    // We look just for the closing head tag. If a user provided a custom index with weird syntax, this might fail\n    if let Some(head) = custom_head {\n        index.insert_str(index.find(\"</head>\").expect(\"Head element to exist\"), &head);\n    }\n\n    // Inject our module loader by looking for a body tag\n    // A failure mode here, obviously, is if the user provided a custom index without a body tag\n    // Might want to document this\n    index.insert_str(\n        index.find(\"</body>\").expect(\"Body element to exist\"),\n        &module_loader(root_name, headless, edit_state),\n    );\n\n    Response::builder()\n        .header(\"Content-Type\", \"text/html\")\n        .header(\"Access-Control-Allow-Origin\", \"*\")\n        .body(index.into())\n        .ok()\n}\n\n/// Construct the inline script that boots up the page and bridges the webview with rust code.\n///\n/// The arguments here:\n/// - root_name: the root element (by Id) that we stream edits into\n/// - headless: is this page being loaded but invisible? Important because not all windows are visible and the\n///   interpreter can't connect until the window is ready.\n/// - port: the port that the websocket server is listening on for edits\n/// - webview_id: the id of the webview that we're loading this into. This is used to differentiate between\n///   multiple webviews in the same application, so that we can send edits to the correct one.\nfn module_loader(root_id: &str, headless: bool, edit_state: &WebviewEdits) -> String {\n    let edits_path = edit_state.wry_queue.edits_path();\n    let expected_key = edit_state.wry_queue.required_server_key();\n\n    format!(\n        r#\"\n<script type=\"module\">\n    // Bring the sledgehammer code\n    {SLEDGEHAMMER_JS}\n\n    // And then extend it with our native bindings\n    {NATIVE_JS}\n\n    // The native interpreter extends the sledgehammer interpreter with a few extra methods that we use for IPC\n    window.interpreter = new NativeInterpreter(\"{BASE_URI}\", {headless});\n\n    // Wait for the page to load before sending the initialize message\n    window.onload = function() {{\n        let root_element = window.document.getElementById(\"{root_id}\");\n        if (root_element != null) {{\n            window.interpreter.initialize(root_element);\n            window.interpreter.sendIpcMessage(\"initialize\");\n        }}\n        window.interpreter.waitForRequest(\"{edits_path}\", \"{expected_key}\");\n    }}\n</script>\n<script type=\"module\">\n    // Include the code for eval\n    {NATIVE_EVAL_JS}\n</script>\n\"#\n    )\n}\n\nfn file_dialog_responder_sync(\n    request: wry::http::Request<Vec<u8>>,\n    responder: wry::RequestAsyncResponder,\n) -> dioxus_core::Result<()> {\n    // Handle the file dialog request\n    // We can't use the body, just the headers\n    let header = request\n        .headers()\n        .get(\"x-dioxus-data\")\n        .context(\"Failed to get x-dioxus-data header\")?;\n\n    let data_from_header = base64::Engine::decode(&BASE64_STANDARD, header.as_bytes())\n        .context(\"Failed to decode x-dioxus-data header from base64\")?;\n\n    let file_dialog: FileDialogRequest = serde_json::from_slice(&data_from_header)\n        .context(\"Failed to parse x-dioxus-data header as JSON\")?;\n\n    #[cfg(feature = \"tokio_runtime\")]\n    tokio::spawn(async move {\n        let file_list = file_dialog.get_file_event_async().await;\n        _ = respond_to_file_dialog(file_dialog, file_list, responder);\n    });\n\n    #[cfg(not(feature = \"tokio_runtime\"))]\n    {\n        let file_list = file_dialog.get_file_event_sync();\n        respond_to_file_dialog(file_dialog, file_list, responder)?;\n    }\n\n    Ok(())\n}\n\nfn respond_to_file_dialog(\n    mut file_dialog: FileDialogRequest,\n    file_list: Vec<PathBuf>,\n    responder: wry::RequestAsyncResponder,\n) -> dioxus_core::Result<()> {\n    // Get the position of the entry we're updating, so we can insert new entries in the same place\n    // If we can't find it, just append to the end. This is usually due to the input not being in a form element.\n    let position_of_entry = file_dialog\n        .values\n        .iter()\n        .position(|x| x.key == file_dialog.target_name)\n        .unwrap_or(file_dialog.values.len());\n\n    // Remove any existing entries\n    file_dialog\n        .values\n        .retain(|x| x.key != file_dialog.target_name);\n\n    // And then insert the new ones\n    for path in file_list {\n        let file = std::fs::metadata(&path).context(\"Failed to get file metadata\")?;\n        file_dialog.values.insert(\n            position_of_entry,\n            SerializedFormObject {\n                key: file_dialog.target_name.clone(),\n                text: None,\n                file: Some(SerializedFileData {\n                    size: file.len(),\n                    last_modified: file\n                        .modified()\n                        .context(\"Failed to get file modified time\")?\n                        .duration_since(std::time::UNIX_EPOCH)\n                        .map(|d| d.as_millis())\n                        .unwrap_or_default() as _,\n                    content_type: Some(\n                        dioxus_asset_resolver::native::get_mime_from_ext(\n                            path.extension().and_then(|s| s.to_str()),\n                        )\n                        .to_string(),\n                    ),\n                    contents: Default::default(),\n                    path,\n                }),\n            },\n        );\n    }\n\n    // And then respond with the updated file dialog\n    let response_data = serde_json::to_vec(&file_dialog)\n        .context(\"Failed to serialize FileDialogRequest to JSON\")?;\n\n    responder.respond(\n        Response::builder()\n            .status(StatusCode::OK)\n            .header(\"Content-Type\", \"application/json\")\n            .body(response_data)\n            .context(\"Failed to build response\")?,\n    );\n\n    Ok(())\n}\n"
  },
  {
    "path": "packages/desktop/src/query.rs",
    "content": "use crate::{DesktopContext, WeakDesktopContext};\nuse futures_util::{FutureExt, StreamExt};\nuse generational_box::Owner;\nuse serde::{de::DeserializeOwned, Deserialize};\nuse serde_json::Value;\nuse slab::Slab;\nuse std::{cell::RefCell, rc::Rc};\nuse thiserror::Error;\n\n/// Tracks what query ids are currently active\npub(crate) struct SharedSlab<T = ()> {\n    pub slab: Rc<RefCell<Slab<T>>>,\n}\n\nimpl<T> Clone for SharedSlab<T> {\n    fn clone(&self) -> Self {\n        Self {\n            slab: self.slab.clone(),\n        }\n    }\n}\n\nimpl<T> Default for SharedSlab<T> {\n    fn default() -> Self {\n        SharedSlab {\n            slab: Rc::new(RefCell::new(Slab::new())),\n        }\n    }\n}\n\npub(crate) struct QueryEntry {\n    channel_sender: futures_channel::mpsc::UnboundedSender<Value>,\n    return_sender: Option<futures_channel::oneshot::Sender<Result<Value, String>>>,\n    pub owner: Option<Owner>,\n}\n\n/// Handles sending and receiving arbitrary queries from the webview. Queries can be resolved non-sequentially, so we use ids to track them.\n#[derive(Clone, Default)]\npub(crate) struct QueryEngine {\n    pub active_requests: SharedSlab<QueryEntry>,\n}\n\nimpl QueryEngine {\n    /// Creates a new query and returns a handle to it. The query will be resolved when the webview returns a result with the same id.\n    pub fn new_query<V: DeserializeOwned>(\n        &self,\n        script: &str,\n        context: DesktopContext,\n    ) -> Query<V> {\n        let (tx, rx) = futures_channel::mpsc::unbounded();\n        let (return_tx, return_rx) = futures_channel::oneshot::channel();\n        let request_id = self.active_requests.slab.borrow_mut().insert(QueryEntry {\n            channel_sender: tx,\n            return_sender: Some(return_tx),\n            owner: None,\n        });\n\n        // start the query\n        // We embed the return of the eval in a function so we can send it back to the main thread\n        if let Err(err) = context.webview.evaluate_script(&format!(\n            r#\"(function(){{\n                let dioxus = window.createQuery({request_id});\n                let post_error = function(err) {{\n                    let returned_value = {{\n                        \"method\": \"query\",\n                        \"params\": {{\n                            \"id\": {request_id},\n                            \"data\": {{\n                                \"data\": err,\n                                \"method\": \"return_error\"\n                            }}\n                        }}\n                    }};\n                    window.ipc.postMessage(\n                        JSON.stringify(returned_value)\n                    );\n                }};\n                try {{\n                    const AsyncFunction = async function () {{}}.constructor;\n                    let promise = (new AsyncFunction(\"dioxus\", {script:?}))(dioxus);\n                    promise\n                        .then((result)=>{{\n                            dioxus.close();\n                            let returned_value = {{\n                                \"method\": \"query\",\n                                \"params\": {{\n                                    \"id\": {request_id},\n                                    \"data\": {{\n                                        \"data\": result,\n                                        \"method\": \"return\"\n                                    }}\n                                }}\n                            }};\n                            window.ipc.postMessage(\n                                JSON.stringify(returned_value)\n                            );\n                        }})\n                        .catch(err => post_error(`Error running JS: ${{err}}`));\n                }} catch (error) {{\n                    dioxus.close();\n                    post_error(`Invalid JS: ${{error}}`);\n                }}\n            }})();\"#\n        )) {\n            tracing::warn!(\"Query error: {err}\");\n        }\n\n        Query {\n            id: request_id,\n            receiver: rx,\n            return_receiver: Some(return_rx),\n            desktop: Rc::downgrade(&context),\n            phantom: std::marker::PhantomData,\n        }\n    }\n\n    /// Send a query channel message to the correct query\n    pub fn send(&self, data: QueryResult) {\n        let QueryResult { id, data } = data;\n        let mut slab = self.active_requests.slab.borrow_mut();\n        if let Some(entry) = slab.get_mut(id) {\n            match data {\n                QueryResultData::Return { data } => {\n                    if let Some(sender) = entry.return_sender.take() {\n                        let _ = sender.send(Ok(data.unwrap_or_default()));\n                    }\n                }\n                QueryResultData::ReturnError { data } => {\n                    if let Some(sender) = entry.return_sender.take() {\n                        let _ = sender.send(Err(data.to_string()));\n                    }\n                }\n                QueryResultData::Drop => {\n                    slab.remove(id);\n                }\n                QueryResultData::Send { data } => {\n                    let _ = entry.channel_sender.unbounded_send(data);\n                }\n            }\n        }\n    }\n}\n\npub(crate) struct Query<V: DeserializeOwned> {\n    desktop: WeakDesktopContext,\n    receiver: futures_channel::mpsc::UnboundedReceiver<Value>,\n    return_receiver: Option<futures_channel::oneshot::Receiver<Result<Value, String>>>,\n    pub id: usize,\n    phantom: std::marker::PhantomData<V>,\n}\n\nimpl<V: DeserializeOwned> Query<V> {\n    /// Resolve the query\n    pub async fn resolve(mut self) -> Result<V, QueryError> {\n        let result = self.result().await?;\n        V::deserialize(result).map_err(QueryError::Deserialize)\n    }\n\n    /// Send a message to the query\n    pub fn send<S: ToString>(&self, message: S) -> Result<(), QueryError> {\n        let queue_id = self.id;\n\n        let data = message.to_string();\n        let script = format!(r#\"window.getQuery({queue_id}).rustSend({data});\"#);\n\n        let desktop = self.desktop.upgrade().ok_or(QueryError::Finished)?;\n        desktop\n            .webview\n            .evaluate_script(&script)\n            .map_err(|e| QueryError::Send(e.to_string()))?;\n\n        Ok(())\n    }\n\n    /// Poll the query for a message\n    pub fn poll_recv(\n        &mut self,\n        cx: &mut std::task::Context<'_>,\n    ) -> std::task::Poll<Result<Value, QueryError>> {\n        self.receiver\n            .poll_next_unpin(cx)\n            .map(|result| result.ok_or(QueryError::Recv(String::from(\"Receive channel closed\"))))\n    }\n\n    /// Receive the result of the query\n    pub async fn result(&mut self) -> Result<Value, QueryError> {\n        match self.return_receiver.take() {\n            Some(receiver) => match receiver.await {\n                Ok(Ok(data)) => Ok(data),\n                Ok(Err(err)) => Err(QueryError::Recv(err)),\n                Err(err) => Err(QueryError::Recv(err.to_string())),\n            },\n            None => Err(QueryError::Finished),\n        }\n    }\n\n    /// Poll the query for a result\n    pub fn poll_result(\n        &mut self,\n        cx: &mut std::task::Context<'_>,\n    ) -> std::task::Poll<Result<Value, QueryError>> {\n        match self.return_receiver.as_mut() {\n            Some(receiver) => receiver.poll_unpin(cx).map(|result| match result {\n                Ok(Ok(data)) => Ok(data),\n                Ok(Err(err)) => Err(QueryError::Recv(err)),\n                Err(err) => Err(QueryError::Recv(err.to_string())),\n            }),\n            None => std::task::Poll::Ready(Err(QueryError::Finished)),\n        }\n    }\n}\n\n#[derive(Error, Debug)]\n#[non_exhaustive]\npub enum QueryError {\n    #[error(\"Error receiving query result: {0}\")]\n    Recv(String),\n    #[error(\"Error sending message to query: {0}\")]\n    Send(String),\n    #[error(\"Error deserializing query result: {0}\")]\n    Deserialize(serde_json::Error),\n    #[error(\"Query has already been resolved\")]\n    Finished,\n}\n\n#[derive(Clone, Debug, Deserialize)]\npub(crate) struct QueryResult {\n    id: usize,\n    data: QueryResultData,\n}\n\n#[derive(Clone, Debug, Deserialize)]\n#[serde(tag = \"method\")]\nenum QueryResultData {\n    #[serde(rename = \"return\")]\n    Return { data: Option<Value> },\n    #[serde(rename = \"return_error\")]\n    ReturnError { data: Value },\n    #[serde(rename = \"send\")]\n    Send { data: Value },\n    #[serde(rename = \"drop\")]\n    Drop,\n}\n"
  },
  {
    "path": "packages/desktop/src/readme.md",
    "content": "Dioxus Desktop Renderer\r\n\r\nRender the Dioxus VirtualDom using the platform's native WebView implementation.\r\n\r\n# Desktop\r\n\r\nOne of Dioxus' flagship features is the ability to quickly build a native desktop app that looks and feels the same across platforms. Apps built with Dioxus are typically <5mb in size and use existing system resources, so they won't hog extreme amounts of RAM or memory.\r\n\r\nDioxus Desktop is built off Tauri. Right now there aren't any Dioxus abstractions over the menubar, handling, etc, so you'll want to leverage Tauri - mostly [Wry](http://github.com/tauri-apps/wry/) and [Tao](http://github.com/tauri-apps/tao) directly. An upcoming release of Dioxus-Desktop will include components and hooks for notifications, global shortcuts, menubar, etc.\r\n\r\n## Getting Set up\r\n\r\nGetting Set up with Dioxus-Desktop is quite easy. Make sure you have Rust and Cargo installed, and then create a new project:\r\n\r\n```shell\r\n$ cargo new --bin demo\r\n$ cd app\r\n```\r\n\r\nAdd Dioxus and the `desktop` renderer feature:\r\n\r\n```shell\r\n$ cargo add dioxus\r\n$ cargo add dioxus-desktop\r\n```\r\n\r\nEdit your `main.rs`:\r\n\r\n```rust, ignore\r\n// main.rs\r\nuse dioxus::prelude::*;\r\n\r\nfn main() {\r\n    dioxus_desktop::launch(app);\r\n}\r\n\r\nfn app() -> Element {\r\n    rsx! {\r\n        div {\r\n            \"hello world!\"\r\n        }\r\n    }\r\n}\r\n```\r\n\r\nTo configure the webview, menubar, and other important desktop-specific features, checkout out some of the launch configuration in the [API reference](https://docs.rs/dioxus-desktop/).\r\n\r\n## Future Steps\r\n\r\nMake sure to read the [Dioxus Guide](https://dioxuslabs.com/learn/0.7/) if you already haven't!\r\n"
  },
  {
    "path": "packages/desktop/src/shortcut.rs",
    "content": "#[cfg(any(\n    target_os = \"windows\",\n    target_os = \"macos\",\n    target_os = \"linux\",\n    target_os = \"dragonfly\",\n    target_os = \"freebsd\",\n    target_os = \"netbsd\",\n    target_os = \"openbsd\"\n))]\npub use global_hotkey::{\n    hotkey::{Code, HotKey},\n    Error as HotkeyError, GlobalHotKeyEvent, GlobalHotKeyManager, HotKeyState,\n};\n\n#[cfg(any(target_os = \"ios\", target_os = \"android\"))]\npub use crate::mobile_shortcut::*;\n\nuse crate::window;\nuse dioxus_html::input_data::keyboard_types::Modifiers;\nuse slab::Slab;\nuse std::{cell::RefCell, collections::HashMap, rc::Rc, str::FromStr};\nuse tao::keyboard::ModifiersState;\n\n/// An global id for a shortcut.\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]\npub struct ShortcutHandle {\n    id: u32,\n    number: usize,\n}\n\nimpl ShortcutHandle {\n    /// Remove the shortcut.\n    pub fn remove(&self) {\n        window().remove_shortcut(*self);\n    }\n}\n\n/// An error that can occur when registering a shortcut.\n#[non_exhaustive]\n#[derive(Debug, Clone)]\npub enum ShortcutRegistryError {\n    /// The shortcut is invalid.\n    InvalidShortcut(String),\n    /// An unknown error occurred.\n    Other(Rc<dyn std::error::Error>),\n}\n\npub(crate) struct ShortcutRegistry {\n    manager: GlobalHotKeyManager,\n    shortcuts: RefCell<HashMap<u32, ShortcutInner>>,\n}\n\nstruct ShortcutInner {\n    #[allow(unused)]\n    shortcut: HotKey,\n    callbacks: Slab<Box<dyn FnMut(HotKeyState)>>,\n}\n\nimpl ShortcutRegistry {\n    pub fn new() -> Self {\n        Self {\n            manager: GlobalHotKeyManager::new().unwrap(),\n            shortcuts: RefCell::new(HashMap::new()),\n        }\n    }\n\n    #[cfg(any(target_os = \"windows\", target_os = \"linux\", target_os = \"macos\"))]\n    pub(crate) fn call_handlers(&self, id: GlobalHotKeyEvent) {\n        if let Some(ShortcutInner { callbacks, .. }) = self.shortcuts.borrow_mut().get_mut(&id.id) {\n            for (_, callback) in callbacks.iter_mut() {\n                (callback)(id.state);\n            }\n        }\n    }\n\n    pub(crate) fn add_shortcut(\n        &self,\n        hotkey: HotKey,\n        callback: Box<dyn FnMut(HotKeyState)>,\n    ) -> Result<ShortcutHandle, ShortcutRegistryError> {\n        let accelerator_id = hotkey.clone().id();\n\n        let mut shortcuts = self.shortcuts.borrow_mut();\n\n        if let Some(callbacks) = shortcuts.get_mut(&accelerator_id) {\n            return Ok(ShortcutHandle {\n                id: accelerator_id,\n                number: callbacks.callbacks.insert(callback),\n            });\n        };\n\n        self.manager.register(hotkey).map_err(|e| match e {\n            HotkeyError::HotKeyParseError(shortcut) => {\n                ShortcutRegistryError::InvalidShortcut(shortcut)\n            }\n            err => ShortcutRegistryError::Other(Rc::new(err)),\n        })?;\n\n        let mut shortcut = ShortcutInner {\n            shortcut: hotkey,\n            callbacks: Slab::new(),\n        };\n\n        let id = shortcut.callbacks.insert(callback);\n\n        shortcuts.insert(accelerator_id, shortcut);\n\n        Ok(ShortcutHandle {\n            id: accelerator_id,\n            number: id,\n        })\n    }\n\n    pub(crate) fn remove_shortcut(&self, id: ShortcutHandle) {\n        let mut shortcuts = self.shortcuts.borrow_mut();\n        if let Some(callbacks) = shortcuts.get_mut(&id.id) {\n            let _ = callbacks.callbacks.remove(id.number);\n            if callbacks.callbacks.is_empty() {\n                if let Some(_shortcut) = shortcuts.remove(&id.id) {\n                    let _ = self.manager.unregister(_shortcut.shortcut);\n                }\n            }\n        }\n    }\n\n    pub(crate) fn remove_all(&self) {\n        let mut shortcuts = self.shortcuts.borrow_mut();\n        let hotkeys: Vec<_> = shortcuts.drain().map(|(_, v)| v.shortcut).collect();\n        let _ = self.manager.unregister_all(&hotkeys);\n    }\n}\n\npub trait IntoAccelerator {\n    fn accelerator(&self) -> HotKey;\n}\n\nimpl IntoAccelerator for (dioxus_html::KeyCode, ModifiersState) {\n    fn accelerator(&self) -> HotKey {\n        HotKey::new(Some(self.1.into_modifiers_state()), self.0.into_key_code())\n    }\n}\n\nimpl IntoAccelerator for (ModifiersState, dioxus_html::KeyCode) {\n    fn accelerator(&self) -> HotKey {\n        HotKey::new(Some(self.0.into_modifiers_state()), self.1.into_key_code())\n    }\n}\n\nimpl IntoAccelerator for dioxus_html::KeyCode {\n    fn accelerator(&self) -> HotKey {\n        HotKey::new(None, self.into_key_code())\n    }\n}\n\nimpl IntoAccelerator for &str {\n    fn accelerator(&self) -> HotKey {\n        HotKey::from_str(self).unwrap()\n    }\n}\n\npub trait IntoModifiersState {\n    fn into_modifiers_state(self) -> Modifiers;\n}\n\nimpl IntoModifiersState for ModifiersState {\n    fn into_modifiers_state(self) -> Modifiers {\n        let mut modifiers = Modifiers::default();\n        if self.shift_key() {\n            modifiers |= Modifiers::SHIFT;\n        }\n        if self.control_key() {\n            modifiers |= Modifiers::CONTROL;\n        }\n        if self.alt_key() {\n            modifiers |= Modifiers::ALT;\n        }\n        if self.super_key() {\n            modifiers |= Modifiers::META;\n        }\n\n        modifiers\n    }\n}\n\nimpl IntoModifiersState for Modifiers {\n    fn into_modifiers_state(self) -> Modifiers {\n        self\n    }\n}\n\npub trait IntoKeyCode {\n    fn into_key_code(self) -> Code;\n}\n\nimpl IntoKeyCode for Code {\n    fn into_key_code(self) -> Code {\n        self\n    }\n}\n\nimpl IntoKeyCode for dioxus_html::KeyCode {\n    fn into_key_code(self) -> Code {\n        match self {\n            dioxus_html::KeyCode::Backspace => Code::Backspace,\n            dioxus_html::KeyCode::Tab => Code::Tab,\n            dioxus_html::KeyCode::Clear => Code::NumpadClear,\n            dioxus_html::KeyCode::Enter => Code::Enter,\n            dioxus_html::KeyCode::Shift => Code::ShiftLeft,\n            dioxus_html::KeyCode::Ctrl => Code::ControlLeft,\n            dioxus_html::KeyCode::Alt => Code::AltLeft,\n            dioxus_html::KeyCode::Pause => Code::Pause,\n            dioxus_html::KeyCode::CapsLock => Code::CapsLock,\n            dioxus_html::KeyCode::Escape => Code::Escape,\n            dioxus_html::KeyCode::Space => Code::Space,\n            dioxus_html::KeyCode::PageUp => Code::PageUp,\n            dioxus_html::KeyCode::PageDown => Code::PageDown,\n            dioxus_html::KeyCode::End => Code::End,\n            dioxus_html::KeyCode::Home => Code::Home,\n            dioxus_html::KeyCode::LeftArrow => Code::ArrowLeft,\n            dioxus_html::KeyCode::UpArrow => Code::ArrowUp,\n            dioxus_html::KeyCode::RightArrow => Code::ArrowRight,\n            dioxus_html::KeyCode::DownArrow => Code::ArrowDown,\n            dioxus_html::KeyCode::Insert => Code::Insert,\n            dioxus_html::KeyCode::Delete => Code::Delete,\n            dioxus_html::KeyCode::Num0 => Code::Numpad0,\n            dioxus_html::KeyCode::Num1 => Code::Numpad1,\n            dioxus_html::KeyCode::Num2 => Code::Numpad2,\n            dioxus_html::KeyCode::Num3 => Code::Numpad3,\n            dioxus_html::KeyCode::Num4 => Code::Numpad4,\n            dioxus_html::KeyCode::Num5 => Code::Numpad5,\n            dioxus_html::KeyCode::Num6 => Code::Numpad6,\n            dioxus_html::KeyCode::Num7 => Code::Numpad7,\n            dioxus_html::KeyCode::Num8 => Code::Numpad8,\n            dioxus_html::KeyCode::Num9 => Code::Numpad9,\n            dioxus_html::KeyCode::A => Code::KeyA,\n            dioxus_html::KeyCode::B => Code::KeyB,\n            dioxus_html::KeyCode::C => Code::KeyC,\n            dioxus_html::KeyCode::D => Code::KeyD,\n            dioxus_html::KeyCode::E => Code::KeyE,\n            dioxus_html::KeyCode::F => Code::KeyF,\n            dioxus_html::KeyCode::G => Code::KeyG,\n            dioxus_html::KeyCode::H => Code::KeyH,\n            dioxus_html::KeyCode::I => Code::KeyI,\n            dioxus_html::KeyCode::J => Code::KeyJ,\n            dioxus_html::KeyCode::K => Code::KeyK,\n            dioxus_html::KeyCode::L => Code::KeyL,\n            dioxus_html::KeyCode::M => Code::KeyM,\n            dioxus_html::KeyCode::N => Code::KeyN,\n            dioxus_html::KeyCode::O => Code::KeyO,\n            dioxus_html::KeyCode::P => Code::KeyP,\n            dioxus_html::KeyCode::Q => Code::KeyQ,\n            dioxus_html::KeyCode::R => Code::KeyR,\n            dioxus_html::KeyCode::S => Code::KeyS,\n            dioxus_html::KeyCode::T => Code::KeyT,\n            dioxus_html::KeyCode::U => Code::KeyU,\n            dioxus_html::KeyCode::V => Code::KeyV,\n            dioxus_html::KeyCode::W => Code::KeyW,\n            dioxus_html::KeyCode::X => Code::KeyX,\n            dioxus_html::KeyCode::Y => Code::KeyY,\n            dioxus_html::KeyCode::Z => Code::KeyZ,\n            dioxus_html::KeyCode::Numpad0 => Code::Numpad0,\n            dioxus_html::KeyCode::Numpad1 => Code::Numpad1,\n            dioxus_html::KeyCode::Numpad2 => Code::Numpad2,\n            dioxus_html::KeyCode::Numpad3 => Code::Numpad3,\n            dioxus_html::KeyCode::Numpad4 => Code::Numpad4,\n            dioxus_html::KeyCode::Numpad5 => Code::Numpad5,\n            dioxus_html::KeyCode::Numpad6 => Code::Numpad6,\n            dioxus_html::KeyCode::Numpad7 => Code::Numpad7,\n            dioxus_html::KeyCode::Numpad8 => Code::Numpad8,\n            dioxus_html::KeyCode::Numpad9 => Code::Numpad9,\n            dioxus_html::KeyCode::Multiply => Code::NumpadMultiply,\n            dioxus_html::KeyCode::Add => Code::NumpadAdd,\n            dioxus_html::KeyCode::Subtract => Code::NumpadSubtract,\n            dioxus_html::KeyCode::DecimalPoint => Code::NumpadDecimal,\n            dioxus_html::KeyCode::Divide => Code::NumpadDivide,\n            dioxus_html::KeyCode::F1 => Code::F1,\n            dioxus_html::KeyCode::F2 => Code::F2,\n            dioxus_html::KeyCode::F3 => Code::F3,\n            dioxus_html::KeyCode::F4 => Code::F4,\n            dioxus_html::KeyCode::F5 => Code::F5,\n            dioxus_html::KeyCode::F6 => Code::F6,\n            dioxus_html::KeyCode::F7 => Code::F7,\n            dioxus_html::KeyCode::F8 => Code::F8,\n            dioxus_html::KeyCode::F9 => Code::F9,\n            dioxus_html::KeyCode::F10 => Code::F10,\n            dioxus_html::KeyCode::F11 => Code::F11,\n            dioxus_html::KeyCode::F12 => Code::F12,\n            dioxus_html::KeyCode::NumLock => Code::NumLock,\n            dioxus_html::KeyCode::ScrollLock => Code::ScrollLock,\n            dioxus_html::KeyCode::Semicolon => Code::Semicolon,\n            dioxus_html::KeyCode::EqualSign => Code::Equal,\n            dioxus_html::KeyCode::Comma => Code::Comma,\n            dioxus_html::KeyCode::Period => Code::Period,\n            dioxus_html::KeyCode::ForwardSlash => Code::Slash,\n            dioxus_html::KeyCode::GraveAccent => Code::Backquote,\n            dioxus_html::KeyCode::OpenBracket => Code::BracketLeft,\n            dioxus_html::KeyCode::BackSlash => Code::Backslash,\n            dioxus_html::KeyCode::CloseBracket => Code::BracketRight,\n            dioxus_html::KeyCode::SingleQuote => Code::Quote,\n            key => panic!(\"Failed to convert {key:?} to tao::keyboard::KeyCode, try using tao::keyboard::KeyCode directly\"),\n        }\n    }\n}\n"
  },
  {
    "path": "packages/desktop/src/trayicon.rs",
    "content": "//! tray icon\n\nuse dioxus_core::{provide_context, try_consume_context, use_hook};\n\n#[cfg(not(any(target_os = \"ios\", target_os = \"android\")))]\npub use tray_icon::*;\n\n/// tray icon menu type trait\n#[cfg(not(any(target_os = \"ios\", target_os = \"android\")))]\npub type DioxusTrayMenu = tray_icon::menu::Menu;\n#[cfg(any(target_os = \"ios\", target_os = \"android\"))]\npub type DioxusTrayMenu = ();\n\n/// tray icon icon type trait\n#[cfg(not(any(target_os = \"ios\", target_os = \"android\")))]\npub type DioxusTrayIcon = tray_icon::Icon;\n#[cfg(any(target_os = \"ios\", target_os = \"android\"))]\npub type DioxusTrayIcon = ();\n\n/// tray icon type trait\n#[cfg(not(any(target_os = \"ios\", target_os = \"android\")))]\npub type DioxusTray = tray_icon::TrayIcon;\n#[cfg(any(target_os = \"ios\", target_os = \"android\"))]\npub type DioxusTray = ();\n\n/// initializes a tray icon\n#[allow(unused)]\npub fn init_tray_icon(menu: DioxusTrayMenu, icon: Option<DioxusTrayIcon>) -> DioxusTray {\n    #[cfg(any(target_os = \"windows\", target_os = \"linux\", target_os = \"macos\"))]\n    {\n        let builder = tray_icon::TrayIconBuilder::new()\n            .with_menu(Box::new(menu))\n            .with_menu_on_left_click(false)\n            .with_icon(match icon {\n                Some(value) => value,\n                None => tray_icon::Icon::from_rgba(\n                    include_bytes!(\"./assets/default_icon.bin\").to_vec(),\n                    460,\n                    460,\n                )\n                .expect(\"image parse failed\"),\n            });\n\n        provide_context(builder.build().expect(\"tray icon builder failed\"))\n    }\n}\n\n/// Returns a default tray icon menu\npub fn default_tray_icon() -> DioxusTrayMenu {\n    #[cfg(any(target_os = \"windows\", target_os = \"linux\", target_os = \"macos\"))]\n    {\n        use tray_icon::menu::{Menu, PredefinedMenuItem};\n        let tray_menu = Menu::new();\n        tray_menu\n            .append_items(&[&PredefinedMenuItem::quit(None)])\n            .unwrap();\n        tray_menu\n    }\n}\n\n/// Provides a hook to the tray icon\n#[cfg(any(target_os = \"windows\", target_os = \"linux\", target_os = \"macos\"))]\npub fn use_tray_icon() -> Option<tray_icon::TrayIcon> {\n    use_hook(try_consume_context)\n}\n"
  },
  {
    "path": "packages/desktop/src/ts/native_eval.ts",
    "content": "import {\n  Channel,\n  DioxusChannel,\n  WeakDioxusChannel,\n} from \"../../../document/src/ts/eval\";\n\n// In dioxus desktop, eval needs to use the window object to store global state because we evaluate separate snippets of javascript in the browser\ndeclare global {\n  interface Window {\n    __msg_queues: WeakDioxusChannel[];\n    finalizationRegistry: FinalizationRegistry<{ id: number }>;\n\n    getQuery(request_id: number): WeakDioxusChannel;\n\n    createQuery(request_id: number): NativeDioxusChannel;\n  }\n}\n\n// A message that can be sent to the desktop renderer about a query\nclass QueryParams {\n  id: number;\n  data: { method: \"drop\" | \"send\"; data?: any };\n\n  constructor(id: number, method: \"drop\" | \"send\", data?: any) {\n    this.id = id;\n    this.data = { method, data };\n  }\n}\n\nwindow.__msg_queues = window.__msg_queues || [];\n// In dioxus desktop, eval is copy so we cannot run a drop handler. Instead, the drop handler is run after the channel is garbage collected in the javascript side\nwindow.finalizationRegistry =\n  window.finalizationRegistry ||\n  new FinalizationRegistry(({ id }) => {\n    // @ts-ignore - wry gives us this\n    window.ipc.postMessage(\n      JSON.stringify({\n        method: \"query\",\n        params: new QueryParams(id, \"drop\"),\n      })\n    );\n  });\n\n// Get a query from the global state\nwindow.getQuery = function (request_id: number): WeakDioxusChannel {\n  return window.__msg_queues[request_id];\n};\n\n// Create a new query (and insert it into the global state)\nwindow.createQuery = function (request_id: number): NativeDioxusChannel {\n  return new NativeDioxusChannel(request_id);\n};\n\nexport class NativeDioxusChannel extends DioxusChannel {\n  rust_to_js: Channel;\n  request_id: number;\n\n  constructor(request_id: number) {\n    super();\n    this.rust_to_js = new Channel();\n    this.request_id = request_id;\n\n    window.__msg_queues[request_id] = this;\n    window.finalizationRegistry.register(this, { id: request_id });\n  }\n\n  // Receive message from Rust\n  async recv() {\n    return await this.rust_to_js.recv();\n  }\n\n  // Send message to rust.\n  send(data: any) {\n    // @ts-ignore - wry gives us this\n    window.ipc.postMessage(\n      JSON.stringify({\n        method: \"query\",\n        params: new QueryParams(this.request_id, \"send\", data),\n      })\n    );\n  }\n\n  // Send data from rust to javascript\n  rustSend(data: any) {\n    this.rust_to_js.send(data);\n  }\n\n  // Receive data sent from javascript in rust. This is a no-op in the native interpreter because the rust code runs remotely\n  async rustRecv(): Promise<any> { }\n\n  // Close the channel, dropping it.\n  close(): void {\n    window.__msg_queues[this.request_id] = null;\n  }\n}\n"
  },
  {
    "path": "packages/desktop/src/waker.rs",
    "content": "use crate::ipc::UserWindowEvent;\nuse futures_util::task::ArcWake;\nuse std::sync::Arc;\nuse tao::{event_loop::EventLoopProxy, window::WindowId};\n\n/// Create a waker that will send a poll event to the event loop.\n///\n/// This lets the VirtualDom \"come up for air\" and process events while the main thread is blocked by the WebView.\n///\n/// All IO and multithreading lives on other threads. Thanks to tokio's work stealing approach, the main thread can never\n/// claim a task while it's blocked by the event loop.\npub fn tao_waker(proxy: EventLoopProxy<UserWindowEvent>, id: WindowId) -> std::task::Waker {\n    struct DomHandle {\n        proxy: EventLoopProxy<UserWindowEvent>,\n        id: WindowId,\n    }\n\n    // this should be implemented by most platforms, but ios is missing this until\n    // https://github.com/tauri-apps/wry/issues/830 is resolved\n    unsafe impl Send for DomHandle {}\n    unsafe impl Sync for DomHandle {}\n\n    impl ArcWake for DomHandle {\n        fn wake_by_ref(arc_self: &Arc<Self>) {\n            _ = arc_self\n                .proxy\n                .send_event(UserWindowEvent::Poll(arc_self.id));\n        }\n    }\n\n    futures_util::task::waker(Arc::new(DomHandle { id, proxy }))\n}\n"
  },
  {
    "path": "packages/desktop/src/webview.rs",
    "content": "use crate::file_upload::{DesktopFileData, DesktopFileDragEvent};\nuse crate::menubar::DioxusMenu;\nuse crate::PendingDesktopContext;\nuse crate::{\n    app::SharedContext, assets::AssetHandlerRegistry, edits::WryQueue,\n    file_upload::NativeFileHover, ipc::UserWindowEvent, protocol, waker::tao_waker, Config,\n    DesktopContext, DesktopService,\n};\nuse crate::{document::DesktopDocument, WeakDesktopContext};\nuse crate::{element::DesktopElement, file_upload::DesktopFormData};\nuse base64::prelude::BASE64_STANDARD;\nuse dioxus_core::{consume_context, provide_context, Runtime, ScopeId, VirtualDom};\nuse dioxus_document::Document;\nuse dioxus_history::{History, MemoryHistory};\nuse dioxus_hooks::to_owned;\nuse dioxus_html::{FileData, FormValue, HtmlEvent, PlatformEventData, SerializedFileData};\nuse futures_util::{pin_mut, FutureExt};\nuse std::sync::{atomic::AtomicBool, Arc};\nuse std::{cell::OnceCell, time::Duration};\nuse std::{rc::Rc, task::Waker};\nuse wry::{DragDropEvent, RequestAsyncResponder, WebContext, WebViewBuilder, WebViewId};\n\n#[derive(Clone)]\npub(crate) struct WebviewEdits {\n    runtime: Rc<Runtime>,\n    pub wry_queue: WryQueue,\n    desktop_context: Rc<OnceCell<WeakDesktopContext>>,\n}\n\nimpl WebviewEdits {\n    fn new(runtime: Rc<Runtime>, wry_queue: WryQueue) -> Self {\n        Self {\n            runtime,\n            wry_queue,\n            desktop_context: Default::default(),\n        }\n    }\n\n    fn set_desktop_context(&self, context: WeakDesktopContext) {\n        _ = self.desktop_context.set(context);\n    }\n\n    pub fn handle_event(\n        &self,\n        request: wry::http::Request<Vec<u8>>,\n        responder: wry::RequestAsyncResponder,\n    ) {\n        let body = self\n            .try_handle_event(request)\n            .expect(\"Writing bodies to succeed\");\n        responder.respond(wry::http::Response::new(body))\n    }\n\n    pub fn try_handle_event(\n        &self,\n        request: wry::http::Request<Vec<u8>>,\n    ) -> Result<Vec<u8>, serde_json::Error> {\n        use serde::de::Error;\n\n        // todo(jon):\n        //\n        // I'm a small bit worried about the size of the header being too big on some platforms.\n        // It's unlikely we'll hit the 256k limit (from 2010 browsers...) but it's important to think about\n        // https://stackoverflow.com/questions/3326210/can-http-headers-be-too-big-for-browsers\n        //\n        // Also important to remember here that we don't pass a body from the JavaScript side of things\n        let data = request\n            .headers()\n            .get(\"dioxus-data\")\n            .ok_or_else(|| Error::custom(\"dioxus-data header not set\"))?;\n\n        let as_utf = std::str::from_utf8(data.as_bytes())\n            .map_err(|_| Error::custom(\"dioxus-data header is not a valid (utf-8) string\"))?;\n\n        let data_from_header = base64::Engine::decode(&BASE64_STANDARD, as_utf)\n            .map_err(|_| Error::custom(\"dioxus-data header is not a base64 string\"))?;\n\n        let response = match serde_json::from_slice(&data_from_header) {\n            Ok(event) => {\n                // we need to wait for the mutex lock to let us munge the main thread..\n                #[cfg(target_os = \"android\")]\n                let _lock = crate::android_sync_lock::android_runtime_lock();\n                self.handle_html_event(event)\n            }\n            Err(err) => {\n                tracing::error!(\n                    \"Error parsing user_event: {:?}. \\n Contents: {:?}, \\nraw: {:#?}\",\n                    err,\n                    String::from_utf8(request.body().to_vec()),\n                    request\n                );\n                SynchronousEventResponse::new(false)\n            }\n        };\n\n        serde_json::to_vec(&response).inspect_err(|err| {\n            tracing::error!(\"failed to serialize SynchronousEventResponse: {err:?}\");\n        })\n    }\n\n    pub fn handle_html_event(&self, event: HtmlEvent) -> SynchronousEventResponse {\n        let HtmlEvent {\n            element,\n            name,\n            bubbles,\n            data,\n        } = event;\n        let Some(desktop_context) = self.desktop_context.get() else {\n            tracing::error!(\n                \"Tried to handle event before setting the desktop context on the event handler\"\n            );\n            return Default::default();\n        };\n\n        let desktop_context = desktop_context.upgrade().unwrap();\n\n        let query = desktop_context.query.clone();\n        let hovered_file = desktop_context.file_hover.clone();\n\n        // check for a mounted event placeholder and replace it with a desktop specific element\n        let as_any = match data {\n            dioxus_html::EventData::Mounted => {\n                let element = DesktopElement::new(element, desktop_context.clone(), query.clone());\n                Rc::new(PlatformEventData::new(Box::new(element)))\n            }\n            dioxus_html::EventData::Form(form) => {\n                Rc::new(PlatformEventData::new(Box::new(DesktopFormData {\n                    value: form.value,\n                    valid: form.valid,\n                    values: form\n                        .values\n                        .into_iter()\n                        .map(|obj| {\n                            if let Some(text) = obj.text {\n                                return (obj.key, FormValue::Text(text));\n                            }\n\n                            if let Some(file_data) = obj.file {\n                                if file_data.path.capacity() == 0 {\n                                    return (obj.key, FormValue::File(None));\n                                }\n\n                                return (\n                                    obj.key,\n                                    FormValue::File(Some(FileData::new(DesktopFileData(\n                                        file_data.path,\n                                    )))),\n                                );\n                            };\n\n                            (obj.key, FormValue::Text(String::new()))\n                        })\n                        .collect(),\n                })))\n            }\n            // Which also includes drops...\n            dioxus_html::EventData::Drag(ref drag) => {\n                // we want to override this with a native file engine, provided by the most recent drag event\n                let full_file_paths = hovered_file.current_paths();\n\n                let xfer_data = drag.data_transfer.clone();\n                let new_file_data = xfer_data\n                    .files\n                    .iter()\n                    .map(|f| {\n                        let new_path = full_file_paths\n                            .iter()\n                            .find(|p| p.ends_with(&f.path))\n                            .unwrap_or(&f.path);\n                        SerializedFileData {\n                            path: new_path.clone(),\n                            ..f.clone()\n                        }\n                    })\n                    .collect::<Vec<_>>();\n                let new_xfer_data = dioxus_html::SerializedDataTransfer {\n                    files: new_file_data,\n                    ..xfer_data\n                };\n\n                Rc::new(PlatformEventData::new(Box::new(DesktopFileDragEvent {\n                    mouse: drag.mouse.clone(),\n                    data_transfer: new_xfer_data,\n                    files: full_file_paths,\n                })))\n            }\n            _ => data.into_any(),\n        };\n\n        let event = dioxus_core::Event::new(as_any, bubbles);\n        self.runtime.handle_event(&name, event.clone(), element);\n\n        // Get the response from the event\n        SynchronousEventResponse::new(!event.default_action_enabled())\n    }\n}\n\npub(crate) struct WebviewInstance {\n    pub dom: VirtualDom,\n    pub edits: WebviewEdits,\n    pub desktop_context: DesktopContext,\n    pub waker: Waker,\n\n    // Wry assumes the webcontext is alive for the lifetime of the webview.\n    // We need to keep the webcontext alive, otherwise the webview will crash\n    _web_context: WebContext,\n\n    // Same with the menu.\n    // Currently it's a DioxusMenu because 1) we don't touch it and 2) we support a number of platforms\n    // like ios where muda does not give us a menu type. It sucks but alas.\n    //\n    // This would be a good thing for someone looking to contribute to fix.\n    _menu: Option<DioxusMenu>,\n}\n\nimpl WebviewInstance {\n    pub(crate) fn new(\n        mut cfg: Config,\n        mut dom: VirtualDom,\n        shared: Rc<SharedContext>,\n    ) -> WebviewInstance {\n        let mut window = cfg.window.clone();\n\n        // tao makes small windows for some reason, make them bigger on desktop\n        //\n        // on mobile, we want them to be `None` so tao makes them the size of the screen. Otherwise we\n        // get a window that is not the size of the screen and weird black bars.\n        #[cfg(not(any(target_os = \"ios\", target_os = \"android\")))]\n        {\n            if cfg.window.window.inner_size.is_none() {\n                window = window.with_inner_size(tao::dpi::LogicalSize::new(800.0, 600.0));\n            }\n        }\n\n        // We assume that if the icon is None in cfg, then the user just didnt set it\n        if cfg.window.window.window_icon.is_none() {\n            window = window.with_window_icon(Some(\n                tao::window::Icon::from_rgba(\n                    include_bytes!(\"./assets/default_icon.bin\").to_vec(),\n                    460,\n                    460,\n                )\n                .expect(\"image parse failed\"),\n            ));\n        }\n\n        let window = Arc::new(window.build(&shared.target).unwrap());\n        if let Some(on_build) = cfg.on_window.as_mut() {\n            on_build(window.clone(), &mut dom);\n        }\n\n        // https://developer.apple.com/documentation/appkit/nswindowcollectionbehavior/nswindowcollectionbehaviormanaged\n        #[cfg(target_os = \"macos\")]\n        #[allow(deprecated)]\n        {\n            use cocoa::appkit::NSWindowCollectionBehavior;\n            use cocoa::base::id;\n            use objc::{msg_send, sel, sel_impl};\n            use tao::platform::macos::WindowExtMacOS;\n\n            unsafe {\n                let window: id = window.ns_window() as id;\n                #[allow(unexpected_cfgs)]\n                let _: () = msg_send![window, setCollectionBehavior: NSWindowCollectionBehavior::NSWindowCollectionBehaviorManaged];\n            }\n        }\n\n        let mut web_context = WebContext::new(cfg.data_dir.clone());\n        let edit_queue = shared.websocket.create_queue();\n        let asset_handlers = AssetHandlerRegistry::new();\n        let edits = WebviewEdits::new(dom.runtime(), edit_queue.clone());\n        let file_hover = NativeFileHover::default();\n        let headless = !cfg.window.window.visible;\n\n        let request_handler = {\n            to_owned![\n                cfg.custom_head,\n                cfg.custom_index,\n                cfg.root_name,\n                asset_handlers,\n                edits\n            ];\n\n            #[cfg(feature = \"tokio_runtime\")]\n            let tokio_rt = tokio::runtime::Handle::current();\n\n            move |_id: WebViewId, request, responder: RequestAsyncResponder| {\n                #[cfg(feature = \"tokio_runtime\")]\n                let _guard = tokio_rt.enter();\n\n                protocol::desktop_handler(\n                    request,\n                    asset_handlers.clone(),\n                    responder,\n                    &edits,\n                    custom_head.clone(),\n                    custom_index.clone(),\n                    &root_name,\n                    headless,\n                )\n            }\n        };\n\n        let ipc_handler = {\n            let window_id = window.id();\n            to_owned![shared.proxy];\n            move |payload: wry::http::Request<String>| {\n                // defer the event to the main thread\n                let body = payload.into_body();\n                if let Ok(msg) = serde_json::from_str(&body) {\n                    _ = proxy.send_event(UserWindowEvent::Ipc { id: window_id, msg });\n                }\n            }\n        };\n\n        let file_drop_handler = {\n            to_owned![file_hover];\n            let (proxy, window_id) = (shared.proxy.to_owned(), window.id());\n            move |evt: DragDropEvent| {\n                if cfg!(not(windows)) {\n                    // Update the most recent file drop event - when the event comes in from the webview we can use the\n                    // most recent event to build a new event with the files in it.\n                    file_hover.set(evt);\n                } else {\n                    // Windows webview blocks HTML-native events when the drop handler is provided.\n                    // The problem is that the HTML-native events don't provide the file, so we need this.\n                    // Solution: this glue code to mimic drag drop events.\n                    file_hover.set(evt.clone());\n                    match evt {\n                        wry::DragDropEvent::Drop {\n                            paths: _,\n                            position: _,\n                        } => {\n                            _ = proxy.send_event(UserWindowEvent::WindowsDragDrop(window_id));\n                        }\n                        wry::DragDropEvent::Over { position } => {\n                            _ = proxy.send_event(UserWindowEvent::WindowsDragOver(\n                                window_id, position.0, position.1,\n                            ));\n                        }\n                        wry::DragDropEvent::Leave => {\n                            _ = proxy.send_event(UserWindowEvent::WindowsDragLeave(window_id));\n                        }\n                        _ => {}\n                    }\n                }\n\n                false\n            }\n        };\n\n        let page_loaded = AtomicBool::new(false);\n\n        let mut webview = WebViewBuilder::new_with_web_context(&mut web_context)\n            .with_bounds(wry::Rect {\n                position: wry::dpi::Position::Logical(wry::dpi::LogicalPosition::new(0.0, 0.0)),\n                size: wry::dpi::Size::Physical(wry::dpi::PhysicalSize::new(\n                    window.inner_size().width,\n                    window.inner_size().height,\n                )),\n            })\n            .with_transparent(cfg.window.window.transparent)\n            .with_url(\"dioxus://index.html/\")\n            .with_ipc_handler(ipc_handler)\n            .with_navigation_handler(move |var| {\n                // We don't want to allow any navigation\n                // We only want to serve the index file and assets\n                if var.starts_with(\"dioxus://\")\n                    || var.starts_with(\"http://dioxus.\")\n                    || var.starts_with(\"https://dioxus.\")\n                {\n                    // After the page has loaded once, don't allow any more navigation\n                    let page_loaded = page_loaded.swap(true, std::sync::atomic::Ordering::SeqCst);\n                    !page_loaded\n                } else {\n                    if var.starts_with(\"http://\")\n                        || var.starts_with(\"https://\")\n                        || var.starts_with(\"mailto:\")\n                    {\n                        _ = webbrowser::open(&var);\n                    }\n                    false\n                }\n            }) // prevent all navigations\n            .with_asynchronous_custom_protocol(String::from(\"dioxus\"), request_handler);\n\n        // Enable https scheme on android, needed for secure context API, like the geolocation API\n        #[cfg(target_os = \"android\")]\n        {\n            use wry::WebViewBuilderExtAndroid as _;\n\n            webview = webview.with_https_scheme(true);\n        };\n\n        // Disable the webview default shortcuts to disable the reload shortcut\n        #[cfg(target_os = \"windows\")]\n        {\n            use wry::WebViewBuilderExtWindows;\n            webview = webview.with_browser_accelerator_keys(false);\n        }\n\n        if !cfg.disable_file_drop_handler {\n            webview = webview.with_drag_drop_handler(file_drop_handler);\n        }\n\n        if let Some(color) = cfg.background_color {\n            webview = webview.with_background_color(color);\n        }\n\n        for (name, handler) in cfg.protocols.drain(..) {\n            #[cfg(feature = \"tokio_runtime\")]\n            let tokio_rt = tokio::runtime::Handle::current();\n\n            webview = webview.with_custom_protocol(name, move |a, b| {\n                #[cfg(feature = \"tokio_runtime\")]\n                let _guard = tokio_rt.enter();\n                handler(a, b)\n            });\n        }\n\n        for (name, handler) in cfg.asynchronous_protocols.drain(..) {\n            #[cfg(feature = \"tokio_runtime\")]\n            let tokio_rt = tokio::runtime::Handle::current();\n\n            webview = webview.with_asynchronous_custom_protocol(name, move |a, b, c| {\n                #[cfg(feature = \"tokio_runtime\")]\n                let _guard = tokio_rt.enter();\n                handler(a, b, c)\n            });\n        }\n\n        const INITIALIZATION_SCRIPT: &str = r#\"\n        if (document.addEventListener) {\n            document.addEventListener('contextmenu', function(e) {\n                e.preventDefault();\n            }, false);\n        } else {\n            document.attachEvent('oncontextmenu', function() {\n                window.event.returnValue = false;\n            });\n        }\n        \"#;\n\n        if cfg.disable_context_menu {\n            // in release mode, we don't want to show the dev tool or reload menus\n            webview = webview.with_initialization_script(INITIALIZATION_SCRIPT)\n        } else {\n            // in debug, we are okay with the reload menu showing and dev tool\n            webview = webview.with_devtools(true);\n        }\n\n        let menu = if cfg!(not(any(target_os = \"android\", target_os = \"ios\"))) {\n            let menu_option = cfg.menu.into();\n            if let Some(menu) = &menu_option {\n                crate::menubar::init_menu_bar(menu, &window);\n            }\n            menu_option\n        } else {\n            None\n        };\n\n        #[cfg(target_os = \"windows\")]\n        {\n            use wry::WebViewBuilderExtWindows;\n            if let Some(additional_windows_args) = &cfg.additional_windows_args {\n                webview = webview.with_additional_browser_args(additional_windows_args);\n            }\n        }\n\n        #[cfg(any(\n            target_os = \"windows\",\n            target_os = \"macos\",\n            target_os = \"ios\",\n            target_os = \"android\"\n        ))]\n        let webview = if cfg.as_child_window {\n            webview.build_as_child(&window)\n        } else {\n            webview.build(&window)\n        };\n\n        #[cfg(not(any(\n            target_os = \"windows\",\n            target_os = \"macos\",\n            target_os = \"ios\",\n            target_os = \"android\"\n        )))]\n        let webview = {\n            use tao::platform::unix::WindowExtUnix;\n            use wry::WebViewBuilderExtUnix;\n            let vbox = window.default_vbox().unwrap();\n            webview.build_gtk(vbox)\n        };\n        let webview = webview.unwrap();\n\n        let desktop_context = Rc::from(DesktopService::new(\n            webview,\n            window,\n            shared.clone(),\n            asset_handlers,\n            file_hover,\n            cfg.window_close_behavior,\n        ));\n\n        // Provide the desktop context to the virtual dom and edit handler\n        edits.set_desktop_context(Rc::downgrade(&desktop_context));\n        let provider: Rc<dyn Document> = Rc::new(DesktopDocument::new(desktop_context.clone()));\n        let history_provider: Rc<dyn History> = Rc::new(MemoryHistory::default());\n        dom.in_scope(ScopeId::ROOT, || {\n            provide_context(desktop_context.clone());\n            provide_context(provider);\n            provide_context(history_provider);\n        });\n\n        // Request an initial redraw\n        desktop_context.window.request_redraw();\n\n        WebviewInstance {\n            dom,\n            edits,\n            waker: tao_waker(shared.proxy.clone(), desktop_context.window.id()),\n            desktop_context,\n            _menu: menu,\n            _web_context: web_context,\n        }\n    }\n\n    pub fn poll_vdom(&mut self) {\n        let mut cx = std::task::Context::from_waker(&self.waker);\n\n        // Continuously poll the virtualdom until it's pending\n        // Wait for work will return Ready when it has edits to be sent to the webview\n        // It will return Pending when it needs to be polled again - nothing is ready\n        loop {\n            // Check if there is a new edit channel we need to send. On IOS,\n            // the websocket will be killed when the device is put into sleep. If we\n            // find the socket has been closed, we create a new socket and send it to\n            // the webview to continue on\n            // https://github.com/DioxusLabs/dioxus/issues/4374\n            if self\n                .edits\n                .wry_queue\n                .poll_new_edits_location(&mut cx)\n                .is_ready()\n            {\n                _ = self.desktop_context.webview.evaluate_script(&format!(\n                    \"window.interpreter.waitForRequest(\\\"{edits_path}\\\", \\\"{expected_key}\\\");\",\n                    edits_path = self.edits.wry_queue.edits_path(),\n                    expected_key = self.edits.wry_queue.required_server_key()\n                ));\n            }\n\n            // If we're waiting for a render, wait for it to finish before we continue\n            let edits_flushed_poll = self.edits.wry_queue.poll_edits_flushed(&mut cx);\n            if edits_flushed_poll.is_pending() {\n                return;\n            }\n\n            {\n                // lock the hack-ed in lock sync wry has some thread-safety issues with event handlers and async tasks\n                #[cfg(target_os = \"android\")]\n                let _lock = crate::android_sync_lock::android_runtime_lock();\n                let fut = self.dom.wait_for_work();\n                pin_mut!(fut);\n\n                match fut.poll_unpin(&mut cx) {\n                    std::task::Poll::Ready(_) => {}\n                    std::task::Poll::Pending => return,\n                }\n            }\n\n            // lock the hack-ed in lock sync wry has some thread-safety issues with event handlers\n            #[cfg(target_os = \"android\")]\n            let _lock = crate::android_sync_lock::android_runtime_lock();\n\n            self.edits\n                .wry_queue\n                .with_mutation_state_mut(|f| self.dom.render_immediate(f));\n            self.edits.wry_queue.send_edits();\n        }\n    }\n\n    #[cfg(all(feature = \"devtools\", debug_assertions))]\n    pub fn kick_stylsheets(&self) {\n        // run eval in the webview to kick the stylesheets by appending a query string\n        // we should do something less clunky than this\n        _ = self\n            .desktop_context\n            .webview\n            .evaluate_script(\"window.interpreter.kickAllStylesheetsOnPage()\");\n    }\n\n    /// Displays a toast to the developer.\n    pub(crate) fn show_toast(\n        &self,\n        header_text: &str,\n        message: &str,\n        level: &str,\n        duration: Duration,\n        after_reload: bool,\n    ) {\n        let as_ms = duration.as_millis();\n\n        let js_fn_name = match after_reload {\n            true => \"scheduleDXToast\",\n            false => \"showDXToast\",\n        };\n\n        _ = self.desktop_context.webview.evaluate_script(&format!(\n            r#\"\n                if (typeof {js_fn_name} !== \"undefined\") {{\n                    window.{js_fn_name}(\"{header_text}\", \"{message}\", \"{level}\", {as_ms});\n                }}\n                \"#,\n        ));\n    }\n}\n\n/// A synchronous response to a browser event which may prevent the default browser's action\n#[derive(serde::Serialize, Default)]\npub struct SynchronousEventResponse {\n    #[serde(rename = \"preventDefault\")]\n    prevent_default: bool,\n}\n\nimpl SynchronousEventResponse {\n    /// Create a new SynchronousEventResponse\n    #[allow(unused)]\n    pub fn new(prevent_default: bool) -> Self {\n        Self { prevent_default }\n    }\n}\n\n/// A webview that is queued to be created. We can't spawn webviews outside of the main event loop because it may\n/// block on windows so we queue them into the shared context and then create them when the main event loop is ready.\npub(crate) struct PendingWebview {\n    dom: VirtualDom,\n    cfg: Config,\n    sender: futures_channel::oneshot::Sender<DesktopContext>,\n}\n\nimpl PendingWebview {\n    pub(crate) fn new(dom: VirtualDom, cfg: Config) -> (Self, PendingDesktopContext) {\n        let (sender, receiver) = futures_channel::oneshot::channel();\n        let webview = Self { dom, cfg, sender };\n        let pending = PendingDesktopContext { receiver };\n        (webview, pending)\n    }\n\n    pub(crate) fn create_window(self, shared: &Rc<SharedContext>) -> WebviewInstance {\n        let window = WebviewInstance::new(self.cfg, self.dom, shared.clone());\n\n        let cx = window\n            .dom\n            .in_scope(ScopeId::ROOT, consume_context::<Rc<DesktopService>>);\n        _ = self.sender.send(cx);\n\n        window\n    }\n}\n"
  },
  {
    "path": "packages/desktop/tsconfig.json",
    "content": "{\n    \"compilerOptions\": {\n        \"module\": \"CommonJS\",\n        \"lib\": [\n            \"ES2015\",\n            \"DOM\",\n            \"dom\",\n            \"dom.iterable\",\n            \"ESNext\"\n        ],\n        \"noImplicitAny\": true,\n        \"removeComments\": true,\n        \"preserveConstEnums\": true,\n    },\n    \"exclude\": [\n        \"**/*.spec.ts\"\n    ]\n}\n"
  },
  {
    "path": "packages/devtools/Cargo.toml",
    "content": "[package]\nname = \"dioxus-devtools\"\nauthors = [\"Jonathan Kelley\", \"Evan Almloff\"]\nversion = { workspace = true }\nedition = \"2021\"\nlicense = \"MIT OR Apache-2.0\"\nrepository = \"https://github.com/DioxusLabs/dioxus/\"\nhomepage = \"https://dioxuslabs.com/learn/0.4/migration/hot_reload\"\ndescription = \"Hot reloading utilities for Dioxus\"\nkeywords = [\"dom\", \"ui\", \"gui\", \"react\", \"hot-reloading\"]\n\n[dependencies]\ndioxus-signals = { workspace = true }\ndioxus-core = { workspace = true, features = [\"serialize\"] }\ndioxus-devtools-types = { workspace = true }\ndioxus-cli-config = { workspace = true }\nserde = { workspace = true, features = [\"derive\"] }\nserde_json = { workspace = true }\nsubsecond = { workspace = true }\nthiserror = { workspace = true }\n\nfutures-util = { workspace = true, features = [\"sink\", \"async-await-macro\"], optional = true }\nfutures-channel = { workspace = true, optional = true }\n\n# hot reloading serve\ntracing = { workspace = true }\n\n[target.'cfg(not(target_arch = \"wasm32\"))'.dependencies]\ntungstenite = { workspace = true }\n\n[dev-dependencies]\ntokio = { workspace = true, features = [\"full\"] }\nserde_json = { workspace = true }\n\n[package.metadata.docs.rs]\ncargo-args = [\"-Zunstable-options\", \"-Zrustdoc-scrape-examples\"]\n\n[features]\ndefault = []\nserve = [\"dep:futures-util\", \"dep:futures-channel\"]\n"
  },
  {
    "path": "packages/devtools/src/lib.rs",
    "content": "use dioxus_core::internal::HotReloadedTemplate;\nuse dioxus_core::{ScopeId, VirtualDom};\nuse dioxus_signals::{GlobalKey, Signal, WritableExt};\n\npub use dioxus_devtools_types::*;\npub use subsecond;\nuse subsecond::PatchError;\n\n/// Applies template and literal changes to the VirtualDom\n///\n/// Assets need to be handled by the renderer.\npub fn apply_changes(dom: &VirtualDom, msg: &HotReloadMsg) {\n    try_apply_changes(dom, msg).unwrap()\n}\n\n/// Applies template and literal changes to the VirtualDom, but doesn't panic if patching fails.\n///\n/// Assets need to be handled by the renderer.\npub fn try_apply_changes(dom: &VirtualDom, msg: &HotReloadMsg) -> Result<(), PatchError> {\n    dom.runtime().in_scope(ScopeId::ROOT, || {\n        // 1. Update signals...\n        let ctx = dioxus_signals::get_global_context();\n        for template in &msg.templates {\n            let value = template.template.clone();\n            let key = GlobalKey::File {\n                file: template.key.file.as_str(),\n                line: template.key.line as _,\n                column: template.key.column as _,\n                index: template.key.index as _,\n            };\n            if let Some(mut signal) = ctx.get_signal_with_key(key.clone()) {\n                signal.set(Some(value));\n            }\n        }\n\n        // 2. Attempt to hotpatch\n        if let Some(jump_table) = msg.jump_table.as_ref().cloned() {\n            if msg.for_build_id == Some(dioxus_cli_config::build_id()) {\n                let our_pid = if cfg!(target_family = \"wasm\") {\n                    None\n                } else {\n                    Some(std::process::id())\n                };\n\n                if msg.for_pid == our_pid {\n                    unsafe { subsecond::apply_patch(jump_table) }?;\n                    dom.runtime().force_all_dirty();\n                    ctx.clear::<Signal<Option<HotReloadedTemplate>>>();\n                }\n            }\n        }\n\n        Ok(())\n    })\n}\n\n/// Connect to the devserver and handle its messages with a callback.\n///\n/// This doesn't use any form of security or protocol, so it's not safe to expose to the internet.\n#[cfg(not(target_family = \"wasm\"))]\npub fn connect(callback: impl FnMut(DevserverMsg) + Send + 'static) {\n    let Some(endpoint) = dioxus_cli_config::devserver_ws_endpoint() else {\n        return;\n    };\n\n    connect_at(endpoint, callback);\n}\n\n/// Connect to the devserver and handle hot-patch messages only, implementing the subsecond hotpatch\n/// protocol.\n///\n/// This is intended to be used by non-dioxus projects that want to use hotpatching.\n///\n/// To handle the full devserver protocol, use `connect` instead.\n#[cfg(not(target_family = \"wasm\"))]\npub fn connect_subsecond() {\n    connect(|msg| {\n        if let DevserverMsg::HotReload(hot_reload_msg) = msg {\n            if let Some(jumptable) = hot_reload_msg.jump_table {\n                if hot_reload_msg.for_pid == Some(std::process::id()) {\n                    unsafe { subsecond::apply_patch(jumptable).unwrap() };\n                }\n            }\n        }\n    });\n}\n\n#[cfg(not(target_family = \"wasm\"))]\npub fn connect_at(endpoint: String, mut callback: impl FnMut(DevserverMsg) + Send + 'static) {\n    std::thread::spawn(move || {\n        let uri = format!(\n            \"{endpoint}?aslr_reference={}&build_id={}&pid={}\",\n            subsecond::aslr_reference(),\n            dioxus_cli_config::build_id(),\n            std::process::id()\n        );\n\n        let (mut websocket, _req) = match tungstenite::connect(uri) {\n            Ok((websocket, req)) => (websocket, req),\n            Err(_) => return,\n        };\n\n        while let Ok(msg) = websocket.read() {\n            if let tungstenite::Message::Text(text) = msg {\n                if let Ok(msg) = serde_json::from_str(&text) {\n                    callback(msg);\n                }\n            }\n        }\n    });\n}\n\n/// Run this asynchronous future to completion.\n///\n/// Whenever your code changes, the future is dropped and a new one is created using the new function.\n///\n/// This is useful for using subsecond outside of dioxus, like with axum. To pass args to the underlying\n/// function, you can use the `serve_subsecond_with_args` function.\n///\n/// ```rust, ignore\n/// #[tokio::main]\n/// async fn main() {\n///     dioxus_devtools::serve_subsecond(router_main).await;\n/// }\n///\n/// async fn router_main() {\n///     use axum::{Router, routing::get};\n///\n///     let app = Router::new().route(\"/\", get(test_route));\n///\n///     let listener = tokio::net::TcpListener::bind(\"0.0.0.0:3000\").await.unwrap();\n///     println!(\"Server running on http://localhost:3000\");\n///\n///     axum::serve(listener, app.clone()).await.unwrap()\n/// }\n///\n/// async fn test_route() -> axum::response::Html<&'static str> {\n///     \"axum works!!!!!\".into()\n/// }\n/// ```\n#[cfg(feature = \"serve\")]\n#[cfg(not(target_family = \"wasm\"))]\npub async fn serve_subsecond<O, F>(mut callback: impl FnMut() -> F)\nwhere\n    F: std::future::Future<Output = O> + 'static,\n{\n    serve_subsecond_with_args((), move |_args| callback()).await\n}\n\n/// Run this asynchronous future to completion.\n///\n/// Whenever your code changes, the future is dropped and a new one is created using the new function.\n///\n/// ```rust, ignore\n/// #[tokio::main]\n/// async fn main() {\n///     let args = (\"abc\".to_string(),);\n///     dioxus_devtools::serve_subsecond_with_args(args, router_main).await;\n/// }\n///\n/// async fn router_main(args: (String,)) {\n///     use axum::{Router, routing::get};\n///\n///     let app = Router::new().route(\"/\", get(test_route));\n///\n///     let listener = tokio::net::TcpListener::bind(\"0.0.0.0:3000\").await.unwrap();\n///     println!(\"Server running on http://localhost:3000 -> {}\", args.0);\n///\n///     axum::serve(listener, app.clone()).await.unwrap()\n/// }\n///\n/// async fn test_route() -> axum::response::Html<&'static str> {\n///     \"axum works!!!!!\".into()\n/// }\n/// ```\n#[cfg(feature = \"serve\")]\npub async fn serve_subsecond_with_args<A: Clone, O, F>(args: A, mut callback: impl FnMut(A) -> F)\nwhere\n    F: std::future::Future<Output = O> + 'static,\n{\n    let (tx, mut rx) = futures_channel::mpsc::unbounded();\n\n    connect(move |msg| {\n        if let DevserverMsg::HotReload(hot_reload_msg) = msg {\n            if let Some(jumptable) = hot_reload_msg.jump_table {\n                if hot_reload_msg.for_pid == Some(std::process::id()) {\n                    unsafe { subsecond::apply_patch(jumptable).unwrap() };\n                    tx.unbounded_send(()).unwrap();\n                }\n            }\n        }\n    });\n\n    let wrapped = move |args| -> std::pin::Pin<Box<dyn std::future::Future<Output = O>>> {\n        Box::pin(callback(args))\n    };\n\n    let mut hotfn = subsecond::HotFn::current(wrapped);\n    let mut cur_future = hotfn.call((args.clone(),));\n\n    loop {\n        use futures_util::StreamExt;\n        let res = futures_util::future::select(cur_future, rx.next()).await;\n\n        match res {\n            futures_util::future::Either::Left(_completed) => _ = rx.next().await,\n            futures_util::future::Either::Right((None, callback)) => {\n                // Receiving `None` here means that the sender is not connected, which\n                // typically means the dioxus devtools protocol has never connected.\n                // We want to run the future to completion and return instead of\n                // re-running the future constantly in the loop.\n                callback.await;\n                return;\n            }\n            futures_util::future::Either::Right((Some(_), _)) => {}\n        }\n\n        cur_future = hotfn.call((args.clone(),));\n    }\n}\n"
  },
  {
    "path": "packages/devtools-types/Cargo.toml",
    "content": "[package]\nname = \"dioxus-devtools-types\"\nedition = \"2021\"\nversion.workspace = true\nauthors = [\"Jonathan Kelley\", \"Evan Almloff\"]\nlicense = \"MIT OR Apache-2.0\"\nrepository = \"https://github.com/DioxusLabs/dioxus/\"\nhomepage = \"https://dioxuslabs.com\"\ndescription = \"CLI Configuration for dioxus-cli\"\nkeywords = [\"dom\", \"ui\", \"gui\", \"react\", ]\n\n[dependencies]\nserde = { workspace = true, features = [\"derive\"] }\ndioxus-core = { workspace = true, features = [\"serialize\"] }\nsubsecond-types = { workspace = true }\n"
  },
  {
    "path": "packages/devtools-types/src/lib.rs",
    "content": "use dioxus_core::internal::HotReloadTemplateWithLocation;\nuse serde::{Deserialize, Serialize};\nuse std::path::PathBuf;\nuse subsecond_types::JumpTable;\n\n/// A message the hot reloading server sends to the client\n#[non_exhaustive]\n#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]\npub enum DevserverMsg {\n    /// Attempt a hotreload\n    /// This includes all the templates/literals/assets/binary patches that have changed in one shot\n    HotReload(HotReloadMsg),\n\n    /// Starting a hotpatch\n    HotPatchStart,\n\n    /// The devserver is starting a full rebuild.\n    FullReloadStart,\n\n    /// The full reload failed.\n    FullReloadFailed,\n\n    /// The app should reload completely if it can\n    FullReloadCommand,\n\n    /// The program is shutting down completely - maybe toss up a splash screen or something?\n    Shutdown,\n}\n\n/// A message the client sends from the frontend to the devserver\n///\n/// This is used to communicate with the devserver\n#[non_exhaustive]\n#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]\npub enum ClientMsg {\n    Log {\n        level: String,\n        messages: Vec<String>,\n    },\n}\n\n#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]\npub struct HotReloadMsg {\n    pub templates: Vec<HotReloadTemplateWithLocation>,\n    pub assets: Vec<PathBuf>,\n    pub ms_elapsed: u64,\n    pub jump_table: Option<JumpTable>,\n    pub for_build_id: Option<u64>,\n    pub for_pid: Option<u32>,\n}\n\nimpl HotReloadMsg {\n    pub fn is_empty(&self) -> bool {\n        self.templates.is_empty() && self.assets.is_empty() && self.jump_table.is_none()\n    }\n}\n"
  },
  {
    "path": "packages/dioxus/Cargo.toml",
    "content": "[package]\nname = \"dioxus\"\nversion = { workspace = true }\nauthors = [\"Jonathan Kelley\", \"Dioxus Labs\", \"ealmloff\"]\nedition = \"2021\"\ndescription = \"Build fullstack web, desktop, and mobile apps with a single codebase.\"\nlicense = \"MIT OR Apache-2.0\"\nrepository = \"https://github.com/DioxusLabs/dioxus/\"\nhomepage = \"https://dioxuslabs.com\"\nkeywords = [\"web\", \"desktop\", \"mobile\", \"gui\", \"wasm\"]\nrust-version = \"1.83.0\"\n\n[dependencies]\ndioxus-core = { workspace = true }\ndioxus-config-macros = { workspace = true }\ndioxus-html = { workspace = true, default-features = false, optional = true }\ndioxus-document = { workspace = true, optional = true }\ndioxus-history = { workspace = true, optional = true }\ndioxus-core-macro = { workspace = true, optional = true }\ndioxus-config-macro = { workspace = true, optional = true }\ndioxus-stores = { workspace = true, optional = true }\ndioxus-hooks = { workspace = true, optional = true }\ndioxus-signals = { workspace = true, optional = true }\ndioxus-router = { workspace = true, optional = true }\ndioxus-web = { workspace = true, default-features = false, optional = true }\ndioxus-desktop = { workspace = true, default-features = true, optional = true }\ndioxus-liveview = { workspace = true, optional = true }\ndioxus-ssr = { workspace = true, optional = true }\ndioxus-native = { workspace = true, optional = true }\ndioxus-server = { workspace = true, default-features = true, optional = true }\ndioxus-fullstack = { workspace = true, default-features = true, optional = true }\ndioxus-fullstack-macro = { workspace = true, optional = true }\ndioxus-asset-resolver = { workspace = true, optional = true }\nmanganis = { workspace = true, features = [\"dioxus\"], optional = true }\ndioxus-logger = { workspace = true, optional = true }\nwarnings = { workspace = true, optional = true }\nwasm-splitter = { workspace = true, optional = true }\nsubsecond = { workspace = true }\n\nserde = { workspace = true, optional = true }\ndioxus-cli-config = { workspace = true, optional = true }\ndioxus-devtools = { workspace = true, optional = true }\n\n[features]\ndefault = [\n  \"launch\",\n  \"devtools\",\n  \"logger\",\n  \"lib\"\n]\n# All features recommended for use in libraries\nlib = [\n  \"macro\",\n  \"html\",\n  \"signals\",\n  \"hooks\",\n  \"mounted\",\n  \"document\",\n  \"asset\",\n  \"warnings\",\n  \"cli-config\",\n]\n# The minimal set of features required to use dioxus renderers for minimal binary size\nminimal = [\"macro\", \"html\", \"signals\", \"hooks\", \"launch\"]\nsignals = [\"dep:dioxus-signals\", \"dep:dioxus-stores\"]\nmacro = [\"dep:dioxus-core-macro\"]\nhtml = [\"dep:dioxus-html\"]\nhooks = [\"dep:dioxus-hooks\"]\ndevtools = [\"dep:dioxus-devtools\", \"dioxus-web?/devtools\"]\nmounted = [\"dioxus-web?/mounted\"]\nasset = [\"dep:manganis\", \"dep:dioxus-asset-resolver\"]\ndocument = [\"dioxus-web?/document\", \"dep:dioxus-document\", \"dep:dioxus-history\"]\nlogger = [\"dep:dioxus-logger\"]\ncli-config = [\"dep:dioxus-cli-config\"]\nwarnings = [\"dep:warnings\"]\nwasm-split = [\n  \"dep:wasm-splitter\",\n  \"dioxus-config-macros/wasm-split\",\n] # note: to turn on the router splitter, you need to manually enable wasm-split on the router\n\nlaunch = [\"dep:dioxus-config-macro\"]\nrouter = [\"dep:dioxus-router\"]\n\n# Platforms\nfullstack = [\n  \"dep:dioxus-fullstack\",\n  \"dioxus-config-macro/fullstack\",\n  \"dep:serde\",\n  \"dioxus-web?/document\",\n  \"dioxus-web?/hydrate\",\n  \"dioxus-server?/document\",\n  \"dioxus-web?/devtools\",\n  \"dioxus-web?/mounted\",\n  \"dioxus-web?/document\"\n]\ndesktop = [\"dep:dioxus-desktop\", \"dioxus-config-macro/desktop\"]\nmobile = [\"dep:dioxus-desktop\", \"dioxus-config-macro/mobile\"]\nweb = [\n  \"dep:dioxus-web\",\n  \"dioxus-fullstack?/web\",\n  \"dioxus-config-macro/web\",\n  \"dep:dioxus-cli-config\",\n  \"dioxus-cli-config?/web\",\n  \"dioxus-asset-resolver?/web\",\n]\nssr = [\"dep:dioxus-ssr\", \"dioxus-config-macro/ssr\"]\nliveview = [\"dep:dioxus-liveview\", \"dioxus-config-macro/liveview\"]\nnative = [\"dep:dioxus-native\", \"dioxus-config-macro/native\"] # todo(jon): decompose the desktop crate such that \"webview\" is the default and native is opt-in\nserver = [\n  \"dep:dioxus-server\",\n  \"dioxus-fullstack?/server\",\n  \"dep:dioxus-fullstack-macro\",\n  \"ssr\",\n  \"dioxus-liveview?/axum\",\n]\n\n# This feature just disables the no-renderer-enabled warning\nthird-party-renderer = []\n\n[dev-dependencies]\nfutures-util = { workspace = true }\ntracing = { workspace = true }\nrand = { workspace = true, features = [\"small_rng\"] }\ncriterion = { workspace = true }\nthiserror = { workspace = true }\nenv_logger = { workspace = true }\ntokio = { workspace = true, features = [\"full\"] }\ndioxus = { workspace = true }\n\n[[bench]]\nname = \"jsframework\"\nharness = false\n\n[package.metadata.docs.rs]\ncargo-args = [\"-Zunstable-options\", \"-Zrustdoc-scrape-examples\"]\nfeatures = [\"router\", \"ssr\", \"web\", \"fullstack\", \"signals\", \"hooks\", \"html\", \"liveview\", \"server\", \"warnings\"]\n"
  },
  {
    "path": "packages/dioxus/README.md",
    "content": "<div align=\"center\">\n    <img\n        src=\"https://github.com/user-attachments/assets/6c7e227e-44ff-4e53-824a-67949051149c\"\n        alt=\"Build web, desktop, and mobile apps with a single codebase.\"\n        width=\"100%\"\n        class=\"darkmode-image\"\n    >\n    <div>\n        <a href=https://dioxuslabs.com/learn/0.7/getting_started>Getting Started</a> | <a href=\"https://dioxuslabs.com/learn/0.7/\">Book (0.7)</a> | <a href=\"https://github.com/DioxusLabs/dioxus/tree/main/examples\">Examples</a>\n    </div>\n</div>\n\n---\n\nDioxus is a framework for building cross-platform apps in Rust. With one codebase, you can build web, desktop, and mobile apps with fullstack server functions. Dioxus is designed to be easy to learn for developers familiar with web technologies like HTML, CSS, and JavaScript.\n\n<div align=\"center\">\n    <img src=\"https://github.com/user-attachments/assets/dddae6a9-c13b-4a88-84e8-dc98c1286d2a\" alt=\"App with dioxus\" height=\"600px\">\n</div>\n\n## At a glance\n\nDioxus is crossplatform app framework that empowers developer to build beautiful, fast, type-safe apps with Rust. By default, Dioxus apps are declared with HTML and CSS. Dioxus includes a number of useful features:\n\n- Hotreloading of RSX markup and assets\n- Interactive CLI with logging, project templates, linting, and more\n- Integrated bundler for deploying to the web, macOS, Linux, and Windows\n- Support for modern web features like SSR, Hydration, and HTML streaming\n- Direct access to system APIs through JNI (Android), CoreFoundation (Apple), and web-sys (web)\n- Type-safe application routing and server functions\n\n## Quick start\n\nTo get started with Dioxus, you'll want to grab the dioxus-cli tool: `dx`. We distribute `dx` with `cargo-binstall` - if you already have binstall skip this step.\n\n```shell\n# skip if you already have cargo-binstall\ncargo install cargo-binstall\n\n# install the precompiled `dx` tool\ncargo binstall dioxus-cli\n\n# create a new app, following the template\ndx new my-app && cd my-app\n\n# and then serve!\ndx serve --desktop\n```\n\n## Your first app\n\nAll Dioxus apps are built by composing functions return an `Element`.\n\nTo launch an app, we use the `launch` method. In the launch function, we pass the app's root `Component`.\n\n```rust, no_run\nuse dioxus::prelude::*;\n\nfn main() {\n    dioxus::launch(App);\n}\n\n// The #[component] attribute streamlines component creation.\n// It's not required, but highly recommended. It will lint incorrect component definitions and help you create props structs.\n#[component]\nfn App() -> Element {\n    rsx! { \"hello world!\" }\n}\n```\n\n## Elements & your first component\n\nYou can use the `rsx!` macro to create elements with a jsx-like syntax.\nAny element in `rsx!` can have attributes, listeners, and children. For\nconsistency, we force all attributes and listeners to be listed _before_\nchildren.\n\n```rust, no_run\n# use dioxus::prelude::*;\nlet value = \"123\";\n\nrsx! {\n    div {\n        class: \"my-class {value}\",                  // <--- attribute\n        onclick: move |_| println!(\"clicked!\"),   // <--- listener\n        h1 { \"hello world\" }                       // <--- child\n    }\n};\n```\n\nThe `rsx!` macro accepts attributes in \"struct form\". Any rust expression contained within curly braces that implements [`IntoDynNode`](dioxus_core::IntoDynNode) will be parsed as a child. We make two exceptions: both `for` loops and `if` statements are parsed where their body is parsed as a rsx nodes.\n\n```rust, no_run\n# use dioxus::prelude::*;\nrsx! {\n    div {\n        for _ in 0..10 {\n            span { \"hello world\" }\n        }\n    }\n};\n```\n\nPutting everything together, we can write a simple component that renders a list of\nelements:\n\n```rust, no_run\n# use dioxus::prelude::*;\n#[component]\nfn App() -> Element {\n    let name = \"dave\";\n    rsx! {\n        h1 { \"Hello, {name}!\" }\n        div { class: \"my-class\", id: \"my-id\",\n            for i in 0..5 {\n                div { \"FizzBuzz: {i}\" }\n            }\n        }\n    }\n}\n```\n\n## Components\n\nWe can compose these function components to build a complex app. Each new component takes some Properties. For components with no explicit properties, we can omit the type altogether.\n\nIn Dioxus, all properties are memoized by default with `Clone` and `PartialEq`. For props you can't clone, simply wrap the fields in a [`ReadSignal`](dioxus_signals::ReadSignal) and Dioxus will handle converting types for you.\n\n```rust, no_run\n# use dioxus::prelude::*;\n# #[component] fn Header(title: String, color: String) -> Element { todo!() }\n#[component]\nfn App() -> Element {\n    rsx! {\n        Header {\n            title: \"My App\",\n            color: \"red\",\n        }\n    }\n}\n```\n\nThe `#[component]` macro will help us automatically create a props struct for our component:\n\n```rust, no_run\n# use dioxus::prelude::*;\n// The component macro turns the arguments for our function into named fields we can pass in to the component in rsx\n#[component]\nfn Header(title: String, color: String) -> Element {\n    rsx! {\n        div {\n            background_color: \"{color}\",\n            h1 { \"{title}\" }\n        }\n    }\n}\n```\n\n> You can read more about props in the [reference](https://dioxuslabs.com/learn/0.7/essentials/ui/components).\n\n## Hooks\n\nWhile components are reusable forms of UI elements, hooks are reusable forms of logic. Hooks provide a way of retrieving state from Dioxus' internal `Scope` and using\nit to render UI elements.\n\nBy convention, all hooks are functions that should start with `use_`. We can use hooks to define the state and modify it from within listeners.\n\n```rust, no_run\n# use dioxus::prelude::*;\n#[component]\nfn App() -> Element {\n    // The use signal hook runs once when the component is created and then returns the current value every run after the first\n    let name = use_signal(|| \"world\");\n\n    rsx! { \"hello {name}!\" }\n}\n```\n\nHooks are sensitive to how they are used. To use hooks, you must abide by the [\"rules of hooks\"](https://dioxuslabs.com/learn/0.7/essentials/basics/hooks#rules-of-hooks):\n\n- Hooks can only be called in the body of a component or another hook. Not inside of another expression like a loop, conditional or function call.\n- Hooks should start with \"use\\_\"\n\nHooks let us add a field of state to our component without declaring an explicit state struct. However, this means we need to \"load\" the struct in the right order. If that order is wrong, then the hook will pick the wrong state and panic.\n\nDioxus includes many built-in hooks that you can use in your components. If those hooks don't fit your use case, you can also extend Dioxus with custom hooks.\n\n## Putting it all together\n\nUsing components, rsx, and hooks, we can build a simple app.\n\n```rust, no_run\nuse dioxus::prelude::*;\n\nfn main() {\n    dioxus::launch(App);\n}\n\n#[component]\nfn App() -> Element {\n    let mut count = use_signal(|| 0);\n\n    rsx! {\n        div { \"Count: {count}\" }\n        button { onclick: move |_| count += 1, \"Increment\" }\n        button { onclick: move |_| count -= 1, \"Decrement\" }\n    }\n}\n```\n\n## Conclusion\n\nThis overview doesn't cover everything. Make sure to check out the [tutorial](https://dioxuslabs.com/learn/0.7/tutorial) and [guides](https://dioxuslabs.com/learn/0.7/tutorial) on the official\nwebsite for more details.\n\nBeyond this overview, Dioxus supports:\n\n- Server-side rendering\n- Concurrent rendering (with async support)\n- Web/Desktop/Mobile support\n- Pre-rendering and hydration\n- Fragments, and Suspense\n- Inline-styles\n- Custom event handlers\n- Custom elements\n- Basic fine-grained reactivity (IE SolidJS/Svelte)\n- and more!\n\nBuild cool things ✌️\n"
  },
  {
    "path": "packages/dioxus/benches/jsframework.rs",
    "content": "#![allow(non_snake_case, non_upper_case_globals)]\n//! This benchmark tests just the overhead of Dioxus itself.\n//!\n//! For the JS Framework Benchmark, both the framework and the browser is benchmarked together. Dioxus prepares changes\n//! to be made, but the change application phase will be just as performant as the vanilla wasm_bindgen code. In essence,\n//! we are measuring the overhead of Dioxus, not the performance of the \"apply\" phase.\n//!\n//!\n//! Pre-templates (Mac M1):\n//! - 3ms to create 1_000 rows\n//! - 30ms to create 10_000 rows\n//!\n//! Post-templates\n//! - 580us to create 1_000 rows\n//! - 6.2ms to create 10_000 rows\n//!\n//! As pure \"overhead\", these are amazing good numbers, mostly slowed down by hitting the global allocator.\n//! These numbers don't represent Dioxus with the heuristic engine installed, so I assume it'll be even faster.\n\nuse criterion::{criterion_group, criterion_main, Criterion};\nuse dioxus::prelude::*;\nuse dioxus_core::NoOpMutations;\nuse rand::prelude::*;\n\ncriterion_group!(mbenches, create_rows);\ncriterion_main!(mbenches);\n\nfn create_rows(c: &mut Criterion) {\n    c.bench_function(\"create rows\", |b| {\n        let mut dom = VirtualDom::new(app);\n        dom.rebuild(&mut dioxus_core::NoOpMutations);\n\n        b.iter(|| {\n            dom.rebuild(&mut NoOpMutations);\n        })\n    });\n}\n\nfn app() -> Element {\n    let mut rng = SmallRng::from_os_rng();\n\n    rsx! (\n        table {\n            tbody {\n                for f in 0..10_000_usize {\n                    table_row {\n                        row_id: f,\n                        label: Label::new(&mut rng)\n                    }\n                }\n            }\n        }\n    )\n}\n\n#[derive(PartialEq, Props, Clone, Copy)]\nstruct RowProps {\n    row_id: usize,\n    label: Label,\n}\nfn table_row(props: RowProps) -> Element {\n    let [adj, col, noun] = props.label.0;\n\n    rsx! {\n        tr {\n            td { class:\"col-md-1\", \"{props.row_id}\" }\n            td { class:\"col-md-1\", onclick: move |_| { /* run onselect */ },\n                a { class: \"lbl\", \"{adj}\" \"{col}\" \"{noun}\" }\n            }\n            td { class: \"col-md-1\",\n                a { class: \"remove\", onclick: move |_| {/* remove */},\n                    span { class: \"glyphicon glyphicon-remove remove\", aria_hidden: \"true\" }\n                }\n            }\n            td { class: \"col-md-6\" }\n        }\n    }\n}\n\n#[derive(PartialEq, Clone, Copy)]\nstruct Label([&'static str; 3]);\n\nimpl Label {\n    fn new(rng: &mut SmallRng) -> Self {\n        Label([\n            ADJECTIVES.choose(rng).unwrap(),\n            COLOURS.choose(rng).unwrap(),\n            NOUNS.choose(rng).unwrap(),\n        ])\n    }\n}\n\nstatic ADJECTIVES: &[&str] = &[\n    \"pretty\",\n    \"large\",\n    \"big\",\n    \"small\",\n    \"tall\",\n    \"short\",\n    \"long\",\n    \"handsome\",\n    \"plain\",\n    \"quaint\",\n    \"clean\",\n    \"elegant\",\n    \"easy\",\n    \"angry\",\n    \"crazy\",\n    \"helpful\",\n    \"mushy\",\n    \"odd\",\n    \"unsightly\",\n    \"adorable\",\n    \"important\",\n    \"inexpensive\",\n    \"cheap\",\n    \"expensive\",\n    \"fancy\",\n];\n\nstatic COLOURS: &[&str] = &[\n    \"red\", \"yellow\", \"blue\", \"green\", \"pink\", \"brown\", \"purple\", \"brown\", \"white\", \"black\",\n    \"orange\",\n];\n\nstatic NOUNS: &[&str] = &[\n    \"table\", \"chair\", \"house\", \"bbq\", \"desk\", \"car\", \"pony\", \"cookie\", \"sandwich\", \"burger\",\n    \"pizza\", \"mouse\", \"keyboard\",\n];\n"
  },
  {
    "path": "packages/dioxus/src/launch.rs",
    "content": "#![allow(clippy::new_without_default)]\n#![allow(unused)]\nuse dioxus_config_macro::*;\nuse dioxus_core::{Element, LaunchConfig};\nuse std::any::Any;\n\nuse crate::prelude::*;\n\n/// Launch your Dioxus application with the given root component, context and config.\n/// The platform will be determined from cargo features.\n///\n/// For a builder API, see `LaunchBuilder` defined in the `dioxus` crate.\n///\n/// # Feature selection\n///\n/// - `web`: Enables the web platform.\n/// - `desktop`: Enables the desktop platform.\n/// - `mobile`: Enables the mobile (ios + android webview) platform.\n/// - `server`: Enables the server (axum + server-side-rendering) platform.\n/// - `liveview`: Enables the liveview (websocke) platform.\n/// - `native`: Enables the native (wgpu + winit renderer) platform.\n///\n/// Currently `native` is its own platform that is not compatible with desktop or mobile since it\n/// unifies both platforms into one. If \"desktop\" and \"native\" are enabled, then the native renderer\n/// will be used.\n///\n/// # Feature priority\n///\n/// If multiple renderers are enabled, the order of priority goes:\n///\n/// 1. liveview\n/// 2. server\n/// 3. native\n/// 4. desktop\n/// 5. mobile\n/// 6. web\n///\n/// However, we don't recommend enabling multiple renderers at the same time due to feature conflicts\n/// and bloating of the binary size.\n///\n/// # Example\n/// ```rust, no_run\n/// use dioxus::prelude::*;\n///\n/// fn main() {\n///     dioxus::launch(app);\n/// }\n///\n/// fn app() -> Element {\n///     rsx! {\n///         div { \"Hello, world!\" }\n///     }\n/// }\n/// ```\npub fn launch(app: fn() -> Element) {\n    #[allow(deprecated)]\n    LaunchBuilder::new().launch(app)\n}\n\n/// A builder for a fullstack app.\n#[must_use]\npub struct LaunchBuilder {\n    platform: KnownPlatform,\n    contexts: Vec<ContextFn>,\n    configs: Vec<Box<dyn Any>>,\n}\n\npub type LaunchFn = fn(fn() -> Element, Vec<ContextFn>, Vec<Box<dyn Any>>);\n\n/// A context function is a Send and Sync closure that returns a boxed trait object\npub type ContextFn = Box<dyn Fn() -> Box<dyn Any> + Send + Sync + 'static>;\n\nenum KnownPlatform {\n    Web,\n    Desktop,\n    Mobile,\n    Server,\n    Liveview,\n    Native,\n    Other(LaunchFn),\n}\n\n#[allow(clippy::redundant_closure)] // clippy doesn't understand our coercion to fn\nimpl LaunchBuilder {\n    /// Create a new builder for your application. This will create a launch configuration for the current platform based on the features enabled on the `dioxus` crate.\n    // If you aren't using a third party renderer and this is not a docs.rs build, generate a warning about no renderer being enabled\n    #[cfg_attr(\n        all(not(any(\n            docsrs,\n            feature = \"third-party-renderer\",\n            feature = \"liveview\",\n            feature = \"desktop\",\n            feature = \"mobile\",\n            feature = \"web\",\n            feature = \"fullstack\",\n        ))),\n        deprecated(\n            note = \"No renderer is enabled. You must enable a renderer feature on the dioxus crate before calling the launch function.\\nAdd `web`, `desktop`, `mobile`, or `fullstack` to the `features` of dioxus field in your Cargo.toml.\\n# Example\\n```toml\\n# ...\\n[dependencies]\\ndioxus = { version = \\\"0.5.0\\\", features = [\\\"web\\\"] }\\n# ...\\n```\"\n        )\n    )]\n    pub fn new() -> LaunchBuilder {\n        let platform = if cfg!(feature = \"native\") {\n            KnownPlatform::Native\n        } else if cfg!(feature = \"desktop\") {\n            KnownPlatform::Desktop\n        } else if cfg!(feature = \"mobile\") {\n            KnownPlatform::Mobile\n        } else if cfg!(feature = \"web\") {\n            KnownPlatform::Web\n        } else if cfg!(feature = \"server\") {\n            KnownPlatform::Server\n        } else if cfg!(feature = \"liveview\") {\n            KnownPlatform::Liveview\n        } else {\n            panic!(\"No platform feature enabled. Please enable one of the following features: liveview, desktop, mobile, web, tui, fullstack to use the launch API.\")\n        };\n\n        LaunchBuilder {\n            platform,\n            contexts: Vec::new(),\n            configs: Vec::new(),\n        }\n    }\n\n    /// Launch your web application.\n    #[cfg(feature = \"web\")]\n    #[cfg_attr(docsrs, doc(cfg(feature = \"web\")))]\n    pub fn web() -> LaunchBuilder {\n        LaunchBuilder {\n            platform: KnownPlatform::Web,\n            contexts: Vec::new(),\n            configs: Vec::new(),\n        }\n    }\n\n    /// Launch your desktop application.\n    #[cfg(feature = \"desktop\")]\n    #[cfg_attr(docsrs, doc(cfg(feature = \"desktop\")))]\n    pub fn desktop() -> LaunchBuilder {\n        LaunchBuilder {\n            platform: KnownPlatform::Desktop,\n            contexts: Vec::new(),\n            configs: Vec::new(),\n        }\n    }\n\n    /// Launch your fullstack axum server.\n    #[cfg(all(feature = \"fullstack\", feature = \"server\"))]\n    #[cfg_attr(docsrs, doc(cfg(all(feature = \"fullstack\", feature = \"server\"))))]\n    pub fn server() -> LaunchBuilder {\n        LaunchBuilder {\n            platform: KnownPlatform::Server,\n            contexts: Vec::new(),\n            configs: Vec::new(),\n        }\n    }\n\n    /// Launch your fullstack application.\n    #[cfg(feature = \"mobile\")]\n    #[cfg_attr(docsrs, doc(cfg(feature = \"mobile\")))]\n    pub fn mobile() -> LaunchBuilder {\n        LaunchBuilder {\n            platform: KnownPlatform::Mobile,\n            contexts: Vec::new(),\n            configs: Vec::new(),\n        }\n    }\n\n    /// Provide a custom launch function for your application.\n    ///\n    /// Useful for third party renderers to tap into the launch builder API without having to reimplement it.\n    ///\n    /// # Example\n    /// ```rust, no_run\n    /// use dioxus::prelude::*;\n    /// use std::any::Any;\n    ///\n    /// #[derive(Default)]\n    /// struct Config;\n    ///\n    /// fn my_custom_launcher(root: fn() -> Element, contexts: Vec<Box<dyn Fn() -> Box<dyn Any> + Send + Sync>>, cfg: Vec<Box<dyn Any>>) {\n    ///     println!(\"launching with root: {:?}\", root());\n    ///     loop {\n    ///         println!(\"running...\");\n    ///     }\n    /// }\n    ///\n    /// fn app() -> Element {\n    ///     rsx! {\n    ///         div { \"Hello, world!\" }\n    ///     }\n    /// }\n    ///\n    /// dioxus::LaunchBuilder::custom(my_custom_launcher).launch(app);\n    /// ```\n    pub fn custom(launch_fn: LaunchFn) -> LaunchBuilder {\n        LaunchBuilder {\n            platform: KnownPlatform::Other(launch_fn),\n            contexts: vec![],\n            configs: Vec::new(),\n        }\n    }\n\n    /// Inject state into the root component's context that is created on the thread that the app is launched on.\n    ///\n    /// # Example\n    /// ```rust, no_run\n    /// use dioxus::prelude::*;\n    /// use std::any::Any;\n    ///\n    /// #[derive(Default)]\n    /// struct MyState {\n    ///     value: i32,\n    /// }\n    ///\n    /// fn app() -> Element {\n    ///     rsx! {\n    ///         div { \"Hello, world!\" }\n    ///     }\n    /// }\n    ///\n    /// dioxus::LaunchBuilder::new()\n    ///     .with_context_provider(|| Box::new(MyState { value: 42 }))\n    ///     .launch(app);\n    /// ```\n    pub fn with_context_provider(\n        mut self,\n        state: impl Fn() -> Box<dyn Any> + Send + Sync + 'static,\n    ) -> Self {\n        self.contexts.push(Box::new(state));\n        self\n    }\n\n    /// Inject state into the root component's context.\n    ///\n    /// # Example\n    /// ```rust, no_run\n    /// use dioxus::prelude::*;\n    /// use std::any::Any;\n    ///\n    /// #[derive(Clone)]\n    /// struct MyState {\n    ///     value: i32,\n    /// }\n    ///\n    /// fn app() -> Element {\n    ///     rsx! {\n    ///         div { \"Hello, world!\" }\n    ///     }\n    /// }\n    ///\n    /// dioxus::LaunchBuilder::new()\n    ///     .with_context(MyState { value: 42 })\n    ///     .launch(app);\n    /// ```\n    pub fn with_context(mut self, state: impl Any + Clone + Send + Sync + 'static) -> Self {\n        self.contexts\n            .push(Box::new(move || Box::new(state.clone())));\n        self\n    }\n\n    /// Provide a platform-specific config to the builder.\n    ///\n    /// # Example\n    /// ```rust, no_run\n    /// use dioxus::prelude::*;\n    /// use dioxus_desktop::{Config, WindowBuilder};\n    ///\n    /// fn app() -> Element {\n    ///     rsx! {\n    ///         div { \"Hello, world!\" }\n    ///     }\n    /// }\n    ///\n    /// dioxus::LaunchBuilder::new()\n    ///    .with_cfg(desktop! {\n    ///        Config::new().with_window(\n    ///            WindowBuilder::new()\n    ///                .with_title(\"My App\")\n    ///        )\n    ///     })\n    ///     .launch(app);\n    /// ```\n    pub fn with_cfg(mut self, config: impl LaunchConfig) -> Self {\n        self.configs.push(Box::new(config));\n        self\n    }\n\n    /// Launch your application.\n    #[allow(clippy::diverging_sub_expression)]\n    pub fn launch(self, app: fn() -> Element) {\n        let Self {\n            platform,\n            contexts,\n            configs,\n        } = self;\n\n        // Make sure to turn on the logger if the user specified the logger feaature\n        #[cfg(feature = \"logger\")]\n        dioxus_logger::initialize_default();\n\n        // Set any flags if we're running under fullstack\n        #[cfg(feature = \"fullstack\")]\n        {\n            use dioxus_fullstack::{get_server_url, set_server_url};\n\n            // Make sure to set the server_fn endpoint if the user specified the fullstack feature\n            // We only set this on native targets\n            #[cfg(any(feature = \"desktop\", feature = \"mobile\", feature = \"native\"))]\n            if get_server_url().is_empty() {\n                let serverurl = format!(\n                    \"http://{}:{}\",\n                    std::env::var(\"DIOXUS_DEVSERVER_IP\")\n                        .unwrap_or_else(|_| \"127.0.0.1\".to_string()),\n                    std::env::var(\"DIOXUS_DEVSERVER_PORT\").unwrap_or_else(|_| \"8080\".to_string())\n                )\n                .leak();\n\n                set_server_url(serverurl);\n            }\n\n            // If there is a base path set, call server functions from that base path\n            #[cfg(feature = \"web\")]\n            if let Some(base_path) = dioxus_cli_config::base_path() {\n                let base_path = base_path.trim_matches('/');\n                set_server_url(format!(\"{}/{}\", get_server_url(), base_path).leak());\n            }\n        }\n\n        // If native is specified, we override the webview launcher\n        #[cfg(feature = \"native\")]\n        if matches!(platform, KnownPlatform::Native) {\n            return dioxus_native::launch_cfg(app, contexts, configs);\n        }\n\n        #[cfg(feature = \"mobile\")]\n        if matches!(platform, KnownPlatform::Mobile) {\n            return dioxus_desktop::launch::launch(app, contexts, configs);\n        }\n\n        #[cfg(feature = \"desktop\")]\n        if matches!(platform, KnownPlatform::Desktop) {\n            return dioxus_desktop::launch::launch(app, contexts, configs);\n        }\n\n        #[cfg(feature = \"server\")]\n        if matches!(platform, KnownPlatform::Server) {\n            return dioxus_server::launch_cfg(app, contexts, configs);\n        }\n\n        #[cfg(feature = \"web\")]\n        if matches!(platform, KnownPlatform::Web) {\n            return dioxus_web::launch::launch(app, contexts, configs);\n        }\n\n        #[cfg(feature = \"liveview\")]\n        if matches!(platform, KnownPlatform::Liveview) {\n            return dioxus_liveview::launch::launch(app, contexts, configs);\n        }\n\n        // If the platform is not one of the above, then we assume it's a custom platform\n        if let KnownPlatform::Other(launch_fn) = platform {\n            return launch_fn(app, contexts, configs);\n        }\n\n        // If we're here, then we have no platform feature enabled and third-party-renderer is enabled\n        if cfg!(feature = \"third-party-renderer\") {\n            panic!(\"No first party renderer feature enabled. It looks like you are trying to use a third party renderer. You will need to use the launch function from the third party renderer crate.\");\n        }\n\n        panic!(\"No platform feature enabled. Please enable one of the following features: liveview, desktop, mobile, web, tui, fullstack to use the launch API.\")\n    }\n}\n"
  },
  {
    "path": "packages/dioxus/src/lib.rs",
    "content": "#![doc = include_str!(\"../README.md\")]\n//!\n//! ## Dioxus Crate Features\n//!\n//! This crate has several features that can be enabled to change the active renderer and enable various integrations:\n//!\n//! - `signals`: (default) re-exports `dioxus-signals`\n//! - `macro`: (default) re-exports `dioxus-macro`\n//! - `html`: (default) exports `dioxus-html` as the default elements to use in rsx\n//! - `hooks`: (default) re-exports `dioxus-hooks`\n//! - `hot-reload`: (default) enables hot rsx reloading in all renderers that support it\n//! - `router`: exports the [router](https://dioxuslabs.com/learn/0.7/essentials/router/) and enables any router features for the current platform\n//! - `third-party-renderer`: Just disables warnings about no active platform when no renderers are enabled\n//! - `logger`: Enable the default tracing subscriber for Dioxus apps\n//!\n//! Platform features (the current platform determines what platform the [`launch()`] function runs):\n//!\n//! - `fullstack`: enables the fullstack platform. This must be used in combination with the `web` feature for wasm builds and `server` feature for server builds\n//! - `desktop`: enables the desktop platform\n//! - `mobile`: enables the mobile platform\n//! - `web`: enables the web platform. If the fullstack platform is enabled, this will set the fullstack platform to client mode\n//! - `liveview`: enables the liveview platform\n//! - `server`: enables the server variant of dioxus\n#![doc(html_logo_url = \"https://avatars.githubusercontent.com/u/79236386\")]\n#![doc(html_favicon_url = \"https://avatars.githubusercontent.com/u/79236386\")]\n#![cfg_attr(docsrs, feature(doc_cfg))]\n\npub use dioxus_core;\n#[doc(inline)]\npub use dioxus_core::{CapturedError, Ok, Result};\n\n#[cfg(feature = \"launch\")]\n#[cfg_attr(docsrs, doc(cfg(feature = \"launch\")))]\nmod launch;\n\npub use dioxus_core as core;\n\n#[cfg(feature = \"launch\")]\n#[cfg_attr(docsrs, doc(cfg(feature = \"launch\")))]\npub use crate::launch::*;\n\n#[cfg(feature = \"hooks\")]\n#[cfg_attr(docsrs, doc(cfg(feature = \"hooks\")))]\npub use dioxus_hooks as hooks;\n\n#[cfg(feature = \"signals\")]\n#[cfg_attr(docsrs, doc(cfg(feature = \"signals\")))]\npub use dioxus_signals as signals;\n\n#[cfg(feature = \"signals\")]\n#[cfg_attr(docsrs, doc(cfg(feature = \"signals\")))]\npub use dioxus_stores as stores;\n\npub mod events {\n    #[cfg(feature = \"html\")]\n    #[cfg_attr(docsrs, doc(cfg(feature = \"html\")))]\n    pub use dioxus_html::events::*;\n}\n\n#[cfg(feature = \"document\")]\n#[cfg_attr(docsrs, doc(cfg(feature = \"document\")))]\npub use dioxus_document as document;\n\n#[cfg(feature = \"document\")]\n#[cfg_attr(docsrs, doc(cfg(feature = \"document\")))]\npub use dioxus_history as history;\n\n#[cfg(feature = \"html\")]\n#[cfg_attr(docsrs, doc(cfg(feature = \"html\")))]\npub use dioxus_html as html;\n\n#[cfg(feature = \"macro\")]\n#[cfg_attr(docsrs, doc(cfg(feature = \"macro\")))]\npub use dioxus_core_macro as core_macro;\n\n#[cfg(feature = \"logger\")]\n#[cfg_attr(docsrs, doc(cfg(feature = \"logger\")))]\npub use dioxus_logger as logger;\n\n#[cfg(feature = \"cli-config\")]\n#[cfg_attr(docsrs, doc(cfg(feature = \"cli-config\")))]\npub use dioxus_cli_config as cli_config;\n\n#[cfg(feature = \"server\")]\n#[cfg_attr(docsrs, doc(cfg(feature = \"server\")))]\npub use dioxus_server as server;\n\n#[cfg(feature = \"server\")]\npub use dioxus_server::serve;\n\n#[cfg(feature = \"devtools\")]\n#[cfg_attr(docsrs, doc(cfg(feature = \"devtools\")))]\npub use dioxus_devtools as devtools;\n\n#[cfg(feature = \"web\")]\n#[cfg_attr(docsrs, doc(cfg(feature = \"web\")))]\npub use dioxus_web as web;\n\n#[cfg(feature = \"router\")]\n#[cfg_attr(docsrs, doc(cfg(feature = \"router\")))]\npub use dioxus_router as router;\n\n#[cfg(feature = \"fullstack\")]\n#[cfg_attr(docsrs, doc(cfg(feature = \"fullstack\")))]\npub use dioxus_fullstack as fullstack;\n\n#[cfg(feature = \"desktop\")]\n#[cfg_attr(docsrs, doc(cfg(feature = \"desktop\")))]\npub use dioxus_desktop as desktop;\n\n#[cfg(feature = \"mobile\")]\n#[cfg_attr(docsrs, doc(cfg(feature = \"mobile\")))]\npub use dioxus_desktop as mobile;\n\n#[cfg(feature = \"native\")]\n#[cfg_attr(docsrs, doc(cfg(feature = \"native\")))]\npub use dioxus_native as native;\n\n#[cfg(feature = \"liveview\")]\n#[cfg_attr(docsrs, doc(cfg(feature = \"liveview\")))]\npub use dioxus_liveview as liveview;\n\n#[cfg(feature = \"ssr\")]\n#[cfg_attr(docsrs, doc(cfg(feature = \"ssr\")))]\npub use dioxus_ssr as ssr;\n\n#[cfg(feature = \"warnings\")]\n#[cfg_attr(docsrs, doc(cfg(feature = \"warnings\")))]\npub use warnings;\n\npub use dioxus_config_macros as config_macros;\n\n#[cfg(feature = \"wasm-split\")]\n#[cfg_attr(docsrs, doc(cfg(feature = \"wasm-split\")))]\npub use wasm_splitter as wasm_split;\n\npub use subsecond;\n\n#[cfg(feature = \"asset\")]\n#[cfg_attr(docsrs, doc(cfg(feature = \"asset\")))]\n#[doc(inline)]\npub use dioxus_asset_resolver as asset_resolver;\n\npub mod prelude {\n    #[cfg(feature = \"document\")]\n    #[cfg_attr(docsrs, doc(cfg(feature = \"document\")))]\n    #[doc(inline)]\n    pub use dioxus_document::{self as document, Meta, Stylesheet, Title};\n\n    #[cfg(feature = \"document\")]\n    #[cfg_attr(docsrs, doc(cfg(feature = \"document\")))]\n    #[doc(inline)]\n    pub use dioxus_history::{history, History};\n\n    #[cfg(feature = \"launch\")]\n    #[cfg_attr(docsrs, doc(cfg(feature = \"launch\")))]\n    #[doc(inline)]\n    pub use crate::launch::*;\n\n    #[cfg(feature = \"hooks\")]\n    #[cfg_attr(docsrs, doc(cfg(feature = \"hooks\")))]\n    #[doc(inline)]\n    pub use crate::hooks::*;\n\n    #[cfg(feature = \"signals\")]\n    #[cfg_attr(docsrs, doc(cfg(feature = \"signals\")))]\n    #[doc(inline)]\n    pub use dioxus_signals::{self, *};\n\n    #[cfg(feature = \"signals\")]\n    pub use dioxus_stores::{self, store, use_store, GlobalStore, ReadStore, Store, WriteStore};\n\n    #[cfg(feature = \"macro\")]\n    #[cfg_attr(docsrs, doc(cfg(feature = \"macro\")))]\n    #[allow(deprecated)]\n    #[doc(inline)]\n    pub use dioxus_core_macro::{component, rsx, Props};\n\n    #[cfg(feature = \"launch\")]\n    #[cfg_attr(docsrs, doc(cfg(feature = \"launch\")))]\n    pub use dioxus_config_macro::*;\n\n    #[cfg(feature = \"html\")]\n    #[cfg_attr(docsrs, doc(cfg(feature = \"html\")))]\n    pub use dioxus_html as dioxus_elements;\n\n    #[cfg(feature = \"html\")]\n    #[cfg_attr(docsrs, doc(cfg(feature = \"html\")))]\n    #[doc(inline)]\n    pub use dioxus_elements::{Code, Key, Location, Modifiers};\n\n    #[cfg(feature = \"html\")]\n    #[cfg_attr(docsrs, doc(cfg(feature = \"html\")))]\n    #[doc(no_inline)]\n    pub use dioxus_elements::{\n        events::*, extensions::*, global_attributes, keyboard_types, svg_attributes, traits::*,\n        GlobalAttributesExtension, SvgAttributesExtension,\n    };\n\n    #[cfg(feature = \"devtools\")]\n    #[cfg_attr(docsrs, doc(cfg(feature = \"devtools\")))]\n    pub use dioxus_devtools;\n\n    pub use dioxus_core;\n\n    #[cfg(feature = \"fullstack\")]\n    #[cfg_attr(docsrs, doc(cfg(feature = \"fullstack\")))]\n    #[doc(inline)]\n    pub use dioxus_fullstack::{\n        self as dioxus_fullstack, delete, get, patch, post, put, server, use_loader,\n        use_server_cached, use_server_future, HttpError, OrHttpError, ServerFnError,\n        ServerFnResult, StatusCode,\n    };\n\n    #[cfg(feature = \"server\")]\n    #[cfg_attr(docsrs, doc(cfg(feature = \"server\")))]\n    #[doc(inline)]\n    pub use dioxus_server::{self, serve, DioxusRouterExt, ServeConfig, ServerFunction};\n\n    #[cfg(feature = \"router\")]\n    #[cfg_attr(docsrs, doc(cfg(feature = \"router\")))]\n    pub use dioxus_router;\n\n    #[cfg(feature = \"router\")]\n    #[cfg_attr(docsrs, doc(cfg(feature = \"router\")))]\n    #[doc(inline)]\n    pub use dioxus_router::{\n        hooks::*, navigator, use_navigator, GoBackButton, GoForwardButton, Link, NavigationTarget,\n        Outlet, Routable, Router,\n    };\n\n    #[cfg(feature = \"asset\")]\n    #[cfg_attr(docsrs, doc(cfg(feature = \"asset\")))]\n    #[doc(inline)]\n    pub use manganis::{self, *};\n\n    #[cfg(feature = \"wasm-split\")]\n    #[cfg_attr(docsrs, doc(cfg(feature = \"wasm-split\")))]\n    pub use wasm_splitter as wasm_split;\n\n    #[doc(inline)]\n    pub use dioxus_core::{\n        consume_context, provide_context, spawn, suspend, try_consume_context, use_drop, use_hook,\n        AnyhowContext, Attribute, Callback, Component, Element, ErrorBoundary, ErrorContext, Event,\n        EventHandler, Fragment, HasAttributes, IntoDynNode, RenderError, Result, ScopeId,\n        SuspenseBoundary, SuspenseContext, VNode, VirtualDom,\n    };\n\n    #[cfg(feature = \"logger\")]\n    pub use dioxus_logger::tracing::{debug, error, info, trace, warn};\n}\n"
  },
  {
    "path": "packages/document/Cargo.toml",
    "content": "[package]\nname = \"dioxus-document\"\nedition = \"2021\"\nversion.workspace = true\nauthors = [\"Jonathan Kelley\", \"Evan Almloff\"]\nlicense = \"MIT OR Apache-2.0\"\nrepository = \"https://github.com/DioxusLabs/dioxus/\"\nhomepage = \"https://dioxuslabs.com\"\ndescription = \"CLI Configuration for dioxus-cli\"\nkeywords = [\"dom\", \"ui\", \"gui\", \"react\"]\n\n[dependencies]\ndioxus-core = { workspace = true }\ndioxus-core-types = { workspace = true }\ndioxus-core-macro = { workspace = true }\ndioxus-html = { workspace = true }\ntracing = { workspace = true }\nserde = { workspace = true }\nserde_json = { workspace = true }\nfutures-channel = { workspace = true }\nfutures-util = { workspace = true }\ngenerational-box = { workspace = true }\n\n[build-dependencies]\nlazy-js-bundle = { workspace = true }\n\n[dev-dependencies]\ndioxus = { workspace = true }\n"
  },
  {
    "path": "packages/document/assets/script.js",
    "content": "// this script is included as an asset!() for a test\n"
  },
  {
    "path": "packages/document/assets/style.css",
    "content": "/* this stylesheet is included as an asset!() for a test */\n"
  },
  {
    "path": "packages/document/build.rs",
    "content": "fn main() {\n    // If any TS files change, re-run the build script\n    lazy_js_bundle::LazyTypeScriptBindings::new()\n        .with_watching(\"./src/ts\")\n        .with_binding(\"./src/ts/head.ts\", \"./src/js/head.js\")\n        .run();\n}\n"
  },
  {
    "path": "packages/document/docs/eval.md",
    "content": "# Communicating with JavaScript\n\nYou can use the `eval` function to execute JavaScript code in your application with the desktop, mobile, web or liveview renderers. Eval takes a block of JavaScript code (that may be asynchronous) and returns a `Eval` object that you can use to send data to the JavaScript code and receive data from it.\n\n<div class=\"warning\">\n\n## Safety\n\nPlease be careful when executing JavaScript code with `eval`. You should only execute code that you trust. **This applies especially to web targets, where the JavaScript context has access to most, if not all of your application data.** Running untrusted code can lead to a [cross-site scripting](https://developer.mozilla.org/en-US/docs/Glossary/Cross-site_scripting) (XSS) vulnerability.\n\n</div>\n\n```rust\nuse dioxus::prelude::*;\n\nfn App() -> Element {\n    rsx! {\n        button {\n            onclick: move |_| async move {\n                // Eval is a global function you can use anywhere inside Dioxus. It will execute the given JavaScript code.\n                let result = document::eval(r#\"console.log(\"Hello World\");\n                return \"Hello World\";\"#);\n\n                // You can use the `await` keyword to wait for the result of the JavaScript code.\n                println!(\"{:?}\", result.await);\n            },\n            \"Log Hello World\"\n        }\n    }\n}\n```\n\n## Sending data to JavaScript\n\nWhen you execute JavaScript code with `eval`, you can pass data to it by formatting the value into the JavaScript code or sending values to the `Eval` channel.\n\n```rust\nuse dioxus::prelude::*;\n\nfn app() -> Element {\n    rsx! {\n        button {\n            onclick: move |_| {\n                // You can pass initial data to the eval function by formatting it into the JavaScript code.\n                const LOOP_COUNT: usize = 10;\n                let eval = document::eval(&format!(r#\"for(let i = 0; i < {LOOP_COUNT}; i++) {{\n                    // You can receive values asynchronously with the `await dioxus.recv()` method.\n                    let value = await dioxus.recv();\n                    console.log(\"Received\", value);\n                }}\"#));\n\n                // You can send values from rust to the JavaScript code with the `send` method on the object returned by `eval`.\n                for i in 0..LOOP_COUNT {\n                    eval.send(i).unwrap();\n                }\n            },\n            \"Log Count\"\n        }\n    }\n}\n```\n\n## Sending data from JavaScript\n\nThe `Eval` struct also contains methods for receiving values you send from JavaScript. You can use the `dioxus.send()` method to send values to the JavaScript code and the `Eval::recv()` method to receive values from the JavaScript code.\n\n```rust\nuse dioxus::prelude::*;\n\nfn app() -> Element {\n    rsx! {\n        button {\n            onclick: move |_| async move {\n                // You can send values from rust to the JavaScript code by using the `send` method on the object returned by `eval`.\n                let mut eval = document::eval(r#\"for(let i = 0; i < 10; i++) {\n                    // You can send values asynchronously with the `dioxus.send()` method.\n                    dioxus.send(i);\n                }\"#);\n\n                // You can receive values from the JavaScript code with the `recv` method on the object returned by `eval`.\n                for _ in 0..10 {\n                    let value: i32 = eval.recv().await.unwrap();\n                    println!(\"Received {}\", value);\n                }\n            },\n            \"Log Count\"\n        }\n    }\n}\n```\n\n## Interacting with the DOM with Eval\n\nYou can also use the `eval` function to execute JavaScript code that reads or modifies the DOM. If you want to interact with the mounted DOM, you need to use `eval` inside the [`use_effect`](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_effect.html) hook which runs after the component has been mounted.\n\n```rust\nuse dioxus::prelude::*;\n\nconst SCRIPT: &str = r#\"\n    let element = document.getElementById(\"my-element\");\n    element.innerHTML = \"Hello World\";\n    return element.getAttribute(\"data-count\");\n\"#;\n\nfn app() -> Element {\n    // ❌ You shouldn't run eval in the body of a component. This will run before the component has been mounted\n    // document::eval(SCRIPT);\n\n    // ✅ You should run eval inside an effect or event. This will run after the component has been mounted\n    use_effect(move || {\n        spawn(async {\n            let count = document::eval(SCRIPT).await;\n            println!(\"Count is {:?}\", count);\n        });\n    });\n\n\n    rsx! {\n        div {\n            id: \"my-element\",\n            \"data-count\": \"123\",\n        }\n    }\n}\n```\n"
  },
  {
    "path": "packages/document/docs/head.md",
    "content": "# Modifying the Head\n\nDioxus includes a series of components that render into the head of the page:\n\n- [Title]\n- [Meta]\n- [document::Link](crate::Link)\n- [Script]\n- [Style]\n\nEach of these components can be used to add extra information to the head of the page. For example, you can use the `Title` component to set the title of the page, or the `Meta` component to add extra metadata to the page.\n\n## Limitations\n\nComponents that render into the head of the page do have a few key limitations:\n\n- With the exception of the `Title` component, all components that render into the head cannot be modified after the first time they are rendered.\n- Components that render into the head will not be removed even after the component is removed from the tree.\n- Components that render into the head do not have a guaranteed ordering; thus, components should ideally not be order dependent, since they may not appear in the head in the order they are defined.\n\n## Example\n\n```rust, no_run\n# use dioxus::prelude::*;\nfn RedirectToDioxusHomepageWithoutJS() -> Element {\n    rsx! {\n        // You can use the meta component to render a meta tag into the head of the page\n        // This meta tag will redirect the user to the dioxuslabs homepage in 10 seconds\n        document::Meta {\n            http_equiv: \"refresh\",\n            content: \"10;url=https://dioxuslabs.com\",\n        }\n    }\n}\n```\n\n## Overcoming Ordering Issues\n\nSince Dioxus does not guarantee head element ordering, one can use es6 imports as a more predictable way to handle dependencies.\n\n```rust, ignore\n# use dioxus::prelude::*;\nstatic HIGHLIGHT: Asset = asset!(\"/assets/highlight/es/highlight.min.js\");\nstatic RUST: Asset = asset!(\"/assets/highlight/es/languages/rust.min.js\");\nstatic STYLE: Asset = asset!(\"/assets/highlight/styles/atom-one-dark.css\");\n\nfn App() -> Element {\n    rsx! {\n        document::Link { rel: \"stylesheet\", href: STYLE }\n        document::Script {\n            type: \"module\",\n            r#\"import hljs from \"{HIGHLIGHT}\";\n            import rust from \"{RUST}\";\n            hljs.registerLanguage('rust', rust);\n            hljs.highlightAll();\"#\n        }\n        pre {\n            code {\n                class: \"language-rust\",\n                \"fn main() {{\\nprintln!(\\\"Hello, world!\\\");\\n}}\"\n            }\n        }\n    }\n}\n```\n\n## Fullstack Rendering\n\nHead components are compatible with fullstack rendering, but only head components that are rendered in the initial render (before suspense boundaries resolve) will be rendered into the head.\n\nIf you have any important metadata that you want to render into the head, make sure to render it outside of any pending suspense boundaries.\n\n```rust, no_run\n# use dioxus::prelude::*;\n# #[component]\n# fn LoadData(children: Element) -> Element { unimplemented!() }\nfn App() -> Element {\n    rsx! {\n        // This will render in SSR\n        document::Title { \"My Page\" }\n        SuspenseBoundary {\n            fallback: |_| rsx! { \"Loading...\" },\n            LoadData {\n                // This will only be rendered on the client after hydration so it may not be visible to search engines\n                document::Meta { name: \"description\", content: \"My Page\" }\n            }\n        }\n    }\n}\n```\n"
  },
  {
    "path": "packages/document/src/document.rs",
    "content": "use std::sync::Arc;\n\nuse super::*;\n\n/// A context for the document\npub type DocumentContext = Arc<dyn Document>;\n\nfn format_string_for_js(s: &str) -> String {\n    let escaped = s\n        .replace('\\\\', \"\\\\\\\\\")\n        .replace('\\n', \"\\\\n\")\n        .replace('\\r', \"\\\\r\")\n        .replace('\"', \"\\\\\\\"\");\n    format!(\"\\\"{escaped}\\\"\")\n}\n\nfn format_attributes(attributes: &[(&str, String)]) -> String {\n    let mut formatted = String::from(\"[\");\n    for (key, value) in attributes {\n        formatted.push_str(&format!(\n            \"[{}, {}],\",\n            format_string_for_js(key),\n            format_string_for_js(value)\n        ));\n    }\n    if formatted.ends_with(',') {\n        formatted.pop();\n    }\n    formatted.push(']');\n    formatted\n}\n\n/// Create a new element in the head with javascript through the [`Document::eval`] method\n///\n/// This can be used to implement the head element creation logic for most [`Document`] implementations.\npub fn create_element_in_head(\n    tag: &str,\n    attributes: &[(&str, String)],\n    children: Option<String>,\n) -> String {\n    let helpers = include_str!(\"./js/head.js\");\n    let attributes = format_attributes(attributes);\n    let children = children\n        .as_deref()\n        .map(format_string_for_js)\n        .unwrap_or(\"null\".to_string());\n    let tag = format_string_for_js(tag);\n    format!(r#\"{helpers};window.createElementInHead({tag}, {attributes}, {children});\"#)\n}\n\n/// A provider for document-related functionality.\n///\n/// Provides things like a history API, a title, a way to run JS, and some other basics/essentials used\n/// by nearly every platform.\n///\n/// An integration with some kind of navigation history.\n///\n/// Depending on your use case, your implementation may deviate from the described procedure. This\n/// is fine, as long as both `current_route` and `current_query` match the described format.\n///\n/// However, you should document all deviations. Also, make sure the navigation is user-friendly.\n/// The described behaviors are designed to mimic a web browser, which most users should already\n/// know. Deviations might confuse them.\npub trait Document: 'static {\n    /// Run `eval` against this document, returning an [`Eval`] that can be used to await the result.\n    fn eval(&self, js: String) -> Eval;\n\n    /// Set the title of the document\n    fn set_title(&self, title: String) {\n        self.eval(format!(\"document.title = {title:?};\"));\n    }\n\n    /// Create a new element in the head\n    fn create_head_element(\n        &self,\n        name: &str,\n        attributes: &[(&str, String)],\n        contents: Option<String>,\n    ) {\n        // This default implementation remains to make the trait compatible with the 0.6 version, but it should not be used\n        // The element should only be created inside an effect so it is not called while the component is suspended\n        self.eval(create_element_in_head(name, attributes, contents));\n    }\n\n    /// Create a new meta tag in the head\n    fn create_meta(&self, props: MetaProps) {\n        let attributes = props.attributes();\n        self.create_head_element(\"meta\", &attributes, None);\n    }\n\n    /// Create a new script tag in the head\n    fn create_script(&self, props: ScriptProps) {\n        let attributes = props.attributes();\n        self.create_head_element(\"script\", &attributes, props.script_contents().ok());\n    }\n\n    /// Create a new style tag in the head\n    fn create_style(&self, props: StyleProps) {\n        let attributes = props.attributes();\n        self.create_head_element(\"style\", &attributes, props.style_contents().ok());\n    }\n\n    /// Create a new link tag in the head\n    fn create_link(&self, props: LinkProps) {\n        let attributes = props.attributes();\n        self.create_head_element(\"link\", &attributes, None);\n    }\n\n    /// Check if we should create a new head component at all. If it returns false, the head component will be skipped.\n    ///\n    /// This runs once per head component and is used to hydrate head components in fullstack.\n    fn create_head_component(&self) -> bool {\n        true\n    }\n}\n\n/// A document that does nothing\n#[derive(Default)]\npub struct NoOpDocument;\n\nimpl Document for NoOpDocument {\n    fn eval(&self, _: String) -> Eval {\n        let owner = generational_box::Owner::default();\n        struct NoOpEvaluator;\n        impl Evaluator for NoOpEvaluator {\n            fn poll_join(\n                &mut self,\n                _: &mut std::task::Context<'_>,\n            ) -> std::task::Poll<Result<serde_json::Value, EvalError>> {\n                std::task::Poll::Ready(Err(EvalError::Unsupported))\n            }\n\n            fn poll_recv(\n                &mut self,\n                _: &mut std::task::Context<'_>,\n            ) -> std::task::Poll<Result<serde_json::Value, EvalError>> {\n                std::task::Poll::Ready(Err(EvalError::Unsupported))\n            }\n\n            fn send(&self, _data: serde_json::Value) -> Result<(), EvalError> {\n                Err(EvalError::Unsupported)\n            }\n        }\n        Eval::new(owner.insert(Box::new(NoOpEvaluator)))\n    }\n\n    fn set_title(&self, _: String) {}\n    fn create_meta(&self, _: MetaProps) {}\n    fn create_script(&self, _: ScriptProps) {}\n    fn create_style(&self, _: StyleProps) {}\n    fn create_link(&self, _: LinkProps) {}\n}\n"
  },
  {
    "path": "packages/document/src/elements/link.rs",
    "content": "use super::*;\nuse crate::document;\nuse dioxus_core::{use_hook, VNode};\nuse dioxus_html as dioxus_elements;\n\n#[non_exhaustive]\n#[derive(Clone, Props, PartialEq)]\npub struct OtherLinkProps {\n    pub rel: String,\n    #[props(extends = link, extends = GlobalAttributes)]\n    pub additional_attributes: Vec<Attribute>,\n}\n\n#[non_exhaustive]\n#[derive(Clone, Props, PartialEq)]\npub struct LinkProps {\n    pub rel: Option<String>,\n    pub media: Option<String>,\n    pub title: Option<String>,\n    pub disabled: Option<bool>,\n    pub r#as: Option<String>,\n    pub sizes: Option<String>,\n    /// Links are deduplicated by their href and rel attributes\n    pub href: Option<String>,\n    pub crossorigin: Option<String>,\n    pub referrerpolicy: Option<String>,\n    pub fetchpriority: Option<String>,\n    pub hreflang: Option<String>,\n    pub integrity: Option<String>,\n    pub r#type: Option<String>,\n    pub blocking: Option<String>,\n    #[props(extends = link, extends = GlobalAttributes)]\n    pub additional_attributes: Vec<Attribute>,\n    pub onload: Option<String>,\n}\n\nimpl LinkProps {\n    /// Get all the attributes for the link tag\n    pub fn attributes(&self) -> Vec<(&'static str, String)> {\n        let mut attributes = Vec::new();\n        extend_attributes(&mut attributes, &self.additional_attributes);\n        if let Some(rel) = &self.rel {\n            attributes.push((\"rel\", rel.clone()));\n        }\n        if let Some(media) = &self.media {\n            attributes.push((\"media\", media.clone()));\n        }\n        if let Some(title) = &self.title {\n            attributes.push((\"title\", title.clone()));\n        }\n        if let Some(disabled) = &self.disabled {\n            attributes.push((\"disabled\", disabled.to_string()));\n        }\n        if let Some(r#as) = &self.r#as {\n            attributes.push((\"as\", r#as.clone()));\n        }\n        if let Some(sizes) = &self.sizes {\n            attributes.push((\"sizes\", sizes.clone()));\n        }\n        if let Some(href) = &self.href {\n            attributes.push((\"href\", href.clone()));\n        }\n        if let Some(crossorigin) = &self.crossorigin {\n            attributes.push((\"crossOrigin\", crossorigin.clone()));\n        }\n        if let Some(referrerpolicy) = &self.referrerpolicy {\n            attributes.push((\"referrerPolicy\", referrerpolicy.clone()));\n        }\n        if let Some(fetchpriority) = &self.fetchpriority {\n            attributes.push((\"fetchPriority\", fetchpriority.clone()));\n        }\n        if let Some(hreflang) = &self.hreflang {\n            attributes.push((\"hrefLang\", hreflang.clone()));\n        }\n        if let Some(integrity) = &self.integrity {\n            attributes.push((\"integrity\", integrity.clone()));\n        }\n        if let Some(r#type) = &self.r#type {\n            attributes.push((\"type\", r#type.clone()));\n        }\n        if let Some(blocking) = &self.blocking {\n            attributes.push((\"blocking\", blocking.clone()));\n        }\n        if let Some(onload) = &self.onload {\n            attributes.push((\"onload\", onload.clone()));\n        }\n        attributes\n    }\n}\n\n/// Render a [`<link>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/link) tag into the head of the page.\n///\n/// > The [Link](https://docs.rs/dioxus-router/latest/dioxus_router/components/fn.Link.html) component in dioxus router and this component are completely different.\n/// > This component links resources in the head of the page, while the router component creates clickable links in the body of the page.\n///\n/// # Example\n/// ```rust, no_run\n/// # use dioxus::prelude::*;\n/// fn RedBackground() -> Element {\n///     rsx! {\n///         // You can use the meta component to render a meta tag into the head of the page\n///         // This meta tag will redirect the user to the dioxuslabs homepage in 10 seconds\n///         document::Link {\n///             href: asset!(\"/assets/style.css\"),\n///             rel: \"stylesheet\",\n///         }\n///     }\n/// }\n/// ```\n///\n/// <div class=\"warning\">\n///\n/// Any updates to the props after the first render will not be reflected in the head.\n///\n/// </div>\n#[doc(alias = \"<link>\")]\n#[component]\npub fn Link(props: LinkProps) -> Element {\n    use_update_warning(&props, \"Link {}\");\n\n    use_hook(|| {\n        let document = document();\n        let mut insert_link = document.create_head_component();\n        if let Some(href) = &props.href {\n            if !should_insert_link(href, props.rel.as_deref()) {\n                insert_link = false;\n            }\n        }\n\n        if !insert_link {\n            return;\n        }\n\n        document.create_link(props);\n    });\n\n    VNode::empty()\n}\n\n#[derive(Default, Clone)]\nstruct LinkContext(DeduplicationContext);\n\nfn should_insert_link(href: &str, rel: Option<&str>) -> bool {\n    // Include rel in the deduplication key so that the same href can be used\n    // with different rel values (e.g., rel=\"preload\" and rel=\"stylesheet\")\n    let key = match rel {\n        Some(rel) => format!(\"{href}|{rel}\"),\n        None => href.to_string(),\n    };\n    get_or_insert_root_context::<LinkContext>()\n        .0\n        .should_insert(&key)\n}\n"
  },
  {
    "path": "packages/document/src/elements/meta.rs",
    "content": "use super::*;\nuse crate::document;\nuse dioxus_core::{use_hook, VNode};\nuse dioxus_html as dioxus_elements;\n\n#[non_exhaustive]\n/// Props for the [`Meta`] component\n#[derive(Clone, Props, PartialEq)]\npub struct MetaProps {\n    pub property: Option<String>,\n    pub name: Option<String>,\n    pub charset: Option<String>,\n    pub http_equiv: Option<String>,\n    pub content: Option<String>,\n    pub data: Option<String>,\n    #[props(extends = meta, extends = GlobalAttributes)]\n    pub additional_attributes: Vec<Attribute>,\n}\n\nimpl MetaProps {\n    /// Get all the attributes for the meta tag\n    pub fn attributes(&self) -> Vec<(&'static str, String)> {\n        let mut attributes = Vec::new();\n        extend_attributes(&mut attributes, &self.additional_attributes);\n        if let Some(property) = &self.property {\n            attributes.push((\"property\", property.clone()));\n        }\n        if let Some(name) = &self.name {\n            attributes.push((\"name\", name.clone()));\n        }\n        if let Some(charset) = &self.charset {\n            attributes.push((\"charset\", charset.clone()));\n        }\n        if let Some(http_equiv) = &self.http_equiv {\n            attributes.push((\"http-equiv\", http_equiv.clone()));\n        }\n        if let Some(content) = &self.content {\n            attributes.push((\"content\", content.clone()));\n        }\n        if let Some(data) = &self.data {\n            attributes.push((\"data\", data.clone()));\n        }\n        attributes\n    }\n}\n\n/// Render a [`<meta>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/meta) tag into the head of the page.\n///\n/// # Example\n///\n/// ```rust, no_run\n/// # use dioxus::prelude::*;\n/// fn RedirectToDioxusHomepageWithoutJS() -> Element {\n///     rsx! {\n///         // You can use the meta component to render a meta tag into the head of the page\n///         // This meta tag will redirect the user to the dioxuslabs homepage in 10 seconds\n///         document::Meta {\n///             http_equiv: \"refresh\",\n///             content: \"10;url=https://dioxuslabs.com\",\n///         }\n///     }\n/// }\n/// ```\n///\n/// <div class=\"warning\">\n///\n/// Any updates to the props after the first render will not be reflected in the head.\n///\n/// </div>\n#[component]\n#[doc(alias = \"<meta>\")]\npub fn Meta(props: MetaProps) -> Element {\n    use_update_warning(&props, \"Meta {}\");\n\n    use_hook(|| {\n        let document = document();\n        let insert_link = document.create_head_component();\n\n        if !insert_link {\n            return;\n        }\n\n        document.create_meta(props);\n    });\n\n    VNode::empty()\n}\n"
  },
  {
    "path": "packages/document/src/elements/mod.rs",
    "content": "#![doc = include_str!(\"../../docs/head.md\")]\n\nuse std::{cell::RefCell, collections::HashSet, rc::Rc};\n\nuse dioxus_core::{\n    Attribute, DynamicNode, Element, RenderError, Runtime, ScopeId, Template, TemplateNode,\n};\nuse dioxus_core_macro::*;\n\nmod link;\npub use link::*;\nmod stylesheet;\npub use stylesheet::*;\nmod meta;\npub use meta::*;\nmod script;\npub use script::*;\nmod style;\npub use style::*;\nmod title;\npub use title::*;\n\n/// Warn the user if they try to change props on a element that is injected into the head\n#[allow(unused)]\nfn use_update_warning<T: PartialEq + Clone + 'static>(value: &T, name: &'static str) {\n    #[cfg(debug_assertions)]\n    {\n        use dioxus_core::use_hook;\n\n        let cloned_value = value.clone();\n        let initial = use_hook(move || value.clone());\n\n        if initial != cloned_value {\n            tracing::warn!(\"Changing the props of `{name}` is not supported \");\n        }\n    }\n}\n\n/// An error that can occur when extracting a single text node from a component\npub enum ExtractSingleTextNodeError<'a> {\n    /// The node contained an render error, so we can't extract the text node\n    RenderError(&'a RenderError),\n    /// There was only one child, but it wasn't a text node\n    NonTextNode,\n    /// There is multiple child nodes\n    NonTemplate,\n}\n\nimpl ExtractSingleTextNodeError<'_> {\n    /// Log a warning depending on the error\n    pub fn log(&self, component: &str) {\n        match self {\n            ExtractSingleTextNodeError::RenderError(err) => {\n                tracing::error!(\"Error while rendering {component}: {err}\");\n            }\n            ExtractSingleTextNodeError::NonTextNode => {\n                tracing::error!(\n                    \"Error while rendering {component}: The children of {component} must be a single text node\"\n                );\n            }\n            ExtractSingleTextNodeError::NonTemplate => {\n                tracing::error!(\n                    \"Error while rendering {component}: The children of {component} must be a single text node\"\n                );\n            }\n        }\n    }\n}\n\nfn extract_single_text_node(children: &Element) -> Result<String, ExtractSingleTextNodeError<'_>> {\n    let vnode = match children {\n        Element::Ok(vnode) => vnode,\n        Element::Err(err) => {\n            return Err(ExtractSingleTextNodeError::RenderError(err));\n        }\n    };\n    // The title's children must be in one of two forms:\n    // 1. rsx! { \"static text\" }\n    // 2. rsx! { \"title: {dynamic_text}\" }\n    match vnode.template {\n        // rsx! { \"static text\" }\n        Template {\n            roots: &[TemplateNode::Text { text }],\n            node_paths: &[],\n            attr_paths: &[],\n            ..\n        } => Ok(text.to_string()),\n        // rsx! { \"title: {dynamic_text}\" }\n        Template {\n            roots: &[TemplateNode::Dynamic { id }],\n            node_paths: &[&[0]],\n            attr_paths: &[],\n            ..\n        } => {\n            let node = &vnode.dynamic_nodes[id];\n            match node {\n                DynamicNode::Text(text) => Ok(text.value.clone()),\n                _ => Err(ExtractSingleTextNodeError::NonTextNode),\n            }\n        }\n        _ => Err(ExtractSingleTextNodeError::NonTemplate),\n    }\n}\n\nfn get_or_insert_root_context<T: Default + Clone + 'static>() -> T {\n    let rt = Runtime::current();\n    match rt.has_context::<T>(ScopeId::ROOT) {\n        Some(context) => context,\n        None => {\n            let context = T::default();\n            rt.provide_context(ScopeId::ROOT, context.clone());\n            context\n        }\n    }\n}\n\n#[derive(Default, Clone)]\nstruct DeduplicationContext(Rc<RefCell<HashSet<String>>>);\n\nimpl DeduplicationContext {\n    fn should_insert(&self, href: &str) -> bool {\n        let mut set = self.0.borrow_mut();\n        let present = set.contains(href);\n        if !present {\n            set.insert(href.to_string());\n            true\n        } else {\n            false\n        }\n    }\n}\n\n/// Extend a list of string attributes with a list of dioxus attribute\npub(crate) fn extend_attributes(\n    attributes: &mut Vec<(&'static str, String)>,\n    additional_attributes: &[Attribute],\n) {\n    for additional_attribute in additional_attributes {\n        let attribute_value_as_string = match &additional_attribute.value {\n            dioxus_core::AttributeValue::Text(v) => v.to_string(),\n            dioxus_core::AttributeValue::Float(v) => v.to_string(),\n            dioxus_core::AttributeValue::Int(v) => v.to_string(),\n            dioxus_core::AttributeValue::Bool(v) => v.to_string(),\n            dioxus_core::AttributeValue::Listener(_) | dioxus_core::AttributeValue::Any(_) => {\n                tracing::error!(\"document::* elements do not support event listeners or any value attributes. Expected displayable attribute, found {:?}\", additional_attribute.value);\n                continue;\n            }\n            dioxus_core::AttributeValue::None => {\n                continue;\n            }\n        };\n        attributes.push((additional_attribute.name, attribute_value_as_string));\n    }\n}\n"
  },
  {
    "path": "packages/document/src/elements/script.rs",
    "content": "use super::*;\nuse crate::document;\nuse dioxus_core::{use_hook, VNode};\nuse dioxus_html as dioxus_elements;\n\n#[non_exhaustive]\n#[derive(Clone, Props, PartialEq)]\npub struct ScriptProps {\n    /// The contents of the script tag. If present, the children must be a single text node.\n    pub children: Element,\n    /// Scripts are deduplicated by their src attribute\n    pub src: Option<String>,\n    pub defer: Option<bool>,\n    pub crossorigin: Option<String>,\n    pub fetchpriority: Option<String>,\n    pub integrity: Option<String>,\n    pub nomodule: Option<bool>,\n    pub nonce: Option<String>,\n    pub referrerpolicy: Option<String>,\n    pub r#type: Option<String>,\n    #[props(extends = script, extends = GlobalAttributes)]\n    pub additional_attributes: Vec<Attribute>,\n}\n\nimpl ScriptProps {\n    /// Get all the attributes for the script tag\n    pub fn attributes(&self) -> Vec<(&'static str, String)> {\n        let mut attributes = Vec::new();\n        extend_attributes(&mut attributes, &self.additional_attributes);\n        if let Some(defer) = &self.defer {\n            attributes.push((\"defer\", defer.to_string()));\n        }\n        if let Some(crossorigin) = &self.crossorigin {\n            attributes.push((\"crossorigin\", crossorigin.clone()));\n        }\n        if let Some(fetchpriority) = &self.fetchpriority {\n            attributes.push((\"fetchpriority\", fetchpriority.clone()));\n        }\n        if let Some(integrity) = &self.integrity {\n            attributes.push((\"integrity\", integrity.clone()));\n        }\n        if let Some(nomodule) = &self.nomodule {\n            attributes.push((\"nomodule\", nomodule.to_string()));\n        }\n        if let Some(nonce) = &self.nonce {\n            attributes.push((\"nonce\", nonce.clone()));\n        }\n        if let Some(referrerpolicy) = &self.referrerpolicy {\n            attributes.push((\"referrerpolicy\", referrerpolicy.clone()));\n        }\n        if let Some(r#type) = &self.r#type {\n            attributes.push((\"type\", r#type.clone()));\n        }\n        if let Some(src) = &self.src {\n            attributes.push((\"src\", src.clone()));\n        }\n        attributes\n    }\n\n    pub fn script_contents(&self) -> Result<String, ExtractSingleTextNodeError<'_>> {\n        extract_single_text_node(&self.children)\n    }\n}\n\n/// Render a [`<script>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/script) tag into the head of the page.\n///\n///\n/// If present, the children of the script component must be a single static or formatted string. If there are more children or the children contain components, conditionals, loops, or fragments, the script will not be added.\n///\n///\n/// Any scripts you add will be deduplicated by their `src` attribute (if present).\n///\n/// # Example\n/// ```rust, no_run\n/// # use dioxus::prelude::*;\n/// fn LoadScript() -> Element {\n///     rsx! {\n///         // You can use the Script component to render a script tag into the head of the page\n///         document::Script {\n///             src: asset!(\"/assets/script.js\"),\n///         }\n///     }\n/// }\n/// ```\n///\n/// <div class=\"warning\">\n///\n/// Any updates to the props after the first render will not be reflected in the head.\n///\n/// </div>\n#[component]\npub fn Script(props: ScriptProps) -> Element {\n    use_update_warning(&props, \"Script {}\");\n\n    use_hook(|| {\n        let document = document();\n        let mut insert_script = document.create_head_component();\n        if let Some(src) = &props.src {\n            if !should_insert_script(src) {\n                insert_script = false;\n            }\n        }\n\n        if !insert_script {\n            return;\n        }\n\n        // Make sure the props are in a valid form - they must either have a source or children\n        if let (None, Err(err)) = (&props.src, props.script_contents()) {\n            // If the script has neither contents nor src, log an error\n            err.log(\"Script\")\n        }\n\n        document.create_script(props);\n    });\n\n    VNode::empty()\n}\n\n#[derive(Default, Clone)]\nstruct ScriptContext(DeduplicationContext);\n\nfn should_insert_script(src: &str) -> bool {\n    get_or_insert_root_context::<ScriptContext>()\n        .0\n        .should_insert(src)\n}\n"
  },
  {
    "path": "packages/document/src/elements/style.rs",
    "content": "use super::*;\nuse crate::document;\nuse dioxus_core::{use_hook, VNode};\nuse dioxus_html as dioxus_elements;\n\n#[non_exhaustive]\n#[derive(Clone, Props, PartialEq)]\npub struct StyleProps {\n    /// Styles are deduplicated by their href attribute\n    pub href: Option<String>,\n    pub media: Option<String>,\n    pub nonce: Option<String>,\n    pub title: Option<String>,\n    /// The contents of the style tag. If present, the children must be a single text node.\n    pub children: Element,\n    #[props(extends = style, extends = GlobalAttributes)]\n    pub additional_attributes: Vec<Attribute>,\n}\n\nimpl StyleProps {\n    /// Get all the attributes for the style tag\n    pub fn attributes(&self) -> Vec<(&'static str, String)> {\n        let mut attributes = Vec::new();\n        extend_attributes(&mut attributes, &self.additional_attributes);\n        if let Some(href) = &self.href {\n            attributes.push((\"href\", href.clone()));\n        }\n        if let Some(media) = &self.media {\n            attributes.push((\"media\", media.clone()));\n        }\n        if let Some(nonce) = &self.nonce {\n            attributes.push((\"nonce\", nonce.clone()));\n        }\n        if let Some(title) = &self.title {\n            attributes.push((\"title\", title.clone()));\n        }\n        attributes\n    }\n\n    pub fn style_contents(&self) -> Result<String, ExtractSingleTextNodeError<'_>> {\n        extract_single_text_node(&self.children)\n    }\n}\n\n/// Render a [`<style>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/style) or [`<link>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/link) tag into the head of the page.\n///\n/// If present, the children of the style component must be a single static or formatted string. If there are more children or the children contain components, conditionals, loops, or fragments, the style will not be added.\n///\n/// # Example\n/// ```rust, no_run\n/// # use dioxus::prelude::*;\n/// fn RedBackground() -> Element {\n///     rsx! {\n///         // You can use the style component to render a style tag into the head of the page\n///         // This style tag will set the background color of the page to red\n///         document::Style {\n///             r#\"\n///                 body {{\n///                     background-color: red;\n///                 }}\n///             \"#\n///         }\n///         // You could also use a style with a href to load a stylesheet asset\n///         document::Style {\n///             href: asset!(\"/assets/style.css\")\n///         }\n///     }\n/// }\n/// ```\n///\n/// <div class=\"warning\">\n///\n/// Any updates to the props after the first render will not be reflected in the head.\n///\n/// </div>\n#[component]\npub fn Style(props: StyleProps) -> Element {\n    use_update_warning(&props, \"Style {}\");\n\n    use_hook(|| {\n        let document = document();\n        let mut insert_style = document.create_head_component();\n        if let Some(href) = &props.href {\n            if !should_insert_style(href) {\n                insert_style = false;\n            }\n        }\n        if !insert_style {\n            return;\n        }\n        let mut attributes = props.attributes();\n        match (&props.href, props.style_contents()) {\n            // The style has inline contents, render it as a style tag\n            (_, Ok(_)) => document.create_style(props),\n            // The style has a src, render it as a link tag\n            (Some(_), _) => {\n                attributes.push((\"type\", \"text/css\".into()));\n                attributes.push((\"rel\", \"stylesheet\".into()));\n                document.create_link(LinkProps {\n                    media: props.media,\n                    title: props.title,\n                    r#type: Some(\"text/css\".to_string()),\n                    additional_attributes: props.additional_attributes,\n                    href: props.href,\n                    rel: Some(\"stylesheet\".to_string()),\n                    disabled: None,\n                    r#as: None,\n                    sizes: None,\n                    crossorigin: None,\n                    referrerpolicy: None,\n                    fetchpriority: None,\n                    hreflang: None,\n                    integrity: None,\n                    blocking: None,\n                    onload: None,\n                });\n            }\n            // The style has neither contents nor src, log an error\n            (None, Err(err)) => err.log(\"Style\"),\n        };\n    });\n\n    VNode::empty()\n}\n\n#[derive(Default, Clone)]\nstruct StyleContext(DeduplicationContext);\n\nfn should_insert_style(href: &str) -> bool {\n    get_or_insert_root_context::<StyleContext>()\n        .0\n        .should_insert(href)\n}\n"
  },
  {
    "path": "packages/document/src/elements/stylesheet.rs",
    "content": "use super::*;\n\n/// Render a [`<link>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/link) tag into the head of the page with the stylesheet rel.\n/// This is equivalent to the [`Link`](Link) component with a slightly more ergonomic API.\n///\n///\n/// # Example\n/// ```rust\n/// # use dioxus::prelude::*;\n/// fn RedBackground() -> Element {\n///     rsx! {\n///         document::Stylesheet {\n///             href: asset!(\"/assets/style.css\")\n///         }\n///     }\n/// }\n/// ```\n///\n/// <div class=\"warning\">\n///\n/// Any updates to the props after the first render will not be reflected in the head.\n///\n/// </div>\n#[component]\npub fn Stylesheet(props: LinkProps) -> Element {\n    super::Link(LinkProps {\n        rel: Some(\"stylesheet\".into()),\n        r#type: Some(\"text/css\".into()),\n        ..props\n    })\n}\n"
  },
  {
    "path": "packages/document/src/elements/title.rs",
    "content": "use dioxus_core::{use_hook, VNode};\n\nuse crate::document;\n\nuse super::*;\n\n#[derive(Clone, Props, PartialEq)]\npub struct TitleProps {\n    /// The contents of the title tag. The children must be a single text node.\n    children: Element,\n}\n\n/// Render the title of the page. On web renderers, this will set the [`<title>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/title) in the head. On desktop, it will set the window title.\n///\n/// Unlike most head components, the Title can be modified after the first render. Only the latest update to the title will be reflected if multiple title components are rendered.\n///\n///\n/// The children of the title component must be a single static or formatted string. If there are more children or the children contain components, conditionals, loops, or fragments, the title will not be updated.\n///\n/// # Example\n///\n/// ```rust, no_run\n/// # use dioxus::prelude::*;\n/// fn App() -> Element {\n///     rsx! {\n///         // You can use the Title component to render a title tag into the head of the page or window\n///         document::Title { \"My Page\" }\n///     }\n/// }\n/// ```\n#[component]\n#[doc(alias = \"<title>\")]\npub fn Title(props: TitleProps) -> Element {\n    let children = props.children;\n    let text = match extract_single_text_node(&children) {\n        Ok(text) => text,\n        Err(err) => {\n            err.log(\"Title\");\n            return VNode::empty();\n        }\n    };\n\n    // Update the title as it changes. NOTE: We don't use use_effect here because we need this to run on the server\n    let document = use_hook(document);\n    let last_text = use_hook(|| {\n        // Set the title initially\n        document.set_title(text.clone());\n        Rc::new(RefCell::new(text.clone()))\n    });\n\n    // If the text changes, update the title\n    let mut last_text = last_text.borrow_mut();\n    if text != *last_text {\n        document.set_title(text.clone());\n        *last_text = text;\n    }\n\n    VNode::empty()\n}\n"
  },
  {
    "path": "packages/document/src/error.rs",
    "content": "use std::error::Error;\nuse std::fmt::Display;\n\n/// Represents an error when evaluating JavaScript\n#[derive(Debug)]\n#[non_exhaustive]\npub enum EvalError {\n    /// The platform does not support evaluating JavaScript.\n    Unsupported,\n\n    /// The provided JavaScript has already been ran.\n    Finished,\n\n    /// The provided JavaScript is not valid and can't be ran.\n    InvalidJs(String),\n\n    /// Represents an error communicating between JavaScript and Rust.\n    Communication(String),\n\n    /// Represents an error serializing or deserializing the result of an eval\n    Serialization(serde_json::Error),\n}\n\nimpl Display for EvalError {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        match self {\n            EvalError::Unsupported => write!(f, \"EvalError::Unsupported - eval is not supported on the current platform\"),\n            EvalError::Finished => write!(f, \"EvalError::Finished - eval has already ran\"),\n            EvalError::InvalidJs(_) => write!(f, \"EvalError::InvalidJs - the provided javascript is invalid\"),\n            EvalError::Communication(_) => write!(f, \"EvalError::Communication - there was an error trying to communicate with between javascript and rust\"),\n            EvalError::Serialization(_) => write!(f, \"EvalError::Serialization - there was an error trying to serialize or deserialize the result of an eval\"),\n        }\n    }\n}\n\nimpl Error for EvalError {}\n"
  },
  {
    "path": "packages/document/src/eval.rs",
    "content": "#![doc = include_str!(\"../docs/eval.md\")]\n\nuse crate::error::EvalError;\nuse generational_box::GenerationalBox;\nuse std::future::{poll_fn, Future, IntoFuture};\nuse std::pin::Pin;\nuse std::task::{Context, Poll};\n\n#[doc = include_str!(\"../docs/eval.md\")]\npub struct Eval {\n    evaluator: GenerationalBox<Box<dyn Evaluator>>,\n}\n\nimpl Eval {\n    /// Create this eval from a dynamic evaluator\n    pub fn new(evaluator: GenerationalBox<Box<dyn Evaluator + 'static>>) -> Self {\n        Self { evaluator }\n    }\n\n    /// Wait until the javascript task is finished and return the result\n    pub async fn join<T: serde::de::DeserializeOwned>(self) -> Result<T, EvalError> {\n        let json_value = poll_fn(|cx| match self.evaluator.try_write() {\n            Ok(mut evaluator) => evaluator.poll_join(cx),\n            Err(_) => Poll::Ready(Err(EvalError::Finished)),\n        })\n        .await?;\n        serde_json::from_value(json_value).map_err(EvalError::Serialization)\n    }\n\n    /// Send a message to the javascript task\n    pub fn send(&self, data: impl serde::Serialize) -> Result<(), EvalError> {\n        match self.evaluator.try_read() {\n            Ok(evaluator) => {\n                evaluator.send(serde_json::to_value(data).map_err(EvalError::Serialization)?)\n            }\n            Err(_) => Err(EvalError::Finished),\n        }\n    }\n\n    /// Receive a message from the javascript task\n    pub async fn recv<T: serde::de::DeserializeOwned>(&mut self) -> Result<T, EvalError> {\n        let json_value = poll_fn(|cx| match self.evaluator.try_write() {\n            Ok(mut evaluator) => evaluator.poll_recv(cx),\n            Err(_) => Poll::Ready(Err(EvalError::Finished)),\n        })\n        .await?;\n        serde_json::from_value(json_value).map_err(EvalError::Serialization)\n    }\n}\n\nimpl IntoFuture for Eval {\n    type Output = Result<serde_json::Value, EvalError>;\n    type IntoFuture = Pin<Box<dyn Future<Output = Self::Output>>>;\n\n    fn into_future(self) -> Self::IntoFuture {\n        Box::pin(self.join().into_future())\n    }\n}\n\nimpl Clone for Eval {\n    fn clone(&self) -> Self {\n        *self\n    }\n}\n\nimpl Copy for Eval {}\n\n/// The platform's evaluator.\npub trait Evaluator {\n    /// Sends a message to the evaluated JavaScript.\n    fn send(&self, data: serde_json::Value) -> Result<(), EvalError>;\n    /// Receive any queued messages from the evaluated JavaScript.\n    fn poll_recv(\n        &mut self,\n        context: &mut Context<'_>,\n    ) -> Poll<Result<serde_json::Value, EvalError>>;\n    /// Gets the return value of the JavaScript\n    fn poll_join(\n        &mut self,\n        context: &mut Context<'_>,\n    ) -> Poll<Result<serde_json::Value, EvalError>>;\n}\n"
  },
  {
    "path": "packages/document/src/js/hash.txt",
    "content": "[5568016277461366671, 8375185156499858125]"
  },
  {
    "path": "packages/document/src/js/head.js",
    "content": "var createElementInHead=function(tag,attributes,children){const element=document.createElement(tag);for(let[key,value]of attributes)element.setAttribute(key,value);if(children)element.appendChild(document.createTextNode(children));document.head.appendChild(element)};window.createElementInHead=createElementInHead;\n"
  },
  {
    "path": "packages/document/src/lib.rs",
    "content": "use std::rc::Rc;\n\nmod document;\nmod elements;\nmod error;\nmod eval;\n\npub use document::*;\npub use elements::*;\npub use error::*;\npub use eval::*;\n\n/// Get the document provider for the current platform or a no-op provider if the platform doesn't document functionality.\npub fn document() -> Rc<dyn Document> {\n    match dioxus_core::try_consume_context::<Rc<dyn Document>>() {\n        Some(document) => document,\n        None => {\n            tracing::error!(\n                \"Unable to find a document in the renderer. Using the default no-op document.\"\n            );\n            Rc::new(NoOpDocument)\n        }\n    }\n}\n\n/// Evaluate some javascript in the current document\n#[doc = include_str!(\"../docs/eval.md\")]\n#[doc(alias = \"javascript\")]\npub fn eval(script: &str) -> Eval {\n    document().eval(script.to_string())\n}\n"
  },
  {
    "path": "packages/document/src/ts/.gitignore",
    "content": "# please dont accidentally run tsc and commit your js in this dir.\n*.js\n\n"
  },
  {
    "path": "packages/document/src/ts/eval.ts",
    "content": "// Handle communication between rust and evaluating javascript\n\nexport class Channel {\n  pending: any[];\n  waiting: ((data: any) => void)[];\n\n  constructor() {\n    this.pending = [];\n    this.waiting = [];\n  }\n\n  send(data: any) {\n    // If there's a waiting callback, call it\n    if (this.waiting.length > 0) {\n      this.waiting.shift()(data);\n      return;\n    }\n    // Otherwise queue the data\n    this.pending.push(data);\n  }\n\n  async recv(): Promise<any> {\n    return new Promise((resolve, _reject) => {\n      // If data already exists, resolve immediately\n      if (this.pending.length > 0) {\n        resolve(this.pending.shift());\n        return;\n      }\n      // Otherwise queue the resolve callback\n      this.waiting.push(resolve);\n    });\n  }\n}\n\nexport class WeakDioxusChannel {\n  inner: WeakRef<DioxusChannel>;\n\n  constructor(channel: DioxusChannel) {\n    this.inner = new WeakRef(channel);\n  }\n\n  // Send data from rust to javascript\n  rustSend(data: any) {\n    let channel = this.inner.deref();\n    if (channel) {\n      channel.rustSend(data);\n    }\n  }\n\n  // Receive data sent from javascript in rust\n  async rustRecv(): Promise<any> {\n    let channel = this.inner.deref();\n    if (channel) {\n      return await channel.rustRecv();\n    }\n  }\n}\n\nexport abstract class DioxusChannel {\n  // Return a weak reference to this channel\n  weak(): WeakDioxusChannel {\n    return new WeakDioxusChannel(this);\n  }\n\n  // Send data from rust to javascript\n  abstract rustSend(data: any): void;\n\n  // Receive data sent from javascript in rust\n  abstract rustRecv(): Promise<any>;\n\n  // Close the channel, dropping it.\n  abstract close(): void;\n}\n"
  },
  {
    "path": "packages/document/src/ts/head.ts",
    "content": "// Helper functions for working with the document head\n\nfunction createElementInHead(\n  tag: string,\n  attributes: [string, string][],\n  children: string | null\n): void {\n  const element = document.createElement(tag);\n  for (const [key, value] of attributes) {\n    element.setAttribute(key, value);\n  }\n  if (children) {\n    element.appendChild(document.createTextNode(children));\n  }\n  document.head.appendChild(element);\n}\n\n// @ts-ignore\nwindow.createElementInHead = createElementInHead;\n"
  },
  {
    "path": "packages/document/tsconfig.json",
    "content": "{\n    \"compilerOptions\": {\n        \"module\": \"CommonJS\",\n        \"lib\": [\n            \"ES2015\",\n            \"DOM\",\n            \"dom\",\n            \"dom.iterable\",\n            \"ESNext\"\n        ],\n        \"noImplicitAny\": true,\n        \"removeComments\": true,\n        \"preserveConstEnums\": true,\n    },\n    \"exclude\": [\n        \"**/*.spec.ts\"\n    ]\n}\n"
  },
  {
    "path": "packages/dx-wire-format/Cargo.toml",
    "content": "[package]\nname = \"dioxus-dx-wire-format\"\nedition = \"2021\"\nversion.workspace = true\npublish = true\nauthors = [\"Jonathan Kelley\"]\ndescription = \"Wire format for the Dioxus CLI\"\nlicense = \"MIT OR Apache-2.0\"\nrepository = \"https://github.com/DioxusLabs/dioxus/\"\nhomepage = \"https://dioxuslabs.com/learn/0.7/\"\nkeywords = [\"dom\", \"ui\", \"gui\", \"react\", \"wasm\"]\nrust-version = \"1.79.0\"\n\n[dependencies]\nserde = { workspace = true, features = [\"derive\"] }\nserde_json = { workspace = true }\ncargo_metadata = { workspace = true }\nmanganis-core = { workspace = true }\nsubsecond-types = { workspace = true }\n"
  },
  {
    "path": "packages/dx-wire-format/src/lib.rs",
    "content": "use cargo_metadata::{diagnostic::Diagnostic, CompilerMessage};\nuse manganis_core::BundledAsset;\nuse serde::{Deserialize, Serialize};\nuse std::{borrow::Cow, collections::HashSet, path::PathBuf};\nuse subsecond_types::JumpTable;\n\npub use cargo_metadata;\n\n/// The structured output for the CLI\n///\n/// This is designed such that third party tools can reliably consume the output of the CLI when\n/// outputting json.\n///\n/// Not every log outputted will be parsable, but all structured logs should be.\n///\n/// This means the debug format of this log needs to be parsable json, not the default debug format.\n///\n/// We guarantee that the last line of the command represents the success of the command, such that\n/// tools can simply parse the last line of the output.\n///\n/// There might be intermediate lines that are parseable as structured logs (which you can put here)\n/// but they are not guaranteed to be, such that we can provide better error messages for the user.\n#[allow(clippy::large_enum_variant)]\n#[non_exhaustive]\n#[derive(Serialize, Deserialize, Clone, Debug)]\npub enum StructuredOutput {\n    BuildsFinished {\n        client: StructuredBuildArtifacts,\n        server: Option<StructuredBuildArtifacts>,\n    },\n    PrintCargoArgs {\n        args: Vec<String>,\n        env: Vec<(Cow<'static, str>, String)>,\n    },\n    BuildFinished {\n        artifacts: StructuredBuildArtifacts,\n    },\n    BuildUpdate {\n        stage: BuildStage,\n    },\n    Hotpatch {\n        jump_table: JumpTable,\n        artifacts: StructuredBuildArtifacts,\n    },\n    CargoOutput {\n        message: CompilerMessage,\n    },\n    RustcOutput {\n        message: Diagnostic,\n    },\n    BundleOutput {\n        bundles: Vec<PathBuf>,\n        client: StructuredBuildArtifacts,\n        server: Option<StructuredBuildArtifacts>,\n    },\n    HtmlTranslate {\n        html: String,\n    },\n    Success,\n    Error {\n        message: String,\n    },\n}\n\nimpl std::fmt::Display for StructuredOutput {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.write_str(&serde_json::to_string(self).map_err(|_e| std::fmt::Error)?)\n    }\n}\n\n#[derive(Debug, Clone, Default, Serialize, Deserialize)]\npub struct StructuredBuildArtifacts {\n    pub path: PathBuf,\n    pub exe: PathBuf,\n    pub rustc_args: Vec<String>,\n    pub rustc_envs: Vec<(String, String)>,\n    pub link_args: Vec<String>,\n    pub assets: HashSet<BundledAsset>, // the serialized asset manifest\n}\n\n/// The current stage of the ongoing build\n///\n/// This is a perma-unstable interface that is subject to change at any time.\n#[non_exhaustive]\n#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]\npub enum BuildStage {\n    Initializing,\n    Starting {\n        crate_count: usize,\n        patch: bool,\n    },\n    InstallingTooling,\n    Compiling {\n        current: usize,\n        total: usize,\n        krate: String,\n    },\n    RunningBindgen,\n    SplittingBundle,\n    OptimizingWasm,\n    Linking,\n    Hotpatching,\n    ExtractingAssets,\n    CopyingAssets {\n        current: usize,\n        total: usize,\n        path: PathBuf,\n    },\n    Bundling,\n    RunningGradle,\n    CompilingNativePlugins {\n        detail: String,\n    },\n    CodeSigning,\n    Success,\n    Failed,\n    Aborted,\n    Restarting,\n    CompressingAssets,\n    Prerendering,\n}\n"
  },
  {
    "path": "packages/extension/.eslintrc.js",
    "content": "/**@type {import('eslint').Linter.Config} */\n// eslint-disable-next-line no-undef\nmodule.exports = {\n\troot: true,\n\tparser: '@typescript-eslint/parser',\n\tplugins: [\n\t\t'@typescript-eslint',\n\t],\n\textends: [\n\t\t'eslint:recommended',\n\t\t'plugin:@typescript-eslint/recommended',\n\t],\n\trules: {\n\t\t'semi': [2, \"always\"],\n\t\t'@typescript-eslint/no-unused-vars': 0,\n\t\t'@typescript-eslint/no-explicit-any': 0,\n\t\t'@typescript-eslint/explicit-module-boundary-types': 0,\n\t\t'@typescript-eslint/no-non-null-assertion': 0,\n\t}\n};\n"
  },
  {
    "path": "packages/extension/.gitignore",
    "content": ".DS_Store\nnpm-debug.log\nThumbs.db\n*/node_modules/\nnode_modules/\n*/out/\nout/\n*/.vs/\n.vs/\ntsconfig.lsif.json\n*.lsif\n*.db\n*.vsix\npkg/\n"
  },
  {
    "path": "packages/extension/.vscode/launch.json",
    "content": "// A launch configuration that compiles the extension and then opens it inside a new window\n// Use IntelliSense to learn about possible attributes.\n// Hover to view descriptions of existing attributes.\n// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387\n{\n\t\"version\": \"0.2.0\",\n\t\"configurations\": [\n\t\t{\n\t\t\t\"name\": \"Run Extension\",\n\t\t\t\"type\": \"extensionHost\",\n\t\t\t\"request\": \"launch\",\n\t\t\t\"args\": [\n\t\t\t\t\"--extensionDevelopmentPath=${workspaceFolder}\"\n\t\t\t],\n\t\t\t\"outFiles\": [\n\t\t\t\t\"${workspaceFolder}/out/**/*.js\"\n\t\t\t]\n\t\t\t// \"preLaunchTask\": \"${defaultBuildTask}\"\n\t\t},\n\t\t{\n\t\t\t\"name\": \"Extension Tests\",\n\t\t\t\"type\": \"extensionHost\",\n\t\t\t\"request\": \"launch\",\n\t\t\t\"args\": [\n\t\t\t\t\"--extensionDevelopmentPath=${workspaceFolder}\",\n\t\t\t\t\"--extensionTestsPath=${workspaceFolder}/out/test/suite/index\"\n\t\t\t],\n\t\t\t\"outFiles\": [\n\t\t\t\t\"${workspaceFolder}/out/test/**/*.js\"\n\t\t\t],\n\t\t\t\"preLaunchTask\": \"${defaultBuildTask}\"\n\t\t}\n\t]\n}\n"
  },
  {
    "path": "packages/extension/.vscode/tasks.json",
    "content": "// See https://go.microsoft.com/fwlink/?LinkId=733558\n// for the documentation about the tasks.json format\n{\n\t\"version\": \"2.0.0\",\n\t\"tasks\": [\n\t\t{\n\t\t\t\"type\": \"npm\",\n\t\t\t\"script\": \"watch\",\n\t\t\t\"problemMatcher\": \"$tsc-watch\",\n\t\t\t\"isBackground\": true,\n\t\t\t\"presentation\": {\n\t\t\t\t\"reveal\": \"never\"\n\t\t\t},\n\t\t\t\"group\": {\n\t\t\t\t\"kind\": \"build\",\n\t\t\t\t\"isDefault\": true\n\t\t\t}\n\t\t}\n\t]\n}\n"
  },
  {
    "path": "packages/extension/Cargo.toml",
    "content": "[package]\nname = \"dioxus-ext\"\nversion = { workspace = true }\nedition = \"2021\"\npublish = false\n\n# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html\n\n[dependencies]\nwasm-bindgen = { workspace = true }\ndioxus-autofmt = { workspace = true }\ndioxus-rsx-rosetta = { workspace = true }\nhtml_parser = { workspace = true }\nsyn = { workspace = true }\n\n[lib]\ncrate-type = [\"cdylib\", \"rlib\"]\n"
  },
  {
    "path": "packages/extension/DEV.md",
    "content": "\n## packaging\n\n```\n$ cd myExtension\n$ vsce package\n# myExtension.vsix generated\n$ vsce publish\n# <publisherID>.myExtension published to VS Code Marketplace\n```\n"
  },
  {
    "path": "packages/extension/LICENSE.txt",
    "content": "MIT License\r\n\r\nCopyright (c) 2021 DioxusLabs\r\n\r\nPermission is hereby granted, free of charge, to any person obtaining a copy\r\nof this software and associated documentation files (the \"Software\"), to deal\r\nin the Software without restriction, including without limitation the rights\r\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\ncopies of the Software, and to permit persons to whom the Software is\r\nfurnished to do so, subject to the following conditions:\r\n\r\nThe above copyright notice and this permission notice shall be included in all\r\ncopies or substantial portions of the Software.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\r\nSOFTWARE."
  },
  {
    "path": "packages/extension/README.md",
    "content": "# Dioxus VSCode Extension\n\n![Dioxus Splash](https://github.com/DioxusLabs/dioxus/raw/main/notes/dioxus_splash_8.avif)\n\nThis extension wraps functionality in Dioxus CLI to be used in your editor!\n\n## Features:\n\n- Auto-format RSX\n- Convert HTML to RSX\n- Convert HTML to Dioxus Component\n- Format RSX\n\n## Current commands:\n\n### Convert HTML to RSX\nConverts a selection of html to valid rsx.\n\n### Convert HTML to Dioxus Component\n\nConverts a selection of html to a valid Dioxus component with all SVGs factored out into their own module.\n\n### Format RSX\n\nFormats the current file as RSX.\n\n## Building this extension from source\n- make sure wasm-bindgen is installed to current version (cargo binstall wasm-bindgen-cli)\n- run `npm install`\n- run `npm run vsix`\n\n# Working with Dioxus:\n\nThis overview provides a brief introduction to Dioxus. For a more in-depth guide, make sure to check out:\n\n- [Getting Started](https://dioxuslabs.com/learn/0.7/getting_started)\n- [Book (0.6)](https://dioxuslabs.com/learn/0.7/)\n\n## Contributing\n- Check out the website [section on contributing](https://dioxuslabs.com/learn/0.7/beyond/contributing).\n- Report issues on our [issue tracker](https://github.com/dioxuslabs/dioxus/issues).\n- [Join](https://discord.gg/XgGxMSkvUM) the discord and ask questions!\n\n\n<a href=\"https://github.com/dioxuslabs/dioxus/graphs/contributors\">\n  <img src=\"https://contrib.rocks/image?repo=dioxuslabs/dioxus&max=30&columns=10\" />\n</a>\n\n## License\nThis project is licensed under the [MIT license].\n\n[mit license]: https://github.com/DioxusLabs/dioxus/blob/master/LICENSE-MIT\n\nUnless you explicitly state otherwise, any contribution intentionally submitted\nfor inclusion in Dioxus by you, shall be licensed as MIT, without any additional\nterms or conditions.\n"
  },
  {
    "path": "packages/extension/package.json",
    "content": "{\n    \"name\": \"dioxus\",\n    \"displayName\": \"Dioxus\",\n    \"description\": \"Useful tools for working with Dioxus\",\n    \"version\": \"0.7.3\",\n    \"publisher\": \"DioxusLabs\",\n    \"private\": true,\n    \"license\": \"MIT\",\n    \"icon\": \"static/icon.png\",\n    \"repository\": {\n        \"type\": \"git\",\n        \"url\": \"https://github.com/DioxusLabs/dioxus\"\n    },\n    \"engines\": {\n        \"vscode\": \"^1.68.1\"\n    },\n    \"categories\": [\n        \"Programming Languages\"\n    ],\n    \"activationEvents\": [\n        \"onLanguage:rust\"\n    ],\n    \"main\": \"./out/main.js\",\n    \"extensionKind\": [\n        \"ui\",\n        \"workspace\"\n    ],\n    \"contributes\": {\n        \"commands\": [\n            {\n                \"command\": \"extension.htmlToDioxusRsx\",\n                \"title\": \"Dioxus: Convert HTML to RSX\"\n            },\n            {\n                \"command\": \"extension.htmlToDioxusComponent\",\n                \"title\": \"Dioxus: Convert HTML to Component\"\n            },\n            {\n                \"command\": \"extension.formatRsx\",\n                \"title\": \"Dioxus: Format RSX\"\n            },\n            {\n                \"command\": \"extension.formatRsxDocument\",\n                \"title\": \"Dioxus: Format RSX Document\"\n            }\n        ],\n        \"configuration\": {\n            \"properties\": {\n                \"dioxus.formatOnSave\": {\n                    \"type\": [\n                        \"string\"\n                    ],\n                    \"default\": \"followFormatOnSave\",\n                    \"enum\": [\n                        \"followFormatOnSave\",\n                        \"enabled\",\n                        \"disabled\"\n                    ],\n                    \"enumItemLabels\": [\n                        \"Follow the normal formatOnSave config\",\n                        \"Enabled\",\n                        \"Disabled\"\n                    ],\n                    \"enumDescriptions\": [\n                        \"Only format Rsx when saving files if the editor.formatOnSave config is enabled\",\n                        \"Always format Rsx when a Rust file is saved\",\n                        \"Never format Rsx when a file is saved\"\n                    ],\n                    \"description\": \"Format RSX when a file is saved.\"\n                }\n            }\n        }\n    },\n    \"scripts\": {\n        \"vscode:prepublish\": \"npm run build-base\",\n        \"vsix\": \"vsce package\",\n        \"build-wasm\": \"cargo build --target wasm32-unknown-unknown --release && cp ../../target/wasm32-unknown-unknown/release/dioxus_ext.wasm pkg/\",\n        \"bind-wasm\": \"wasm-bindgen --out-dir=pkg --target=web --omit-default-module-path --omit-imports pkg/dioxus_ext.wasm\",\n        \"build-base\": \"npm run build-wasm && npm run bind-wasm && webpack\",\n        \"webpack\": \"webpack --mode development\",\n        \"webpack-dev\": \"webpack --mode development --watch\",\n        \"package\": \"webpack --mode production --devtool hidden-source-map\",\n        \"test-compile\": \"tsc -p ./\",\n        \"build\": \"npm run build-base -- --devtool hidden-source-map\",\n        \"watch\": \"npm run build-base -- --devtool hidden-source-map --watch\",\n        \"lint\": \"prettier --check . && eslint -c .eslintrc.js --ext ts ./src ./tests\",\n        \"fix\": \"prettier --write . && eslint -c .eslintrc.js --ext ts ./src ./tests --fix\",\n        \"pretest\": \"tsc && npm run build\",\n        \"test\": \"cross-env TEST_VARIABLE=test node ./out/tests/runTests.js\"\n    },\n    \"devDependencies\": {\n        \"@types/node\": \"^18.0.2\",\n        \"@types/vscode\": \"^1.68.1\",\n        \"@typescript-eslint/eslint-plugin\": \"^5.30.5\",\n        \"@typescript-eslint/parser\": \"^5.30.5\",\n        \"cross-env\": \"^7.0.3\",\n        \"eslint\": \"^8.19.0\",\n        \"eslint-config-prettier\": \"^8.5.0\",\n        \"ovsx\": \"^0.5.1\",\n        \"prettier\": \"^2.6.2\",\n        \"ts-loader\": \"^9.4.4\",\n        \"tslib\": \"^2.3.0\",\n        \"typescript\": \"^4.7.4\",\n        \"vsce\": \"^2.7.0\",\n        \"webpack\": \"^5.94.0\",\n        \"webpack-cli\": \"^5.1.4\"\n    },\n    \"dependencies\": {\n        \"dioxus-ext\": \"./pkg\",\n        \"vsce\": \"^2.9.2\"\n    }\n}\n"
  },
  {
    "path": "packages/extension/src/lib.rs",
    "content": "//! This file exports functions into the vscode extension\n\nuse dioxus_autofmt::{FormattedBlock, IndentOptions, IndentType};\nuse wasm_bindgen::prelude::*;\n\n#[wasm_bindgen]\npub fn format_rsx(raw: String, use_tabs: bool, indent_size: usize) -> String {\n    let block = dioxus_autofmt::fmt_block(\n        &raw,\n        0,\n        IndentOptions::new(\n            if use_tabs {\n                IndentType::Tabs\n            } else {\n                IndentType::Spaces\n            },\n            indent_size,\n            false,\n        ),\n    );\n    block.unwrap()\n}\n\n#[wasm_bindgen]\npub fn format_selection(\n    raw: String,\n    use_tabs: bool,\n    indent_size: usize,\n    base_indent: usize,\n) -> String {\n    let block = dioxus_autofmt::fmt_block(\n        &raw,\n        base_indent,\n        IndentOptions::new(\n            if use_tabs {\n                IndentType::Tabs\n            } else {\n                IndentType::Spaces\n            },\n            indent_size,\n            false,\n        ),\n    );\n    block.unwrap()\n}\n\n#[wasm_bindgen]\npub struct FormatBlockInstance {\n    new: String,\n    _edits: Vec<FormattedBlock>,\n}\n\n#[wasm_bindgen]\nimpl FormatBlockInstance {\n    #[wasm_bindgen]\n    pub fn formatted(&self) -> String {\n        self.new.clone()\n    }\n\n    #[wasm_bindgen]\n    pub fn length(&self) -> usize {\n        self._edits.len()\n    }\n}\n\n#[wasm_bindgen]\npub fn format_file(contents: String, use_tabs: bool, indent_size: usize) -> FormatBlockInstance {\n    // todo: use rustfmt for this instead\n    let options = IndentOptions::new(\n        if use_tabs {\n            IndentType::Tabs\n        } else {\n            IndentType::Spaces\n        },\n        indent_size,\n        false,\n    );\n\n    let Ok(Ok(_edits)) = syn::parse_file(&contents)\n        .map(|file| dioxus_autofmt::try_fmt_file(&contents, &file, options))\n    else {\n        return FormatBlockInstance {\n            new: contents,\n            _edits: Vec::new(),\n        };\n    };\n\n    let out = dioxus_autofmt::apply_formats(&contents, _edits.clone());\n    FormatBlockInstance { new: out, _edits }\n}\n\n#[wasm_bindgen]\npub fn translate_rsx(contents: String, _component: bool) -> String {\n    // Ensure we're loading valid HTML\n    let dom = html_parser::Dom::parse(&contents).unwrap();\n\n    let callbody = dioxus_rsx_rosetta::rsx_from_html(&dom);\n\n    // Convert the HTML to RSX\n    dioxus_autofmt::write_block_out(&callbody).unwrap()\n}\n"
  },
  {
    "path": "packages/extension/src/main.ts",
    "content": "import * as vscode from 'vscode';\nimport init, * as dioxus from 'dioxus-ext';\n\nexport async function activate(context: vscode.ExtensionContext) {\n\t// Load the wasm from the file system\n\tconst wasmSourceCode = await vscode.workspace.fs.readFile(vscode.Uri.joinPath(context.extensionUri, \"./pkg/dioxus_ext_bg.wasm\"));\n\n\t// Wait for the initialization to finish\n\t// This is using the byte buffer directly which won't go through the \"fetch\" machinery\n\t//\n\t// For whatever reason, wasm-bindgen generates `fetch` when we don't want it to\n\t// VSCode doesn't have a `fetch` implementation, but we don't really care about polyfilling it\n\tawait init(wasmSourceCode);\n\n\t// Register format-on-save handler using waitUntil() for proper synchronization\n\t// This runs alongside (not instead of) other formatters like rust-analyzer,\n\t// allowing both rustfmt and Dioxus RSX formatting to work together\n\tcontext.subscriptions.push(\n\t\tvscode.workspace.onWillSaveTextDocument(formatOnSave)\n\t);\n\n\t// Todo:\n\t// I want a paste-handler that translates HTML to RSX whenever HTML is pasted into an Rsx block\n\t// Or, a little tooltip that pops up and asks if you want to translate the HTML to RSX\n\tcontext.subscriptions.push(\n\t\tvscode.commands.registerCommand('extension.htmlToDioxusRsx', () => translate(false)),\n\t\tvscode.commands.registerCommand('extension.htmlToDioxusComponent', () => translate(true)),\n\t\tvscode.commands.registerCommand('extension.formatRsx', fmtSelection),\n\t\tvscode.commands.registerCommand('extension.formatRsxDocument', formatRsxDocument)\n\t);\n\n\tcontext.subscriptions.push(vscode.window.registerUriHandler(new UriLaunchServer()));\n}\n\n// Format RSX on save using waitUntil() for proper synchronization with VSCode's save pipeline\nfunction formatOnSave(e: vscode.TextDocumentWillSaveEvent) {\n\tif (e.document.languageId !== 'rust') {\n\t\treturn;\n\t}\n\n\t// Check if Dioxus formatting is enabled\n\tconst dioxusConfig = vscode.workspace.getConfiguration('dioxus', e.document).get('formatOnSave');\n\tif (dioxusConfig === 'disabled') {\n\t\treturn;\n\t}\n\n\t// Use waitUntil() to properly synchronize with VSCode's save pipeline\n\t// This returns TextEdit[] which VSCode applies before completing the save\n\te.waitUntil(formatDocument(e.document));\n}\n\n// Returns a promise of TextEdit[] for use with waitUntil()\nfunction formatDocument(document: vscode.TextDocument): Thenable<vscode.TextEdit[]> {\n\treturn new Promise((resolve) => {\n\t\ttry {\n\t\t\tconst contents = document.getText();\n\n\t\t\t// Get editor options for this document\n\t\t\tconst editor = vscode.window.visibleTextEditors.find(\n\t\t\t\te => e.document.uri.toString() === document.uri.toString()\n\t\t\t);\n\n\t\t\tconst tabSize = (typeof editor?.options.tabSize === 'number') ? editor.options.tabSize : 4;\n\t\t\tconst useTabs = editor ? !editor.options.insertSpaces : false;\n\n\t\t\tconst formatted = dioxus.format_file(contents, useTabs, tabSize);\n\n\t\t\tif (formatted.length() > 0) {\n\t\t\t\tconst fullRange = new vscode.Range(\n\t\t\t\t\tdocument.positionAt(0),\n\t\t\t\t\tdocument.positionAt(contents.length)\n\t\t\t\t);\n\t\t\t\tresolve([vscode.TextEdit.replace(fullRange, formatted.formatted())]);\n\t\t\t} else {\n\t\t\t\tresolve([]);\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tvscode.window.showWarningMessage(`Dioxus formatting error: ${error}`);\n\t\t\tresolve([]);\n\t\t}\n\t});\n}\n\nfunction translate(component: boolean) {\n\t// Load the activate editor\n\tconst editor = vscode.window.activeTextEditor;\n\tif (!editor) return;\n\n\t// Get the selected text\n\tconst html = editor.document.getText(editor.selection);\n\tif (html.length == 0) {\n\t\tvscode.window.showWarningMessage(\"Please select HTML fragment before invoking this command!\");\n\t\treturn;\n\t}\n\n\t// Translate the HTML to RSX\n\tconst out = dioxus.translate_rsx(html, component);\n\tif (out.length > 0) {\n\t\teditor.edit(editBuilder => editBuilder.replace(editor.selection, out));\n\t} else {\n\t\tvscode.window.showWarningMessage(`Errors occurred while translating, make sure this block of HTML is valid`);\n\t}\n}\n\n\nasync function formatRsxDocument() {\n\tconst editor = vscode.window.activeTextEditor;\n\tif (!editor) return;\n\n\t// Apply RSX formatting directly using a WorkspaceEdit\n\tconst edits = await formatDocument(editor.document);\n\tif (edits.length > 0) {\n\t\tconst workspaceEdit = new vscode.WorkspaceEdit();\n\t\tfor (const edit of edits) {\n\t\t\tworkspaceEdit.replace(editor.document.uri, edit.range, edit.newText);\n\t\t}\n\t\tawait vscode.workspace.applyEdit(workspaceEdit);\n\t}\n}\n\nfunction fmtSelection() {\n\tconst editor = vscode.window.activeTextEditor;\n\tif (!editor) return;\n\n\tif (editor.document.languageId !== \"rust\") {\n\t\treturn;\n\t}\n\n\tlet end_line = editor.selection.end.line;\n\n\t// Select full lines of selection\n\tlet selection_range = new vscode.Range(\n\t\teditor.selection.start.line,\n\t\t0,\n\t\tend_line,\n\t\teditor.document.lineAt(end_line).range.end.character\n\t);\n\n\tlet unformatted = editor.document.getText(selection_range);\n\n\tif (unformatted.trim().length == 0) {\n\t\tvscode.window.showWarningMessage(\"Please select rsx invoking this command!\");\n\t\treturn;\n\t}\n\n\t// If number of closing braces is lower than opening braces, expand selection to end of initial block\n\twhile ((unformatted.match(/{/g) || []).length > (unformatted.match(/}/g) || []).length && end_line < editor.document.lineCount - 1) {\n\t\tend_line += 1;\n\n\t\tselection_range = new vscode.Range(\n\t\t\teditor.selection.start.line,\n\t\t\t0,\n\t\t\tend_line,\n\t\t\teditor.document.lineAt(end_line).range.end.character\n\t\t);\n\n\t\tunformatted = editor.document.getText(selection_range);\n\t}\n\n\tlet tabSize: number;\n\tif (typeof editor.options.tabSize === 'number') {\n\t\ttabSize = editor.options.tabSize;\n\t} else {\n\t\ttabSize = 4;\n\t}\n\n\tlet end_above = Math.max(editor.selection.start.line - 1, 0);\n\n\tlet lines_above = editor.document.getText(\n\t\tnew vscode.Range(\n\t\t\t0,\n\t\t\t0,\n\t\t\tend_above,\n\t\t\teditor.document.lineAt(end_above).range.end.character\n\t\t)\n\t);\n\n\t// Calculate indent for current selection\n\tlet base_indentation = (lines_above.match(/{/g) || []).length - (lines_above.match(/}/g) || []).length - 1;\n\n\ttry {\n\t\tlet formatted = dioxus.format_selection(unformatted, !editor.options.insertSpaces, tabSize, base_indentation);\n\t\tfor (let i = 0; i <= base_indentation; i++) {\n\t\t\tformatted = (editor.options.insertSpaces ? \" \".repeat(tabSize) : \"\\t\") + formatted;\n\t\t}\n\t\tif (formatted.length > 0) {\n\t\t\teditor.edit(editBuilder => {\n\t\t\t\teditBuilder.replace(selection_range, formatted);\n\t\t\t});\n\t\t}\n\t} catch (error) {\n\t\tvscode.window.showErrorMessage(`Errors occurred while formatting. Make sure you have the most recent Dioxus-CLI installed and you have selected valid rsx with your cursor! \\n${error}`);\n\t}\n\n}\n\nclass UriLaunchServer implements vscode.UriHandler {\n\thandleUri(uri: vscode.Uri): vscode.ProviderResult<void> {\n\t\tif (uri.path === '/debugger') {\n\t\t\tlet query = decodeURIComponent(uri.query);\n\t\t\tlet params = new URLSearchParams(query);\n\t\t\tlet route = params.get('uri');\n\t\t\tvscode.window.showInformationMessage(`Opening Chrome debugger: ${route}`);\n\t\t\tvscode.commands.executeCommand('extension.js-debug.debugLink', route);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "packages/extension/tsconfig.json",
    "content": "{\n\t\"compilerOptions\": {\n\t\t\"module\": \"commonjs\",\n\t\t\"target\": \"es2021\",\n\t\t\"lib\": [\n\t\t\t\"ES2021\"\n\t\t],\n\t\t\"outDir\": \"out\",\n\t\t\"sourceMap\": true,\n\t\t\"strict\": true,\n\t\t\"rootDir\": \"src\"\n\t},\n\t\"exclude\": [\n\t\t\"node_modules\",\n\t\t\".vscode-test\",\n\t\t\"out\"\n\t]\n}\n"
  },
  {
    "path": "packages/extension/webpack.config.js",
    "content": "//@ts-check\n'use strict';\n\nconst path = require('path');\nconst webpack = require('webpack');\n\n/**@type {import('webpack').Configuration}*/\nconst config = {\n  target: 'webworker', // vscode extensions run in webworker context for VS Code web 📖 -> https://webpack.js.org/configuration/target/#target\n  experiments: {\n      asyncWebAssembly: true,\n      layers: true,\n  },\n  entry: './src/main.ts', // the entry point of this extension, 📖 -> https://webpack.js.org/configuration/entry-context/\n  output: {\n    // the bundle is stored in the 'dist' folder (check package.json), 📖 -> https://webpack.js.org/configuration/output/\n    path: path.resolve(__dirname, 'out'),\n    filename: 'main.js',\n    libraryTarget: 'commonjs2',\n    devtoolModuleFilenameTemplate: '../[resource-path]',\n    publicPath: '',\n  },\n  devtool: 'source-map',\n  externals: {\n    vscode: 'commonjs vscode' // the vscode-module is created on-the-fly and must be excluded. Add other modules that cannot be webpack'ed, 📖 -> https://webpack.js.org/configuration/externals/\n  },\n  resolve: {\n    // support reading TypeScript and JavaScript files, 📖 -> https://github.com/TypeStrong/ts-loader\n    mainFields: ['browser', 'module', 'main'], // look for `browser` entry point in imported node modules\n    extensions: ['.ts', '.js'],\n    alias: {\n      // provides alternate implementation for node module and source files\n    },\n    fallback: {\n      // Webpack 5 no longer polyfills Node.js core modules automatically.\n      // see https://webpack.js.org/configuration/resolve/#resolvefallback\n      // for the list of Node.js core module polyfills.\n      // \"util\": require.resolve(\"util/\")\n    }\n  },\n  module: {\n    rules: [\n      {\n        test: /\\.ts$/,\n        exclude: /node_modules/,\n        use: [\n          {\n            loader: 'ts-loader'\n          }\n        ]\n      }\n    ]\n  },\n\n};\nmodule.exports = config;\n"
  },
  {
    "path": "packages/fullstack/.vscode/settings.json",
    "content": "{\n    \"rust-analyzer.cargo.features\": \"all\",\n    \"rust-analyzer.check.features\": \"all\",\n    \"rust-analyzer.cargo.extraArgs\": [\n        \"--tests\"\n    ],\n}\n"
  },
  {
    "path": "packages/fullstack/Cargo.toml",
    "content": "[package]\nname = \"dioxus-fullstack\"\nauthors = [\"Jonathan Kelley\", \"Evan Almloff\"]\nversion = { workspace = true }\nedition = \"2021\"\ndescription = \"Library for fetching resources from servers in Dioxus apps.\"\nlicense = \"MIT OR Apache-2.0\"\nrepository = \"https://github.com/DioxusLabs/dioxus/\"\nhomepage = \"https://dioxuslabs.com\"\nkeywords = [\"web\", \"gui\", \"server\"]\nresolver = \"2\"\n\n[dependencies]\n# shared for both server + client\nfutures-channel = { workspace = true }\nserde = { workspace = true, features = [\"derive\"] }\nciborium = { workspace = true }\nbase64 = { workspace = true }\ntracing = { workspace = true }\nthiserror = { workspace = true }\ndioxus-fullstack-core = { workspace = true }\nhttp = { workspace = true }\nanyhow = { workspace = true }\ndioxus-core = { workspace = true }\ndioxus-signals = { workspace = true }\ndioxus-hooks = { workspace = true }\ndioxus-html = { workspace = true }\ndioxus-cli-config = { workspace = true }\nfutures = { workspace = true, default-features = true }\nbytes = { workspace = true, features = [\"serde\"] }\ndioxus-fullstack-macro = { workspace = true }\nhttp-body-util = \"0.1.3\"\nfutures-util = { workspace = true }\naxum-core = { workspace = true }\nsend_wrapper = { features = [\"futures\"], workspace = true, default-features = true }\nserde_json = { workspace = true }\npin-project = { workspace = true }\nserde_qs = { workspace = true, default-features = true}\nmime = \"0.3.17\"\nreqwest = { workspace = true, features = [\"json\", \"rustls-tls\", \"cookies\", \"stream\", \"multipart\"] }\nurl = { workspace =  true }\nserde_urlencoded = { workspace = true }\nform_urlencoded = { workspace = true }\naxum = { workspace = true, default-features = false, features = [\"json\", \"form\", \"query\", \"multipart\", \"query\"] }\nhttp-body = { workspace = true }\nheaders = \"0.4.1\"\ndioxus-asset-resolver = { workspace = true, features = [\"native\"] }\n\nconst_format = { workspace = true, default-features = true }\nconst-str = { workspace = true, default-features = true }\nrustversion = { workspace = true, default-features = true }\nxxhash-rust = { features = [\"const_xxh64\"], workspace = true, default-features = true }\n# rmp-serde = { version = \"1.3\", default-features = true }\n\nderive_more = { version = \"2.0.1\", features = [\"deref\", \"deref_mut\", \"display\", \"from\"] }\n\n\n# server deps\naxum-extra = { workspace =  true, optional = true, features = [\"typed-header\"] }\ninventory = { workspace = true, optional = true }\ntokio = { workspace = true, optional = true }\ntokio-tungstenite = { workspace = true, optional = true }\ntokio-stream = { workspace = true, features = [\"sync\"], optional = true }\ntower = { workspace = true, features = [\"util\"], optional = true }\ntower-http = { workspace = true, features = [\"fs\", \"limit\"], optional = true }\ntower-layer = { version = \"0.3.3\", optional = true }\n\n# payloads\npostcard = { features = [\"alloc\", \"use-std\"], optional = true, workspace = true, default-features = true }\nrmp-serde = { version = \"1.3\", optional = true }\nasync-stream = \"0.3.6\"\n\n# websocket stuff\n# [target.'cfg(target_arch = \"wasm32\")'.dependencies]\nweb-sys = { version = \"0.3\", features = [\"WebSocket\", \"CloseEvent\", \"ErrorEvent\", \"Event\", \"MessageEvent\", \"BinaryType\", \"FormData\", \"Response\", \"ReadableStream\", \"AbortController\", \"File\", \"ReadableStream\", \"HtmlFormElement\", \"FormData\"], optional = true }\ngloo-net = \"0.6.0\"\njs-sys = { workspace = true }\nwasm-bindgen = { workspace = true }\nwasm-bindgen-futures = { workspace = true }\nwasm-streams = \"0.4.2\"\ncontent_disposition = \"0.4.0\"\n\n# native websocket deps\n[target.'cfg(not(target_arch = \"wasm32\"))'.dependencies]\nasync-tungstenite = { version = \"0.31.0\", default-features = false, features = [\"futures-03-sink\"], optional = true }\ntungstenite = { version = \"0.27\", default-features = false, features = [\"handshake\"], optional = true }\ntokio-util = { workspace = true, features = [\"codec\", \"compat\", \"rt\"] }\n\n[dev-dependencies]\ndioxus = { workspace = true, features = [\"fullstack\", \"router\"] }\ndioxus-server = { workspace = true }\n\n[features]\ndefault = [\"ws\"]\nweb = [\n    \"dep:web-sys\"\n]\nnative = []\nserver = [\n    \"dep:inventory\",\n    \"dep:tokio\",\n    \"dep:tokio-tungstenite\",\n    \"dep:tokio-stream\",\n    \"dep:async-tungstenite\",\n    \"dep:tungstenite\",\n    \"dep:tower\",\n    \"dep:tower-http\",\n    \"dep:tower-layer\",\n    \"dep:axum-extra\",\n    \"axum/ws\",\n    \"axum/tokio\",\n]\npostcard = [\"dep:postcard\"]\nmsgpack = [\"dep:rmp-serde\"]\nws = [\"dep:async-tungstenite\", \"dep:tungstenite\"]\n\n[package.metadata.docs.rs]\ncargo-args = [\"-Zunstable-options\", \"-Zrustdoc-scrape-examples\"]\n"
  },
  {
    "path": "packages/fullstack/README.md",
    "content": "# dioxus-fullstack\n\n- wraps dioxus-server\n- exposes rpc system\n\n"
  },
  {
    "path": "packages/fullstack/src/client.rs",
    "content": "#![allow(unreachable_code)]\n\nuse crate::{reqwest_error_to_request_error, StreamingError};\nuse bytes::Bytes;\nuse dioxus_fullstack_core::RequestError;\nuse futures::Stream;\nuse futures::{TryFutureExt, TryStreamExt};\nuse headers::{ContentType, Header};\nuse http::{response::Parts, Extensions, HeaderMap, HeaderName, HeaderValue, Method, StatusCode};\nuse send_wrapper::SendWrapper;\nuse serde::{de::DeserializeOwned, Serialize};\nuse std::sync::{LazyLock, Mutex, OnceLock};\nuse std::{fmt::Display, pin::Pin, prelude::rust_2024::Future};\nuse url::Url;\n\npub static GLOBAL_REQUEST_CLIENT: OnceLock<reqwest::Client> = OnceLock::new();\n\npub type ClientResult = Result<ClientResponse, RequestError>;\n\npub struct ClientRequest {\n    pub url: Url,\n    pub headers: HeaderMap,\n    pub method: Method,\n    pub extensions: Extensions,\n}\n\nimpl ClientRequest {\n    /// Create a new ClientRequest with the given method, url path, and query parameters.\n    pub fn new(method: http::Method, path: String, params: &impl Serialize) -> Self {\n        Self::fetch_inner(method, path, serde_qs::to_string(params).unwrap())\n    }\n\n    // Shrink monomorphization bloat by moving this to its own function\n    fn fetch_inner(method: http::Method, path: String, query: String) -> ClientRequest {\n        // On wasm, this doesn't matter since we always use relative URLs when making requests anyways\n        let mut server_url = get_server_url();\n\n        if server_url.is_empty() {\n            server_url = \"http://this.is.not.a.real.url:9000\";\n        }\n\n        let url = format!(\n            \"{server_url}{path}{params}\",\n            params = if query.is_empty() {\n                \"\".to_string()\n            } else {\n                format!(\"?{}\", query)\n            }\n        )\n        .parse()\n        .unwrap();\n\n        let headers = get_request_headers();\n\n        ClientRequest {\n            method,\n            url,\n            headers,\n            extensions: Extensions::new(),\n        }\n    }\n\n    /// Get the HTTP method of this Request.\n    pub fn method(&self) -> &Method {\n        &self.method\n    }\n\n    pub fn url(&self) -> &Url {\n        &self.url\n    }\n\n    /// Extend the query parameters of this request with the given serialzable struct.\n    ///\n    /// This will use `serde_qs` to serialize the struct into query parameters. `serde_qs` has various\n    /// restrictions - make sure to read its documentation!\n    pub fn extend_query(mut self, query: &impl Serialize) -> Self {\n        let old_query = self.url.query().unwrap_or(\"\");\n        let new_query = serde_qs::to_string(query).unwrap();\n        let combined_query = format!(\n            \"{}{}{}\",\n            old_query,\n            if old_query.is_empty() { \"\" } else { \"&\" },\n            new_query\n        );\n        self.url.set_query(Some(&combined_query));\n        self\n    }\n\n    /// Add a `Header` to this Request.\n    pub fn header(\n        mut self,\n        name: impl TryInto<HeaderName, Error = impl Display>,\n        value: impl TryInto<HeaderValue, Error = impl Display>,\n    ) -> Result<Self, RequestError> {\n        self.headers.append(\n            name.try_into()\n                .map_err(|d| RequestError::Builder(d.to_string()))?,\n            value\n                .try_into()\n                .map_err(|d| RequestError::Builder(d.to_string()))?,\n        );\n        Ok(self)\n    }\n\n    /// Add a `Header` to this Request.\n    pub fn typed_header<H: Header>(mut self, header: H) -> Self {\n        let mut headers = vec![];\n        header.encode(&mut headers);\n        for header in headers {\n            self.headers.append(H::name(), header);\n        }\n        self\n    }\n\n    /// Creates a new reqwest client with cookies set\n    pub fn new_reqwest_client() -> reqwest::Client {\n        #[allow(unused_mut)]\n        let mut client = reqwest::Client::builder();\n\n        #[cfg(not(target_arch = \"wasm32\"))]\n        {\n            use std::sync::Arc;\n            use std::sync::LazyLock;\n\n            static COOKIES: LazyLock<Arc<reqwest::cookie::Jar>> =\n                LazyLock::new(|| Arc::new(reqwest::cookie::Jar::default()));\n\n            client = client.cookie_store(true).cookie_provider(COOKIES.clone());\n        }\n\n        client.build().unwrap()\n    }\n\n    /// Creates a new reqwest request builder with the method, url, and headers set from this ClientRequest\n    ///\n    /// Using this method attaches `X-Request-Client: dioxus` header to the request.\n    pub fn new_reqwest_request(&self) -> reqwest::RequestBuilder {\n        let client = GLOBAL_REQUEST_CLIENT.get_or_init(Self::new_reqwest_client);\n\n        let mut req = client\n            .request(self.method.clone(), self.url.clone())\n            .header(\"X-Request-Client\", \"dioxus\");\n\n        for (key, value) in self.headers.iter() {\n            req = req.header(key, value);\n        }\n\n        req\n    }\n\n    /// Using this method attaches `X-Request-Client-Dioxus` header to the request.\n    #[cfg(feature = \"web\")]\n    pub fn new_gloo_request(&self) -> gloo_net::http::RequestBuilder {\n        let mut builder = gloo_net::http::RequestBuilder::new(\n            format!(\n                \"{path}{query_string}\",\n                path = self.url.path(),\n                query_string = self\n                    .url\n                    .query()\n                    .map(|query| format!(\"?{query}\"))\n                    .unwrap_or_default()\n            )\n            .as_str(),\n        )\n        .header(\"X-Request-Client\", \"dioxus\")\n        .method(self.method.clone());\n\n        for (key, value) in self.headers.iter() {\n            let value = match value.to_str() {\n                Ok(v) => v,\n                Err(er) => {\n                    tracing::error!(\"Error converting header {key} value: {}\", er);\n                    continue;\n                }\n            };\n\n            builder = builder.header(key.as_str(), value);\n        }\n\n        builder\n    }\n\n    /// Sends the request with multipart/form-data body constructed from the given FormData.\n    #[cfg(not(target_arch = \"wasm32\"))]\n    pub async fn send_multipart(\n        self,\n        form: &dioxus_html::FormData,\n    ) -> Result<ClientResponse, RequestError> {\n        let mut outgoing = reqwest::multipart::Form::new();\n\n        for (key, value) in form.values() {\n            match value {\n                dioxus_html::FormValue::Text(text) => {\n                    outgoing = outgoing.text(key.to_string(), text.to_string());\n                }\n                dioxus_html::FormValue::File(Some(file_data)) => {\n                    outgoing = outgoing\n                        .file(key.to_string(), file_data.path())\n                        .await\n                        .map_err(|e| {\n                            RequestError::Builder(format!(\n                                \"Failed to add file to multipart form: {e}\",\n                            ))\n                        })?;\n                }\n                dioxus_html::FormValue::File(None) => {\n                    // No file was selected for this input, so we skip it.\n                    outgoing = outgoing.part(key.to_string(), reqwest::multipart::Part::bytes(b\"\"));\n                }\n            }\n        }\n\n        let res = self\n            .new_reqwest_request()\n            .multipart(outgoing)\n            .send()\n            .await\n            .map_err(reqwest_error_to_request_error)?;\n\n        Ok(ClientResponse {\n            response: Box::new(res),\n            extensions: self.extensions,\n        })\n    }\n\n    pub async fn send_form(self, data: &impl Serialize) -> Result<ClientResponse, RequestError> {\n        // For GET and HEAD requests, we encode the form data as query parameters.\n        // For other request methods, we encode the form data as the request body.\n        if matches!(*self.method(), Method::GET | Method::HEAD) {\n            return self.extend_query(data).send_empty_body().await;\n        }\n\n        let body =\n            serde_urlencoded::to_string(data).map_err(|err| RequestError::Body(err.to_string()))?;\n\n        self.typed_header(ContentType::form_url_encoded())\n            .send_raw_bytes(body)\n            .await\n    }\n\n    /// Sends the request with an empty body.\n    pub async fn send_empty_body(self) -> Result<ClientResponse, RequestError> {\n        #[cfg(feature = \"web\")]\n        if cfg!(target_arch = \"wasm32\") {\n            return self.send_js_value(wasm_bindgen::JsValue::UNDEFINED).await;\n        }\n\n        #[cfg(not(target_arch = \"wasm32\"))]\n        {\n            let res = self\n                .new_reqwest_request()\n                .send()\n                .await\n                .map_err(reqwest_error_to_request_error)?;\n\n            return Ok(ClientResponse {\n                response: Box::new(res),\n                extensions: self.extensions,\n            });\n        }\n\n        unimplemented!()\n    }\n\n    pub async fn send_raw_bytes(\n        self,\n        bytes: impl Into<Bytes>,\n    ) -> Result<ClientResponse, RequestError> {\n        #[cfg(feature = \"web\")]\n        if cfg!(target_arch = \"wasm32\") {\n            let bytes = bytes.into();\n            let uint_8_array = js_sys::Uint8Array::from(&bytes[..]);\n            return self.send_js_value(uint_8_array.into()).await;\n        }\n\n        #[cfg(not(target_arch = \"wasm32\"))]\n        {\n            let res = self\n                .new_reqwest_request()\n                .body(bytes.into())\n                .send()\n                .await\n                .map_err(reqwest_error_to_request_error)?;\n\n            return Ok(ClientResponse {\n                response: Box::new(res),\n                extensions: self.extensions,\n            });\n        }\n\n        unimplemented!()\n    }\n\n    /// Sends text data with the `text/plain; charset=utf-8` content type.\n    pub async fn send_text(\n        self,\n        text: impl Into<String> + Into<Bytes>,\n    ) -> Result<ClientResponse, RequestError> {\n        self.typed_header(ContentType::text_utf8())\n            .send_raw_bytes(text)\n            .await\n    }\n\n    /// Sends JSON data with the `application/json` content type.\n    pub async fn send_json(self, json: &impl Serialize) -> Result<ClientResponse, RequestError> {\n        let bytes =\n            serde_json::to_vec(json).map_err(|e| RequestError::Serialization(e.to_string()))?;\n\n        if bytes.is_empty() || bytes == b\"{}\" || bytes == b\"null\" {\n            return self.send_empty_body().await;\n        }\n\n        self.typed_header(ContentType::json())\n            .send_raw_bytes(bytes)\n            .await\n    }\n\n    pub async fn send_body_stream(\n        self,\n        stream: impl Stream<Item = Result<Bytes, StreamingError>> + Send + 'static,\n    ) -> Result<ClientResponse, RequestError> {\n        #[cfg(not(target_arch = \"wasm32\"))]\n        {\n            let res = self\n                .new_reqwest_request()\n                .body(reqwest::Body::wrap_stream(stream))\n                .send()\n                .await\n                .map_err(reqwest_error_to_request_error)?;\n\n            return Ok(ClientResponse {\n                response: Box::new(res),\n                extensions: self.extensions,\n            });\n        }\n\n        // On the web, we have to buffer the entire stream into a Blob before sending it,\n        // since the Fetch API doesn't support streaming request bodies on browsers yet.\n        #[cfg(feature = \"web\")]\n        {\n            use wasm_bindgen::JsValue;\n\n            let stream: Vec<Bytes> = stream.try_collect().await.map_err(|e| {\n                RequestError::Request(format!(\"Error collecting stream for request body: {}\", e))\n            })?;\n\n            let uint_8_array =\n                js_sys::Uint8Array::new_with_length(stream.iter().map(|b| b.len() as u32).sum());\n\n            let mut offset = 0;\n            for chunk in stream {\n                uint_8_array.set(&js_sys::Uint8Array::from(&chunk[..]), offset);\n                offset += chunk.len() as u32;\n            }\n\n            return self.send_js_value(JsValue::from(uint_8_array)).await;\n        }\n\n        unimplemented!()\n    }\n\n    #[cfg(feature = \"web\")]\n    pub async fn send_js_value(\n        self,\n        value: wasm_bindgen::JsValue,\n    ) -> Result<ClientResponse, RequestError> {\n        use std::str::FromStr;\n\n        let inner = self\n            .new_gloo_request()\n            .body(value)\n            .map_err(|e| RequestError::Request(e.to_string()))?\n            .send()\n            .await\n            .map_err(|e| RequestError::Request(e.to_string()))?;\n\n        let status = inner.status();\n        let url = inner\n            .url()\n            .parse()\n            .map_err(|e| RequestError::Request(format!(\"Error parsing response URL: {}\", e)))?;\n\n        let headers = {\n            let mut map = HeaderMap::new();\n            for (key, value) in inner.headers().entries() {\n                if let Ok(header_value) = http::HeaderValue::from_str(&value) {\n                    let header = HeaderName::from_str(&key).unwrap();\n                    map.append(header, header_value);\n                }\n            }\n            map\n        };\n\n        let content_length = headers\n            .get(http::header::CONTENT_LENGTH)\n            .and_then(|val| val.to_str().ok())\n            .and_then(|s| s.parse::<u64>().ok());\n\n        let status = http::StatusCode::from_u16(status).unwrap_or(http::StatusCode::OK);\n\n        Ok(ClientResponse {\n            extensions: self.extensions,\n            response: Box::new(browser::WrappedGlooResponse {\n                inner,\n                headers,\n                status,\n                url,\n                content_length,\n            }),\n        })\n    }\n}\n\n// On wasm reqwest not being send/sync gets annoying, but it's not relevant since wasm is single-threaded\nunsafe impl Send for ClientRequest {}\nunsafe impl Sync for ClientRequest {}\n\n/// A wrapper type over the platform's HTTP response type.\n///\n/// This abstracts over the inner `reqwest::Response` type and provides the original request\n/// and a way to store state associated with the response.\n///\n/// On the web, it uses `web_sys::Response` instead of `reqwest::Response` to avoid pulling in\n/// the entire `reqwest` crate and to support native browser APIs.\npub struct ClientResponse {\n    pub(crate) response: Box<dyn ClientResponseDriver>,\n    pub(crate) extensions: Extensions,\n}\n\nimpl ClientResponse {\n    pub fn status(&self) -> StatusCode {\n        self.response.status()\n    }\n\n    pub fn headers(&self) -> &HeaderMap {\n        self.response.headers()\n    }\n\n    pub fn url(&self) -> &Url {\n        self.response.url()\n    }\n\n    pub fn content_length(&self) -> Option<u64> {\n        self.response.content_length()\n    }\n\n    pub async fn bytes(self) -> Result<Bytes, RequestError> {\n        self.response.bytes().await\n    }\n\n    pub fn bytes_stream(\n        self,\n    ) -> impl futures_util::Stream<Item = Result<Bytes, StreamingError>> + 'static + Unpin + Send\n    {\n        self.response.bytes_stream()\n    }\n\n    pub fn extensions(&self) -> &Extensions {\n        &self.extensions\n    }\n\n    pub fn extensions_mut(&mut self) -> &mut Extensions {\n        &mut self.extensions\n    }\n\n    pub async fn json<T: DeserializeOwned>(self) -> Result<T, RequestError> {\n        serde_json::from_slice(&self.bytes().await?)\n            .map_err(|e| RequestError::Decode(e.to_string()))\n    }\n\n    pub async fn text(self) -> Result<String, RequestError> {\n        self.response.text().await\n    }\n\n    /// Creates the `http::response::Parts` from this response.\n    pub fn make_parts(&self) -> Parts {\n        let mut response = http::response::Response::builder().status(self.response.status());\n\n        response = response.version(self.response.version());\n\n        for (key, value) in self.response.headers().iter() {\n            response = response.header(key, value);\n        }\n\n        let (parts, _) = response.body(()).unwrap().into_parts();\n\n        parts\n    }\n\n    /// Consumes the response, returning the head and a stream of the body.\n    pub fn into_parts(self) -> (Parts, impl Stream<Item = Result<Bytes, StreamingError>>) {\n        (self.make_parts(), self.bytes_stream())\n    }\n}\n\n/// Set the root server URL that all server function paths are relative to for the client.\n///\n/// If this is not set, it defaults to the origin.\npub fn set_server_url(url: &'static str) {\n    ROOT_URL.set(url).unwrap();\n}\n\n/// Returns the root server URL for all server functions.\npub fn get_server_url() -> &'static str {\n    ROOT_URL.get().copied().unwrap_or(\"\")\n}\n\nstatic ROOT_URL: OnceLock<&'static str> = OnceLock::new();\n\n/// Delete the extra request headers for all server functions.\npub fn clear_request_headers() {\n    REQUEST_HEADERS.lock().unwrap().clear();\n}\n\n/// Set the extra request headers for all server functions.\npub fn set_request_headers(headers: HeaderMap) {\n    *REQUEST_HEADERS.lock().unwrap() = headers;\n}\n\n/// Returns the extra request headers for all server functions.\npub fn get_request_headers() -> HeaderMap {\n    REQUEST_HEADERS.lock().unwrap().clone()\n}\n\nstatic REQUEST_HEADERS: LazyLock<Mutex<HeaderMap>> = LazyLock::new(|| Mutex::new(HeaderMap::new()));\n\npub trait ClientResponseDriver {\n    fn status(&self) -> StatusCode;\n    fn headers(&self) -> &HeaderMap;\n    fn url(&self) -> &Url;\n    fn version(&self) -> http::Version {\n        http::Version::HTTP_2\n    }\n    fn content_length(&self) -> Option<u64>;\n    fn bytes(self: Box<Self>) -> Pin<Box<dyn Future<Output = Result<Bytes, RequestError>> + Send>>;\n    fn bytes_stream(\n        self: Box<Self>,\n    ) -> Pin<Box<dyn Stream<Item = Result<Bytes, StreamingError>> + 'static + Unpin + Send>>;\n\n    fn text(self: Box<Self>) -> Pin<Box<dyn Future<Output = Result<String, RequestError>> + Send>>;\n}\n\nmod native {\n    use futures::Stream;\n\n    use super::*;\n\n    impl ClientResponseDriver for reqwest::Response {\n        fn status(&self) -> http::StatusCode {\n            reqwest::Response::status(self)\n        }\n\n        fn version(&self) -> http::Version {\n            #[cfg(target_arch = \"wasm32\")]\n            {\n                return http::Version::HTTP_2;\n            }\n\n            reqwest::Response::version(self)\n        }\n\n        fn headers(&self) -> &http::HeaderMap {\n            reqwest::Response::headers(self)\n        }\n\n        fn url(&self) -> &url::Url {\n            reqwest::Response::url(self)\n        }\n\n        fn content_length(&self) -> Option<u64> {\n            reqwest::Response::content_length(self)\n        }\n\n        fn bytes(\n            self: Box<Self>,\n        ) -> Pin<Box<dyn Future<Output = Result<Bytes, RequestError>> + Send>> {\n            Box::pin(SendWrapper::new(async move {\n                reqwest::Response::bytes(*self)\n                    .map_err(reqwest_error_to_request_error)\n                    .await\n            }))\n        }\n\n        fn bytes_stream(\n            self: Box<Self>,\n        ) -> Pin<Box<dyn Stream<Item = Result<Bytes, StreamingError>> + 'static + Unpin + Send>>\n        {\n            Box::pin(SendWrapper::new(\n                reqwest::Response::bytes_stream(*self).map_err(|_| StreamingError::Failed),\n            ))\n        }\n\n        fn text(\n            self: Box<Self>,\n        ) -> Pin<Box<dyn Future<Output = Result<String, RequestError>> + Send>> {\n            Box::pin(SendWrapper::new(async move {\n                reqwest::Response::text(*self)\n                    .map_err(reqwest_error_to_request_error)\n                    .await\n            }))\n        }\n    }\n}\n\n#[cfg(feature = \"web\")]\nmod browser {\n    use crate::{ClientResponseDriver, StreamingError};\n    use bytes::Bytes;\n    use dioxus_fullstack_core::RequestError;\n    use futures::{Stream, StreamExt};\n    use http::{HeaderMap, StatusCode};\n    use js_sys::Uint8Array;\n    use send_wrapper::SendWrapper;\n    use std::{pin::Pin, prelude::rust_2024::Future};\n    use wasm_bindgen::JsCast;\n\n    pub(crate) struct WrappedGlooResponse {\n        pub(crate) inner: gloo_net::http::Response,\n        pub(crate) headers: HeaderMap,\n        pub(crate) status: StatusCode,\n        pub(crate) url: url::Url,\n        pub(crate) content_length: Option<u64>,\n    }\n\n    impl ClientResponseDriver for WrappedGlooResponse {\n        fn status(&self) -> StatusCode {\n            self.status\n        }\n\n        fn headers(&self) -> &HeaderMap {\n            &self.headers\n        }\n\n        fn url(&self) -> &url::Url {\n            &self.url\n        }\n\n        fn content_length(&self) -> Option<u64> {\n            self.content_length\n        }\n\n        fn bytes(\n            self: Box<Self>,\n        ) -> Pin<Box<dyn Future<Output = Result<Bytes, RequestError>> + Send>> {\n            Box::pin(SendWrapper::new(async move {\n                let bytes = self\n                    .inner\n                    .binary()\n                    .await\n                    .map_err(|e| RequestError::Request(e.to_string()))?;\n                Ok(bytes.into())\n            }))\n        }\n\n        fn bytes_stream(\n            self: Box<Self>,\n        ) -> Pin<Box<dyn Stream<Item = Result<Bytes, StreamingError>> + 'static + Unpin + Send>>\n        {\n            let body = match self.inner.body() {\n                Some(body) => body,\n                None => {\n                    return Box::pin(SendWrapper::new(futures::stream::iter([Err(\n                        StreamingError::Failed,\n                    )])));\n                }\n            };\n\n            Box::pin(SendWrapper::new(\n                wasm_streams::ReadableStream::from_raw(body)\n                    .into_stream()\n                    .map(|chunk| {\n                        let array = chunk\n                            .map_err(|_| StreamingError::Failed)?\n                            .dyn_into::<Uint8Array>()\n                            .map_err(|_| StreamingError::Failed)?;\n                        Ok(array.to_vec().into())\n                    }),\n            ))\n        }\n\n        fn text(\n            self: Box<Self>,\n        ) -> Pin<Box<dyn Future<Output = Result<String, RequestError>> + Send>> {\n            Box::pin(SendWrapper::new(async move {\n                self.inner\n                    .text()\n                    .await\n                    .map_err(|e| RequestError::Request(e.to_string()))\n            }))\n        }\n    }\n}\n"
  },
  {
    "path": "packages/fullstack/src/encoding.rs",
    "content": "use bytes::Bytes;\nuse serde::{de::DeserializeOwned, Serialize};\n\n/// A trait for encoding and decoding data.\n///\n/// This takes an owned self to make it easier for zero-copy encodings.\npub trait Encoding: 'static {\n    fn content_type() -> &'static str;\n    fn stream_content_type() -> &'static str;\n    fn to_bytes(data: impl Serialize) -> Option<Bytes> {\n        let mut buf = Vec::new();\n        Self::encode(data, &mut buf)?;\n        Some(buf.into())\n    }\n    fn encode(data: impl Serialize, buf: &mut Vec<u8>) -> Option<usize>;\n    fn decode<O: DeserializeOwned>(bytes: Bytes) -> Option<O>;\n}\n\npub struct JsonEncoding;\nimpl Encoding for JsonEncoding {\n    fn content_type() -> &'static str {\n        \"application/json\"\n    }\n    fn stream_content_type() -> &'static str {\n        \"application/stream+json\"\n    }\n\n    fn encode(data: impl Serialize, mut buf: &mut Vec<u8>) -> Option<usize> {\n        let len = buf.len();\n        serde_json::to_writer(&mut buf, &data).ok()?;\n        Some(buf.len() - len)\n    }\n\n    fn decode<O: DeserializeOwned>(bytes: Bytes) -> Option<O> {\n        serde_json::from_slice(&bytes).ok()\n    }\n}\n\npub struct CborEncoding;\nimpl Encoding for CborEncoding {\n    fn content_type() -> &'static str {\n        \"application/cbor\"\n    }\n    fn stream_content_type() -> &'static str {\n        \"application/stream+cbor\"\n    }\n\n    fn decode<O: DeserializeOwned>(bytes: Bytes) -> Option<O> {\n        ciborium::de::from_reader(bytes.as_ref()).ok()\n    }\n\n    fn encode(data: impl Serialize, mut buf: &mut Vec<u8>) -> Option<usize> {\n        let len = buf.len();\n        ciborium::into_writer(&data, &mut buf).ok()?;\n        Some(buf.len() - len)\n    }\n}\n\n#[cfg(feature = \"postcard\")]\npub struct PostcardEncoding;\n#[cfg(feature = \"postcard\")]\nimpl Encoding for PostcardEncoding {\n    fn content_type() -> &'static str {\n        \"application/postcard\"\n    }\n    fn stream_content_type() -> &'static str {\n        \"application/stream+postcard\"\n    }\n\n    fn encode(data: impl Serialize, mut buf: &mut Vec<u8>) -> Option<usize> {\n        let len = buf.len();\n        postcard::to_io(&data, &mut buf).ok()?;\n        Some(buf.len() - len)\n    }\n\n    fn decode<O: DeserializeOwned>(bytes: Bytes) -> Option<O> {\n        postcard::from_bytes(bytes.as_ref()).ok()\n    }\n}\n\n#[cfg(feature = \"msgpack\")]\npub struct MsgPackEncoding;\n#[cfg(feature = \"msgpack\")]\nimpl Encoding for MsgPackEncoding {\n    fn content_type() -> &'static str {\n        \"application/msgpack\"\n    }\n    fn stream_content_type() -> &'static str {\n        \"application/stream+msgpack\"\n    }\n    fn encode(data: impl Serialize, buf: &mut Vec<u8>) -> Option<usize> {\n        let len = buf.len();\n        rmp_serde::encode::write(buf, &data).ok()?;\n        Some(buf.len() - len)\n    }\n\n    fn decode<O: DeserializeOwned>(bytes: Bytes) -> Option<O> {\n        rmp_serde::from_slice(&bytes).ok()\n    }\n}\n\n// todo: ... add rkyv support\n// pub struct RkyvEncoding;\n// impl Encoding for RkyvEncoding {\n//     fn content_type() -> &'static str {\n//         \"application/rkyv\"\n//     }\n//     fn stream_content_type() -> &'static str {\n//         \"application/stream+rkyv\"\n//     }\n//     fn to_bytes(data: impl Serialize) -> Option<Bytes> {\n//         let mut buf = rkyv::ser::Serializer::new(rkyv::ser::AllocSerializer::new());\n//         rkyv::ser::Serializer::serialize(&mut buf, &data).ok()?;\n//         Some(Bytes::from(buf.into_inner()))\n//     }\n//     fn from_bytes<O: DeserializeOwned>(bytes: Bytes) -> Option<O> {\n//         let archived = unsafe { rkyv::archived_root::<O>(&bytes) };\n//         rkyv::Deserialize::deserialize(archived).ok()\n//     }\n// }\n"
  },
  {
    "path": "packages/fullstack/src/lazy.rs",
    "content": "#![allow(clippy::needless_return)]\n\nuse dioxus_core::CapturedError;\nuse std::{hint::black_box, prelude::rust_2024::Future, sync::atomic::AtomicBool};\n\n/// `Lazy` is a thread-safe, lazily-initialized global variable.\n///\n/// Unlike other async once-cell implementations, accessing the value of a `Lazy` instance is synchronous\n/// and done on `deref`.\n///\n/// This is done by offloading the async initialization to a blocking thread during the first access,\n/// and then using the initialized value for all subsequent accesses.\n///\n/// It uses `std::sync::OnceLock` internally to ensure that the value is only initialized once.\npub struct Lazy<T> {\n    value: std::sync::OnceLock<T>,\n    started_initialization: AtomicBool,\n    constructor: Option<fn() -> Result<T, CapturedError>>,\n    _phantom: std::marker::PhantomData<T>,\n}\n\nimpl<T: Send + Sync + 'static> Lazy<T> {\n    /// Create a new `Lazy` instance.\n    ///\n    /// This internally calls `std::sync::OnceLock::new()` under the hood.\n    #[allow(clippy::self_named_constructors)]\n    pub const fn lazy() -> Self {\n        Self {\n            _phantom: std::marker::PhantomData,\n            constructor: None,\n            started_initialization: AtomicBool::new(false),\n            value: std::sync::OnceLock::new(),\n        }\n    }\n\n    pub const fn new<F, G, E>(constructor: F) -> Self\n    where\n        F: Fn() -> G + Copy,\n        G: Future<Output = Result<T, E>> + Send + 'static,\n        E: Into<CapturedError>,\n    {\n        if std::mem::size_of::<F>() != 0 {\n            panic!(\"The constructor function must be a zero-sized type (ZST). Consider using a function pointer or a closure without captured variables.\");\n        }\n\n        // Prevent the constructor from being optimized out\n        black_box(constructor);\n\n        Self {\n            _phantom: std::marker::PhantomData,\n            value: std::sync::OnceLock::new(),\n            started_initialization: AtomicBool::new(false),\n            constructor: Some(blocking_initialize::<T, F, G, E>),\n        }\n    }\n\n    /// Set the value of the `Lazy` instance.\n    ///\n    /// This should only be called once during the server setup phase, typically inside `dioxus::serve`.\n    /// Future calls to this method will return an error containing the provided value.\n    pub fn set(&self, pool: T) -> Result<(), CapturedError> {\n        let res = self.value.set(pool);\n        if res.is_err() {\n            return Err(anyhow::anyhow!(\"Lazy value is already initialized.\").into());\n        }\n\n        Ok(())\n    }\n\n    pub fn try_set(&self, pool: T) -> Result<(), T> {\n        self.value.set(pool)\n    }\n\n    /// Initialize the value of the `Lazy` instance if it hasn't been initialized yet.\n    pub fn initialize(&self) -> Result<(), CapturedError> {\n        if let Some(constructor) = self.constructor {\n            // If we're already initializing this value, wait on the receiver.\n            if self\n                .started_initialization\n                .swap(true, std::sync::atomic::Ordering::SeqCst)\n            {\n                self.value.wait();\n                return Ok(());\n            }\n\n            // Otherwise, we need to initialize the value\n            self.set(constructor().unwrap())?;\n        }\n        Ok(())\n    }\n\n    /// Get a reference to the value of the `Lazy` instance. This will block the current thread if the\n    /// value is not yet initialized.\n    pub fn get(&self) -> &T {\n        if self.constructor.is_none() {\n            return self.value.get().expect(\"Lazy value is not initialized. Make sure to call `initialize` before dereferencing.\");\n        };\n\n        if self.value.get().is_none() {\n            self.initialize().expect(\"Failed to initialize lazy value\");\n        }\n\n        self.value.get().unwrap()\n    }\n}\n\nimpl<T: Send + Sync + 'static> Default for Lazy<T> {\n    fn default() -> Self {\n        Self::lazy()\n    }\n}\n\nimpl<T: Send + Sync + 'static> std::ops::Deref for Lazy<T> {\n    type Target = T;\n\n    fn deref(&self) -> &Self::Target {\n        self.get()\n    }\n}\n\nimpl<T: std::fmt::Debug + Send + Sync + 'static> std::fmt::Debug for Lazy<T> {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_struct(\"Lazy\").field(\"value\", self.get()).finish()\n    }\n}\n\n/// This is a small hack that allows us to staple the async initialization into a blocking context.\n///\n/// We call the `rust-call` method of the zero-sized constructor function. This is safe because we're\n/// not actually dereferencing any unsafe data, just calling its vtable entry to get the future.\nfn blocking_initialize<T, F, G, E>() -> Result<T, CapturedError>\nwhere\n    T: Send + Sync + 'static,\n    F: Fn() -> G + Copy,\n    G: Future<Output = Result<T, E>> + Send + 'static,\n    E: Into<CapturedError>,\n{\n    assert_eq!(std::mem::size_of::<F>(), 0, \"The constructor function must be a zero-sized type (ZST). Consider using a function pointer or a closure without captured variables.\");\n\n    #[cfg(feature = \"server\")]\n    {\n        let ptr: F = unsafe { std::mem::zeroed() };\n        let fut = ptr();\n        return std::thread::spawn(move || {\n            tokio::runtime::Builder::new_current_thread()\n                .enable_all()\n                .build()\n                .unwrap()\n                .block_on(fut)\n                .map_err(|e| e.into())\n        })\n        .join()\n        .unwrap();\n    }\n\n    // todo: technically we can support constructors in wasm with the same tricks inventory uses with `__wasm_call_ctors`\n    // the host would need to decide when to cal the ctors and when to block them.\n    #[cfg(not(feature = \"server\"))]\n    unimplemented!(\"Lazy initialization is only supported with tokio and threads enabled.\")\n}\n"
  },
  {
    "path": "packages/fullstack/src/lib.rs",
    "content": "// #![warn(missing_docs)]\n#![allow(clippy::manual_async_fn)]\n#![allow(clippy::needless_return)]\n\npub use client::{get_server_url, set_server_url};\npub use dioxus_fullstack_core::*;\n\n#[doc(inline)]\npub use dioxus_fullstack_macro::*;\n\npub use axum_core;\npub use headers;\npub use http;\npub use reqwest;\npub use serde;\n\n/// Re-export commonly used items from axum, http, and hyper for convenience.\npub use axum::{body, extract, response, routing};\n\n#[doc(hidden)]\npub use const_format;\n#[doc(hidden)]\npub use const_str;\n#[doc(hidden)]\npub use xxhash_rust;\n\n#[cfg(feature = \"server\")]\npub use {axum, axum_extra::TypedHeader, inventory};\n\n#[cfg(feature = \"server\")]\npub(crate) mod spawn;\n#[cfg(feature = \"server\")]\npub(crate) use spawn::*;\n\npub mod magic;\npub use magic::*;\n\npub mod request;\npub use request::*;\n\npub use http::StatusCode;\n\npub mod encoding;\npub use encoding::*;\n\npub mod lazy;\npub use lazy::*;\n\npub use http::{HeaderMap, HeaderValue, Method};\n\nmod client;\npub use client::*;\n\npub use axum::extract::Json;\npub use axum::response::{NoContent, Redirect};\n\npub use crate::request::{FromResponse, FromResponseParts};\n\npub use payloads::*;\npub mod payloads {\n    use crate::{ClientRequest, ClientResponse, ClientResult, IntoRequest};\n    use crate::{FromResponse, FromResponseParts};\n    use axum::extract::FromRequest;\n    use axum::response::{IntoResponse, IntoResponseParts, ResponseParts};\n    use bytes::Bytes;\n    use dioxus_fullstack_core::ServerFnError;\n    use futures::Stream;\n    use headers::Header;\n    use http::{header::InvalidHeaderValue, HeaderValue};\n    use serde::{de::DeserializeOwned, Serialize};\n    use std::future::Future;\n\n    mod axum_types;\n\n    pub mod cbor;\n    pub use cbor::*;\n\n    pub mod form;\n    pub use form::*;\n\n    pub mod multipart;\n    pub use multipart::*;\n\n    #[cfg(feature = \"postcard\")]\n    pub mod postcard;\n\n    #[cfg(feature = \"postcard\")]\n    pub use postcard::*;\n\n    #[cfg(feature = \"msgpack\")]\n    pub mod msgpack;\n    #[cfg(feature = \"msgpack\")]\n    pub use msgpack::*;\n\n    pub mod text;\n    pub use text::*;\n\n    pub mod sse;\n    pub use sse::*;\n\n    pub mod stream;\n    pub use stream::*;\n\n    pub mod files;\n    pub use files::*;\n\n    pub mod header;\n    pub use header::*;\n\n    pub mod query;\n    pub use query::*;\n\n    #[cfg(feature = \"ws\")]\n    pub mod websocket;\n    #[cfg(feature = \"ws\")]\n    pub use websocket::*;\n}\n"
  },
  {
    "path": "packages/fullstack/src/magic.rs",
    "content": "//! ServerFn request magical 🧙 decoders and encoders.\n//!\n//! The Dioxus Server Function implementation brings a lot of *magic* to the types of endpoints we can handle.\n//! Our ultimate goal is to handle *all* endpoints, even axum endpoints, with the macro.\n//!\n//! Unfortunately, some axum traits like `FromRequest` overlap with some of the default magic we want\n//! to provide, like allowing DeserializedOwned groups.\n//!\n//! Our ultimate goal - to accept all axum handlers - is feasible but not fully implemented.\n//!\n//! Broadly, we support the following categories of handlers arguments:\n//! - Handlers with a single argument that implements `FromRequest` + `IntoRequest`\n//! - Handlers with multiple arguments that implement *all* `DeserializeOwned` (and thus can be deserialized from a JSON body)\n//!\n//! The handler error return types we support are:\n//! - `Result<T, E> where E: From<ServerFnError> + Serialize + DeserializeOwned` (basically any custom `thiserror` impl)\n//! - `Result<T, anyhow::Error>` where we transport the error as a string and/or through ServerFnError\n//!\n//! The handler return types we support are:\n//! - `T where T: FromResponse`\n//! - `T where T: DeserializeOwned`\n//!\n//! Note that FromResponse and IntoRequest are *custom* traits defined in this crate. The intention\n//! is to provide \"inverse\" traits of the axum traits, allowing types to flow seamlessly between client and server.\n//!\n//! These are unfortunately in conflict with the serialization traits. Types like `Bytes` implement both\n//! IntoResponse and Serialize, so what should you use?\n//!\n//! This module implements auto-deref specialization to allow tiering of the above cases.\n//!\n//! This is sadly quite \"magical\", but it works. Because the FromResponse traits are defined in this crate,\n//! they are sealed against types that implement Deserialize/Serialize, meaning you cannot implement\n//! FromResponse for a type that implements Serialize.\n//!\n//! This module is broken up into several parts, attempting to match how the server macro generates code:\n//! - ReqwestEncoder: encodes a set of arguments into a reqwest request\n\nuse crate::{\n    CantEncode, ClientRequest, ClientResponse, EncodeIsVerified, FromResponse, HttpError,\n    IntoRequest, ServerFnError,\n};\nuse axum::response::IntoResponse;\nuse axum_core::extract::{FromRequest, Request};\nuse bytes::Bytes;\nuse dioxus_fullstack_core::RequestError;\nuse http::StatusCode;\nuse send_wrapper::SendWrapper;\nuse serde::Serialize;\nuse serde::{de::DeserializeOwned, Deserialize};\nuse std::fmt::Display;\nuse std::{marker::PhantomData, prelude::rust_2024::Future};\n\n#[doc(hidden)]\npub struct ServerFnEncoder<In, Out>(PhantomData<fn() -> (In, Out)>);\nimpl<In, Out> ServerFnEncoder<In, Out> {\n    #[doc(hidden)]\n    pub fn new() -> Self {\n        ServerFnEncoder(PhantomData)\n    }\n}\n\n#[doc(hidden)]\npub struct ServerFnDecoder<Out>(PhantomData<fn() -> Out>);\nimpl<Out> ServerFnDecoder<Out> {\n    #[doc(hidden)]\n    pub fn new() -> Self {\n        ServerFnDecoder(PhantomData)\n    }\n}\n\n/// A response structure for a regular REST API, with a success and error case where the status is\n/// encoded in the body and all fields are serializable. This lets you call fetch().await.json()\n/// and get a strongly typed result.\n///\n/// Eventually we want to support JsonRPC which requires a different format.\n///\n/// We use the `___status` field to avoid conflicts with user-defined fields. Hopefully no one uses this field name!\n#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]\npub enum RestEndpointPayload<T, E> {\n    #[serde(rename = \"success\")]\n    Success(T),\n\n    #[serde(rename = \"error\")]\n    Error(ErrorPayload<E>),\n}\n\n/// The error payload structure for REST API errors.\n#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]\npub struct ErrorPayload<E> {\n    pub(crate) message: String,\n\n    pub(crate) code: u16,\n\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub(crate) data: Option<E>,\n}\n\n/// Convert a `RequestError` into a `ServerFnError`.\n///\n/// This is a separate function to avoid bringing in `reqwest` into fullstack-core.\npub fn reqwest_response_to_serverfn_err(err: reqwest::Error) -> ServerFnError {\n    ServerFnError::Request(reqwest_error_to_request_error(err))\n}\n\npub fn reqwest_error_to_request_error(err: reqwest::Error) -> RequestError {\n    let message = err.to_string();\n    if err.is_timeout() {\n        RequestError::Timeout(message)\n    } else if err.is_request() {\n        RequestError::Request(message)\n    } else if err.is_body() {\n        RequestError::Body(message)\n    } else if err.is_decode() {\n        RequestError::Decode(message)\n    } else if err.is_redirect() {\n        RequestError::Redirect(message)\n    } else if let Some(status) = err.status() {\n        RequestError::Status(message, status.as_u16())\n    } else {\n        #[cfg(not(target_arch = \"wasm32\"))]\n        {\n            if err.is_connect() {\n                RequestError::Connect(message)\n            } else {\n                RequestError::Request(message)\n            }\n        }\n\n        #[cfg(target_arch = \"wasm32\")]\n        {\n            RequestError::Request(message)\n        }\n    }\n}\n\npub use req_to::*;\npub mod req_to {\n    use super::*;\n\n    pub trait EncodeRequest<In, Out, R> {\n        type VerifyEncode;\n        fn fetch_client(\n            &self,\n            ctx: ClientRequest,\n            data: In,\n            map: fn(In) -> Out,\n        ) -> impl Future<Output = Result<R, RequestError>> + 'static;\n        fn verify_can_serialize(&self) -> Self::VerifyEncode;\n    }\n\n    /// Using the deserialize path\n    impl<T, O> EncodeRequest<T, O, ClientResponse> for &&&&&&&&&&ServerFnEncoder<T, O>\n    where\n        T: DeserializeOwned + Serialize + 'static,\n    {\n        type VerifyEncode = EncodeIsVerified;\n        fn fetch_client(\n            &self,\n            ctx: ClientRequest,\n            data: T,\n            _map: fn(T) -> O,\n        ) -> impl Future<Output = Result<ClientResponse, RequestError>> + 'static {\n            async move { ctx.send_json(&data).await }\n        }\n\n        fn verify_can_serialize(&self) -> Self::VerifyEncode {\n            EncodeIsVerified\n        }\n    }\n\n    /// When we use the FromRequest path, we don't need to deserialize the input type on the client,\n    impl<T, O, R> EncodeRequest<T, O, R> for &&&&&&&&&ServerFnEncoder<T, O>\n    where\n        T: 'static,\n        O: IntoRequest<R>,\n    {\n        type VerifyEncode = EncodeIsVerified;\n        fn fetch_client(\n            &self,\n            ctx: ClientRequest,\n            data: T,\n            map: fn(T) -> O,\n        ) -> impl Future<Output = Result<R, RequestError>> + 'static {\n            O::into_request(map(data), ctx)\n        }\n\n        fn verify_can_serialize(&self) -> Self::VerifyEncode {\n            EncodeIsVerified\n        }\n    }\n\n    /// The fall-through case that emits a `CantEncode` type which fails to compile when checked by the macro\n    impl<T, O> EncodeRequest<T, O, ClientResponse> for &ServerFnEncoder<T, O>\n    where\n        T: 'static,\n    {\n        type VerifyEncode = CantEncode;\n        #[allow(clippy::manual_async_fn)]\n        fn fetch_client(\n            &self,\n            _ctx: ClientRequest,\n            _data: T,\n            _map: fn(T) -> O,\n        ) -> impl Future<Output = Result<ClientResponse, RequestError>> + 'static {\n            async move { unimplemented!() }\n        }\n\n        fn verify_can_serialize(&self) -> Self::VerifyEncode {\n            CantEncode\n        }\n    }\n}\n\npub use decode_ok::*;\nmod decode_ok {\n\n    use crate::{CantDecode, DecodeIsVerified};\n\n    use super::*;\n\n    /// Convert the reqwest response into the desired type, in place.\n    /// The point here is to prefer FromResponse types *first* and then DeserializeOwned types second.\n    ///\n    /// This is because FromResponse types are more specialized and can handle things like websockets and files.\n    /// DeserializeOwned types are more general and can handle things like JSON responses.\n    pub trait RequestDecodeResult<T, R> {\n        type VerifyDecode;\n        fn decode_client_response(\n            &self,\n            res: Result<R, RequestError>,\n        ) -> impl Future<Output = Result<Result<T, ServerFnError>, RequestError>> + Send;\n        fn verify_can_deserialize(&self) -> Self::VerifyDecode;\n    }\n\n    impl<T: FromResponse<R>, E, R> RequestDecodeResult<T, R> for &&&ServerFnDecoder<Result<T, E>> {\n        type VerifyDecode = DecodeIsVerified;\n        fn decode_client_response(\n            &self,\n            res: Result<R, RequestError>,\n        ) -> impl Future<Output = Result<Result<T, ServerFnError>, RequestError>> + Send {\n            SendWrapper::new(async move {\n                match res {\n                    Err(err) => Err(err),\n                    Ok(res) => Ok(T::from_response(res).await),\n                }\n            })\n        }\n        fn verify_can_deserialize(&self) -> Self::VerifyDecode {\n            DecodeIsVerified\n        }\n    }\n\n    impl<T: DeserializeOwned, E> RequestDecodeResult<T, ClientResponse>\n        for &&ServerFnDecoder<Result<T, E>>\n    {\n        type VerifyDecode = DecodeIsVerified;\n        fn decode_client_response(\n            &self,\n            res: Result<ClientResponse, RequestError>,\n        ) -> impl Future<Output = Result<Result<T, ServerFnError>, RequestError>> + Send {\n            SendWrapper::new(async move {\n                match res {\n                    Err(err) => Err(err),\n                    Ok(res) => {\n                        let status = res.status();\n\n                        let bytes = res.bytes().await.unwrap();\n                        let as_bytes = if bytes.is_empty() {\n                            b\"null\".as_slice()\n                        } else {\n                            &bytes\n                        };\n\n                        let res = if status.is_success() {\n                            serde_json::from_slice::<T>(as_bytes)\n                                .map(RestEndpointPayload::Success)\n                                .map_err(|e| ServerFnError::Deserialization(e.to_string()))\n                        } else {\n                            match serde_json::from_slice::<ErrorPayload<serde_json::Value>>(\n                                as_bytes,\n                            ) {\n                                Ok(res) => Ok(RestEndpointPayload::Error(ErrorPayload {\n                                    message: res.message,\n                                    code: res.code,\n                                    data: res.data,\n                                })),\n                                Err(err) => {\n                                    if let Ok(text) = String::from_utf8(as_bytes.to_vec()) {\n                                        Ok(RestEndpointPayload::Error(ErrorPayload {\n                                            message: format!(\"HTTP {}: {}\", status.as_u16(), text),\n                                            code: status.as_u16(),\n                                            data: None,\n                                        }))\n                                    } else {\n                                        Err(ServerFnError::Deserialization(err.to_string()))\n                                    }\n                                }\n                            }\n                        };\n\n                        match res {\n                            Ok(RestEndpointPayload::Success(t)) => Ok(Ok(t)),\n                            Ok(RestEndpointPayload::Error(err)) => {\n                                Ok(Err(ServerFnError::ServerError {\n                                    message: err.message,\n                                    details: err.data,\n                                    code: err.code,\n                                }))\n                            }\n                            Err(e) => Ok(Err(e)),\n                        }\n                    }\n                }\n            })\n        }\n        fn verify_can_deserialize(&self) -> Self::VerifyDecode {\n            DecodeIsVerified\n        }\n    }\n\n    impl<T, R, E> RequestDecodeResult<T, R> for &ServerFnDecoder<Result<T, E>> {\n        type VerifyDecode = CantDecode;\n\n        fn decode_client_response(\n            &self,\n            _res: Result<R, RequestError>,\n        ) -> impl Future<Output = Result<Result<T, ServerFnError>, RequestError>> + Send {\n            async move { unimplemented!() }\n        }\n\n        fn verify_can_deserialize(&self) -> Self::VerifyDecode {\n            CantDecode\n        }\n    }\n\n    pub trait RequestDecodeErr<T, E> {\n        fn decode_client_err(\n            &self,\n            res: Result<Result<T, ServerFnError>, RequestError>,\n        ) -> impl Future<Output = Result<T, E>> + Send;\n    }\n\n    impl<T, E> RequestDecodeErr<T, E> for &&&ServerFnDecoder<Result<T, E>>\n    where\n        E: From<ServerFnError> + DeserializeOwned + Serialize,\n    {\n        fn decode_client_err(\n            &self,\n            res: Result<Result<T, ServerFnError>, RequestError>,\n        ) -> impl Future<Output = Result<T, E>> + Send {\n            SendWrapper::new(async move {\n                match res {\n                    Ok(Ok(res)) => Ok(res),\n                    Ok(Err(e)) => match e {\n                        ServerFnError::ServerError {\n                            details,\n                            message,\n                            code,\n                        } => {\n                            // If there are \"details\", then we try to deserialize them into the error type.\n                            // If there aren't, we just create a generic ServerFnError::ServerError with the message.\n                            match details {\n                                Some(details) => match serde_json::from_value::<E>(details) {\n                                    Ok(res) => Err(res),\n                                    Err(err) => Err(E::from(ServerFnError::Deserialization(\n                                        err.to_string(),\n                                    ))),\n                                },\n                                None => Err(E::from(ServerFnError::ServerError {\n                                    message,\n                                    details: None,\n                                    code,\n                                })),\n                            }\n                        }\n                        err => Err(err.into()),\n                    },\n                    // todo: implement proper through-error conversion, instead of just ServerFnError::Request\n                    // we should expand these cases.\n                    Err(err) => Err(ServerFnError::from(err).into()),\n                }\n            })\n        }\n    }\n\n    /// Here we convert to ServerFnError and then into the anyhow::Error, letting the user downcast\n    /// from the ServerFnError if they want to.\n    ///\n    /// This loses any actual type information, but is the most flexible for users.\n    impl<T> RequestDecodeErr<T, anyhow::Error> for &&ServerFnDecoder<Result<T, anyhow::Error>> {\n        fn decode_client_err(\n            &self,\n            res: Result<Result<T, ServerFnError>, RequestError>,\n        ) -> impl Future<Output = Result<T, anyhow::Error>> + Send {\n            SendWrapper::new(async move {\n                match res {\n                    Ok(Ok(res)) => Ok(res),\n                    Ok(Err(e)) => Err(anyhow::Error::from(e)),\n                    Err(err) => Err(anyhow::Error::from(err)),\n                }\n            })\n        }\n    }\n\n    /// This converts to statuscode, which can be useful but loses a lot of information.\n    impl<T> RequestDecodeErr<T, StatusCode> for &ServerFnDecoder<Result<T, StatusCode>> {\n        fn decode_client_err(\n            &self,\n            res: Result<Result<T, ServerFnError>, RequestError>,\n        ) -> impl Future<Output = Result<T, StatusCode>> + Send {\n            SendWrapper::new(async move {\n                match res {\n                    Ok(Ok(res)) => Ok(res),\n\n                    // We do a best-effort conversion from ServerFnError to StatusCode.\n                    Ok(Err(e)) => match e {\n                        ServerFnError::Request(error) => {\n                            Err(StatusCode::from_u16(error.status_code().unwrap_or(500))\n                                .unwrap_or(StatusCode::INTERNAL_SERVER_ERROR))\n                        }\n\n                        ServerFnError::ServerError {\n                            message: _message,\n                            details: _details,\n                            code,\n                        } => {\n                            Err(StatusCode::from_u16(code)\n                                .unwrap_or(StatusCode::INTERNAL_SERVER_ERROR))\n                        }\n\n                        ServerFnError::Registration(_) | ServerFnError::MiddlewareError(_) => {\n                            Err(StatusCode::INTERNAL_SERVER_ERROR)\n                        }\n\n                        ServerFnError::Deserialization(_)\n                        | ServerFnError::Serialization(_)\n                        | ServerFnError::Args(_)\n                        | ServerFnError::MissingArg(_)\n                        | ServerFnError::StreamError(_) => Err(StatusCode::UNPROCESSABLE_ENTITY),\n\n                        ServerFnError::UnsupportedRequestMethod(_) => {\n                            Err(StatusCode::METHOD_NOT_ALLOWED)\n                        }\n\n                        ServerFnError::Response(_) => Err(StatusCode::INTERNAL_SERVER_ERROR),\n                    },\n\n                    // The reqwest error case, we try to convert the reqwest error into a status code.\n                    Err(reqwest_err) => {\n                        let code = reqwest_err\n                            .status()\n                            .unwrap_or(StatusCode::SERVICE_UNAVAILABLE);\n                        Err(code)\n                    }\n                }\n            })\n        }\n    }\n\n    impl<T> RequestDecodeErr<T, HttpError> for &ServerFnDecoder<Result<T, HttpError>> {\n        fn decode_client_err(\n            &self,\n            res: Result<Result<T, ServerFnError>, RequestError>,\n        ) -> impl Future<Output = Result<T, HttpError>> + Send {\n            SendWrapper::new(async move {\n                match res {\n                    Ok(Ok(res)) => Ok(res),\n                    Ok(Err(res)) => match res {\n                        ServerFnError::ServerError { message, code, .. } => Err(HttpError {\n                            status: StatusCode::from_u16(code)\n                                .unwrap_or(StatusCode::INTERNAL_SERVER_ERROR),\n                            message: Some(message),\n                        }),\n                        _ => HttpError::internal_server_error(\"Internal Server Error\"),\n                    },\n                    Err(err) => Err(HttpError::new(\n                        err.status().unwrap_or(StatusCode::INTERNAL_SERVER_ERROR),\n                        err.to_string(),\n                    )),\n                }\n            })\n        }\n    }\n}\n\npub use req_from::*;\npub mod req_from {\n    use super::*;\n    use axum::{extract::FromRequestParts, response::Response};\n    use dioxus_fullstack_core::FullstackContext;\n\n    pub trait ExtractRequest<In, Out, H, M = ()> {\n        fn extract_axum(\n            &self,\n            state: FullstackContext,\n            request: Request,\n            map: fn(In) -> Out,\n        ) -> impl Future<Output = Result<(Out, H), Response>> + 'static;\n    }\n\n    /// When you're extracting entirely on the server, we need to reject client-consuning request bodies\n    /// This sits above priority in the combined headers on server / body on client case.\n    impl<In, M, H> ExtractRequest<In, (), H, M> for &&&&&&&&&&&ServerFnEncoder<In, ()>\n    where\n        H: FromRequest<FullstackContext, M> + 'static,\n    {\n        fn extract_axum(\n            &self,\n            state: FullstackContext,\n            request: Request,\n            _map: fn(In) -> (),\n        ) -> impl Future<Output = Result<((), H), Response>> + 'static {\n            async move {\n                H::from_request(request, &state)\n                    .await\n                    .map_err(|e| e.into_response())\n                    .map(|out| ((), out))\n            }\n        }\n    }\n\n    // One-arg case\n    impl<In, Out, H> ExtractRequest<In, Out, H> for &&&&&&&&&&ServerFnEncoder<In, Out>\n    where\n        In: DeserializeOwned + 'static,\n        Out: 'static,\n        H: FromRequestParts<FullstackContext>,\n    {\n        fn extract_axum(\n            &self,\n            _state: FullstackContext,\n            request: Request,\n            map: fn(In) -> Out,\n        ) -> impl Future<Output = Result<(Out, H), Response>> + 'static {\n            async move {\n                let (mut parts, body) = request.into_parts();\n                let headers = H::from_request_parts(&mut parts, &_state)\n                    .await\n                    .map_err(|e| e.into_response())?;\n\n                let request = Request::from_parts(parts, body);\n                let bytes = Bytes::from_request(request, &()).await.unwrap();\n                let as_str = String::from_utf8_lossy(&bytes);\n\n                let bytes = if as_str.is_empty() {\n                    \"{}\".as_bytes()\n                } else {\n                    &bytes\n                };\n\n                let out = serde_json::from_slice::<In>(bytes)\n                    .map(map)\n                    .map_err(|e| ServerFnError::from(e).into_response())?;\n\n                Ok((out, headers))\n            }\n        }\n    }\n\n    /// We skip the BodySerialize wrapper and just go for the output type directly.\n    impl<In, Out, M, H> ExtractRequest<In, Out, H, M> for &&&&&&&&&ServerFnEncoder<In, Out>\n    where\n        Out: FromRequest<FullstackContext, M> + 'static,\n        H: FromRequestParts<FullstackContext>,\n    {\n        fn extract_axum(\n            &self,\n            state: FullstackContext,\n            request: Request,\n            _map: fn(In) -> Out,\n        ) -> impl Future<Output = Result<(Out, H), Response>> + 'static {\n            async move {\n                let (mut parts, body) = request.into_parts();\n                let headers = H::from_request_parts(&mut parts, &state)\n                    .await\n                    .map_err(|e| e.into_response())?;\n\n                let request = Request::from_parts(parts, body);\n\n                let res = Out::from_request(request, &state)\n                    .await\n                    .map_err(|e| e.into_response());\n\n                res.map(|out| (out, headers))\n            }\n        }\n    }\n}\n\npub use resp::*;\nmod resp {\n    use crate::HttpError;\n\n    use super::*;\n    use axum::response::Response;\n    use dioxus_core::CapturedError;\n    use http::HeaderValue;\n\n    /// A trait for converting the result of the Server Function into an Axum response.\n    ///\n    /// This is to work around the issue where we want to return both Deserialize types and FromResponse types.\n    /// Stuff like websockets\n    ///\n    /// We currently have an `Input` type even though it's not useful since we might want to support regular axum endpoints later.\n    /// For now, it's just Result<T, E> where T is either DeserializeOwned or FromResponse\n    pub trait MakeAxumResponse<T, E, R> {\n        fn make_axum_response(self, result: Result<T, E>) -> Result<Response, E>;\n    }\n\n    // Higher priority impl for special types like websocket/file responses that generate their own responses\n    // The FromResponse impl helps narrow types to those usable on the client\n    impl<T, E, R> MakeAxumResponse<T, E, R> for &&&&ServerFnDecoder<Result<T, E>>\n    where\n        T: FromResponse<R> + IntoResponse,\n    {\n        fn make_axum_response(self, result: Result<T, E>) -> Result<Response, E> {\n            result.map(|v| v.into_response())\n        }\n    }\n\n    // Lower priority impl for regular serializable types\n    // We try to match the encoding from the incoming request, otherwise default to JSON\n    impl<T, E> MakeAxumResponse<T, E, ()> for &&&ServerFnDecoder<Result<T, E>>\n    where\n        T: DeserializeOwned + Serialize,\n    {\n        fn make_axum_response(self, result: Result<T, E>) -> Result<Response, E> {\n            match result {\n                Ok(res) => {\n                    let body = serde_json::to_string(&res).unwrap();\n                    let mut resp = Response::new(body.into());\n                    resp.headers_mut().insert(\n                        http::header::CONTENT_TYPE,\n                        HeaderValue::from_static(\"application/json\"),\n                    );\n                    *resp.status_mut() = StatusCode::OK;\n                    Ok(resp)\n                }\n                Err(err) => Err(err),\n            }\n        }\n    }\n\n    #[allow(clippy::result_large_err)]\n    pub trait MakeAxumError<E> {\n        fn make_axum_error(self, result: Result<Response, E>) -> Response;\n    }\n\n    /// Get the status code from the error type if possible.\n    pub trait AsStatusCode {\n        fn as_status_code(&self) -> StatusCode;\n    }\n\n    impl AsStatusCode for ServerFnError {\n        fn as_status_code(&self) -> StatusCode {\n            match self {\n                Self::ServerError { code, .. } => {\n                    StatusCode::from_u16(*code).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR)\n                }\n                _ => StatusCode::INTERNAL_SERVER_ERROR,\n            }\n        }\n    }\n\n    impl<T, E> MakeAxumError<E> for &&&ServerFnDecoder<Result<T, E>>\n    where\n        E: AsStatusCode + From<ServerFnError> + Serialize + DeserializeOwned + Display,\n    {\n        fn make_axum_error(self, result: Result<Response, E>) -> Response {\n            match result {\n                Ok(res) => res,\n                Err(err) => {\n                    let status_code = err.as_status_code();\n                    let err = ErrorPayload {\n                        code: status_code.as_u16(),\n                        message: err.to_string(),\n                        data: Some(err),\n                    };\n                    let body = serde_json::to_string(&err).unwrap();\n                    let mut resp = Response::new(body.into());\n                    resp.headers_mut().insert(\n                        http::header::CONTENT_TYPE,\n                        HeaderValue::from_static(\"application/json\"),\n                    );\n                    *resp.status_mut() = status_code;\n                    resp\n                }\n            }\n        }\n    }\n\n    impl<T> MakeAxumError<CapturedError> for &&ServerFnDecoder<Result<T, CapturedError>> {\n        fn make_axum_error(self, result: Result<Response, CapturedError>) -> Response {\n            match result {\n                Ok(res) => res,\n\n                // Optimize the case where we have sole ownership of the error\n                Err(errr) if errr._strong_count() == 1 => {\n                    let err = errr.into_inner().unwrap();\n                    <&&ServerFnDecoder<Result<T, anyhow::Error>> as MakeAxumError<anyhow::Error>>::make_axum_error(\n                        &&ServerFnDecoder::new(),\n                        Err(err),\n                    )\n                }\n\n                Err(errr) => {\n                    // The `WithHttpError` trait emits ServerFnErrors so we can downcast them here\n                    // to create richer responses.\n                    let payload = match errr.downcast_ref::<ServerFnError>() {\n                        Some(ServerFnError::ServerError {\n                            message,\n                            code,\n                            details,\n                        }) => ErrorPayload {\n                            message: message.clone(),\n                            code: *code,\n                            data: details.clone(),\n                        },\n                        Some(other) => ErrorPayload {\n                            message: other.to_string(),\n                            code: 500,\n                            data: None,\n                        },\n                        None => match errr.downcast_ref::<HttpError>() {\n                            Some(http_err) => ErrorPayload {\n                                message: http_err\n                                    .message\n                                    .clone()\n                                    .unwrap_or_else(|| http_err.status.to_string()),\n                                code: http_err.status.as_u16(),\n                                data: None,\n                            },\n                            None => ErrorPayload {\n                                code: 500,\n                                message: errr.to_string(),\n                                data: None,\n                            },\n                        },\n                    };\n\n                    let body = serde_json::to_string(&payload).unwrap();\n                    let mut resp = Response::new(body.into());\n                    resp.headers_mut().insert(\n                        http::header::CONTENT_TYPE,\n                        HeaderValue::from_static(\"application/json\"),\n                    );\n                    *resp.status_mut() = StatusCode::INTERNAL_SERVER_ERROR;\n                    resp\n                }\n            }\n        }\n    }\n\n    impl<T> MakeAxumError<anyhow::Error> for &&ServerFnDecoder<Result<T, anyhow::Error>> {\n        fn make_axum_error(self, result: Result<Response, anyhow::Error>) -> Response {\n            match result {\n                Ok(res) => res,\n                Err(errr) => {\n                    // The `WithHttpError` trait emits ServerFnErrors so we can downcast them here\n                    // to create richer responses.\n                    let payload = match errr.downcast::<ServerFnError>() {\n                        Ok(ServerFnError::ServerError {\n                            message,\n                            code,\n                            details,\n                        }) => ErrorPayload {\n                            message,\n                            code,\n                            data: details,\n                        },\n                        Ok(other) => ErrorPayload {\n                            message: other.to_string(),\n                            code: 500,\n                            data: None,\n                        },\n                        Err(err) => match err.downcast::<HttpError>() {\n                            Ok(http_err) => ErrorPayload {\n                                message: http_err\n                                    .message\n                                    .unwrap_or_else(|| http_err.status.to_string()),\n                                code: http_err.status.as_u16(),\n                                data: None,\n                            },\n                            Err(err) => ErrorPayload {\n                                code: 500,\n                                message: err.to_string(),\n                                data: None,\n                            },\n                        },\n                    };\n\n                    let body = serde_json::to_string(&payload).unwrap();\n                    let mut resp = Response::new(body.into());\n                    resp.headers_mut().insert(\n                        http::header::CONTENT_TYPE,\n                        HeaderValue::from_static(\"application/json\"),\n                    );\n                    *resp.status_mut() = StatusCode::INTERNAL_SERVER_ERROR;\n                    resp\n                }\n            }\n        }\n    }\n\n    impl<T> MakeAxumError<StatusCode> for &&ServerFnDecoder<Result<T, StatusCode>> {\n        fn make_axum_error(self, result: Result<Response, StatusCode>) -> Response {\n            match result {\n                Ok(resp) => resp,\n                Err(status) => {\n                    let body = serde_json::to_string(&ErrorPayload::<()> {\n                        code: status.as_u16(),\n                        message: status.to_string(),\n                        data: None,\n                    })\n                    .unwrap();\n                    let mut resp = Response::new(body.into());\n                    resp.headers_mut().insert(\n                        http::header::CONTENT_TYPE,\n                        HeaderValue::from_static(\"application/json\"),\n                    );\n                    *resp.status_mut() = status;\n                    resp\n                }\n            }\n        }\n    }\n\n    impl<T> MakeAxumError<HttpError> for &ServerFnDecoder<Result<T, HttpError>> {\n        fn make_axum_error(self, result: Result<Response, HttpError>) -> Response {\n            match result {\n                Ok(resp) => resp,\n                Err(http_err) => {\n                    let body = serde_json::to_string(&ErrorPayload::<()> {\n                        code: http_err.status.as_u16(),\n                        message: http_err\n                            .message\n                            .unwrap_or_else(|| http_err.status.to_string()),\n                        data: None,\n                    })\n                    .unwrap();\n                    let mut resp = Response::new(body.into());\n                    resp.headers_mut().insert(\n                        http::header::CONTENT_TYPE,\n                        HeaderValue::from_static(\"application/json\"),\n                    );\n                    *resp.status_mut() = http_err.status;\n                    resp\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "packages/fullstack/src/payloads/axum_types.rs",
    "content": "use super::*;\nuse crate::{ClientResponse, FromResponse};\npub use axum::extract::Json;\nuse axum::response::{Html, NoContent, Redirect};\nuse dioxus_fullstack_core::{RequestError, ServerFnError};\nuse futures::StreamExt;\nuse http::StatusCode;\nuse std::future::Future;\n\nimpl<T: From<String>> FromResponse for Html<T> {\n    fn from_response(res: ClientResponse) -> impl Future<Output = Result<Self, ServerFnError>> {\n        async move {\n            let content = res.text().await?;\n            Ok(Html(content.into()))\n        }\n    }\n}\n\nimpl<T> IntoRequest for Json<T>\nwhere\n    T: Serialize + 'static + DeserializeOwned,\n{\n    fn into_request(self, request: ClientRequest) -> impl Future<Output = ClientResult> + 'static {\n        async move { request.send_json(&self.0).await }\n    }\n}\n\nimpl<T: DeserializeOwned> FromResponse for Json<T> {\n    fn from_response(res: ClientResponse) -> impl Future<Output = Result<Self, ServerFnError>> {\n        async move {\n            let data = res.json::<T>().await?;\n            Ok(Json(data))\n        }\n    }\n}\n\nimpl FromResponse for Redirect {\n    fn from_response(res: ClientResponse) -> impl Future<Output = Result<Self, ServerFnError>> {\n        async move {\n            let location = res\n                .headers()\n                .get(http::header::LOCATION)\n                .ok_or_else(|| RequestError::Redirect(\"Missing Location header\".into()))?\n                .to_str()\n                .map_err(|_| RequestError::Redirect(\"Invalid Location header\".into()))?;\n            match res.status() {\n                StatusCode::SEE_OTHER => Ok(Redirect::to(location)),\n                StatusCode::TEMPORARY_REDIRECT => Ok(Redirect::temporary(location)),\n                StatusCode::PERMANENT_REDIRECT => Ok(Redirect::permanent(location)),\n                _ => Err(RequestError::Redirect(\"Not a redirect status code\".into()).into()),\n            }\n        }\n    }\n}\n\nimpl FromResponse for NoContent {\n    fn from_response(res: ClientResponse) -> impl Future<Output = Result<Self, ServerFnError>> {\n        async move {\n            let status = res.status();\n            if status == StatusCode::NO_CONTENT {\n                Ok(NoContent)\n            } else {\n                let body = res.text().await.unwrap_or_else(|_| \"\".into());\n                Err(RequestError::Status(body, status.into()).into())\n            }\n        }\n    }\n}\n\n/// Implementation of `FromResponse` for `axum::response::Response`.\n///\n/// This allows converting a `ClientResponse` (from a client-side HTTP request)\n/// into an `axum::Response` for server-side handling. The response's status,\n/// headers, and body are transferred from the client response to the axum response.\nimpl FromResponse for axum::response::Response {\n    fn from_response(res: ClientResponse) -> impl Future<Output = Result<Self, ServerFnError>> {\n        async move {\n            let parts = res.make_parts();\n            let body = axum::body::Body::from_stream(res.bytes_stream());\n            let response = axum::response::Response::from_parts(parts, body);\n            Ok(response)\n        }\n    }\n}\n\n/// Implementation of `IntoRequest` for `axum::extract::Request`.\n///\n/// This allows converting an `axum::Request` (from server-side extraction)\n/// into a `ClientRequest` that can be sent as an HTTP request. The request's\n/// headers and body are transferred from the axum request to the client request.\nimpl IntoRequest for axum::extract::Request {\n    fn into_request(\n        self,\n        mut request: ClientRequest,\n    ) -> impl Future<Output = Result<ClientResponse, RequestError>> + 'static {\n        async move {\n            let (parts, body) = self.into_parts();\n\n            for (key, value) in &parts.headers {\n                request = request.header(key, value)?;\n            }\n\n            request\n                .send_body_stream(\n                    body.into_data_stream()\n                        .map(|res| res.map_err(|_| StreamingError::Failed)),\n                )\n                .await\n        }\n    }\n}\n"
  },
  {
    "path": "packages/fullstack/src/payloads/cbor.rs",
    "content": "use axum::{\n    body::Bytes,\n    extract::{rejection::BytesRejection, FromRequest, Request},\n    http::{header, HeaderMap, HeaderValue, StatusCode},\n    response::{IntoResponse, Response},\n};\nuse serde::{de::DeserializeOwned, Serialize};\n\n/// CBOR Extractor / Response.\n///\n/// When used as an extractor, it can deserialize request bodies into some type that\n/// implements [`serde::Deserialize`]. The request will be rejected (and a [`CborRejection`] will\n/// be returned) if:\n///\n/// - The request doesn't have a `Content-Type: application/cbor` (or similar) header.\n/// - The body doesn't contain syntactically valid CBOR.\n/// - The body contains syntactically valid CBOR but it couldn't be deserialized into the target type.\n/// - Buffering the request body fails.\n///\n/// ⚠️ Since parsing CBOR requires consuming the request body, the `Cbor` extractor must be\n/// *last* if there are multiple extractors in a handler.\n/// See [\"the order of extractors\"][order-of-extractors]\n///\n/// [order-of-extractors]: mod@crate::extract#the-order-of-extractors\n#[must_use]\npub struct Cbor<T>(pub T);\n\n/// Check if the request has a valid CBOR content type header.\n///\n/// This function validates that the `Content-Type` header is set to `application/cbor`\n/// or a compatible CBOR media type (including subtypes with `+cbor` suffix).\nfn is_valid_cbor_content_type(headers: &HeaderMap) -> bool {\n    let Some(content_type) = headers.get(header::CONTENT_TYPE) else {\n        return false;\n    };\n\n    let Ok(content_type) = content_type.to_str() else {\n        return false;\n    };\n\n    let Ok(mime) = content_type.parse::<mime::Mime>() else {\n        return false;\n    };\n\n    let is_cbor_content_type = mime.type_() == \"application\"\n        && (mime.subtype() == \"cbor\" || mime.suffix().is_some_and(|name| name == \"cbor\"));\n\n    is_cbor_content_type\n}\n\nimpl<S, T> FromRequest<S> for Cbor<T>\nwhere\n    S: Send + Sync,\n    T: DeserializeOwned,\n{\n    type Rejection = CborRejection;\n\n    /// Extract a CBOR payload from the request body.\n    ///\n    /// This implementation validates the content type and deserializes the CBOR data.\n    /// Returns a `CborRejection` if validation or deserialization fails.\n    async fn from_request(req: Request, state: &S) -> Result<Self, Self::Rejection> {\n        if !is_valid_cbor_content_type(req.headers()) {\n            return Err(CborRejection::MissingCborContentType);\n        }\n        let bytes = Bytes::from_request(req, state).await?;\n        let value =\n            ciborium::from_reader(&bytes as &[u8]).map_err(|_| CborRejection::FailedToParseCbor)?;\n        Ok(Cbor(value))\n    }\n}\n\nimpl<T> IntoResponse for Cbor<T>\nwhere\n    T: Serialize,\n{\n    /// Convert the CBOR payload into an HTTP response.\n    ///\n    /// This serializes the inner value to CBOR format and sets the appropriate\n    /// `Content-Type: application/cbor` header. Returns a 500 Internal Server Error\n    /// if serialization fails.\n    fn into_response(self) -> Response {\n        let mut buf = Vec::new();\n        match ciborium::into_writer(&self.0, &mut buf) {\n            Err(_) => (\n                StatusCode::INTERNAL_SERVER_ERROR,\n                [(\n                    header::CONTENT_TYPE,\n                    HeaderValue::from_static(mime::TEXT_PLAIN_UTF_8.as_ref()),\n                )],\n                \"Failed to serialize to CBOR\".to_string(),\n            )\n                .into_response(),\n            Ok(()) => (\n                [(\n                    header::CONTENT_TYPE,\n                    HeaderValue::from_static(\"application/cbor\"),\n                )],\n                buf,\n            )\n                .into_response(),\n        }\n    }\n}\n\nimpl<T> From<T> for Cbor<T> {\n    /// Create a `Cbor<T>` from the inner value.\n    ///\n    /// This is a convenience constructor that wraps any value in the `Cbor` struct.\n    fn from(inner: T) -> Self {\n        Self(inner)\n    }\n}\n\nimpl<T> Cbor<T>\nwhere\n    T: DeserializeOwned,\n{\n    /// Construct a `Cbor<T>` from a byte slice.\n    ///\n    /// This method attempts to deserialize the provided bytes as CBOR data.\n    /// Returns a `CborRejection` if deserialization fails.\n    pub fn from_bytes(bytes: &[u8]) -> Result<Self, CborRejection> {\n        ciborium::de::from_reader(bytes)\n            .map(Cbor)\n            .map_err(|_| CborRejection::FailedToParseCbor)\n    }\n}\n\n/// Rejection type for CBOR extraction failures.\n///\n/// This enum represents the various ways that CBOR extraction can fail.\n/// It implements `IntoResponse` to provide appropriate HTTP responses for each error type.\n#[derive(thiserror::Error, Debug)]\npub enum CborRejection {\n    /// The request is missing the required `Content-Type: application/cbor` header.\n    #[error(\"Expected request with `Content-Type: application/cbor`\")]\n    MissingCborContentType,\n\n    /// Failed to parse the request body as valid CBOR.\n    #[error(\"Invalid CBOR data\")]\n    FailedToParseCbor,\n\n    /// Failed to read the request body bytes.\n    #[error(transparent)]\n    BytesRejection(#[from] BytesRejection),\n}\n\nimpl IntoResponse for CborRejection {\n    fn into_response(self) -> Response {\n        use CborRejection::*;\n        match self {\n            MissingCborContentType => {\n                (StatusCode::UNSUPPORTED_MEDIA_TYPE, self.to_string()).into_response()\n            }\n            FailedToParseCbor => (StatusCode::BAD_REQUEST, self.to_string()).into_response(),\n            BytesRejection(rejection) => rejection.into_response(),\n        }\n    }\n}\n"
  },
  {
    "path": "packages/fullstack/src/payloads/files.rs",
    "content": "use super::*;\nuse axum_core::extract::Request;\nuse dioxus_fullstack_core::RequestError;\nuse dioxus_html::FileData;\n\n#[cfg(feature = \"server\")]\nuse std::path::Path;\nuse std::{\n    pin::Pin,\n    task::{Context, Poll},\n};\n\n/// A payload for uploading files using streams.\n///\n/// The `FileUpload` struct allows you to upload files by streaming their data. It can be constructed\n/// from a stream of bytes and can be sent as part of an HTTP request. This is particularly useful for\n/// handling large files without loading them entirely into memory.\n///\n/// On the web, this uses the `ReadableStream` API to stream file data.\npub struct FileStream {\n    data: Option<FileData>,\n    name: String,\n    size: Option<u64>,\n    content_type: Option<String>,\n    #[cfg(feature = \"server\")]\n    server_body: Option<axum_core::body::BodyDataStream>,\n\n    // For downloaded files...\n    #[allow(clippy::type_complexity)]\n    client_body: Option<Pin<Box<dyn Stream<Item = Result<Bytes, StreamingError>> + Send>>>,\n}\n\nimpl FileStream {\n    /// Get the name of the file.\n    pub fn file_name(&self) -> &str {\n        &self.name\n    }\n\n    /// Get the size of the file, if known.\n    pub fn size(&self) -> Option<u64> {\n        self.size\n    }\n\n    /// Get the content type of the file, if available.\n    pub fn content_type(&self) -> Option<&str> {\n        self.content_type.as_deref()\n    }\n\n    /// Return the underlying body stream, assuming the `FileStream` was created by a server request.\n    #[cfg(feature = \"server\")]\n    pub fn body_mut(&mut self) -> Option<&mut axum_core::body::BodyDataStream> {\n        self.server_body.as_mut()\n    }\n\n    /// Create a new `FileStream` from a file path. This is only available on the server.\n    #[cfg(feature = \"server\")]\n    pub async fn from_path(file: impl AsRef<Path>) -> Result<Self, std::io::Error> {\n        Self::from_path_buf(file.as_ref()).await\n    }\n\n    #[cfg(feature = \"server\")]\n    async fn from_path_buf(file: &Path) -> Result<Self, std::io::Error> {\n        let metadata = file.metadata()?;\n        let contents = tokio::fs::File::open(&file).await?;\n        let mime = dioxus_asset_resolver::native::get_mime_from_ext(\n            file.extension().and_then(|s| s.to_str()),\n        );\n        let size = metadata.len();\n        let name = file\n            .file_name()\n            .and_then(|s| s.to_str())\n            .unwrap_or(\"file\")\n            .to_string();\n\n        // Convert the tokio file into an async byte stream\n        let reader_stream = tokio_util::io::ReaderStream::new(contents);\n\n        // Attempt to construct a BodyDataStream from the reader stream.\n        // Many axum_core versions provide a `from_stream` or similar constructor.\n        let body = axum_core::body::Body::from_stream(reader_stream).into_data_stream();\n\n        Ok(Self {\n            data: None,\n            name,\n            size: Some(size),\n            content_type: Some(mime.to_string()),\n            #[cfg(feature = \"server\")]\n            server_body: Some(body),\n            client_body: None,\n        })\n    }\n\n    /// Create a new `FileStream` from raw components.\n    ///\n    /// This is meant to be used on the server where a file might not even exist but you still want\n    /// to stream it to the client as a download.\n    #[cfg(feature = \"server\")]\n    pub fn from_raw(\n        name: String,\n        size: Option<u64>,\n        content_type: String,\n        body: axum_core::body::BodyDataStream,\n    ) -> Self {\n        Self {\n            data: None,\n            name,\n            size,\n            content_type: Some(content_type),\n            #[cfg(feature = \"server\")]\n            server_body: Some(body),\n            client_body: None,\n        }\n    }\n}\n\nimpl IntoRequest for FileStream {\n    #[allow(unreachable_code)]\n    fn into_request(\n        self,\n        #[allow(unused_mut)] mut builder: ClientRequest,\n    ) -> impl Future<Output = ClientResult> + 'static {\n        async move {\n            let Some(file_data) = self.data else {\n                return Err(RequestError::Request(\n                    \"FileStream has no data to send\".into(),\n                ));\n            };\n\n            #[cfg(feature = \"web\")]\n            if cfg!(target_arch = \"wasm32\") {\n                use js_sys::escape;\n                use wasm_bindgen::JsCast;\n\n                let as_file = file_data.inner().downcast_ref::<web_sys::File>().unwrap();\n                let as_blob = as_file.dyn_ref::<web_sys::Blob>().unwrap();\n                let content_type = as_blob.type_();\n                let content_length = as_blob.size().to_string();\n                let name = as_file.name();\n\n                // Set both Content-Length and X-Content-Size for compatibility with server extraction.\n                // In browsers, content-length is often overwritten, so we set X-Content-Size as well\n                // for better compatibility with dioxus-based clients.\n                return builder\n                    .header(\"Content-Type\", content_type)?\n                    .header(\"Content-Length\", content_length.clone())?\n                    .header(\"X-Content-Size\", content_length)?\n                    .header(\n                        \"Content-Disposition\",\n                        format!(\"attachment; filename=\\\"{}\\\"\", escape(&name)),\n                    )?\n                    .send_js_value(as_blob.clone().into())\n                    .await;\n            }\n\n            #[cfg(not(target_arch = \"wasm32\"))]\n            {\n                use std::ascii::escape_default;\n\n                use futures::TryStreamExt;\n\n                let content_type = self\n                    .content_type\n                    .unwrap_or_else(|| \"application/octet-stream\".to_string());\n                let content_length = self.size.map(|s| s.to_string());\n                let name = self.name;\n                let stream = file_data.byte_stream().map_err(|_| StreamingError::Failed);\n\n                // Ascii escape the filename to avoid issues with special characters.\n                let mut chars = vec![];\n                for byte in name.chars() {\n                    chars.extend(escape_default(byte as u8));\n                }\n                let filename = String::from_utf8(chars).map_err(|_| {\n                    RequestError::Request(\n                        \"Failed to escape filename for Content-Disposition\".into(),\n                    )\n                });\n\n                if let Some(length) = content_length {\n                    builder = builder.header(\"Content-Length\", length)?;\n                }\n\n                if let Ok(filename) = filename {\n                    builder = builder.header(\n                        \"Content-Disposition\",\n                        format!(\"attachment; filename=\\\"{}\\\"\", filename),\n                    )?;\n                }\n\n                return builder\n                    .header(\"Content-Type\", content_type)?\n                    .send_body_stream(stream)\n                    .await;\n            }\n\n            unimplemented!(\"FileStream::into_request is only implemented for web targets\");\n        }\n    }\n}\n\nimpl<S> FromRequest<S> for FileStream {\n    type Rejection = ServerFnError;\n\n    fn from_request(\n        req: Request,\n        _: &S,\n    ) -> impl Future<Output = Result<Self, Self::Rejection>> + Send {\n        async move {\n            tracing::info!(\"Extracting FileUpload from request: {:?}\", req);\n\n            let disposition = req.headers().get(\"Content-Disposition\");\n            let filename = match disposition.map(|s| s.to_str()) {\n                Some(Ok(dis)) => {\n                    let content = content_disposition::parse_content_disposition(dis);\n                    content\n                        .filename_full()\n                        .unwrap_or_else(|| \"file\".to_string())\n                }\n                _ => \"file\".to_string(),\n            };\n\n            // Content-length is unreliable, so we use `X-Content-Size` as an indicator.\n            // For stream requests with known bodies, the browser will still set Content-Length to 0, unfortunately.\n            let size = req\n                .headers()\n                .get(\"X-Content-Size\")\n                .and_then(|s| s.to_str().ok())\n                .and_then(|s| s.parse::<u64>().ok());\n\n            let content_type = req\n                .headers()\n                .get(\"Content-Type\")\n                .and_then(|s| s.to_str().ok())\n                .map(|s| s.to_string());\n\n            Ok(FileStream {\n                data: None,\n                name: filename,\n                content_type,\n                size,\n                client_body: None,\n                #[cfg(feature = \"server\")]\n                server_body: Some(req.into_body().into_data_stream()),\n            })\n        }\n    }\n}\n\nimpl FromResponse for FileStream {\n    fn from_response(res: ClientResponse) -> impl Future<Output = Result<Self, ServerFnError>> {\n        async move {\n            // Check status code first - don't try to stream error responses as files\n            if !res.status().is_success() {\n                let status_code = res.status().as_u16();\n                let canonical_reason = res\n                    .status()\n                    .canonical_reason()\n                    .unwrap_or(\"Unknown error\")\n                    .to_string();\n                let bytes = res.bytes().await.unwrap_or_default();\n                let message = String::from_utf8(bytes.to_vec()).unwrap_or(canonical_reason);\n\n                return Err(ServerFnError::ServerError {\n                    message,\n                    code: status_code,\n                    details: None,\n                });\n            }\n\n            // Extract filename from Content-Disposition header if present.\n            let name = res\n                .headers()\n                .get(\"Content-Disposition\")\n                .and_then(|h| h.to_str().ok())\n                .and_then(|dis| {\n                    let cd = content_disposition::parse_content_disposition(dis);\n                    cd.filename().map(|(name, _)| name.to_string())\n                })\n                .unwrap_or_else(|| \"file\".to_string());\n\n            // Extract content type header\n            let content_type = res\n                .headers()\n                .get(\"Content-Type\")\n                .and_then(|h| h.to_str().ok())\n                .map(|s| s.to_string());\n\n            // Prefer the response's known content length but fall back to X-Content-Size header.\n            let size = res.content_length().or_else(|| {\n                res.headers()\n                    .get(\"X-Content-Size\")\n                    .and_then(|h| h.to_str().ok())\n                    .and_then(|s| s.parse::<u64>().ok())\n            });\n\n            Ok(Self {\n                data: None,\n                name,\n                size,\n                content_type,\n                client_body: Some(Box::pin(res.bytes_stream())),\n                #[cfg(feature = \"server\")]\n                server_body: None,\n            })\n        }\n    }\n}\n\n#[cfg(feature = \"server\")]\nimpl IntoResponse for FileStream {\n    fn into_response(self) -> axum::response::Response {\n        use axum::body::Body;\n\n        let Some(body) = self.server_body else {\n            use dioxus_fullstack_core::HttpError;\n            return HttpError::new(http::StatusCode::BAD_REQUEST, \"FileStream has no body\")\n                .into_response();\n        };\n\n        let mut res = axum::response::Response::new(Body::from_stream(body));\n\n        // Set relevant headers if available\n        if let Some(content_type) = &self.content_type {\n            res.headers_mut()\n                .insert(\"Content-Type\", content_type.parse().unwrap());\n        }\n        if let Some(size) = self.size {\n            res.headers_mut()\n                .insert(\"Content-Length\", size.to_string().parse().unwrap());\n        }\n        res.headers_mut().insert(\n            \"Content-Disposition\",\n            format!(\"attachment; filename=\\\"{}\\\"\", self.name)\n                .parse()\n                .unwrap(),\n        );\n\n        res\n    }\n}\n\nimpl From<FileData> for FileStream {\n    fn from(value: FileData) -> Self {\n        Self {\n            name: value.name().to_string(),\n            content_type: value.content_type().map(|s| s.to_string()),\n            size: Some(value.size()),\n            data: Some(value),\n            client_body: None,\n            #[cfg(feature = \"server\")]\n            server_body: None,\n        }\n    }\n}\n\nimpl Stream for FileStream {\n    type Item = Result<Bytes, StreamingError>;\n\n    fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {\n        // For server-side builds, poll the server_body stream if it exists.\n        #[cfg(feature = \"server\")]\n        if let Some(body) = self.server_body.as_mut() {\n            return Pin::new(body)\n                .poll_next(cx)\n                .map_err(|_| StreamingError::Failed);\n        }\n\n        // For client-side builds, poll the client_body stream if it exists.\n        if let Some(body) = self.client_body.as_mut() {\n            return body.as_mut().poll_next(cx);\n        }\n\n        // Otherwise, the stream is exhausted.\n        Poll::Ready(None)\n    }\n}\n\nimpl std::fmt::Debug for FileStream {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_struct(\"FileStream\")\n            .field(\"name\", &self.name)\n            .field(\"size\", &self.size)\n            .field(\"content_type\", &self.content_type)\n            .finish()\n    }\n}\n"
  },
  {
    "path": "packages/fullstack/src/payloads/form.rs",
    "content": "use super::*;\n\npub use axum::extract::Form;\n\nimpl<T> IntoRequest for Form<T>\nwhere\n    T: Serialize + 'static + DeserializeOwned,\n{\n    fn into_request(self, req: ClientRequest) -> impl Future<Output = ClientResult> + 'static {\n        async move { req.send_form(&self.0).await }\n    }\n}\n"
  },
  {
    "path": "packages/fullstack/src/payloads/header.rs",
    "content": "use super::*;\n\npub use headers::Cookie;\npub use headers::SetCookie;\n\n#[derive(Clone, Debug)]\npub struct SetHeader<Data> {\n    data: Option<Data>,\n}\n\nimpl<T: Header> SetHeader<T> {\n    pub fn new(\n        value: impl TryInto<HeaderValue, Error = InvalidHeaderValue>,\n    ) -> Result<Self, headers::Error> {\n        let values = value.try_into().map_err(|_| headers::Error::invalid())?;\n\n        let res = T::decode(&mut std::iter::once(&values))?;\n\n        Ok(Self { data: Some(res) })\n    }\n}\n\nimpl<T: Header> IntoResponseParts for SetHeader<T> {\n    type Error = ();\n\n    fn into_response_parts(self, res: ResponseParts) -> Result<ResponseParts, Self::Error> {\n        let data = self.data.expect(\"SetHeader must have data to set\");\n\n        let mut headers = vec![];\n        data.encode(&mut headers);\n\n        Ok(axum::response::AppendHeaders(\n            headers.into_iter().map(|value| (T::name().clone(), value)),\n        )\n        .into_response_parts(res)\n        .unwrap())\n    }\n}\n\nimpl<T: Header> FromResponseParts for SetHeader<T> {\n    fn from_response_parts(parts: &mut axum::http::response::Parts) -> Result<Self, ServerFnError> {\n        let Some(header) = parts.headers.remove(T::name()) else {\n            return Ok(SetHeader { data: None });\n        };\n\n        let data = T::decode(&mut std::iter::once(&header))\n            .map_err(|_| ServerFnError::Deserialization(\"Failed to decode header\".into()))?;\n\n        Ok(SetHeader { data: Some(data) })\n    }\n}\n\nimpl<T: Header> IntoResponse for SetHeader<T> {\n    fn into_response(self) -> axum::response::Response {\n        let mut values = vec![];\n        self.data.unwrap().encode(&mut values);\n\n        let mut response = axum::response::Response::builder();\n\n        for value in values {\n            response = response.header(T::name(), value);\n        }\n\n        response.body(axum_core::body::Body::empty()).unwrap()\n    }\n}\n"
  },
  {
    "path": "packages/fullstack/src/payloads/msgpack.rs",
    "content": "#![forbid(unsafe_code)]\n\nuse axum::{\n    body::{Body, Bytes},\n    extract::{FromRequest, Request},\n    http::{header::HeaderValue, StatusCode},\n    response::{IntoResponse, Response},\n};\nuse axum::{extract::rejection::BytesRejection, http, BoxError};\nuse derive_more::{Deref, DerefMut, From};\nuse serde::{de::DeserializeOwned, Serialize};\n\n/// MessagePack Extractor / Response.\n///\n/// When used as an extractor, it can deserialize request bodies into some type that\n/// implements [`serde::Deserialize`]. If the request body cannot be parsed, or value of the\n/// `Content-Type` header does not match any of the `application/msgpack`, `application/x-msgpack`\n/// or `application/*+msgpack` it will reject the request and return a `400 Bad Request` response.\n///\n/// When used as a response, it can serialize any type that implements [`serde::Serialize`] to\n/// `MsgPack`, and will automatically set `Content-Type: application/msgpack` header.\n#[derive(Debug, Clone, Copy, Default, Deref, DerefMut, From)]\npub struct MsgPack<T>(pub T);\n\nimpl<T, S> FromRequest<S> for MsgPack<T>\nwhere\n    T: DeserializeOwned,\n    S: Send + Sync,\n{\n    type Rejection = MsgPackRejection;\n\n    async fn from_request(req: Request, state: &S) -> Result<Self, Self::Rejection> {\n        if !message_pack_content_type(&req) {\n            return Err(MsgPackRejection::MissingMsgPackContentType);\n        }\n        let bytes = Bytes::from_request(req, state).await?;\n        let value = rmp_serde::from_slice(&bytes)\n            .map_err(|e| MsgPackRejection::InvalidMsgPackBody(e.into()))?;\n        Ok(MsgPack(value))\n    }\n}\n\nimpl<T> IntoResponse for MsgPack<T>\nwhere\n    T: Serialize,\n{\n    fn into_response(self) -> Response {\n        let bytes = match rmp_serde::encode::to_vec_named(&self.0) {\n            Ok(res) => res,\n            Err(err) => {\n                return Response::builder()\n                    .status(StatusCode::INTERNAL_SERVER_ERROR)\n                    .header(\"Content-Type\", \"text/plain\")\n                    .body(Body::new(err.to_string()))\n                    .unwrap();\n            }\n        };\n\n        let mut res = bytes.into_response();\n\n        res.headers_mut().insert(\n            \"Content-Type\",\n            HeaderValue::from_static(\"application/msgpack\"),\n        );\n        res\n    }\n}\n\n/// MessagePack Extractor / Response.\n///\n/// When used as an extractor, it can deserialize request bodies into some type that\n/// implements [`serde::Deserialize`]. If the request body cannot be parsed, or value of the\n/// `Content-Type` header does not match any of the `application/msgpack`, `application/x-msgpack`\n/// or `application/*+msgpack` it will reject the request and return a `400 Bad Request` response.\n#[derive(Debug, Clone, Copy, Default, Deref, DerefMut, From)]\npub struct MsgPackRaw<T>(pub T);\n\nimpl<T, S> FromRequest<S> for MsgPackRaw<T>\nwhere\n    T: DeserializeOwned,\n    S: Send + Sync,\n{\n    type Rejection = MsgPackRejection;\n\n    async fn from_request(req: Request, state: &S) -> Result<Self, Self::Rejection> {\n        if !message_pack_content_type(&req) {\n            return Err(MsgPackRejection::MissingMsgPackContentType);\n        }\n        let bytes = Bytes::from_request(req, state).await?;\n        let value = rmp_serde::from_slice(&bytes)\n            .map_err(|e| MsgPackRejection::InvalidMsgPackBody(e.into()))?;\n        Ok(MsgPackRaw(value))\n    }\n}\n\nimpl<T> IntoResponse for MsgPackRaw<T>\nwhere\n    T: Serialize,\n{\n    fn into_response(self) -> Response {\n        let bytes = match rmp_serde::encode::to_vec(&self.0) {\n            Ok(res) => res,\n            Err(err) => {\n                return Response::builder()\n                    .status(StatusCode::INTERNAL_SERVER_ERROR)\n                    .header(\"Content-Type\", \"text/plain\")\n                    .body(Body::new(err.to_string()))\n                    .unwrap();\n            }\n        };\n\n        let mut res = bytes.into_response();\n\n        res.headers_mut().insert(\n            \"Content-Type\",\n            HeaderValue::from_static(\"application/msgpack\"),\n        );\n        res\n    }\n}\n\nfn message_pack_content_type<B>(req: &Request<B>) -> bool {\n    let Some(content_type) = req.headers().get(\"Content-Type\") else {\n        return false;\n    };\n\n    let Ok(content_type) = content_type.to_str() else {\n        return false;\n    };\n\n    match content_type {\n        \"application/msgpack\" => true,\n        \"application/x-msgpack\" => true,\n        ct if ct.starts_with(\"application/\") && ct.ends_with(\"+msgpack\") => true,\n        _ => false,\n    }\n}\n\n#[derive(Debug, thiserror::Error)]\n#[non_exhaustive]\npub enum MsgPackRejection {\n    #[error(\"Failed to parse the request body as MsgPack: {0}\")]\n    InvalidMsgPackBody(BoxError),\n\n    #[error(\"Expected request with `Content-Type: application/msgpack`\")]\n    MissingMsgPackContentType,\n\n    #[error(\"Cannot have two request body extractors for a single handler\")]\n    BodyAlreadyExtracted,\n\n    #[error(transparent)]\n    BytesRejection(#[from] BytesRejection),\n}\n\nimpl IntoResponse for MsgPackRejection {\n    fn into_response(self) -> Response {\n        match self {\n            Self::InvalidMsgPackBody(inner) => {\n                let mut res = Response::new(Body::from(format!(\n                    \"Failed to parse the request body as MsgPack: {}\",\n                    inner\n                )));\n                *res.status_mut() = http::StatusCode::BAD_REQUEST;\n                res\n            }\n\n            Self::MissingMsgPackContentType => {\n                let mut res = Response::new(Body::from(\n                    \"Expected request with `Content-Type: application/msgpack`\",\n                ));\n                *res.status_mut() = http::StatusCode::BAD_REQUEST;\n                res\n            }\n\n            Self::BodyAlreadyExtracted => {\n                let mut res = Response::new(Body::from(\n                    \"Cannot have two request body extractors for a single handler\",\n                ));\n                *res.status_mut() = http::StatusCode::INTERNAL_SERVER_ERROR;\n                res\n            }\n\n            Self::BytesRejection(inner) => inner.into_response(),\n        }\n    }\n}\n"
  },
  {
    "path": "packages/fullstack/src/payloads/multipart.rs",
    "content": "#![allow(unreachable_code)]\n\nuse crate::{ClientRequest, ClientResponse, IntoRequest};\nuse axum::{\n    extract::{FromRequest, Request},\n    response::{IntoResponse, Response},\n};\nuse dioxus_fullstack_core::RequestError;\nuse dioxus_html::{FormData, FormEvent};\nuse std::{prelude::rust_2024::Future, rc::Rc};\n\n#[cfg(feature = \"server\")]\nuse axum::extract::multipart::{Field, MultipartError};\n\n/// A streaming multipart form data handler.\n///\n/// This type makes it easy to send and receive multipart form data in a streaming fashion by directly\n/// leveraging the corresponding `dioxus_html::FormData` and `axum::extract::Multipart` types.\n///\n/// On the client, you can create a `MultipartFormData` instance by using `.into()` on a `FormData` instance.\n/// This is typically done by using the `FormEvent`'s `.data()` method.\n///\n/// On the server, you can extract a `MultipartFormData` instance by using it as an extractor in your handler function.\n/// This gives you access to axum's `Multipart` extractor, which allows you to handle the various fields\n/// and files in the multipart form data.\n///\n/// ## Axum Usage\n///\n/// Extractor that parses `multipart/form-data` requests (commonly used with file uploads).\n///\n/// ⚠️ Since extracting multipart form data from the request requires consuming the body, the\n/// `Multipart` extractor must be *last* if there are multiple extractors in a handler.\n/// See [\"the order of extractors\"][order-of-extractors]\n///\n/// [order-of-extractors]: mod@crate::extract#the-order-of-extractors\n///\n/// # Large Files\n///\n/// For security reasons, by default, `Multipart` limits the request body size to 2MB.\n/// See [`DefaultBodyLimit`][default-body-limit] for how to configure this limit.\n///\n/// [default-body-limit]: crate::extract::DefaultBodyLimit\npub struct MultipartFormData<T = ()> {\n    #[cfg(feature = \"server\")]\n    form: Option<axum::extract::Multipart>,\n\n    _client: Option<Rc<FormData>>,\n\n    _phantom: std::marker::PhantomData<T>,\n}\n\nimpl MultipartFormData {\n    #[cfg(feature = \"server\")]\n    pub async fn next_field(&mut self) -> Result<Option<Field<'_>>, MultipartError> {\n        if let Some(form) = &mut self.form {\n            form.next_field().await\n        } else {\n            Ok(None)\n        }\n    }\n}\n\nimpl<S> IntoRequest for MultipartFormData<S> {\n    fn into_request(\n        self,\n        _req: ClientRequest,\n    ) -> impl Future<Output = Result<ClientResponse, RequestError>> + 'static {\n        async move {\n            // On the web, it's just easier to convert the form data into a blob and then send that\n            // blob as the body of the request. This handles setting the correct headers, wiring\n            // up file uploads as streams, and encoding the request.\n            #[cfg(feature = \"web\")]\n            if cfg!(target_arch = \"wasm32\") {\n                let data = self._client.clone().ok_or_else(|| {\n                    RequestError::Builder(\"Failed to get FormData from event\".into())\n                })?;\n\n                fn get_form_data(data: Rc<FormData>) -> Option<wasm_bindgen::JsValue> {\n                    use wasm_bindgen::JsCast;\n                    let event: &web_sys::Event = data.downcast()?;\n                    let target = event.target()?;\n                    let form: &web_sys::HtmlFormElement = target.dyn_ref()?;\n                    let data: web_sys::FormData = web_sys::FormData::new_with_form(form).ok()?;\n                    Some(data.into())\n                }\n\n                let js_form_data = get_form_data(data).ok_or_else(|| {\n                    RequestError::Builder(\"Failed to get FormData from event\".into())\n                })?;\n\n                return _req.send_js_value(js_form_data).await;\n            }\n\n            // On non-web platforms, we actually need to read the values out of the FormData\n            // and construct a multipart form body manually.\n            #[cfg(not(target_arch = \"wasm32\"))]\n            {\n                let data = self._client.clone().ok_or_else(|| {\n                    RequestError::Builder(\"Failed to get FormData from event\".into())\n                })?;\n\n                return _req.send_multipart(&data).await;\n            }\n\n            unimplemented!(\"Non web wasm32 clients are not supported yet\")\n        }\n    }\n}\nimpl<S: Send + Sync + 'static, D> FromRequest<S> for MultipartFormData<D> {\n    type Rejection = Response;\n\n    #[doc = \" Perform the extraction.\"]\n    fn from_request(\n        req: Request,\n        state: &S,\n    ) -> impl Future<Output = Result<Self, Self::Rejection>> + Send {\n        #[cfg(feature = \"server\")]\n        return async move {\n            let form = axum::extract::multipart::Multipart::from_request(req, state)\n                .await\n                .map_err(|err| err.into_response())?;\n\n            Ok(MultipartFormData {\n                form: Some(form),\n                _client: None,\n                _phantom: std::marker::PhantomData,\n            })\n        };\n\n        #[cfg(not(feature = \"server\"))]\n        async {\n            use dioxus_fullstack_core::HttpError;\n\n            let _ = req;\n            let _ = state;\n            Err(HttpError::new(\n                http::StatusCode::INTERNAL_SERVER_ERROR,\n                \"MultipartFormData extractor is not supported on non-server builds\",\n            )\n            .into_response())\n        }\n    }\n}\n\nimpl<T> From<Rc<FormData>> for MultipartFormData<T> {\n    fn from(_value: Rc<FormData>) -> Self {\n        MultipartFormData {\n            #[cfg(feature = \"server\")]\n            form: None,\n            _client: Some(_value),\n            _phantom: std::marker::PhantomData,\n        }\n    }\n}\n\nimpl<T> From<FormEvent> for MultipartFormData<T> {\n    fn from(event: FormEvent) -> Self {\n        let data = event.data();\n        MultipartFormData {\n            #[cfg(feature = \"server\")]\n            form: None,\n            _client: Some(data),\n            _phantom: std::marker::PhantomData,\n        }\n    }\n}\n\nunsafe impl Send for MultipartFormData {}\nunsafe impl Sync for MultipartFormData {}\n"
  },
  {
    "path": "packages/fullstack/src/payloads/postcard.rs",
    "content": "use axum::{\n    body::Bytes,\n    extract::{rejection::BytesRejection, FromRequest},\n    http::{header, HeaderMap, StatusCode},\n    response::{IntoResponse, Response},\n};\nuse postcard::{from_bytes, to_allocvec};\nuse serde::{de::DeserializeOwned, Serialize};\nuse std::future::Future;\n\n/// Postcard Extractor / Response.\n///\n/// When used as an extractor, it can deserialize request bodies into some type that\n/// implements [`serde::Deserialize`]. The request will be rejected (and a [`PostcardRejection`] will\n/// be returned) if:\n///\n/// - The request doesn't have a `Content-Type: application/postcard` (or similar) header.\n/// - The body doesn't contain syntactically valid Postcard.\n/// - The body contains syntactically valid Postcard but it couldn't be deserialized into the target type.\n/// - Buffering the request body fails.\n///\n/// ⚠️ Since parsing Postcard requires consuming the request body, the `Postcard` extractor must be\n/// *last* if there are multiple extractors in a handler.\n/// See [\"the order of extractors\"][order-of-extractors]\n///\n/// [order-of-extractors]: mod@crate::extract#the-order-of-extractors\npub struct Postcard<T>(pub T);\n\n#[derive(thiserror::Error, Debug)]\npub enum PostcardRejection {\n    #[error(\"Expected request with `Content-Type: application/postcard`\")]\n    MissingPostcardContentType,\n\n    #[error(transparent)]\n    PostcardError(#[from] postcard::Error),\n\n    #[error(transparent)]\n    Bytes(#[from] BytesRejection),\n}\n\nimpl IntoResponse for PostcardRejection {\n    fn into_response(self) -> Response {\n        use PostcardRejection::*;\n        // its often easiest to implement `IntoResponse` by calling other implementations\n        match self {\n            MissingPostcardContentType => {\n                (StatusCode::UNSUPPORTED_MEDIA_TYPE, self.to_string()).into_response()\n            }\n            PostcardError(err) => (StatusCode::BAD_REQUEST, err.to_string()).into_response(),\n            _ => (StatusCode::INTERNAL_SERVER_ERROR, self.to_string()).into_response(),\n        }\n    }\n}\n\nimpl<T, S> FromRequest<S> for Postcard<T>\nwhere\n    T: DeserializeOwned,\n    S: Send + Sync,\n{\n    type Rejection = PostcardRejection;\n\n    fn from_request(\n        req: axum::extract::Request,\n        state: &S,\n    ) -> impl Future<Output = Result<Self, Self::Rejection>> + Send {\n        async move {\n            if postcard_content_type(req.headers()) {\n                let bytes = Bytes::from_request(req, state).await?;\n                let value = match from_bytes(&bytes) {\n                    Ok(value) => value,\n                    Err(err) => return Err(PostcardRejection::PostcardError(err)),\n                };\n                Ok(Postcard(value))\n            } else {\n                Err(PostcardRejection::MissingPostcardContentType)\n            }\n        }\n    }\n}\n\nfn postcard_content_type(headers: &HeaderMap) -> bool {\n    let content_type = if let Some(content_type) = headers.get(header::CONTENT_TYPE) {\n        content_type\n    } else {\n        return false;\n    };\n\n    let content_type = if let Ok(content_type) = content_type.to_str() {\n        content_type\n    } else {\n        return false;\n    };\n\n    let mime = if let Ok(mime) = content_type.parse::<mime::Mime>() {\n        mime\n    } else {\n        return false;\n    };\n\n    let is_postcard_content_type = mime.type_() == \"application\"\n        && (mime.subtype() == \"postcard\" || mime.suffix().is_some_and(|name| name == \"postcard\"));\n\n    is_postcard_content_type\n}\n\nimpl<T> IntoResponse for Postcard<T>\nwhere\n    T: Serialize,\n{\n    fn into_response(self) -> Response {\n        // TODO: maybe use 128 bytes cause serde is doing something like that\n        match to_allocvec(&self.0) {\n            Ok(value) => ([(header::CONTENT_TYPE, \"application/postcard\")], value).into_response(),\n            Err(err) => (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()).into_response(),\n        }\n    }\n}\n"
  },
  {
    "path": "packages/fullstack/src/payloads/query.rs",
    "content": "use std::ops::Deref;\n\nuse crate::ServerFnError;\nuse axum::extract::FromRequestParts;\nuse http::request::Parts;\nuse serde::de::DeserializeOwned;\n\n/// An extractor that deserializes query parameters into the given type `T`.\n///\n/// This uses `serde_qs` under the hood to support complex query parameter structures.\n#[derive(Debug, Clone, Copy, Default)]\npub struct Query<T>(pub T);\n\nimpl<T, S> FromRequestParts<S> for Query<T>\nwhere\n    T: DeserializeOwned,\n    S: Send + Sync,\n{\n    type Rejection = ServerFnError;\n\n    async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {\n        let inner: T = serde_qs::from_str(parts.uri.query().unwrap_or_default())\n            .map_err(|e| ServerFnError::Deserialization(e.to_string()))?;\n        Ok(Self(inner))\n    }\n}\n\nimpl<T> Deref for Query<T> {\n    type Target = T;\n\n    fn deref(&self) -> &Self::Target {\n        &self.0\n    }\n}\n"
  },
  {
    "path": "packages/fullstack/src/payloads/redirect.rs",
    "content": ""
  },
  {
    "path": "packages/fullstack/src/payloads/sse.rs",
    "content": "use crate::{ClientResponse, FromResponse, RequestError, ServerFnError};\n#[cfg(feature = \"server\")]\nuse axum::{\n    response::sse::{Event, KeepAlive},\n    BoxError,\n};\nuse futures::io::AsyncBufReadExt;\nuse futures::Stream;\nuse futures::{StreamExt, TryStreamExt};\nuse http::{header::CONTENT_TYPE, HeaderValue, StatusCode};\nuse serde::de::DeserializeOwned;\nuse std::pin::Pin;\nuse std::task::{Context, Poll};\nuse std::time::Duration;\n\n/// A stream of Server-Sent Events (SSE) that can be used to receive events from the server.\n///\n/// This type implements `Stream` for asynchronous iteration over events.\n/// Events are automatically deserialized from JSON to the specified type `T`.\n#[allow(clippy::type_complexity)]\npub struct ServerEvents<T> {\n    _marker: std::marker::PhantomData<fn() -> T>,\n\n    // The receiving end from the server\n    client: Option<Pin<Box<dyn Stream<Item = Result<ServerSentEvent, ServerFnError>>>>>,\n\n    #[cfg(feature = \"server\")]\n    keep_alive: Option<KeepAlive>,\n\n    // The actual SSE response to send to the client\n    #[cfg(feature = \"server\")]\n    sse: Option<axum::response::Sse<Pin<Box<dyn Stream<Item = Result<Event, BoxError>> + Send>>>>,\n}\n\nimpl<T: DeserializeOwned> ServerEvents<T> {\n    /// Receives the next event from the stream, deserializing it to `T`.\n    ///\n    /// Returns `None` if the stream has ended.\n    pub async fn recv(&mut self) -> Option<Result<T, ServerFnError>> {\n        let event = self.next_event().await?;\n        match event {\n            Ok(event) => {\n                let data: Result<T, ServerFnError> =\n                    serde_json::from_str(&event.data).map_err(|err| {\n                        ServerFnError::Serialization(format!(\n                            \"failed to deserialize event data: {}\",\n                            err\n                        ))\n                    });\n                Some(data)\n            }\n            Err(err) => Some(Err(err)),\n        }\n    }\n}\n\nimpl<T> ServerEvents<T> {\n    /// Receives the next raw event from the stream.\n    ///\n    /// Returns `None` if the stream has ended.\n    pub async fn next_event(&mut self) -> Option<Result<ServerSentEvent, ServerFnError>> {\n        self.client.as_mut()?.next().await\n    }\n}\n\nimpl<T: DeserializeOwned> Stream for ServerEvents<T> {\n    type Item = Result<T, ServerFnError>;\n\n    fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {\n        let Some(client) = self.client.as_mut() else {\n            return Poll::Ready(None);\n        };\n\n        match client.as_mut().poll_next(cx) {\n            Poll::Ready(Some(Ok(event))) => {\n                let data = serde_json::from_str(&event.data).map_err(|err| {\n                    ServerFnError::Serialization(format!(\n                        \"failed to deserialize event data: {}\",\n                        err\n                    ))\n                });\n                Poll::Ready(Some(data))\n            }\n            Poll::Ready(Some(Err(err))) => Poll::Ready(Some(Err(err))),\n            Poll::Ready(None) => Poll::Ready(None),\n            Poll::Pending => Poll::Pending,\n        }\n    }\n}\n\nimpl<T> FromResponse for ServerEvents<T> {\n    async fn from_response(res: ClientResponse) -> Result<Self, ServerFnError> {\n        let status = res.status();\n        if status != StatusCode::OK {\n            return Err(ServerFnError::Request(RequestError::Status(\n                format!(\"Expected status 200 OK, got {}\", status),\n                status.as_u16(),\n            )));\n        }\n\n        let content_type = res.headers().get(CONTENT_TYPE);\n        if content_type != Some(&HeaderValue::from_static(mime::TEXT_EVENT_STREAM.as_ref())) {\n            return Err(ServerFnError::Request(RequestError::Request(format!(\n                \"Expected content type 'text/event-stream', got {:?}\",\n                content_type\n            ))));\n        }\n\n        let mut stream = res\n            .bytes_stream()\n            .map(|result| result.map_err(std::io::Error::other))\n            .into_async_read();\n\n        let mut line_buffer = String::new();\n        let mut event_buffer = EventBuffer::new();\n\n        let stream: Pin<Box<dyn Stream<Item = Result<ServerSentEvent, ServerFnError>>>> = Box::pin(\n            async_stream::try_stream! {\n                loop {\n                    line_buffer.clear();\n                    if stream.read_line(&mut line_buffer).await.map_err(|err| ServerFnError::StreamError(err.to_string()))? == 0 {\n                        break;\n                    }\n\n                    let line = if let Some(line) = line_buffer.strip_suffix('\\n') {\n                        line\n                    } else {\n                        &line_buffer\n                    };\n\n                    // dispatch\n                    if line.is_empty() {\n                        if let Some(event) = event_buffer.produce_event() {\n                            yield event;\n                        }\n                        continue;\n                    }\n\n                    // Parse line to split field name and value, applying proper trimming.\n                    let (field, value) = line.split_once(':').unwrap_or((line, \"\"));\n                    let value = value.strip_prefix(' ').unwrap_or(value);\n\n                    // Handle fields - these are the in SSE speci.\n                    match field {\n                        \"event\" => event_buffer.set_event_type(value),\n                        \"data\" => event_buffer.push_data(value),\n                        \"id\" => event_buffer.set_id(value),\n                        \"retry\" => {\n                            if let Ok(millis) = value.parse() {\n                                event_buffer.set_retry(Duration::from_millis(millis));\n                            }\n                        }\n                        _ => {}\n                    }\n                }\n            },\n        );\n\n        Ok(Self {\n            _marker: std::marker::PhantomData,\n            client: Some(stream),\n\n            #[cfg(feature = \"server\")]\n            keep_alive: None,\n\n            #[cfg(feature = \"server\")]\n            sse: None,\n        })\n    }\n}\n\n/// Server-Sent Event representation.\n#[derive(Debug, Clone, Eq, PartialEq)]\npub struct ServerSentEvent {\n    /// A string identifying the type of event described.\n    pub event_type: String,\n\n    /// The data field for the message.\n    pub data: String,\n\n    /// Last event ID value.\n    pub last_event_id: Option<String>,\n\n    /// Reconnection time.\n    pub retry: Option<Duration>,\n}\n\n/// Internal buffer used to accumulate lines of an SSE (Server-Sent Events) stream.\nstruct EventBuffer {\n    event_type: String,\n    data: String,\n    last_event_id: Option<String>,\n    retry: Option<Duration>,\n}\n\nimpl EventBuffer {\n    /// Creates fresh new [`EventBuffer`].\n    #[allow(clippy::new_without_default)]\n    fn new() -> Self {\n        Self {\n            event_type: String::new(),\n            data: String::new(),\n            last_event_id: None,\n            retry: None,\n        }\n    }\n\n    /// Produces a [`Event`], if current state allow it.\n    ///\n    /// Reset the internal state to process further data.\n    fn produce_event(&mut self) -> Option<ServerSentEvent> {\n        let event = if self.data.is_empty() {\n            None\n        } else {\n            Some(ServerSentEvent {\n                event_type: if self.event_type.is_empty() {\n                    \"message\".to_string()\n                } else {\n                    self.event_type.clone()\n                },\n                data: self.data.to_string(),\n                last_event_id: self.last_event_id.clone(),\n                retry: self.retry,\n            })\n        };\n\n        self.event_type.clear();\n        self.data.clear();\n\n        event\n    }\n\n    /// Set the [`Event`]'s type. Override previous value.\n    fn set_event_type(&mut self, event_type: &str) {\n        self.event_type.clear();\n        self.event_type.push_str(event_type);\n    }\n\n    /// Extends internal data with given data.\n    fn push_data(&mut self, data: &str) {\n        if !self.data.is_empty() {\n            self.data.push('\\n');\n        }\n        self.data.push_str(data);\n    }\n\n    fn set_id(&mut self, id: &str) {\n        self.last_event_id = Some(id.to_string());\n    }\n\n    fn set_retry(&mut self, retry: Duration) {\n        self.retry = Some(retry);\n    }\n}\n\n#[cfg(feature = \"server\")]\npub use server_impl::*;\n\n#[cfg(feature = \"server\")]\nmod server_impl {\n    use super::*;\n    use crate::spawn_platform;\n    use axum::response::sse::Sse;\n    use axum_core::response::IntoResponse;\n    use futures::Future;\n    use futures::SinkExt;\n    use futures::{Sink, TryStream};\n    use serde::Serialize;\n\n    impl<T: 'static> ServerEvents<T> {\n        /// Create a `ServerEvents` from a function that is given a sender to send events to the client.\n        ///\n        /// By default, we send a comment every 15 seconds to keep the connection alive.\n        pub fn new<F, R>(f: impl FnOnce(SseTx<T>) -> F + Send + 'static) -> Self\n        where\n            F: Future<Output = R> + 'static,\n            R: 'static + Send,\n        {\n            let (tx, mut rx) = futures_channel::mpsc::unbounded();\n\n            let tx = SseTx {\n                sender: tx,\n                _marker: std::marker::PhantomData,\n            };\n\n            // Spawn the user function in the background\n            spawn_platform(move || f(tx));\n\n            // Create the stream of events, mapping the incoming events to `Ok`\n            // If the user function ends, the stream will end and the connection will be closed\n            let stream = futures::stream::poll_fn(move |cx| match rx.poll_next_unpin(cx) {\n                std::task::Poll::Ready(Some(event)) => std::task::Poll::Ready(Some(\n                    Ok(event) as Result<axum::response::sse::Event, BoxError>\n                )),\n                std::task::Poll::Ready(None) => std::task::Poll::Ready(None),\n                std::task::Poll::Pending => std::task::Poll::Pending,\n            });\n\n            let sse = Sse::new(stream.boxed());\n\n            Self {\n                _marker: std::marker::PhantomData,\n                client: None,\n                keep_alive: Some(KeepAlive::new().interval(Duration::from_secs(15))),\n                sse: Some(sse),\n            }\n        }\n\n        /// Create a `ServerEvents` from a `TryStream` of events.\n        pub fn from_stream<S>(stream: S) -> Self\n        where\n            S: TryStream<Ok = T, Error = BoxError> + Send + 'static,\n            T: Serialize,\n        {\n            let stream = stream.map_ok(|event| {\n                axum::response::sse::Event::default()\n                    .json_data(event)\n                    .expect(\"Failed to serialize SSE event\")\n            });\n            let sse = axum::response::Sse::new(stream.boxed());\n            Self {\n                _marker: std::marker::PhantomData,\n                client: None,\n                keep_alive: Some(KeepAlive::new().interval(Duration::from_secs(15))),\n                sse: Some(sse),\n            }\n        }\n\n        /// Set the keep-alive configuration for the SSE connection.\n        ///\n        /// A `None` value will disable the default `KeepAlive` of 15 seconds.\n        pub fn with_keep_alive(mut self, keep_alive: Option<KeepAlive>) -> Self {\n            self.keep_alive = keep_alive;\n            self\n        }\n\n        /// Create a `ServerEvents` from an existing Axum `Sse` response.\n        #[allow(clippy::type_complexity)]\n        pub fn from_sse(\n            sse: Sse<Pin<Box<dyn Stream<Item = Result<Event, BoxError>> + Send>>>,\n        ) -> Self {\n            Self {\n                _marker: std::marker::PhantomData,\n                client: None,\n                keep_alive: None,\n                sse: Some(sse),\n            }\n        }\n    }\n\n    impl<T> IntoResponse for ServerEvents<T> {\n        fn into_response(self) -> axum_core::response::Response {\n            let sse = self\n                .sse\n                .expect(\"SSE should be initialized before using it as a response\");\n\n            if let Some(keep_alive) = self.keep_alive {\n                sse.keep_alive(keep_alive).into_response()\n            } else {\n                sse.into_response()\n            }\n        }\n    }\n\n    /// A transmitter for sending events to the SSE stream.\n    pub struct SseTx<T> {\n        sender: futures_channel::mpsc::UnboundedSender<axum::response::sse::Event>,\n        _marker: std::marker::PhantomData<fn() -> T>,\n    }\n\n    impl<T: Serialize> SseTx<T> {\n        /// Sends an event to the SSE stream.\n        pub async fn send(&mut self, event: T) -> anyhow::Result<()> {\n            let event = axum::response::sse::Event::default().json_data(event)?;\n            self.sender.unbounded_send(event)?;\n            Ok(())\n        }\n    }\n\n    impl<T> std::ops::Deref for SseTx<T> {\n        type Target = futures_channel::mpsc::UnboundedSender<axum::response::sse::Event>;\n        fn deref(&self) -> &Self::Target {\n            &self.sender\n        }\n    }\n\n    impl<T> std::ops::DerefMut for SseTx<T> {\n        fn deref_mut(&mut self) -> &mut Self::Target {\n            &mut self.sender\n        }\n    }\n\n    impl<T: Serialize> Sink<T> for SseTx<T> {\n        type Error = anyhow::Error;\n\n        fn poll_ready(\n            mut self: Pin<&mut Self>,\n            _cx: &mut Context<'_>,\n        ) -> Poll<Result<(), Self::Error>> {\n            self.sender.poll_ready_unpin(_cx).map_err(|e| e.into())\n        }\n\n        fn start_send(mut self: Pin<&mut Self>, item: T) -> Result<(), Self::Error> {\n            let event = axum::response::sse::Event::default().json_data(item)?;\n            self.sender.start_send(event).map_err(|e| e.into())\n        }\n\n        fn poll_flush(\n            mut self: Pin<&mut Self>,\n            _cx: &mut Context<'_>,\n        ) -> Poll<Result<(), Self::Error>> {\n            self.sender.poll_flush_unpin(_cx).map_err(|e| e.into())\n        }\n\n        fn poll_close(\n            mut self: Pin<&mut Self>,\n            _cx: &mut Context<'_>,\n        ) -> Poll<Result<(), Self::Error>> {\n            self.sender.poll_close_unpin(_cx).map_err(|e| e.into())\n        }\n    }\n}\n"
  },
  {
    "path": "packages/fullstack/src/payloads/stream.rs",
    "content": "#![allow(clippy::type_complexity)]\n\nuse crate::{\n    CborEncoding, ClientRequest, ClientResponse, Encoding, FromResponse, IntoRequest, JsonEncoding,\n    ServerFnError,\n};\nuse axum::extract::{FromRequest, Request};\nuse axum_core::response::IntoResponse;\nuse bytes::{Buf as _, Bytes};\nuse dioxus_fullstack_core::{HttpError, RequestError};\nuse futures::{Stream, StreamExt};\n#[cfg(feature = \"server\")]\nuse futures_channel::mpsc::UnboundedSender;\nuse headers::{ContentType, Header};\nuse send_wrapper::SendWrapper;\nuse serde::{de::DeserializeOwned, Serialize};\nuse std::{future::Future, marker::PhantomData, pin::Pin};\n\n/// A stream of text data.\n///\n/// # Chunking\n///\n/// Note that strings sent by the server might not arrive in the same chunking as they were sent.\n///\n/// This is because the underlying transport layer (HTTP/2 or HTTP/3) may choose to split or combine\n/// chunks for efficiency.\n///\n/// If you need to preserve individual string boundaries, consider using `ChunkedTextStream` or another\n/// encoding that preserves chunk boundaries.\npub type TextStream = Streaming<String>;\n\n/// A stream of binary data.\n///\n/// # Chunking\n///\n/// Note that bytes sent by the server might not arrive in the same chunking as they were sent.\n/// This is because the underlying transport layer (HTTP/2 or HTTP/3) may choose to split or combine\n/// chunks for efficiency.\n///\n/// If you need to preserve individual byte boundaries, consider using `ChunkedByteStream` or another\n/// encoding that preserves chunk boundaries.\npub type ByteStream = Streaming<Bytes>;\n\n/// A stream of JSON-encoded data.\n///\n/// # Chunking\n///\n/// Normally, it's not possible to stream JSON over HTTP because browsers are free to re-chunk\n/// data as they see fit. However, this implementation manually frames each JSON as if it were an unmasked\n/// websocket message.\n///\n/// If you need to send a stream of JSON data without framing, consider using TextStream instead and\n/// manually handling JSON buffering.\npub type JsonStream<T> = Streaming<T, JsonEncoding>;\n\n/// A stream of Cbor-encoded data.\n///\n/// # Chunking\n///\n/// Normally, it's not possible to stream JSON over HTTP because browsers are free to re-chunk\n/// data as they see fit. However, this implementation manually frames each item as if it were an unmasked\n/// websocket message.\npub type CborStream<T> = Streaming<T, CborEncoding>;\n\n/// A stream of manually chunked binary data.\n///\n/// This encoding preserves chunk boundaries by framing each chunk with its length, using Websocket\n/// Framing.\npub type ChunkedByteStream = Streaming<Bytes, CborEncoding>;\n\n/// A stream of manually chunked text data.\n///\n/// This encoding preserves chunk boundaries by framing each chunk with its length, using Websocket\n/// Framing.\npub type ChunkedTextStream = Streaming<String, CborEncoding>;\n\n/// A streaming payload.\n///\n/// ## Frames and Chunking\n///\n/// The streaming payload sends and receives data in discrete chunks or \"frames\". The size is converted\n/// to hex and sent before each chunk, followed by a CRLF, the chunk data, and another CRLF.\n///\n/// This mimics actual HTTP chunked transfer encoding, but allows us to define our own framing\n/// protocol on top of it.\n///\n/// Arbitrary bytes can be encoded between these frames, but the frames do come with some overhead.\n///\n/// ## Browser Support for Streaming Input\n///\n/// Browser fetch requests do not currently support full request duplexing, which\n/// means that they do not begin handling responses until the full request has been sent.\n///\n/// This means that if you use a streaming input encoding, the input stream needs to\n/// end before the output will begin.\n///\n/// Streaming requests are only allowed over HTTP2 or HTTP3.\n///\n/// Also note that not all browsers support streaming bodies to servers.\npub struct Streaming<T = String, E = ()> {\n    stream: Pin<Box<dyn Stream<Item = Result<T, StreamingError>> + Send>>,\n    encoding: PhantomData<E>,\n}\n\n#[derive(thiserror::Error, Debug, Clone, PartialEq, Eq, Hash)]\npub enum StreamingError {\n    /// The streaming request was interrupted and could not be completed.\n    #[error(\"The streaming request was interrupted\")]\n    Interrupted,\n\n    /// The stream failed to decode a chunk - possibly due to invalid data or version mismatch.\n    #[error(\"The stream failed to decode a chunk\")]\n    Decoding,\n\n    /// The stream failed to connect or encountered an error.\n    #[error(\"The streaming request failed\")]\n    Failed,\n}\n\nimpl<T: 'static + Send, E> Streaming<T, E> {\n    /// Creates a new stream from the given stream.\n    pub fn new(value: impl Stream<Item = T> + Send + 'static) -> Self {\n        // Box and pin the incoming stream and store as a trait object\n        Self {\n            stream: Box::pin(value.map(|item| Ok(item)))\n                as Pin<Box<dyn Stream<Item = Result<T, StreamingError>> + Send>>,\n            encoding: PhantomData,\n        }\n    }\n\n    /// Spawns a new task that produces items for the stream.\n    ///\n    /// The callback is provided an `UnboundedSender` that can be used to send items to the stream.\n    #[cfg(feature = \"server\")]\n    pub fn spawn<F>(callback: impl FnOnce(UnboundedSender<T>) -> F + Send + 'static) -> Self\n    where\n        F: Future<Output = ()> + 'static,\n        T: Send,\n    {\n        let (tx, rx) = futures_channel::mpsc::unbounded();\n\n        crate::spawn_platform(move || callback(tx));\n\n        Self::new(rx)\n    }\n\n    /// Returns the next item in the stream, or `None` if the stream has ended.\n    pub async fn next(&mut self) -> Option<Result<T, StreamingError>> {\n        self.stream.as_mut().next().await\n    }\n\n    /// Consumes the wrapper, returning the inner stream.\n    pub fn into_inner(self) -> impl Stream<Item = Result<T, StreamingError>> + Send {\n        self.stream\n    }\n\n    /// Creates a streaming payload from an existing stream of bytes.\n    ///\n    /// This uses the internal framing mechanism to decode the stream into items of type `T`.\n    fn from_bytes(stream: impl Stream<Item = Result<T, StreamingError>> + Send + 'static) -> Self {\n        Self {\n            stream: Box::pin(stream),\n            encoding: PhantomData,\n        }\n    }\n}\n\nimpl<S, U> From<S> for TextStream\nwhere\n    S: Stream<Item = U> + Send + 'static,\n    U: Into<String>,\n{\n    fn from(value: S) -> Self {\n        Self::new(value.map(|data| data.into()))\n    }\n}\n\nimpl<S, E> From<S> for ByteStream\nwhere\n    S: Stream<Item = Result<Bytes, E>> + Send + 'static,\n{\n    fn from(value: S) -> Self {\n        Self {\n            stream: Box::pin(value.map(|data| data.map_err(|_| StreamingError::Failed))),\n            encoding: PhantomData,\n        }\n    }\n}\n\nimpl<T, S, U, E> From<S> for Streaming<T, E>\nwhere\n    S: Stream<Item = U> + Send + 'static,\n    U: Into<T>,\n    T: 'static + Send,\n    E: Encoding,\n{\n    fn from(value: S) -> Self {\n        Self::from_bytes(value.map(|data| Ok(data.into())))\n    }\n}\n\nimpl IntoResponse for Streaming<String> {\n    fn into_response(self) -> axum_core::response::Response {\n        axum::response::Response::builder()\n            .header(\"Content-Type\", \"text/plain; charset=utf-8\")\n            .body(axum::body::Body::from_stream(self.stream))\n            .unwrap()\n    }\n}\n\nimpl IntoResponse for Streaming<Bytes> {\n    fn into_response(self) -> axum_core::response::Response {\n        axum::response::Response::builder()\n            .header(\"Content-Type\", \"application/octet-stream\")\n            .body(axum::body::Body::from_stream(self.stream))\n            .unwrap()\n    }\n}\n\nimpl<T: DeserializeOwned + Serialize + 'static, E: Encoding> IntoResponse for Streaming<T, E> {\n    fn into_response(self) -> axum_core::response::Response {\n        let res = self.stream.map(|r| match r {\n            Ok(res) => match encode_stream_frame::<T, E>(res) {\n                Some(bytes) => Ok(bytes),\n                None => Err(StreamingError::Failed),\n            },\n            Err(_err) => Err(StreamingError::Failed),\n        });\n\n        axum::response::Response::builder()\n            .header(\"Content-Type\", E::stream_content_type())\n            .body(axum::body::Body::from_stream(res))\n            .unwrap()\n    }\n}\n\nimpl FromResponse for Streaming<String> {\n    fn from_response(res: ClientResponse) -> impl Future<Output = Result<Self, ServerFnError>> {\n        SendWrapper::new(async move {\n            let client_stream = Box::pin(res.bytes_stream().map(|byte| match byte {\n                Ok(bytes) => match String::from_utf8(bytes.to_vec()) {\n                    Ok(string) => Ok(string),\n                    Err(_) => Err(StreamingError::Decoding),\n                },\n                Err(_) => Err(StreamingError::Failed),\n            }));\n\n            Ok(Self {\n                stream: client_stream,\n                encoding: PhantomData,\n            })\n        })\n    }\n}\n\nimpl FromResponse for Streaming<Bytes> {\n    fn from_response(res: ClientResponse) -> impl Future<Output = Result<Self, ServerFnError>> {\n        async move {\n            let client_stream = Box::pin(SendWrapper::new(res.bytes_stream().map(\n                |byte| match byte {\n                    Ok(bytes) => Ok(bytes),\n                    Err(_) => Err(StreamingError::Failed),\n                },\n            )));\n\n            Ok(Self {\n                stream: client_stream,\n                encoding: PhantomData,\n            })\n        }\n    }\n}\n\nimpl<T: DeserializeOwned + Serialize + 'static + Send, E: Encoding> FromResponse\n    for Streaming<T, E>\n{\n    fn from_response(res: ClientResponse) -> impl Future<Output = Result<Self, ServerFnError>> {\n        SendWrapper::new(async move {\n            Ok(Self {\n                stream: byte_stream_to_client_stream::<E, _, _, _>(res.bytes_stream()),\n                encoding: PhantomData,\n            })\n        })\n    }\n}\n\nimpl<S> FromRequest<S> for Streaming<String> {\n    type Rejection = ServerFnError;\n\n    fn from_request(\n        req: Request,\n        _state: &S,\n    ) -> impl Future<Output = Result<Self, Self::Rejection>> + Send {\n        async move {\n            let (parts, body) = req.into_parts();\n            let content_type = parts\n                .headers\n                .get(\"content-type\")\n                .and_then(|v| v.to_str().ok())\n                .unwrap_or(\"\");\n\n            if !content_type.starts_with(\"text/plain\") {\n                HttpError::bad_request(\"Invalid content type\")?;\n            }\n\n            let stream = body.into_data_stream();\n\n            Ok(Self {\n                stream: Box::pin(stream.map(|byte| match byte {\n                    Ok(bytes) => match String::from_utf8(bytes.to_vec()) {\n                        Ok(string) => Ok(string),\n                        Err(_) => Err(StreamingError::Decoding),\n                    },\n                    Err(_) => Err(StreamingError::Failed),\n                })),\n                encoding: PhantomData,\n            })\n        }\n    }\n}\n\nimpl<S> FromRequest<S> for ByteStream {\n    type Rejection = ServerFnError;\n\n    fn from_request(\n        req: Request,\n        _state: &S,\n    ) -> impl Future<Output = Result<Self, Self::Rejection>> + Send {\n        async move {\n            let (parts, body) = req.into_parts();\n            let content_type = parts\n                .headers\n                .get(\"content-type\")\n                .and_then(|v| v.to_str().ok())\n                .unwrap_or(\"\");\n\n            if !content_type.starts_with(\"application/octet-stream\") {\n                HttpError::bad_request(\"Invalid content type\")?;\n            }\n\n            let stream = body.into_data_stream();\n\n            Ok(Self {\n                stream: Box::pin(stream.map(|byte| match byte {\n                    Ok(bytes) => Ok(bytes),\n                    Err(_) => Err(StreamingError::Failed),\n                })),\n                encoding: PhantomData,\n            })\n        }\n    }\n}\n\nimpl<T: DeserializeOwned + Serialize + 'static + Send, E: Encoding, S> FromRequest<S>\n    for Streaming<T, E>\n{\n    type Rejection = ServerFnError;\n\n    fn from_request(\n        req: Request,\n        _state: &S,\n    ) -> impl Future<Output = Result<Self, Self::Rejection>> + Send {\n        async move {\n            let (parts, body) = req.into_parts();\n            let content_type = parts\n                .headers\n                .get(\"content-type\")\n                .and_then(|v| v.to_str().ok())\n                .unwrap_or(\"\");\n\n            if !content_type.starts_with(E::stream_content_type()) {\n                HttpError::bad_request(\"Invalid content type\")?;\n            }\n\n            let stream = body.into_data_stream();\n\n            Ok(Self {\n                stream: byte_stream_to_client_stream::<E, _, _, _>(stream),\n                encoding: PhantomData,\n            })\n        }\n    }\n}\n\nimpl IntoRequest for Streaming<String> {\n    fn into_request(\n        self,\n        builder: ClientRequest,\n    ) -> impl Future<Output = Result<ClientResponse, RequestError>> + 'static {\n        async move {\n            builder\n                .header(\"Content-Type\", \"text/plain; charset=utf-8\")?\n                .send_body_stream(self.stream.map(|e| e.map(Bytes::from)))\n                .await\n        }\n    }\n}\n\nimpl IntoRequest for ByteStream {\n    fn into_request(\n        self,\n        builder: ClientRequest,\n    ) -> impl Future<Output = Result<ClientResponse, RequestError>> + 'static {\n        async move {\n            builder\n                .header(ContentType::name(), \"application/octet-stream\")?\n                .send_body_stream(self.stream)\n                .await\n        }\n    }\n}\n\nimpl<T: DeserializeOwned + Serialize + 'static + Send, E: Encoding> IntoRequest\n    for Streaming<T, E>\n{\n    fn into_request(\n        self,\n        builder: ClientRequest,\n    ) -> impl Future<Output = Result<ClientResponse, RequestError>> + 'static {\n        async move {\n            builder\n                .header(\"Content-Type\", E::stream_content_type())?\n                .send_body_stream(self.stream.map(|r| {\n                    r.and_then(|item| {\n                        encode_stream_frame::<T, E>(item).ok_or(StreamingError::Failed)\n                    })\n                }))\n                .await\n        }\n    }\n}\n\nimpl<T> std::fmt::Debug for Streaming<T> {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_tuple(\"Streaming\").finish()\n    }\n}\n\nimpl<T, E: Encoding> std::fmt::Debug for Streaming<T, E> {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_struct(\"Streaming\")\n            .field(\"encoding\", &std::any::type_name::<E>())\n            .finish()\n    }\n}\n\n/// This function encodes a single frame of a streaming payload using the specified encoding.\n///\n/// The resulting `Bytes` object is encoded as a websocket frame, so you can send it over a streaming\n/// HTTP response or even a websocket connection.\n///\n/// Note that the packet is not masked, as it is assumed to be sent over a trusted connection.\npub fn encode_stream_frame<T: Serialize, E: Encoding>(data: T) -> Option<Bytes> {\n    // We use full advantage of `BytesMut` here, writing a maximally full frame and then shrinking it\n    // down to size at the end.\n    //\n    // Also note we don't do any masking over this data since it's not going over an untrusted\n    // network like a websocket would.\n    //\n    // We allocate 10 extra bytes to account for framing overhead, which we'll shrink after\n    let mut bytes = vec![0u8; 10];\n\n    E::encode(data, &mut bytes)?;\n\n    let len = (bytes.len() - 10) as u64;\n    let opcode = 0x82; // FIN + binary opcode\n\n    // Write the header directly into the allocated space.\n    let offset = if len <= 125 {\n        bytes[8] = opcode;\n        bytes[9] = len as u8;\n        8\n    } else if len <= u16::MAX as u64 {\n        bytes[6] = opcode;\n        bytes[7] = 126;\n        let len_bytes = (len as u16).to_be_bytes();\n        bytes[8] = len_bytes[0];\n        bytes[9] = len_bytes[1];\n        6\n    } else {\n        bytes[0] = opcode;\n        bytes[1] = 127;\n        bytes[2..10].copy_from_slice(&len.to_be_bytes());\n        0\n    };\n\n    // Shrink down to the actual used size - is zero copy!\n    Some(Bytes::from(bytes).slice(offset..))\n}\n\nfn byte_stream_to_client_stream<E, T, S, E1>(\n    stream: S,\n) -> Pin<Box<dyn Stream<Item = Result<T, StreamingError>> + Send>>\nwhere\n    S: Stream<Item = Result<Bytes, E1>> + 'static + Send,\n    E: Encoding,\n    T: DeserializeOwned + 'static,\n{\n    Box::pin(stream.flat_map(|bytes| {\n        enum DecodeIteratorState {\n            Empty,\n            Failed,\n            Checked(Bytes),\n            UnChecked(Bytes),\n        }\n\n        let mut state = match bytes {\n            Ok(bytes) => DecodeIteratorState::UnChecked(bytes),\n            Err(_) => DecodeIteratorState::Failed,\n        };\n\n        futures::stream::iter(std::iter::from_fn(move || {\n            match std::mem::replace(&mut state, DecodeIteratorState::Empty) {\n                DecodeIteratorState::Empty => None,\n                DecodeIteratorState::Failed => Some(Err(StreamingError::Failed)),\n                DecodeIteratorState::Checked(mut bytes) => {\n                    let r = decode_stream_frame_multi::<T, E>(&mut bytes);\n                    if r.is_some() {\n                        state = DecodeIteratorState::Checked(bytes)\n                    }\n                    r\n                }\n                DecodeIteratorState::UnChecked(mut bytes) => {\n                    let r = decode_stream_frame_multi::<T, E>(&mut bytes);\n                    if r.is_some() {\n                        state = DecodeIteratorState::Checked(bytes);\n                        r\n                    } else {\n                        Some(Err(StreamingError::Decoding))\n                    }\n                }\n            }\n        }))\n    }))\n}\n\n/// Decode a websocket-framed streaming payload produced by [`encode_stream_frame`].\n///\n/// This function returns `None` if the frame is invalid or cannot be decoded.\n///\n/// It cannot handle masked frames, as those are not produced by our encoding function.\npub fn decode_stream_frame<T, E>(mut frame: Bytes) -> Option<T>\nwhere\n    E: Encoding,\n    T: DeserializeOwned,\n{\n    decode_stream_frame_multi::<T, E>(&mut frame).and_then(|r| r.ok())\n}\n\n/// Decode one value and advance the bytes pointer\n///\n/// If the frame is empty return None.\n///\n/// Otherwise, if the initial opcode is not the one expected for binary stream\n/// or the frame is not large enough return error StreamingError::Decoding\nfn decode_stream_frame_multi<T, E>(frame: &mut Bytes) -> Option<Result<T, StreamingError>>\nwhere\n    E: Encoding,\n    T: DeserializeOwned,\n{\n    let (offset, payload_len) = match offset_payload_len(frame)? {\n        Ok(r) => r,\n        Err(e) => return Some(Err(e)),\n    };\n\n    let r = E::decode(frame.slice(offset..offset + payload_len));\n    frame.advance(offset + payload_len);\n    r.map(|r| Ok(r))\n}\n\n/// Compute (offset,len) for decoding data\nfn offset_payload_len(frame: &Bytes) -> Option<Result<(usize, usize), StreamingError>> {\n    let data = frame.as_ref();\n\n    if data.is_empty() {\n        return None;\n    }\n\n    if data.len() < 2 {\n        return Some(Err(StreamingError::Decoding));\n    }\n\n    let first = data[0];\n    let second = data[1];\n\n    // Require FIN with binary opcode and no RSV bits\n    let fin = first & 0x80 != 0;\n    let opcode = first & 0x0F;\n    let rsv = first & 0x70;\n    if !fin || opcode != 0x02 || rsv != 0 {\n        return Some(Err(StreamingError::Decoding));\n    }\n\n    // Mask bit must be zero for our framing\n    if second & 0x80 != 0 {\n        return Some(Err(StreamingError::Decoding));\n    }\n\n    let mut offset = 2usize;\n    let mut payload_len = (second & 0x7F) as usize;\n\n    if payload_len == 126 {\n        if data.len() < offset + 2 {\n            return Some(Err(StreamingError::Decoding));\n        }\n\n        payload_len = u16::from_be_bytes([data[offset], data[offset + 1]]) as usize;\n        offset += 2;\n    } else if payload_len == 127 {\n        if data.len() < offset + 8 {\n            return Some(Err(StreamingError::Decoding));\n        }\n\n        let mut len_bytes = [0u8; 8];\n        len_bytes.copy_from_slice(&data[offset..offset + 8]);\n        let len_u64 = u64::from_be_bytes(len_bytes);\n\n        if len_u64 > usize::MAX as u64 {\n            return Some(Err(StreamingError::Decoding));\n        }\n\n        payload_len = len_u64 as usize;\n        offset += 8;\n    }\n\n    if data.len() < offset + payload_len {\n        return Some(Err(StreamingError::Decoding));\n    }\n    Some(Ok((offset, payload_len)))\n}\n"
  },
  {
    "path": "packages/fullstack/src/payloads/text.rs",
    "content": "use crate::{ClientResponse, FromResponse};\nuse axum_core::response::{IntoResponse, Response};\nuse dioxus_fullstack_core::ServerFnError;\nuse send_wrapper::SendWrapper;\nuse std::future::Future;\n\n/// A simple text response type.\n///\n/// The `T` parameter can be anything that converts to and from `String`, such as `Rc<str>` or `String`.\n///\n/// Unlike `Json` or plain `String`, this uses the `text/plain` content type. The `text/plain` header\n/// will be set on the request.\npub struct Text<T>(pub T);\n\nimpl<T> Text<T> {\n    /// Create a new text response.\n    pub fn new(text: T) -> Self {\n        Self(text)\n    }\n}\n\nimpl<T: Into<String>> IntoResponse for Text<T> {\n    fn into_response(self) -> Response {\n        Response::builder()\n            .header(\"Content-Type\", \"text/plain; charset=utf-8\")\n            .body(axum_core::body::Body::from(self.0.into()))\n            .unwrap()\n    }\n}\n\nimpl<T: From<String>> FromResponse for Text<T> {\n    fn from_response(res: ClientResponse) -> impl Future<Output = Result<Self, ServerFnError>> {\n        SendWrapper::new(async move {\n            let text = res.text().await?;\n            Ok(Text::new(text.into()))\n        })\n    }\n}\n"
  },
  {
    "path": "packages/fullstack/src/payloads/websocket.rs",
    "content": "#![allow(unreachable_code)]\n#![allow(unused_imports)]\n\n//! This module implements WebSocket support for Dioxus Fullstack applications.\n//!\n//! WebSockets provide a full-duplex communication channel over a single, long-lived connection.\n//!\n//! This makes them ideal for real-time applications where the server and the client need to communicate\n//! frequently and with low latency. Unlike Server-Sent Events (SSE), WebSockets allow the direct\n//! transport of binary data, enabling things like video and audio streaming as well as more efficient\n//! zero-copy serialization formats.\n//!\n//! This module implements a variety of types:\n//! - `Websocket<In, Out, E>`: Represents a WebSocket connection that can send messages of type `In` and receive messages of type `Out`, using the encoding `E`.\n//! - `UseWebsocket<In, Out, E>`: A hook that provides a reactive interface to a WebSocket connection.\n//! - `WebSocketOptions`: Configuration options for establishing a WebSocket connection.\n//! - `TypedWebsocket<In, Out, E>`: A typed wrapper around an Axum WebSocket connection for server-side use.\n//! - `WebsocketState`: An enum representing the state of the WebSocket connection.\n//! - plus a variety of error types and traits for encoding/decoding messages.\n//!\n//! Dioxus Fullstack websockets are typed in both directions, letting the happy path (`.send()` and `.recv()`)\n//! automatically serialize and deserialize messages for you.\n\nuse crate::{ClientRequest, Encoding, FromResponse, IntoRequest, JsonEncoding, ServerFnError};\nuse axum::{\n    extract::{FromRequest, Request},\n    http::StatusCode,\n};\nuse axum_core::response::{IntoResponse, Response};\nuse bytes::Bytes;\nuse dioxus_core::{use_hook, CapturedError, Result};\nuse dioxus_fullstack_core::{HttpError, RequestError};\nuse dioxus_hooks::{use_resource, Resource, UseWaker};\nuse dioxus_hooks::{use_signal, use_waker};\nuse dioxus_signals::{ReadSignal, ReadableExt, ReadableOptionExt, Signal, WritableExt};\nuse futures::{\n    stream::{SplitSink, SplitStream},\n    Sink, SinkExt, Stream, StreamExt, TryFutureExt,\n};\nuse serde::{de::DeserializeOwned, Serialize};\nuse std::{\n    marker::PhantomData,\n    pin::Pin,\n    prelude::rust_2024::Future,\n    rc::Rc,\n    task::{ready, Context, Poll},\n};\n\n#[cfg(feature = \"web\")]\nuse {\n    futures_util::lock::Mutex,\n    gloo_net::websocket::{futures::WebSocket as WsWebsocket, Message as WsMessage},\n};\n\n/// A hook that provides a reactive interface to a WebSocket connection.\n///\n/// WebSockets provide a full-duplex communication channel over a single, long-lived connection.\n///\n/// This makes them ideal for real-time applications where the server and the client need to communicate\n/// frequently and with low latency. Unlike Server-Sent Events (SSE), WebSockets allow the direct\n/// transport of binary data, enabling things like video and audio streaming as well as more efficient\n/// zero-copy serialization formats.\n///\n/// This hook takes a function that returns a future which resolves to a `Websocket<In, Out, E>` -\n/// usually a server function.\npub fn use_websocket<\n    In: 'static,\n    Out: 'static,\n    E: Into<CapturedError> + 'static,\n    F: Future<Output = Result<Websocket<In, Out, Enc>, E>> + 'static,\n    Enc: Encoding,\n>(\n    mut connect_to_websocket: impl FnMut() -> F + 'static,\n) -> UseWebsocket<In, Out, Enc> {\n    let mut waker = use_waker();\n    let mut status = use_signal(|| WebsocketState::Connecting);\n    let status_read = use_hook(|| ReadSignal::new(status));\n\n    let connection = use_resource(move || {\n        let connection = connect_to_websocket().map_err(|e| e.into());\n        async move {\n            let connection = connection.await;\n\n            // Update the status based on the result of the connection attempt\n            match connection.as_ref() {\n                Ok(_) => status.set(WebsocketState::Open),\n                Err(_) => status.set(WebsocketState::FailedToConnect),\n            }\n\n            // Wake up the `.recv()` calls waiting for the connection to be established\n            waker.wake(());\n\n            // Wrap in Rc so we can clone it out of the Resource without holding\n            // a borrow guard across await points\n            connection.map(Rc::new)\n        }\n    });\n\n    UseWebsocket {\n        connection,\n        waker,\n        status,\n        status_read,\n    }\n}\n\n/// The return type of the `use_websocket` hook.\n///\n/// See the `use_websocket` documentation for more details.\n///\n/// This handle provides methods to send and receive messages, check the connection status,\n/// and wait for the connection to be established.\npub struct UseWebsocket<In, Out, Enc = JsonEncoding>\nwhere\n    In: 'static,\n    Out: 'static,\n    Enc: 'static,\n{\n    #[allow(clippy::type_complexity)]\n    connection: Resource<Result<Rc<Websocket<In, Out, Enc>>, CapturedError>>,\n    waker: UseWaker<()>,\n    status: Signal<WebsocketState>,\n    status_read: ReadSignal<WebsocketState>,\n}\n\nimpl<In, Out, E> UseWebsocket<In, Out, E> {\n    /// Wait for the connection to be established. This guarantees that subsequent calls to methods like\n    /// `.try_recv()` will not fail due to the connection not being ready.\n    pub async fn connect(&self) -> WebsocketState {\n        // Wait for the connection to be established\n        while !self.connection.finished() {\n            _ = self.waker.wait().await;\n        }\n\n        self.status.cloned()\n    }\n\n    /// Returns true if the WebSocket is currently connecting.\n    ///\n    /// This can be useful to present a loading state to the user while the connection is being established.\n    pub fn connecting(&self) -> bool {\n        matches!(self.status.cloned(), WebsocketState::Connecting)\n    }\n\n    /// Returns true if the Websocket is closed due to an error.\n    pub fn is_err(&self) -> bool {\n        matches!(self.status.cloned(), WebsocketState::FailedToConnect)\n    }\n\n    /// Returns true if the WebSocket is currently shut down and cannot be used to send or receive messages.\n    pub fn is_closed(&self) -> bool {\n        matches!(\n            self.status.cloned(),\n            WebsocketState::Closed | WebsocketState::FailedToConnect\n        )\n    }\n\n    /// Get the current status of the WebSocket connection.\n    pub fn status(&self) -> ReadSignal<WebsocketState> {\n        self.status_read\n    }\n\n    /// Send a raw message over the WebSocket connection\n    ///\n    /// To send a message with a particular type, see the `.send()` method instead.\n    pub async fn send_raw(&self, msg: Message) -> Result<(), WebsocketError> {\n        self.connect().await;\n        self.get_connection()?.send_raw(msg).await\n    }\n\n    /// Receive a raw message from the WebSocket connection\n    ///\n    /// To receive a message with a particular type, see the `.recv()` method instead.\n    pub async fn recv_raw(&mut self) -> Result<Message, WebsocketError> {\n        self.connect().await;\n        let ws = self.get_connection()?;\n\n        // Race the recv against the waker — if the connection is being recreated\n        // (e.g. a reactive dependency changed), the waker fires and we return an error\n        // so the caller's loop can restart and pick up the new connection.\n        let recv_fut = ws.recv_raw();\n        let waker_fut = self.waker.wait();\n        futures::pin_mut!(recv_fut, waker_fut);\n\n        match futures::future::select(recv_fut, waker_fut).await {\n            futures::future::Either::Left((recv_result, _)) => {\n                if let Err(WebsocketError::ConnectionClosed { .. }) = recv_result.as_ref() {\n                    self.received_shutdown();\n                }\n                recv_result\n            }\n            futures::future::Either::Right(_) => Err(WebsocketError::ConnectionClosed {\n                code: CloseCode::Away,\n                description: \"Connection replaced by a new one\".to_string(),\n            }),\n        }\n    }\n\n    pub async fn send(&self, msg: In) -> Result<(), WebsocketError>\n    where\n        In: Serialize,\n        E: Encoding,\n    {\n        self.send_raw(Message::Binary(\n            E::to_bytes(&msg).ok_or_else(WebsocketError::serialization)?,\n        ))\n        .await\n    }\n\n    /// Receive the next message from the WebSocket connection, deserialized into the `Out` type.\n    ///\n    /// If the connection is still opening, this will wait until the connection is established.\n    /// If the connection fails to open or is killed while waiting, an error will be returned.\n    ///\n    /// This method returns an error if the connection is closed since we assume closed connections\n    /// are a \"failure\".\n    pub async fn recv(&mut self) -> Result<Out, WebsocketError>\n    where\n        Out: DeserializeOwned,\n        E: Encoding,\n    {\n        self.connect().await;\n        let ws = self.get_connection()?;\n\n        let recv_fut = ws.recv();\n        let waker_fut = self.waker.wait();\n        futures::pin_mut!(recv_fut, waker_fut);\n\n        match futures::future::select(recv_fut, waker_fut).await {\n            futures::future::Either::Left((recv_result, _)) => {\n                if let Err(WebsocketError::ConnectionClosed { .. }) = recv_result.as_ref() {\n                    self.received_shutdown();\n                }\n                recv_result\n            }\n            futures::future::Either::Right(_) => Err(WebsocketError::ConnectionClosed {\n                code: CloseCode::Away,\n                description: \"Connection replaced by a new one\".to_string(),\n            }),\n        }\n    }\n\n    /// Set the WebSocket connection.\n    ///\n    /// This method takes a `Result<Websocket<In, Out, E>, Err>`, allowing you to drive the connection\n    /// into an errored state manually.\n    pub fn set<Err: Into<CapturedError>>(&mut self, socket: Result<Websocket<In, Out, E>, Err>) {\n        match socket {\n            Ok(_) => self.status.set(WebsocketState::Open),\n            Err(_) => self.status.set(WebsocketState::FailedToConnect),\n        }\n\n        self.connection\n            .set(Some(socket.map(Rc::new).map_err(|e| e.into())));\n        self.waker.wake(());\n    }\n\n    /// Mark the WebSocket as closed. This is called internally when the connection is closed.\n    fn received_shutdown(&self) {\n        let mut _self = *self;\n        _self.status.set(WebsocketState::Closed);\n        _self.waker.wake(());\n    }\n\n    /// Clone the `Rc<Websocket>` out of the Resource using peek, so we don't hold a borrow\n    /// guard across await points. This prevents AlreadyBorrowed panics when the Resource\n    /// tries to write while recv() is awaiting.\n    #[allow(clippy::result_large_err)]\n    fn get_connection(&self) -> Result<Rc<Websocket<In, Out, E>>, WebsocketError> {\n        self.connection.with_peek(|opt| {\n            opt.as_ref()\n                .ok_or_else(WebsocketError::closed_away)?\n                .as_ref()\n                .map(Rc::clone)\n                .map_err(|_| WebsocketError::AlreadyClosed)\n        })\n    }\n}\n\nimpl<In, Out, E> Copy for UseWebsocket<In, Out, E> {}\nimpl<In, Out, E> Clone for UseWebsocket<In, Out, E> {\n    fn clone(&self) -> Self {\n        *self\n    }\n}\n\n#[derive(Debug, Clone, PartialEq, Copy)]\npub enum WebsocketState {\n    /// The WebSocket is connecting.\n    Connecting,\n\n    /// The WebSocket is open and ready to send and receive messages.\n    Open,\n\n    /// The WebSocket is closing.\n    Closing,\n\n    /// The WebSocket is closed and cannot be used to send or receive messages.\n    Closed,\n\n    /// The WebSocket failed to connect\n    FailedToConnect,\n}\n\n/// A WebSocket connection that can send and receive messages of type `In` and `Out`.\npub struct Websocket<In = String, Out = String, E = JsonEncoding> {\n    protocol: Option<String>,\n\n    #[allow(clippy::type_complexity)]\n    _in: std::marker::PhantomData<fn() -> (In, Out, E)>,\n\n    #[cfg(not(target_arch = \"wasm32\"))]\n    native: Option<native::SplitSocket>,\n\n    #[cfg(feature = \"web\")]\n    web: Option<WebsysSocket>,\n\n    response: Option<axum::response::Response>,\n}\n\nimpl<I, O, E> Websocket<I, O, E> {\n    pub async fn recv(&self) -> Result<O, WebsocketError>\n    where\n        O: DeserializeOwned,\n        E: Encoding,\n    {\n        loop {\n            let msg = self.recv_raw().await?;\n            match msg {\n                Message::Text(text) => {\n                    let e: O =\n                        E::decode(text.into()).ok_or_else(WebsocketError::deserialization)?;\n                    return Ok(e);\n                }\n                Message::Binary(bytes) => {\n                    let e: O = E::decode(bytes).ok_or_else(WebsocketError::deserialization)?;\n                    return Ok(e);\n                }\n                Message::Close { code, reason } => {\n                    return Err(WebsocketError::ConnectionClosed {\n                        code,\n                        description: reason,\n                    });\n                }\n\n                // todo - are we supposed to response to pings?\n                Message::Ping(_bytes) => continue,\n                Message::Pong(_bytes) => continue,\n            }\n        }\n    }\n\n    /// Send a typed message over the WebSocket connection.\n    ///\n    /// This method serializes the message using the specified encoding `E` before sending it.\n    /// The message will always be sent as a binary message, even if the encoding is valid UTF-8\n    /// like JSON.\n    pub async fn send(&self, msg: I) -> Result<(), WebsocketError>\n    where\n        I: Serialize,\n        E: Encoding,\n    {\n        let bytes = E::to_bytes(&msg).ok_or_else(WebsocketError::serialization)?;\n        self.send_raw(Message::Binary(bytes)).await\n    }\n\n    /// Send a raw message over the WebSocket connection.\n    ///\n    /// This method allows sending text, binary, ping, pong, and close messages directly.\n    pub async fn send_raw(&self, message: Message) -> Result<(), WebsocketError> {\n        #[cfg(feature = \"web\")]\n        if cfg!(target_arch = \"wasm32\") {\n            let mut sender = self\n                .web\n                .as_ref()\n                .ok_or_else(|| WebsocketError::Uninitialized)?\n                .sender\n                .lock()\n                .await;\n\n            match message {\n                Message::Text(s) => {\n                    sender.send(gloo_net::websocket::Message::Text(s)).await?;\n                }\n                Message::Binary(bytes) => {\n                    sender\n                        .send(gloo_net::websocket::Message::Bytes(bytes.into()))\n                        .await?;\n                }\n                Message::Close { .. } => {\n                    sender.close().await?;\n                }\n                Message::Ping(_bytes) => return Ok(()),\n                Message::Pong(_bytes) => return Ok(()),\n            }\n\n            return Ok(());\n        }\n\n        #[cfg(not(target_arch = \"wasm32\"))]\n        {\n            let mut sender = self\n                .native\n                .as_ref()\n                .ok_or_else(|| WebsocketError::Uninitialized)?\n                .sender\n                .lock()\n                .await;\n\n            sender\n                .send(message.into())\n                .await\n                .map_err(WebsocketError::from)?;\n        }\n\n        Ok(())\n    }\n\n    /// Receive a raw message from the WebSocket connection.\n    pub async fn recv_raw(&self) -> Result<Message, WebsocketError> {\n        #[cfg(feature = \"web\")]\n        if cfg!(target_arch = \"wasm32\") {\n            let mut conn = self.web.as_ref().unwrap().receiver.lock().await;\n            return match conn.next().await {\n                Some(Ok(WsMessage::Text(text))) => Ok(Message::Text(text)),\n                Some(Ok(WsMessage::Bytes(items))) => Ok(Message::Binary(items.into())),\n                Some(Err(e)) => Err(WebsocketError::from(e)),\n                None => Err(WebsocketError::closed_away()),\n            };\n        }\n\n        #[cfg(not(target_arch = \"wasm32\"))]\n        {\n            use tungstenite::Message as TMessage;\n            let mut conn = self.native.as_ref().unwrap().receiver.lock().await;\n            return match conn.next().await {\n                Some(Ok(res)) => match res {\n                    TMessage::Text(utf8_bytes) => Ok(Message::Text(utf8_bytes.to_string())),\n                    TMessage::Binary(bytes) => Ok(Message::Binary(bytes)),\n                    TMessage::Close(Some(cf)) => Ok(Message::Close {\n                        code: cf.code.into(),\n                        reason: cf.reason.to_string(),\n                    }),\n                    TMessage::Close(None) => Ok(Message::Close {\n                        code: CloseCode::Away,\n                        reason: \"Away\".to_string(),\n                    }),\n                    TMessage::Ping(bytes) => Ok(Message::Ping(bytes)),\n                    TMessage::Pong(bytes) => Ok(Message::Pong(bytes)),\n                    TMessage::Frame(_frame) => Err(WebsocketError::Unexpected),\n                },\n                Some(Err(e)) => Err(WebsocketError::from(e)),\n                None => Err(WebsocketError::closed_away()),\n            };\n        }\n\n        unimplemented!(\"Non web wasm32 clients are not supported yet\")\n    }\n\n    pub fn protocol(&self) -> Option<&str> {\n        self.protocol.as_deref()\n    }\n}\n\n// no two websockets are ever equal\nimpl<I, O, E> PartialEq for Websocket<I, O, E> {\n    fn eq(&self, _other: &Self) -> bool {\n        false\n    }\n}\n\n// Create a new WebSocket connection that uses the provided function to handle incoming messages\nimpl<In, Out, E> IntoResponse for Websocket<In, Out, E> {\n    fn into_response(self) -> Response {\n        let Some(response) = self.response else {\n            return HttpError::new(\n                StatusCode::INTERNAL_SERVER_ERROR,\n                \"WebSocket response not initialized\",\n            )\n            .into_response();\n        };\n\n        response.into_response()\n    }\n}\n\nimpl<I, O, E> FromResponse<UpgradingWebsocket> for Websocket<I, O, E> {\n    fn from_response(res: UpgradingWebsocket) -> impl Future<Output = Result<Self, ServerFnError>> {\n        async move {\n            #[cfg(not(target_arch = \"wasm32\"))]\n            let native = res.native;\n\n            #[cfg(feature = \"web\")]\n            let web = res.web.map(|f| {\n                let (sender, receiver) = f.split();\n                WebsysSocket {\n                    sender: Mutex::new(sender),\n                    receiver: Mutex::new(receiver),\n                }\n            });\n\n            Ok(Websocket {\n                protocol: res.protocol,\n                #[cfg(not(target_arch = \"wasm32\"))]\n                native,\n                #[cfg(feature = \"web\")]\n                web,\n                response: None,\n                _in: PhantomData,\n            })\n        }\n    }\n}\n\npub struct WebSocketOptions {\n    protocols: Vec<String>,\n    automatic_reconnect: bool,\n    #[cfg(feature = \"server\")]\n    upgrade: Option<axum::extract::ws::WebSocketUpgrade>,\n    #[cfg(feature = \"server\")]\n    on_failed_upgrade: Option<Box<dyn FnOnce(axum::Error) + Send + 'static>>,\n}\n\nimpl WebSocketOptions {\n    pub fn new() -> Self {\n        Self {\n            protocols: Vec::new(),\n            automatic_reconnect: false,\n\n            #[cfg(feature = \"server\")]\n            upgrade: None,\n\n            #[cfg(feature = \"server\")]\n            on_failed_upgrade: None,\n        }\n    }\n\n    /// Automatically reconnect if the connection is lost. This uses an exponential backoff strategy.\n    pub fn with_automatic_reconnect(mut self) -> Self {\n        self.automatic_reconnect = true;\n        self\n    }\n\n    #[cfg(feature = \"server\")]\n    pub fn on_failed_upgrade(\n        mut self,\n        callback: impl FnOnce(axum::Error) + Send + 'static,\n    ) -> Self {\n        self.on_failed_upgrade = Some(Box::new(callback));\n\n        self\n    }\n\n    #[cfg(feature = \"server\")]\n    pub fn on_upgrade<F, Fut, In, Out, Enc>(mut self, callback: F) -> Websocket<In, Out, Enc>\n    where\n        F: FnOnce(TypedWebsocket<In, Out, Enc>) -> Fut + Send + 'static,\n        Fut: Future<Output = ()> + 'static,\n    {\n        let on_failed_upgrade = self.on_failed_upgrade.take();\n        let response = self\n            .upgrade\n            .unwrap()\n            .on_failed_upgrade(|e| {\n                if let Some(callback) = on_failed_upgrade {\n                    callback(e);\n                }\n            })\n            .on_upgrade(|socket| {\n                let res = crate::spawn_platform(move || {\n                    callback(TypedWebsocket {\n                        _in: PhantomData,\n                        _out: PhantomData,\n                        _enc: PhantomData,\n                        inner: socket,\n                    })\n                });\n                async move {\n                    let _ = res.await;\n                }\n            });\n\n        Websocket {\n            // the protocol is none here since it won't be accessible until after the upgrade\n            protocol: None,\n            response: Some(response),\n            _in: PhantomData,\n\n            #[cfg(not(target_arch = \"wasm32\"))]\n            native: None,\n\n            #[cfg(feature = \"web\")]\n            web: None,\n        }\n    }\n}\n\nimpl Default for WebSocketOptions {\n    fn default() -> Self {\n        Self::new()\n    }\n}\n\nimpl IntoRequest<UpgradingWebsocket> for WebSocketOptions {\n    fn into_request(\n        self,\n        request: ClientRequest,\n    ) -> impl Future<Output = std::result::Result<UpgradingWebsocket, RequestError>> + 'static {\n        async move {\n            #[cfg(feature = \"web\")]\n            if cfg!(target_arch = \"wasm32\") {\n                let url_path = request.url().path();\n                let url_query = request.url().query();\n                let url_fragment = request.url().fragment();\n                let path_and_query = format!(\n                    \"{}{}{}\",\n                    url_path,\n                    url_query.map_or(\"\".to_string(), |q| format!(\"?{q}\")),\n                    url_fragment.map_or(\"\".to_string(), |f| format!(\"#{f}\"))\n                );\n\n                let socket = gloo_net::websocket::futures::WebSocket::open_with_protocols(\n                    // ! very important we use the path here and not the full url on web.\n                    // for as long as serverfns are meant to target the same origin, this is fine.\n                    &path_and_query,\n                    &self\n                        .protocols\n                        .iter()\n                        .map(String::as_str)\n                        .collect::<Vec<_>>(),\n                )\n                .map_err(|error| RequestError::Connect(error.to_string()))?;\n\n                return Ok(UpgradingWebsocket {\n                    protocol: Some(socket.protocol()),\n                    web: Some(socket),\n                    #[cfg(not(target_arch = \"wasm32\"))]\n                    native: None,\n                });\n            }\n\n            #[cfg(not(target_arch = \"wasm32\"))]\n            {\n                let response = native::send_request(request, &self.protocols).await?;\n\n                let (inner, protocol) = response\n                    .into_stream_and_protocol(self.protocols, None)\n                    .await?;\n\n                return Ok(UpgradingWebsocket {\n                    protocol,\n                    native: Some(inner),\n                    #[cfg(feature = \"web\")]\n                    web: None,\n                });\n            }\n\n            unimplemented!(\"Non web wasm32 clients are not supported yet\")\n        }\n    }\n}\n\nimpl<S: Send> FromRequest<S> for WebSocketOptions {\n    type Rejection = axum::response::Response;\n\n    fn from_request(\n        _req: Request,\n        _: &S,\n    ) -> impl Future<Output = Result<Self, Self::Rejection>> + Send {\n        #[cfg(not(feature = \"server\"))]\n        return async move { Err(StatusCode::NOT_IMPLEMENTED.into_response()) };\n\n        #[cfg(feature = \"server\")]\n        async move {\n            let ws = match axum::extract::ws::WebSocketUpgrade::from_request(_req, &()).await {\n                Ok(ws) => ws,\n                Err(rejection) => return Err(rejection.into_response()),\n            };\n\n            Ok(WebSocketOptions {\n                protocols: vec![],\n                automatic_reconnect: false,\n                upgrade: Some(ws),\n                on_failed_upgrade: None,\n            })\n        }\n    }\n}\n\n#[doc(hidden)]\npub struct UpgradingWebsocket {\n    protocol: Option<String>,\n\n    #[cfg(feature = \"web\")]\n    web: Option<gloo_net::websocket::futures::WebSocket>,\n\n    #[cfg(not(target_arch = \"wasm32\"))]\n    native: Option<native::SplitSocket>,\n}\n\nunsafe impl Send for UpgradingWebsocket {}\nunsafe impl Sync for UpgradingWebsocket {}\n\n#[cfg(feature = \"server\")]\npub struct TypedWebsocket<In, Out, E = JsonEncoding> {\n    _in: std::marker::PhantomData<fn() -> In>,\n    _out: std::marker::PhantomData<fn() -> Out>,\n    _enc: std::marker::PhantomData<fn() -> E>,\n\n    inner: axum::extract::ws::WebSocket,\n}\n\n#[cfg(feature = \"server\")]\nimpl<In: DeserializeOwned, Out: Serialize, E: Encoding> TypedWebsocket<In, Out, E> {\n    /// Receive an incoming message from the client.\n    pub async fn recv(&mut self) -> Result<In, WebsocketError> {\n        self.next()\n            .await\n            .unwrap_or(Err(WebsocketError::closed_away()))\n    }\n\n    /// Send an outgoing message.\n    pub async fn send(&mut self, msg: Out) -> Result<(), WebsocketError> {\n        SinkExt::send(self, msg).await\n    }\n\n    /// Receive another message.\n    pub async fn recv_raw(&mut self) -> Result<Message, WebsocketError> {\n        use axum::extract::ws::Message as AxumMessage;\n\n        let message = self\n            .inner\n            .next()\n            .await\n            .ok_or_else(WebsocketError::closed_away)?\n            .map_err(|_| WebsocketError::AlreadyClosed)?;\n\n        Ok(match message {\n            AxumMessage::Text(utf8_bytes) => Message::Text(utf8_bytes.to_string()),\n            AxumMessage::Binary(bytes) => Message::Binary(bytes),\n            AxumMessage::Ping(bytes) => Message::Ping(bytes),\n            AxumMessage::Pong(bytes) => Message::Pong(bytes),\n            AxumMessage::Close(close_frame) => Message::Close {\n                code: close_frame\n                    .clone()\n                    .map_or(CloseCode::Away, |cf| cf.code.into()),\n                reason: close_frame.map_or(\"Away\".to_string(), |cf| cf.reason.to_string()),\n            },\n        })\n    }\n\n    /// Send a message.\n    pub async fn send_raw(&mut self, msg: Message) -> Result<(), WebsocketError> {\n        let real = match msg {\n            Message::Text(text) => axum::extract::ws::Message::Text(text.into()),\n            Message::Binary(bytes) => axum::extract::ws::Message::Binary(bytes),\n            Message::Ping(bytes) => axum::extract::ws::Message::Ping(bytes),\n            Message::Pong(bytes) => axum::extract::ws::Message::Pong(bytes),\n            Message::Close { code, reason } => {\n                axum::extract::ws::Message::Close(Some(axum::extract::ws::CloseFrame {\n                    code: code.into(),\n                    reason: reason.into(),\n                }))\n            }\n        };\n\n        self.inner\n            .send(real)\n            .await\n            .map_err(|_err| WebsocketError::AlreadyClosed)\n    }\n\n    /// Return the selected WebSocket subprotocol, if one has been chosen.\n    pub fn protocol(&self) -> Option<&http::HeaderValue> {\n        self.inner.protocol()\n    }\n\n    /// Get a mutable reference to the underlying Axum WebSocket.\n    pub fn socket(&mut self) -> &mut axum::extract::ws::WebSocket {\n        &mut self.inner\n    }\n}\n\n#[cfg(feature = \"server\")]\nimpl<In: DeserializeOwned, Out: Serialize, E: Encoding> Stream for TypedWebsocket<In, Out, E> {\n    type Item = Result<In, WebsocketError>;\n\n    fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {\n        use axum::extract::ws::Message as AxumMessage;\n\n        loop {\n            match ready!(self.inner.poll_next_unpin(cx)) {\n                Some(Ok(msg)) => match msg {\n                    AxumMessage::Text(utf8_bytes) => {\n                        let e: In = E::decode(utf8_bytes.into())\n                            .ok_or_else(WebsocketError::deserialization)?;\n                        return Poll::Ready(Some(Ok(e)));\n                    }\n                    AxumMessage::Binary(bytes) => {\n                        let e: In = E::decode(bytes).ok_or_else(WebsocketError::deserialization)?;\n                        return Poll::Ready(Some(Ok(e)));\n                    }\n\n                    AxumMessage::Close(Some(close_frame)) => {\n                        return Poll::Ready(Some(Err(WebsocketError::ConnectionClosed {\n                            code: close_frame.code.into(),\n                            description: close_frame.reason.to_string(),\n                        })));\n                    }\n                    AxumMessage::Close(None) => {\n                        return Poll::Ready(Some(Err(WebsocketError::AlreadyClosed)));\n                    }\n\n                    AxumMessage::Ping(_bytes) => continue,\n                    AxumMessage::Pong(_bytes) => continue,\n                },\n                Some(Err(_)) => {\n                    return Poll::Ready(Some(Err(WebsocketError::closed_away())));\n                }\n                None => return Poll::Ready(None),\n            }\n        }\n    }\n}\n\n#[cfg(feature = \"server\")]\nimpl<In: DeserializeOwned, Out: Serialize, E: Encoding> Sink<Out> for TypedWebsocket<In, Out, E> {\n    type Error = WebsocketError;\n\n    fn poll_ready(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {\n        Pin::new(&mut self.inner)\n            .poll_ready(cx)\n            .map_err(|_| WebsocketError::AlreadyClosed)\n    }\n\n    fn start_send(mut self: Pin<&mut Self>, item: Out) -> Result<(), Self::Error> {\n        use axum::extract::ws::Message;\n\n        let to_bytes = E::to_bytes(&item).ok_or_else(|| {\n            WebsocketError::Serialization(anyhow::anyhow!(\"Failed to serialize message\").into())\n        })?;\n\n        Pin::new(&mut self.inner)\n            .start_send(Message::Binary(to_bytes))\n            .map_err(|_| WebsocketError::AlreadyClosed)\n    }\n\n    fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {\n        Pin::new(&mut self.inner)\n            .poll_flush(cx)\n            .map_err(|_| WebsocketError::AlreadyClosed)\n    }\n\n    fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {\n        Pin::new(&mut self.inner)\n            .poll_close(cx)\n            .map_err(|_| WebsocketError::AlreadyClosed)\n    }\n}\n\n#[derive(thiserror::Error, Debug)]\npub enum WebsocketError {\n    #[error(\"Connection closed\")]\n    ConnectionClosed {\n        code: CloseCode,\n        description: String,\n    },\n\n    #[error(\"WebSocket already closed\")]\n    AlreadyClosed,\n\n    #[error(\"WebSocket capacity reached\")]\n    Capacity,\n\n    #[error(\"An unexpected internal error occurred\")]\n    Unexpected,\n\n    #[error(\"WebSocket is not initialized on this platform\")]\n    Uninitialized,\n\n    #[cfg(not(target_arch = \"wasm32\"))]\n    #[error(\"websocket upgrade failed\")]\n    Handshake(#[from] native::HandshakeError),\n\n    #[error(\"reqwest error\")]\n    Reqwest(#[from] reqwest::Error),\n\n    #[cfg(not(target_arch = \"wasm32\"))]\n    #[error(\"tungstenite error\")]\n    Tungstenite(#[from] tungstenite::Error),\n\n    /// Error during serialization/deserialization.\n    #[error(\"error during serialization/deserialization\")]\n    Deserialization(Box<dyn std::error::Error + Send + Sync>),\n\n    /// Error during serialization/deserialization.\n    #[error(\"error during serialization/deserialization\")]\n    Serialization(Box<dyn std::error::Error + Send + Sync>),\n\n    /// Error during serialization/deserialization.\n    #[error(\"serde_json error\")]\n    Json(#[from] serde_json::Error),\n\n    /// Error during serialization/deserialization.\n    #[error(\"ciborium error\")]\n    Cbor(#[from] ciborium::de::Error<std::io::Error>),\n}\n\n#[cfg(feature = \"web\")]\nimpl From<gloo_net::websocket::WebSocketError> for WebsocketError {\n    fn from(value: gloo_net::websocket::WebSocketError) -> Self {\n        use gloo_net::websocket::WebSocketError;\n        match value {\n            WebSocketError::ConnectionError => WebsocketError::AlreadyClosed,\n            WebSocketError::ConnectionClose(close_event) => WebsocketError::ConnectionClosed {\n                code: close_event.code.into(),\n                description: close_event.reason,\n            },\n            WebSocketError::MessageSendError(_js_error) => WebsocketError::Unexpected,\n            _ => WebsocketError::Unexpected,\n        }\n    }\n}\n\nimpl WebsocketError {\n    pub fn closed_away() -> Self {\n        Self::ConnectionClosed {\n            code: CloseCode::Normal,\n            description: \"Connection closed normally\".into(),\n        }\n    }\n\n    pub fn deserialization() -> Self {\n        Self::Deserialization(anyhow::anyhow!(\"Failed to deserialize message\").into())\n    }\n\n    pub fn serialization() -> Self {\n        Self::Serialization(anyhow::anyhow!(\"Failed to serialize message\").into())\n    }\n}\n\n#[cfg(feature = \"web\")]\nstruct WebsysSocket {\n    sender: Mutex<SplitSink<WsWebsocket, WsMessage>>,\n    receiver: Mutex<SplitStream<WsWebsocket>>,\n}\n\n/// A `WebSocket` message, which can be a text string or binary data.\n#[derive(Clone, Debug)]\npub enum Message {\n    /// A text `WebSocket` message.\n    // note: we can't use `tungstenite::Utf8String` here, since we don't have tungstenite on wasm.\n    Text(String),\n\n    /// A binary `WebSocket` message.\n    Binary(Bytes),\n\n    /// A ping message with the specified payload.\n    ///\n    /// The payload here must have a length less than 125 bytes.\n    ///\n    /// # WASM\n    ///\n    /// This variant is ignored for WASM targets.\n    Ping(Bytes),\n\n    /// A pong message with the specified payload.\n    ///\n    /// The payload here must have a length less than 125 bytes.\n    ///\n    /// # WASM\n    ///\n    /// This variant is ignored for WASM targets.\n    Pong(Bytes),\n\n    /// A close message.\n    ///\n    /// Sending this will not close the connection, though the remote peer will likely close the connection after receiving this.\n    Close { code: CloseCode, reason: String },\n}\n\nimpl From<String> for Message {\n    #[inline]\n    fn from(value: String) -> Self {\n        Self::Text(value)\n    }\n}\n\nimpl From<&str> for Message {\n    #[inline]\n    fn from(value: &str) -> Self {\n        Self::from(value.to_owned())\n    }\n}\n\nimpl From<Bytes> for Message {\n    #[inline]\n    fn from(value: Bytes) -> Self {\n        Self::Binary(value)\n    }\n}\n\nimpl From<Vec<u8>> for Message {\n    #[inline]\n    fn from(value: Vec<u8>) -> Self {\n        Self::from(Bytes::from(value))\n    }\n}\n\nimpl From<&[u8]> for Message {\n    #[inline]\n    fn from(value: &[u8]) -> Self {\n        Self::from(Bytes::copy_from_slice(value))\n    }\n}\n\n/// Status code used to indicate why an endpoint is closing the `WebSocket`\n/// connection.[1]\n///\n/// [1]: https://datatracker.ietf.org/doc/html/rfc6455\n#[derive(Debug, Default, Eq, PartialEq, Clone, Copy)]\n#[non_exhaustive]\npub enum CloseCode {\n    /// Indicates a normal closure, meaning that the purpose for\n    /// which the connection was established has been fulfilled.\n    #[default]\n    Normal,\n\n    /// Indicates that an endpoint is \"going away\", such as a server\n    /// going down or a browser having navigated away from a page.\n    Away,\n\n    /// Indicates that an endpoint is terminating the connection due\n    /// to a protocol error.\n    Protocol,\n\n    /// Indicates that an endpoint is terminating the connection\n    /// because it has received a type of data it cannot accept (e.g., an\n    /// endpoint that understands only text data MAY send this if it\n    /// receives a binary message).\n    Unsupported,\n\n    /// Indicates that no status code was included in a closing frame. This\n    /// close code makes it possible to use a single method, `on_close` to\n    /// handle even cases where no close code was provided.\n    Status,\n\n    /// Indicates an abnormal closure. If the abnormal closure was due to an\n    /// error, this close code will not be used. Instead, the `on_error` method\n    /// of the handler will be called with the error. However, if the connection\n    /// is simply dropped, without an error, this close code will be sent to the\n    /// handler.\n    Abnormal,\n\n    /// Indicates that an endpoint is terminating the connection\n    /// because it has received data within a message that was not\n    /// consistent with the type of the message (e.g., non-UTF-8 \\[RFC3629\\]\n    /// data within a text message).\n    Invalid,\n\n    /// Indicates that an endpoint is terminating the connection\n    /// because it has received a message that violates its policy.  This\n    /// is a generic status code that can be returned when there is no\n    /// other more suitable status code (e.g., Unsupported or Size) or if there\n    /// is a need to hide specific details about the policy.\n    Policy,\n\n    /// Indicates that an endpoint is terminating the connection\n    /// because it has received a message that is too big for it to\n    /// process.\n    Size,\n\n    /// Indicates that an endpoint (client) is terminating the\n    /// connection because it has expected the server to negotiate one or\n    /// more extension, but the server didn't return them in the response\n    /// message of the `WebSocket` handshake.  The list of extensions that\n    /// are needed should be given as the reason for closing.\n    /// Note that this status code is not used by the server, because it\n    /// can fail the `WebSocket` handshake instead.\n    Extension,\n\n    /// Indicates that a server is terminating the connection because\n    /// it encountered an unexpected condition that prevented it from\n    /// fulfilling the request.\n    Error,\n\n    /// Indicates that the server is restarting. A client may choose to\n    /// reconnect, and if it does, it should use a randomized delay of 5-30\n    /// seconds between attempts.\n    Restart,\n\n    /// Indicates that the server is overloaded and the client should either\n    /// connect to a different IP (when multiple targets exist), or\n    /// reconnect to the same IP when a user has performed an action.\n    Again,\n\n    /// Indicates that the connection was closed due to a failure to perform a\n    /// TLS handshake (e.g., the server certificate can't be verified). This\n    /// is a reserved value and MUST NOT be set as a status code in a Close\n    /// control frame by an endpoint.\n    Tls,\n\n    /// Reserved status codes.\n    Reserved(u16),\n\n    /// Reserved for use by libraries, frameworks, and applications. These\n    /// status codes are registered directly with IANA. The interpretation of\n    /// these codes is undefined by the `WebSocket` protocol.\n    Iana(u16),\n\n    /// Reserved for private use. These can't be registered and can be used by\n    /// prior agreements between `WebSocket` applications. The interpretation of\n    /// these codes is undefined by the `WebSocket` protocol.\n    Library(u16),\n\n    /// Unused / invalid status codes.\n    Bad(u16),\n}\n\nimpl CloseCode {\n    /// Check if this `CloseCode` is allowed.\n    #[must_use]\n    pub const fn is_allowed(self) -> bool {\n        !matches!(\n            self,\n            Self::Bad(_) | Self::Reserved(_) | Self::Status | Self::Abnormal | Self::Tls\n        )\n    }\n}\n\nimpl std::fmt::Display for CloseCode {\n    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {\n        let code: u16 = (*self).into();\n        write!(f, \"{code}\")\n    }\n}\n\nimpl From<CloseCode> for u16 {\n    fn from(code: CloseCode) -> Self {\n        match code {\n            CloseCode::Normal => 1000,\n            CloseCode::Away => 1001,\n            CloseCode::Protocol => 1002,\n            CloseCode::Unsupported => 1003,\n            CloseCode::Status => 1005,\n            CloseCode::Abnormal => 1006,\n            CloseCode::Invalid => 1007,\n            CloseCode::Policy => 1008,\n            CloseCode::Size => 1009,\n            CloseCode::Extension => 1010,\n            CloseCode::Error => 1011,\n            CloseCode::Restart => 1012,\n            CloseCode::Again => 1013,\n            CloseCode::Tls => 1015,\n            CloseCode::Reserved(code)\n            | CloseCode::Iana(code)\n            | CloseCode::Library(code)\n            | CloseCode::Bad(code) => code,\n        }\n    }\n}\n\nimpl From<u16> for CloseCode {\n    fn from(code: u16) -> Self {\n        match code {\n            1000 => Self::Normal,\n            1001 => Self::Away,\n            1002 => Self::Protocol,\n            1003 => Self::Unsupported,\n            1005 => Self::Status,\n            1006 => Self::Abnormal,\n            1007 => Self::Invalid,\n            1008 => Self::Policy,\n            1009 => Self::Size,\n            1010 => Self::Extension,\n            1011 => Self::Error,\n            1012 => Self::Restart,\n            1013 => Self::Again,\n            1015 => Self::Tls,\n            1016..=2999 => Self::Reserved(code),\n            3000..=3999 => Self::Iana(code),\n            4000..=4999 => Self::Library(code),\n            _ => Self::Bad(code),\n        }\n    }\n}\n\n#[cfg(not(target_arch = \"wasm32\"))]\nmod native {\n    use std::borrow::Cow;\n\n    use crate::ClientRequest;\n\n    use super::{CloseCode, Message, WebsocketError};\n    use dioxus_fullstack_core::RequestError;\n    use reqwest::{\n        header::{HeaderName, HeaderValue},\n        Response, StatusCode, Version,\n    };\n    use tungstenite::protocol::WebSocketConfig;\n\n    pub(crate) struct SplitSocket {\n        pub sender: futures_util::lock::Mutex<\n            async_tungstenite::WebSocketSender<tokio_util::compat::Compat<reqwest::Upgraded>>,\n        >,\n\n        pub receiver: futures_util::lock::Mutex<\n            async_tungstenite::WebSocketReceiver<tokio_util::compat::Compat<reqwest::Upgraded>>,\n        >,\n    }\n\n    pub async fn send_request(\n        request: ClientRequest,\n        protocols: &[String],\n    ) -> Result<WebSocketResponse, WebsocketError> {\n        let request_builder = request.new_reqwest_request();\n        let (client, request_result) = request_builder.build_split();\n        let mut request = request_result?;\n\n        // change the scheme from wss? to https?\n        let url = request.url_mut();\n        match url.scheme() {\n            \"ws\" => {\n                url.set_scheme(\"http\")\n                    .expect(\"url should accept http scheme\");\n            }\n            \"wss\" => {\n                url.set_scheme(\"https\")\n                    .expect(\"url should accept https scheme\");\n            }\n            _ => {}\n        }\n\n        // prepare request\n        let version = request.version();\n        let nonce = match version {\n            Version::HTTP_10 | Version::HTTP_11 => {\n                // HTTP 1 requires us to set some headers.\n                let nonce_value = tungstenite::handshake::client::generate_key();\n                let headers = request.headers_mut();\n                headers.insert(\n                    reqwest::header::CONNECTION,\n                    HeaderValue::from_static(\"upgrade\"),\n                );\n                headers.insert(\n                    reqwest::header::UPGRADE,\n                    HeaderValue::from_static(\"websocket\"),\n                );\n                headers.insert(\n                    reqwest::header::SEC_WEBSOCKET_KEY,\n                    HeaderValue::from_str(&nonce_value).expect(\"nonce is a invalid header value\"),\n                );\n                headers.insert(\n                    reqwest::header::SEC_WEBSOCKET_VERSION,\n                    HeaderValue::from_static(\"13\"),\n                );\n                if !protocols.is_empty() {\n                    headers.insert(\n                        reqwest::header::SEC_WEBSOCKET_PROTOCOL,\n                        HeaderValue::from_str(&protocols.join(\", \"))\n                            .expect(\"protocols is an invalid header value\"),\n                    );\n                }\n\n                Some(nonce_value)\n            }\n            Version::HTTP_2 => {\n                // TODO: Implement websocket upgrade for HTTP 2.\n                return Err(HandshakeError::UnsupportedHttpVersion(version).into());\n            }\n            _ => {\n                return Err(HandshakeError::UnsupportedHttpVersion(version).into());\n            }\n        };\n\n        // execute request\n        let response = client.execute(request).await?;\n\n        Ok(WebSocketResponse {\n            response,\n            version,\n            nonce,\n        })\n    }\n\n    pub type WebSocketStream =\n        async_tungstenite::WebSocketStream<tokio_util::compat::Compat<reqwest::Upgraded>>;\n\n    /// Error during `Websocket` handshake.\n    #[derive(Debug, thiserror::Error, Clone)]\n    pub enum HandshakeError {\n        #[error(\"unsupported http version: {0:?}\")]\n        UnsupportedHttpVersion(Version),\n\n        #[error(\n            \"the server responded with a different http version. this could be the case because reqwest silently upgraded the connection to http2. see: https://github.com/jgraef/reqwest-websocket/issues/2\"\n        )]\n        ServerRespondedWithDifferentVersion,\n\n        #[error(\"missing header {header}\")]\n        MissingHeader { header: HeaderName },\n\n        #[error(\"unexpected value for header {header}: expected {expected}, but got {got:?}.\")]\n        UnexpectedHeaderValue {\n            header: HeaderName,\n            got: HeaderValue,\n            expected: Cow<'static, str>,\n        },\n\n        #[error(\"expected the server to select a protocol.\")]\n        ExpectedAProtocol,\n\n        #[error(\"unexpected protocol: {got}\")]\n        UnexpectedProtocol { got: String },\n\n        #[error(\"unexpected status code: {0}\")]\n        UnexpectedStatusCode(StatusCode),\n    }\n\n    pub struct WebSocketResponse {\n        pub response: Response,\n        pub version: Version,\n        pub nonce: Option<String>,\n    }\n\n    impl WebSocketResponse {\n        pub async fn into_stream_and_protocol(\n            self,\n            protocols: Vec<String>,\n            web_socket_config: Option<WebSocketConfig>,\n        ) -> Result<(SplitSocket, Option<String>), WebsocketError> {\n            let headers = self.response.headers();\n\n            if self.response.version() != self.version {\n                return Err(HandshakeError::ServerRespondedWithDifferentVersion.into());\n            }\n\n            if self.response.status() != reqwest::StatusCode::SWITCHING_PROTOCOLS {\n                tracing::debug!(status_code = %self.response.status(), \"server responded with unexpected status code\");\n                return Err(HandshakeError::UnexpectedStatusCode(self.response.status()).into());\n            }\n\n            if let Some(header) = headers.get(reqwest::header::CONNECTION) {\n                if !header\n                    .to_str()\n                    .is_ok_and(|s| s.eq_ignore_ascii_case(\"upgrade\"))\n                {\n                    tracing::debug!(\"server responded with invalid Connection header: {header:?}\");\n                    return Err(HandshakeError::UnexpectedHeaderValue {\n                        header: reqwest::header::CONNECTION,\n                        got: header.clone(),\n                        expected: \"upgrade\".into(),\n                    }\n                    .into());\n                }\n            } else {\n                tracing::debug!(\"missing Connection header\");\n                return Err(HandshakeError::MissingHeader {\n                    header: reqwest::header::CONNECTION,\n                }\n                .into());\n            }\n\n            if let Some(header) = headers.get(reqwest::header::UPGRADE) {\n                if !header\n                    .to_str()\n                    .is_ok_and(|s| s.eq_ignore_ascii_case(\"websocket\"))\n                {\n                    tracing::debug!(\"server responded with invalid Upgrade header: {header:?}\");\n                    return Err(HandshakeError::UnexpectedHeaderValue {\n                        header: reqwest::header::UPGRADE,\n                        got: header.clone(),\n                        expected: \"websocket\".into(),\n                    }\n                    .into());\n                }\n            } else {\n                tracing::debug!(\"missing Upgrade header\");\n                return Err(HandshakeError::MissingHeader {\n                    header: reqwest::header::UPGRADE,\n                }\n                .into());\n            }\n\n            if let Some(nonce) = &self.nonce {\n                let expected_nonce = tungstenite::handshake::derive_accept_key(nonce.as_bytes());\n\n                if let Some(header) = headers.get(reqwest::header::SEC_WEBSOCKET_ACCEPT) {\n                    if !header.to_str().is_ok_and(|s| s == expected_nonce) {\n                        tracing::debug!(\n                            \"server responded with invalid Sec-Websocket-Accept header: {header:?}\"\n                        );\n                        return Err(HandshakeError::UnexpectedHeaderValue {\n                            header: reqwest::header::SEC_WEBSOCKET_ACCEPT,\n                            got: header.clone(),\n                            expected: expected_nonce.into(),\n                        }\n                        .into());\n                    }\n                } else {\n                    tracing::debug!(\"missing Sec-Websocket-Accept header\");\n                    return Err(HandshakeError::MissingHeader {\n                        header: reqwest::header::SEC_WEBSOCKET_ACCEPT,\n                    }\n                    .into());\n                }\n            }\n\n            let protocol = headers\n                .get(reqwest::header::SEC_WEBSOCKET_PROTOCOL)\n                .and_then(|v| v.to_str().ok())\n                .map(ToOwned::to_owned);\n\n            match (protocols.is_empty(), &protocol) {\n                (true, None) => {\n                    // we didn't request any protocols, so we don't expect one\n                    // in return\n                }\n                (false, None) => {\n                    // server didn't reply with a protocol\n                    return Err(HandshakeError::ExpectedAProtocol.into());\n                }\n                (false, Some(protocol)) => {\n                    if !protocols.contains(protocol) {\n                        // the responded protocol is none which we requested\n                        return Err(HandshakeError::UnexpectedProtocol {\n                            got: protocol.clone(),\n                        }\n                        .into());\n                    }\n                }\n                (true, Some(protocol)) => {\n                    // we didn't request any protocols but got one anyway\n                    return Err(HandshakeError::UnexpectedProtocol {\n                        got: protocol.clone(),\n                    }\n                    .into());\n                }\n            }\n\n            use tokio_util::compat::TokioAsyncReadCompatExt;\n\n            let inner = WebSocketStream::from_raw_socket(\n                self.response.upgrade().await?.compat(),\n                tungstenite::protocol::Role::Client,\n                web_socket_config,\n            )\n            .await;\n\n            let split: (\n                async_tungstenite::WebSocketSender<tokio_util::compat::Compat<reqwest::Upgraded>>,\n                async_tungstenite::WebSocketReceiver<tokio_util::compat::Compat<reqwest::Upgraded>>,\n            ) = inner.split();\n\n            let split_socket = SplitSocket {\n                sender: futures_util::lock::Mutex::new(split.0),\n                receiver: futures_util::lock::Mutex::new(split.1),\n            };\n\n            Ok((split_socket, protocol))\n        }\n    }\n\n    #[derive(Debug, thiserror::Error)]\n    #[error(\"could not convert message\")]\n    pub struct FromTungsteniteMessageError {\n        pub original: tungstenite::Message,\n    }\n\n    impl TryFrom<tungstenite::Message> for Message {\n        type Error = FromTungsteniteMessageError;\n\n        fn try_from(value: tungstenite::Message) -> Result<Self, Self::Error> {\n            match value {\n                tungstenite::Message::Text(text) => Ok(Self::Text(text.as_str().to_owned())),\n                tungstenite::Message::Binary(data) => Ok(Self::Binary(data)),\n                tungstenite::Message::Ping(data) => Ok(Self::Ping(data)),\n                tungstenite::Message::Pong(data) => Ok(Self::Pong(data)),\n                tungstenite::Message::Close(Some(tungstenite::protocol::CloseFrame {\n                    code,\n                    reason,\n                })) => Ok(Self::Close {\n                    code: code.into(),\n                    reason: reason.as_str().to_owned(),\n                }),\n                tungstenite::Message::Close(None) => Ok(Self::Close {\n                    code: CloseCode::default(),\n                    reason: \"\".to_owned(),\n                }),\n                tungstenite::Message::Frame(_) => {\n                    Err(FromTungsteniteMessageError { original: value })\n                }\n            }\n        }\n    }\n\n    impl From<Message> for tungstenite::Message {\n        fn from(value: Message) -> Self {\n            match value {\n                Message::Text(text) => Self::Text(tungstenite::Utf8Bytes::from(text)),\n                Message::Binary(data) => Self::Binary(data),\n                Message::Ping(data) => Self::Ping(data),\n                Message::Pong(data) => Self::Pong(data),\n                Message::Close { code, reason } => {\n                    Self::Close(Some(tungstenite::protocol::CloseFrame {\n                        code: code.into(),\n                        reason: reason.into(),\n                    }))\n                }\n            }\n        }\n    }\n\n    impl From<tungstenite::protocol::frame::coding::CloseCode> for CloseCode {\n        fn from(value: tungstenite::protocol::frame::coding::CloseCode) -> Self {\n            u16::from(value).into()\n        }\n    }\n\n    impl From<CloseCode> for tungstenite::protocol::frame::coding::CloseCode {\n        fn from(value: CloseCode) -> Self {\n            u16::from(value).into()\n        }\n    }\n\n    impl From<HandshakeError> for RequestError {\n        fn from(value: HandshakeError) -> Self {\n            let string = value.to_string();\n            match value {\n                HandshakeError::UnexpectedStatusCode(status) => {\n                    Self::Status(string, status.as_u16())\n                }\n                HandshakeError::UnsupportedHttpVersion(_)\n                | HandshakeError::MissingHeader { .. }\n                | HandshakeError::UnexpectedHeaderValue { .. }\n                | HandshakeError::ExpectedAProtocol\n                | HandshakeError::UnexpectedProtocol { .. }\n                | HandshakeError::ServerRespondedWithDifferentVersion => Self::Connect(string),\n            }\n        }\n    }\n\n    trait IntoRequestError {\n        fn into_request_error(self) -> RequestError;\n    }\n\n    impl IntoRequestError for reqwest::Error {\n        fn into_request_error(self) -> RequestError {\n            const DEFAULT_STATUS_CODE: u16 = 0;\n            let string = self.to_string();\n            if self.is_builder() {\n                RequestError::Builder(string)\n            } else if self.is_redirect() {\n                RequestError::Redirect(string)\n            } else if self.is_status() {\n                RequestError::Status(\n                    string,\n                    self.status()\n                        .as_ref()\n                        .map(StatusCode::as_u16)\n                        .unwrap_or(DEFAULT_STATUS_CODE),\n                )\n            } else if self.is_body() {\n                RequestError::Body(string)\n            } else if self.is_decode() {\n                RequestError::Decode(string)\n            } else if self.is_upgrade() {\n                RequestError::Connect(string)\n            } else {\n                RequestError::Request(string)\n            }\n        }\n    }\n\n    impl IntoRequestError for tungstenite::Error {\n        fn into_request_error(self) -> RequestError {\n            match self {\n                tungstenite::Error::ConnectionClosed => {\n                    RequestError::Connect(\"websocket connection closed\".to_owned())\n                }\n                tungstenite::Error::AlreadyClosed => {\n                    RequestError::Connect(\"websocket already closed\".to_owned())\n                }\n                tungstenite::Error::Io(error) => RequestError::Connect(error.to_string()),\n                tungstenite::Error::Tls(error) => RequestError::Connect(error.to_string()),\n                tungstenite::Error::Capacity(error) => RequestError::Body(error.to_string()),\n                tungstenite::Error::Protocol(error) => RequestError::Request(error.to_string()),\n                tungstenite::Error::WriteBufferFull(message) => {\n                    RequestError::Body(message.to_string())\n                }\n                tungstenite::Error::Utf8(error) => RequestError::Decode(error),\n                tungstenite::Error::AttackAttempt => {\n                    RequestError::Request(\"Tungstenite attack attempt detected\".to_owned())\n                }\n                tungstenite::Error::Url(error) => RequestError::Builder(error.to_string()),\n                tungstenite::Error::Http(response) => {\n                    let status_code = response.status();\n                    RequestError::Status(format!(\"HTTP error: {status_code}\"), status_code.as_u16())\n                }\n                tungstenite::Error::HttpFormat(error) => RequestError::Builder(error.to_string()),\n            }\n        }\n    }\n\n    impl From<WebsocketError> for RequestError {\n        fn from(value: WebsocketError) -> Self {\n            match value {\n                WebsocketError::ConnectionClosed { code, description } => {\n                    Self::Connect(format!(\"connection closed ({code}): {description}\"))\n                }\n                WebsocketError::AlreadyClosed => Self::Connect(value.to_string()),\n                WebsocketError::Capacity => Self::Body(value.to_string()),\n                WebsocketError::Unexpected => Self::Request(value.to_string()),\n                WebsocketError::Uninitialized => Self::Builder(value.to_string()),\n                WebsocketError::Handshake(error) => error.into(),\n                WebsocketError::Reqwest(error) => error.into_request_error(),\n                WebsocketError::Tungstenite(error) => error.into_request_error(),\n                WebsocketError::Serialization(error) => Self::Serialization(error.to_string()),\n                WebsocketError::Deserialization(error) => Self::Decode(error.to_string()),\n                WebsocketError::Json(error) => Self::Decode(error.to_string()),\n                WebsocketError::Cbor(error) => Self::Decode(error.to_string()),\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "packages/fullstack/src/request.rs",
    "content": "use dioxus_fullstack_core::{RequestError, ServerFnError};\n#[cfg(feature = \"server\")]\nuse headers::Header;\nuse http::response::Parts;\nuse std::{future::Future, pin::Pin};\n\nuse crate::{ClientRequest, ClientResponse, ErrorPayload};\n\n/// The `IntoRequest` trait allows types to be used as the body of a request to a HTTP endpoint or server function.\n///\n/// `IntoRequest` allows for types handle the calling of `ClientRequest::send` where the result is then\n/// passed to `FromResponse` to decode the response.\n///\n/// You can think of the `IntoRequest` and `FromResponse` traits are \"inverse\" traits of the axum\n/// `FromRequest` and `IntoResponse` traits. Just like a type can be decoded from a request via `FromRequest`,\n/// a type can be encoded into a request via `IntoRequest`.\n///\n/// ## Generic State\n///\n/// `IntoRequest` is generic over the response type `R` which defaults to `ClientResponse`. The default\n/// `ClientResponse` is the base response type that internally wraps `reqwest::Response`.\n///\n/// However, some responses might need state from the initial request to properly decode the response.\n/// Most state can be extended via the `.extension()` method on `ClientRequest`. In some cases, like\n/// websockets, the response needs to retain an initial connection from the request. Here, you can use\n///  the `R` generic to specify a concrete response type. The resulting type that implements `FromResponse`\n/// must also be generic over the same `R` type.\npub trait IntoRequest<R = ClientResponse>: Sized {\n    fn into_request(\n        self,\n        req: ClientRequest,\n    ) -> impl Future<Output = Result<R, RequestError>> + 'static;\n}\n\nimpl<A, R> IntoRequest<R> for (A,)\nwhere\n    A: IntoRequest<R> + 'static + Send,\n{\n    fn into_request(\n        self,\n        req: ClientRequest,\n    ) -> impl Future<Output = Result<R, RequestError>> + 'static {\n        A::into_request(self.0, req)\n    }\n}\n\npub trait FromResponse<R = ClientResponse>: Sized {\n    fn from_response(res: R) -> impl Future<Output = Result<Self, ServerFnError>>;\n}\n\nimpl<A> FromResponse for A\nwhere\n    A: FromResponseParts,\n{\n    fn from_response(res: ClientResponse) -> impl Future<Output = Result<Self, ServerFnError>> {\n        async move {\n            let status = res.status();\n\n            if status.is_success() {\n                let (parts, _body) = res.into_parts();\n                let mut parts = parts;\n                A::from_response_parts(&mut parts)\n            } else {\n                let ErrorPayload::<serde_json::Value> {\n                    message,\n                    code,\n                    data,\n                } = res.json().await?;\n                Err(ServerFnError::ServerError {\n                    message,\n                    code,\n                    details: data,\n                })\n            }\n        }\n    }\n}\n\nimpl<A, B> FromResponse for (A, B)\nwhere\n    A: FromResponseParts,\n    B: FromResponse,\n{\n    fn from_response(res: ClientResponse) -> impl Future<Output = Result<Self, ServerFnError>> {\n        async move {\n            let mut parts = res.make_parts();\n            let a = A::from_response_parts(&mut parts)?;\n            let b = B::from_response(res).await?;\n            Ok((a, b))\n        }\n    }\n}\n\nimpl<A, B, C> FromResponse for (A, B, C)\nwhere\n    A: FromResponseParts,\n    B: FromResponseParts,\n    C: FromResponse,\n{\n    fn from_response(res: ClientResponse) -> impl Future<Output = Result<Self, ServerFnError>> {\n        async move {\n            let mut parts = res.make_parts();\n            let a = A::from_response_parts(&mut parts)?;\n            let b = B::from_response_parts(&mut parts)?;\n            let c = C::from_response(res).await?;\n            Ok((a, b, c))\n        }\n    }\n}\n\npub trait FromResponseParts\nwhere\n    Self: Sized,\n{\n    fn from_response_parts(parts: &mut Parts) -> Result<Self, ServerFnError>;\n}\n\n#[cfg(feature = \"server\")]\nimpl<T: Header> FromResponseParts for axum_extra::TypedHeader<T> {\n    fn from_response_parts(parts: &mut Parts) -> Result<Self, ServerFnError> {\n        use headers::HeaderMapExt;\n\n        let t = parts\n            .headers\n            .typed_get::<T>()\n            .ok_or_else(|| ServerFnError::Serialization(\"Invalid header value\".into()))?;\n\n        Ok(axum_extra::TypedHeader(t))\n    }\n}\n\n/*\ntodo: make the serverfns return ServerFnRequest which lets us control the future better\n*/\n#[pin_project::pin_project]\n#[must_use = \"Requests do nothing unless you `.await` them\"]\npub struct ServerFnRequest<Output> {\n    _phantom: std::marker::PhantomData<Output>,\n    #[pin]\n    fut: Pin<Box<dyn Future<Output = Output> + Send>>,\n}\n\nimpl<O> ServerFnRequest<O> {\n    pub fn new(res: impl Future<Output = O> + Send + 'static) -> Self {\n        ServerFnRequest {\n            _phantom: std::marker::PhantomData,\n            fut: Box::pin(res),\n        }\n    }\n}\n\nimpl<T, E> std::future::Future for ServerFnRequest<Result<T, E>> {\n    type Output = Result<T, E>;\n\n    fn poll(\n        self: std::pin::Pin<&mut Self>,\n        cx: &mut std::task::Context<'_>,\n    ) -> std::task::Poll<Self::Output> {\n        self.project().fut.poll(cx)\n    }\n}\n\n#[doc(hidden)]\n#[diagnostic::on_unimplemented(\n    message = \"The return type of a server function must be `Result<T, E>`\",\n    note = \"`T` is either `impl IntoResponse` *or* `impl Serialize`\",\n    note = \"`E` is either `From<ServerFnError> + Serialize`, `dioxus::CapturedError` or `StatusCode`.\"\n)]\npub trait AssertIsResult {}\nimpl<T, E> AssertIsResult for Result<T, E> {}\n\n#[doc(hidden)]\npub fn assert_is_result<T: AssertIsResult>() {}\n\n#[diagnostic::on_unimplemented(message = r#\"❌ Invalid Arguments to ServerFn ❌\n\nThe arguments to the server function must be either:\n\n- a single `impl FromRequest + IntoRequest` argument\n- or multiple `DeserializeOwned` arguments.\n\nDid you forget to implement `IntoRequest` or `Deserialize` for one of the arguments?\n\n`IntoRequest` is a trait that allows payloads to be sent to the server function.\n\n> See https://dioxuslabs.com/learn/0.7/essentials/fullstack/server_functions for more details.\n\n\"#)]\npub trait AssertCanEncode {}\n\npub struct CantEncode;\n\npub struct EncodeIsVerified;\nimpl AssertCanEncode for EncodeIsVerified {}\n\n#[diagnostic::on_unimplemented(message = r#\"❌ Invalid return type from ServerFn ❌\n\nThe arguments to the server function must be either:\n\n- a single `impl FromResponse` return type\n- a single `impl Serialize + DeserializedOwned` return type\n\nDid you forget to implement `FromResponse` or `DeserializeOwned` for one of the arguments?\n\n`FromResponse` is a trait that allows payloads to be decoded from the server function response.\n\n> See https://dioxuslabs.com/learn/0.7/essentials/fullstack/server_functions for more details.\n\n\"#)]\npub trait AssertCanDecode {}\npub struct CantDecode;\npub struct DecodeIsVerified;\nimpl AssertCanDecode for DecodeIsVerified {}\n\n#[doc(hidden)]\npub fn assert_can_encode(_t: impl AssertCanEncode) {}\n\n#[doc(hidden)]\npub fn assert_can_decode(_t: impl AssertCanDecode) {}\n\n#[cfg(test)]\nmod test {\n    use http::Extensions;\n\n    use super::*;\n\n    #[derive(Debug)]\n    struct TestFromResponse;\n\n    impl FromResponseParts for TestFromResponse {\n        fn from_response_parts(_parts: &mut Parts) -> Result<Self, ServerFnError> {\n            Ok(Self)\n        }\n    }\n\n    fn build_response(status: u16, body: String) -> ClientResponse {\n        let http_response = http::Response::builder()\n            .status(status)\n            .body(body.into_bytes())\n            .unwrap();\n        let reqwest_response = reqwest::Response::from(http_response);\n        ClientResponse {\n            response: Box::new(reqwest_response),\n            extensions: Extensions::new(),\n        }\n    }\n\n    #[test]\n    fn fromresponseparts_path_decodes_ok_on_2xx() {\n        futures::executor::block_on(async {\n            let response = build_response(200, \"\".to_string());\n            let result = TestFromResponse::from_response(response).await;\n            assert!(\n                result.is_ok(),\n                \"expected Ok(..) for HTTP 200 success case, got: {:?}\",\n                result\n            );\n        });\n    }\n\n    #[test]\n    fn fromresponseparts_falls_back_to_request_error_on_unparsable_error_body() {\n        futures::executor::block_on(async {\n            let response = build_response(400, \"\".to_string());\n            let result = TestFromResponse::from_response(response).await;\n            assert!(result.is_err(), \"expected Err(..) for HTTP 400 failed case\");\n            let error = result.unwrap_err();\n            assert!(matches!(\n                error,\n                ServerFnError::Request(RequestError::Decode(_))\n            ));\n        });\n    }\n\n    #[test]\n    fn fromresponseparts_parses_error_payload_on_http_error() {\n        futures::executor::block_on(async {\n            let body = r#\"{\n                \"message\": \"qwerty\",\n                \"code\": 400\n            }\"#;\n            let response = build_response(400, body.to_string());\n            let result = TestFromResponse::from_response(response).await;\n            assert!(result.is_err(), \"expected Err(..) for HTTP 400 failed case\");\n            let error = result.unwrap_err();\n            assert_eq!(\n                error,\n                ServerFnError::ServerError {\n                    message: \"qwerty\".to_string(),\n                    code: 400,\n                    details: None\n                }\n            );\n        });\n    }\n}\n"
  },
  {
    "path": "packages/fullstack/src/spawn.rs",
    "content": "use std::future::Future;\n\n/// Spawn a task in the background. If wasm is enabled, this will use the single threaded tokio runtime\npub(crate) fn spawn_platform<Fut>(\n    f: impl FnOnce() -> Fut + Send + 'static,\n) -> tokio::task::JoinHandle<Fut::Output>\nwhere\n    Fut: Future + 'static,\n    Fut::Output: Send + 'static,\n{\n    #[cfg(not(target_arch = \"wasm32\"))]\n    {\n        use tokio_util::task::LocalPoolHandle;\n        static TASK_POOL: std::sync::OnceLock<LocalPoolHandle> = std::sync::OnceLock::new();\n\n        let pool = TASK_POOL.get_or_init(|| {\n            LocalPoolHandle::new(\n                std::thread::available_parallelism()\n                    .map(usize::from)\n                    .unwrap_or(1),\n            )\n        });\n\n        pool.spawn_pinned(f)\n    }\n\n    #[cfg(target_arch = \"wasm32\")]\n    {\n        tokio::task::spawn_local(f())\n    }\n}\n"
  },
  {
    "path": "packages/fullstack/tests/compile-test.rs",
    "content": "#![allow(clippy::manual_async_fn)]\n#![allow(unused_variables)]\n\nuse anyhow::Result;\nuse axum::extract::FromRequest;\nuse axum::response::IntoResponse;\nuse axum::{response::Html, Json};\nuse bytes::Bytes;\nuse dioxus::prelude::*;\nuse dioxus_fullstack::{get, FileStream, ServerFnError, Text, TextStream, Websocket};\nuse futures::StreamExt;\nuse http::HeaderMap;\nuse http::StatusCode;\nuse http_body_util::BodyExt;\nuse serde::{Deserialize, Serialize};\nuse std::future::Future;\n\nfn main() {}\n\nmod simple_extractors {\n    use super::*;\n\n    /// We can extract the state and return anything thats IntoResponse\n    #[get(\"/home\")]\n    async fn one() -> Result<String> {\n        Ok(\"hello home\".to_string())\n    }\n\n    /// We can extract the path arg and return anything thats IntoResponse\n    #[get(\"/home/{id}\")]\n    async fn two(id: String) -> Result<String> {\n        Ok(format!(\"hello home {}\", id))\n    }\n\n    /// We can do basically nothing\n    #[get(\"/\")]\n    async fn three() -> Result<()> {\n        Ok(())\n    }\n\n    /// We can do basically nothing, with args\n    #[get(\"/{one}/{two}?a&b&c\")]\n    async fn four(one: String, two: String, a: String, b: String, c: String) -> Result<()> {\n        Ok(())\n    }\n\n    /// We can return anything that implements IntoResponse\n    #[get(\"/hello\")]\n    async fn five() -> Result<Html<String>> {\n        Ok(Html(\"<h1>Hello!</h1>\".to_string()))\n    }\n\n    /// We can return anything that implements IntoResponse\n    #[get(\"/hello\")]\n    async fn six() -> Result<Json<String>> {\n        Ok(Json(\"Hello!\".to_string()))\n    }\n\n    /// We can return our own custom `Text<T>` type for sending plain text\n    #[get(\"/hello\")]\n    async fn six_2() -> Result<Text<String>> {\n        Ok(Text(\"Hello!\".to_string()))\n    }\n\n    /// We can return our own custom TextStream type for sending plain text streams\n    #[get(\"/hello\")]\n    async fn six_3() -> Result<TextStream> {\n        Ok(TextStream::new(futures::stream::iter(vec![\n            \"Hello 1\".to_string(),\n            \"Hello 2\".to_string(),\n            \"Hello 3\".to_string(),\n        ])))\n    }\n\n    /// We can return a Result with anything that implements IntoResponse\n    #[get(\"/hello\")]\n    async fn seven() -> Result<Bytes> {\n        Ok(Bytes::from_static(b\"Hello!\"))\n    }\n\n    /// We can return a Result with anything that implements IntoResponse\n    #[get(\"/hello\")]\n    async fn eight() -> Result<Bytes, StatusCode> {\n        Ok(Bytes::from_static(b\"Hello!\"))\n    }\n\n    /// We can use the anyhow error type\n    #[get(\"/hello\")]\n    async fn nine() -> Result<Bytes> {\n        Ok(Bytes::from_static(b\"Hello!\"))\n    }\n\n    /// We can use the ServerFnError error type\n    #[get(\"/hello\")]\n    async fn ten() -> Result<Bytes, ServerFnError> {\n        Ok(Bytes::from_static(b\"Hello!\"))\n    }\n\n    /// We can use the ServerFnError error type\n    #[get(\"/hello\")]\n    async fn elevent() -> Result<Bytes> {\n        Ok(Bytes::from_static(b\"Hello!\"))\n    }\n\n    /// We can use multiple args that are Deserialize\n    #[get(\"/hello\")]\n    async fn twelve(a: i32, b: i32, c: i32) -> Result<Bytes> {\n        Ok(format!(\"Hello! {} {} {}\", a, b, c).into())\n    }\n\n    // How should we handle generics? Doesn't make a lot of sense with distributed registration?\n    // I think we should just not support them for now. Reworking it will be a big change though.\n    //\n    // /// We can use generics\n    // #[get(\"/hello\")]\n    // async fn thirteen<S: Serialize + DeserializeOwned>(a: S) -> Result<Bytes> {\n    //     Ok(format!(\"Hello! {}\", serde_json::to_string(&a)?).into())\n    // }\n\n    // /// We can use impl-style generics\n    // #[get(\"/hello\")]\n    // async fn fourteen(a: impl Serialize + DeserializeOwned) -> Result<Bytes> {\n    //     Ok(format!(\"Hello! {}\", serde_json::to_string(&a)?).into())\n    // }\n}\n\nmod custom_serialize {\n    use super::*;\n\n    #[derive(Serialize, Deserialize)]\n    struct YourObject {\n        id: i32,\n        amount: Option<i32>,\n        offset: Option<i32>,\n    }\n\n    /// Directly return the object, and it will be serialized to JSON\n    #[get(\"/item/{id}?amount&offset\")]\n    async fn get_item1(\n        id: i32,\n        amount: Option<i32>,\n        offset: Option<i32>,\n    ) -> Result<Json<YourObject>> {\n        Ok(Json(YourObject { id, amount, offset }))\n    }\n\n    #[get(\"/item/{id}?amount&offset\")]\n    async fn get_item2(\n        id: i32,\n        amount: Option<i32>,\n        offset: Option<i32>,\n    ) -> Result<Json<YourObject>> {\n        Ok(Json(YourObject { id, amount, offset }))\n    }\n\n    #[get(\"/item/{id}?amount&offset\")]\n    async fn get_item3(id: i32, amount: Option<i32>, offset: Option<i32>) -> Result<YourObject> {\n        Ok(YourObject { id, amount, offset })\n    }\n\n    #[get(\"/item/{id}?amount&offset\")]\n    async fn get_item4(\n        id: i32,\n        amount: Option<i32>,\n        offset: Option<i32>,\n    ) -> Result<YourObject, StatusCode> {\n        Ok(YourObject { id, amount, offset })\n    }\n}\n\nmod custom_types {\n    use axum::response::Response;\n    // use axum_core::response::Response;\n    use dioxus_fullstack::{\n        ClientRequest, ClientResponse, FromResponse, IntoRequest, RequestError, WebSocketOptions,\n    };\n\n    use super::*;\n\n    /// We can extract the path arg and return anything thats IntoResponse\n    #[get(\"/upload/image/\")]\n    async fn streaming_file(body: FileStream) -> Result<Json<i32>> {\n        unimplemented!()\n    }\n\n    /// We can extract the path arg and return anything thats IntoResponse\n    #[get(\"/upload/image/?name&size&ftype\")]\n    async fn streaming_file_args(\n        name: String,\n        size: usize,\n        ftype: String,\n        body: FileStream,\n    ) -> Result<Json<i32>> {\n        unimplemented!()\n    }\n\n    #[get(\"/\")]\n    async fn ws_endpoint(options: WebSocketOptions) -> Result<Websocket<String, String>> {\n        unimplemented!()\n    }\n\n    struct MyCustomPayload {}\n    impl FromResponse for MyCustomPayload {\n        fn from_response(res: ClientResponse) -> impl Future<Output = Result<Self, ServerFnError>> {\n            async move { Ok(MyCustomPayload {}) }\n        }\n    }\n    impl IntoResponse for MyCustomPayload {\n        fn into_response(self) -> Response {\n            unimplemented!()\n        }\n    }\n    impl<T> FromRequest<T> for MyCustomPayload {\n        type Rejection = ServerFnError;\n        #[allow(clippy::manual_async_fn)]\n        fn from_request(\n            _req: axum::extract::Request,\n            _state: &T,\n        ) -> impl Future<Output = Result<Self, Self::Rejection>> + Send {\n            async move { Ok(MyCustomPayload {}) }\n        }\n    }\n    impl IntoRequest for MyCustomPayload {\n        fn into_request(\n            self,\n            request_builder: ClientRequest,\n        ) -> impl Future<Output = Result<ClientResponse, RequestError>> + 'static {\n            async move { unimplemented!() }\n        }\n    }\n\n    #[get(\"/myendpoint\")]\n    async fn my_custom_handler1(payload: MyCustomPayload) -> Result<MyCustomPayload> {\n        Ok(payload)\n    }\n\n    #[get(\"/myendpoint2\")]\n    async fn my_custom_handler2(payload: MyCustomPayload) -> Result<MyCustomPayload, StatusCode> {\n        Ok(payload)\n    }\n}\n\nmod overlap {\n    use super::*;\n\n    #[derive(Serialize, Deserialize)]\n    struct MyCustomPayload {}\n    impl IntoResponse for MyCustomPayload {\n        fn into_response(self) -> axum::response::Response {\n            unimplemented!()\n        }\n    }\n    impl<T> FromRequest<T> for MyCustomPayload {\n        type Rejection = ServerFnError;\n        #[allow(clippy::manual_async_fn)]\n        fn from_request(\n            _req: axum::extract::Request,\n            _state: &T,\n        ) -> impl Future<Output = Result<Self, Self::Rejection>> + Send {\n            async move { Ok(MyCustomPayload {}) }\n        }\n    }\n\n    /// When we have overlapping serialize + IntoResponse impls, the autoref logic will only pick Serialize\n    /// if IntoResponse is not available. Otherwise, IntoResponse is preferred.\n    #[get(\"/myendpoint\")]\n    async fn my_custom_handler3(payload: MyCustomPayload) -> Result<MyCustomPayload, StatusCode> {\n        Ok(payload)\n    }\n\n    /// Same, but with the anyhow::Error path\n    #[get(\"/myendpoint\")]\n    async fn my_custom_handler4(payload: MyCustomPayload) -> Result<MyCustomPayload> {\n        Ok(payload)\n    }\n}\n\nmod http_ext {\n    use dioxus::Result;\n\n    use super::*;\n\n    /// Extract requests directly for full control\n    #[get(\"/myendpoint\")]\n    async fn my_custom_handler1(request: axum::extract::Request) -> Result<()> {\n        let mut data = request.into_data_stream();\n        while let Some(chunk) = data.next().await {\n            let _ = chunk.unwrap();\n        }\n        Ok(())\n    }\n}\n\nmod input_types {\n    use super::*;\n\n    #[derive(Serialize, Deserialize)]\n    struct CustomPayload {\n        name: String,\n        age: u32,\n    }\n\n    /// We can take `()` as input\n    #[post(\"/\")]\n    async fn zero(a: (), b: (), c: ()) -> Result<()> {\n        Ok(())\n    }\n\n    /// We can take `()` as input in serde types\n    #[post(\"/\")]\n    async fn zero_1(a: Json<()>) -> Result<()> {\n        Ok(())\n    }\n\n    /// We can take regular axum extractors as input\n    #[post(\"/\")]\n    async fn one(data: Json<CustomPayload>) -> Result<()> {\n        Ok(())\n    }\n\n    /// We can take Deserialize types as input, and they will be deserialized from JSON\n    #[post(\"/\")]\n    async fn two(name: String, age: u32) -> Result<()> {\n        Ok(())\n    }\n\n    /// We can take Deserialize types as input, with custom server extensions\n    #[post(\"/\", headers: HeaderMap)]\n    async fn three(name: String) -> Result<()> {\n        Ok(())\n    }\n\n    /// We can take a regular axum-like mix with extractors and Deserialize types\n    #[post(\"/\", headers: HeaderMap)]\n    async fn four(data: Json<CustomPayload>) -> Result<()> {\n        Ok(())\n    }\n\n    /// We can even accept string in the final position.\n    #[post(\"/\")]\n    async fn five(age: u32, name: String) -> Result<()> {\n        Ok(())\n    }\n}\n\nmod handlers {\n    use super::*;\n\n    #[get(\"/handlers/get\")]\n    async fn handle_get() -> Result<String> {\n        Ok(\"handled get\".to_string())\n    }\n\n    #[post(\"/handlers/post\")]\n    async fn handle_post() -> Result<String> {\n        Ok(\"handled post\".to_string())\n    }\n\n    #[put(\"/handlers/put\")]\n    async fn handle_put() -> Result<String> {\n        Ok(\"handled put\".to_string())\n    }\n\n    #[patch(\"/handlers/patch\")]\n    async fn handle_patch() -> Result<String> {\n        Ok(\"handled patch\".to_string())\n    }\n\n    #[delete(\"/handlers/delete\")]\n    async fn handle_delete() -> Result<String> {\n        Ok(\"handled delete\".to_string())\n    }\n}\n"
  },
  {
    "path": "packages/fullstack-core/Cargo.toml",
    "content": "[package]\nname = \"dioxus-fullstack-core\"\nauthors = [\"Jonathan Kelley\", \"Evan Almloff\"]\nversion = { workspace = true }\nedition = \"2021\"\ndescription = \"Hooks for serializing futures, values in dioxus-fullstack and other utilities\"\nlicense = \"MIT OR Apache-2.0\"\nrepository = \"https://github.com/DioxusLabs/dioxus/\"\nhomepage = \"https://dioxuslabs.com\"\nkeywords = [\"web\", \"gui\", \"server\"]\nresolver = \"2\"\n\n[dependencies]\ndioxus-core = { workspace = true, features = [\"serialize\"]}\ndioxus-signals = { workspace = true }\ndioxus-hooks = { workspace = true }\nfutures-channel = { workspace = true }\nserde = { workspace = true }\ndioxus-history = { workspace = true }\ndioxus-document = { workspace = true }\nciborium = { workspace = true }\nbase64 = { workspace = true }\ntracing = { workspace = true }\nthiserror = { workspace = true }\naxum-core = { workspace = true }\nhttp = { workspace = true }\nanyhow = { workspace = true }\ninventory = { workspace = true }\nserde_json = { workspace = true }\ngenerational-box = { workspace = true }\nfutures-util = { workspace = true, features = [\"std\"] }\ntokio = { workspace = true, features = [\"rt\"] }\nparking_lot = { workspace = true }\n\n[features]\nweb = []\nserver = []\n\n[dev-dependencies]\ndioxus-fullstack = { workspace = true }\ndioxus = { workspace = true, features = [\"fullstack\"] }\n\n[package.metadata.docs.rs]\ncargo-args = [\"-Zunstable-options\", \"-Zrustdoc-scrape-examples\"]\n"
  },
  {
    "path": "packages/fullstack-core/README.md",
    "content": "# Dioxus Fullstack Core\n\nDioxus-fullstack-core provides types, traits, hooks, contexts for [`dioxus-fullstack`](https://crates.io/crates/dioxus-fullstack). Libraries that need to integrate with dioxus-fullstack should rely on this crate instead of the full-fledged renderer for quicker build times.\n\n## Usage\n\nTo start using this crate, you can run the following command:\n\n```bash\ncargo add dioxus-fullstack-hooks\n```\n\nThen you can use hooks like `use_server_future` in your components:\n\n```rust\nuse dioxus::prelude::*;\n\n\nfn App() -> Element {\n    let mut article_id = use_signal(|| 0);\n\n    // `use_server_future` will spawn a task that runs on the server and serializes the result to send to the client.\n    // The future will rerun any time the\n    // Since we bubble up the suspense with `?`, the server will wait for the future to resolve before rendering\n    let article = use_server_future(move || fetch_article(article_id()))?;\n\n    rsx! {\n        \"{article().unwrap()}\"\n    }\n}\n\nasync fn fetch_article(id: u32) -> String {\n    format!(\"Article {}\", id)\n}\n```\n"
  },
  {
    "path": "packages/fullstack-core/src/document.rs",
    "content": "//! On the client, we use the [`FullstackWebDocument`] implementation to render the head for any elements that were not rendered on the server.\n\nuse dioxus_document::*;\n\nfn head_element_written_on_server() -> bool {\n    crate::transport::head_element_hydration_entry()\n        .get()\n        .ok()\n        .unwrap_or_default()\n}\n\n/// A document provider for fullstack web clients\n#[derive(Clone)]\npub struct FullstackWebDocument<D> {\n    document: D,\n}\n\nimpl<D> From<D> for FullstackWebDocument<D> {\n    fn from(document: D) -> Self {\n        Self { document }\n    }\n}\n\nimpl<D: Document> Document for FullstackWebDocument<D> {\n    fn eval(&self, js: String) -> Eval {\n        self.document.eval(js)\n    }\n\n    /// Set the title of the document\n    fn set_title(&self, title: String) {\n        self.document.set_title(title);\n    }\n\n    /// Create a new meta tag in the head\n    fn create_meta(&self, props: MetaProps) {\n        self.document.create_meta(props);\n    }\n\n    /// Create a new script tag in the head\n    fn create_script(&self, props: ScriptProps) {\n        self.document.create_script(props);\n    }\n\n    /// Create a new style tag in the head\n    fn create_style(&self, props: StyleProps) {\n        self.document.create_style(props);\n    }\n\n    /// Create a new link tag in the head\n    fn create_link(&self, props: LinkProps) {\n        self.document.create_link(props);\n    }\n\n    fn create_head_component(&self) -> bool {\n        !head_element_written_on_server()\n    }\n}\n"
  },
  {
    "path": "packages/fullstack-core/src/error.rs",
    "content": "use axum_core::response::IntoResponse;\nuse futures_util::TryStreamExt;\nuse http::StatusCode;\nuse serde::{Deserialize, Serialize};\nuse std::fmt::Debug;\n\nuse crate::HttpError;\n\n/// A default result type for server functions, which can either be successful or contain an error. The [`ServerFnResult`] type\n/// is a convenient alias for a `Result` type that uses [`ServerFnError`] as the error type.\n///\n/// # Example\n/// ```rust\n/// use dioxus::prelude::*;\n///\n/// #[server]\n/// async fn parse_number(number: String) -> Result<f32> {\n///     let parsed_number: f32 = number.parse()?;\n///     Ok(parsed_number)\n/// }\n/// ```\npub type ServerFnResult<T = ()> = std::result::Result<T, ServerFnError>;\n\n/// The error type for the server function system. This enum encompasses all possible errors that can occur\n/// during the registration, invocation, and processing of server functions.\n#[derive(thiserror::Error, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]\npub enum ServerFnError {\n    /// Occurs when there is an error while actually running the function on the server.\n    ///\n    /// The `details` field can optionally contain additional structured information about the error.\n    /// When passing typed errors from the server to the client, the `details` field contains the serialized\n    /// representation of the error.\n    #[error(\"error running server function: {message} (details: {details:#?})\")]\n    ServerError {\n        /// A human-readable message describing the error.\n        message: String,\n\n        /// HTTP status code associated with the error.\n        code: u16,\n\n        #[serde(skip_serializing_if = \"Option::is_none\")]\n        details: Option<serde_json::Value>,\n    },\n\n    /// Occurs on the client if there is a network error while trying to run function on server.\n    #[error(\"error reaching server to call server function: {0} \")]\n    Request(RequestError),\n\n    /// Occurs on the client if there is an error while trying to read the response body as a stream.\n    #[error(\"error reading response body stream: {0}\")]\n    StreamError(String),\n\n    /// Error while trying to register the server function (only occurs in case of poisoned RwLock).\n    #[error(\"error while trying to register the server function: {0}\")]\n    Registration(String),\n\n    /// Occurs on the client if trying to use an unsupported `HTTP` method when building a request.\n    #[error(\"error trying to build `HTTP` method request: {0}\")]\n    UnsupportedRequestMethod(String),\n\n    /// Occurs when there is an error while actually running the middleware on the server.\n    #[error(\"error running middleware: {0}\")]\n    MiddlewareError(String),\n\n    /// Occurs on the client if there is an error deserializing the server's response.\n    #[error(\"error deserializing server function results: {0}\")]\n    Deserialization(String),\n\n    /// Occurs on the client if there is an error serializing the server function arguments.\n    #[error(\"error serializing server function arguments: {0}\")]\n    Serialization(String),\n\n    /// Occurs on the server if there is an error deserializing one of the arguments that's been sent.\n    #[error(\"error deserializing server function arguments: {0}\")]\n    Args(String),\n\n    /// Occurs on the server if there's a missing argument.\n    #[error(\"missing argument {0}\")]\n    MissingArg(String),\n\n    /// Occurs on the server if there is an error creating an HTTP response.\n    #[error(\"error creating response {0}\")]\n    Response(String),\n}\n\nimpl ServerFnError {\n    /// Create a new server error (status code 500) with a message.\n    pub fn new(f: impl ToString) -> Self {\n        ServerFnError::ServerError {\n            message: f.to_string(),\n            details: None,\n            code: 500,\n        }\n    }\n\n    /// Create a new server error (status code 500) with a message and details.\n    pub async fn from_axum_response(resp: axum_core::response::Response) -> Self {\n        let status = resp.status();\n        let message = resp\n            .into_body()\n            .into_data_stream()\n            .try_fold(Vec::new(), |mut acc, chunk| async move {\n                acc.extend_from_slice(&chunk);\n                Ok(acc)\n            })\n            .await\n            .ok()\n            .and_then(|bytes| String::from_utf8(bytes).ok())\n            .unwrap_or_else(|| status.canonical_reason().unwrap_or(\"\").to_string());\n\n        ServerFnError::ServerError {\n            message,\n            code: status.as_u16(),\n            details: None,\n        }\n    }\n}\n\nimpl From<anyhow::Error> for ServerFnError {\n    fn from(value: anyhow::Error) -> Self {\n        ServerFnError::ServerError {\n            message: value.to_string(),\n            details: None,\n            code: 500,\n        }\n    }\n}\n\nimpl From<serde_json::Error> for ServerFnError {\n    fn from(value: serde_json::Error) -> Self {\n        ServerFnError::Deserialization(value.to_string())\n    }\n}\n\nimpl From<ServerFnError> for http::StatusCode {\n    fn from(value: ServerFnError) -> Self {\n        match value {\n            ServerFnError::ServerError { code, .. } => {\n                http::StatusCode::from_u16(code).unwrap_or(http::StatusCode::INTERNAL_SERVER_ERROR)\n            }\n            ServerFnError::Request(err) => match err {\n                RequestError::Status(_, code) => http::StatusCode::from_u16(code)\n                    .unwrap_or(http::StatusCode::INTERNAL_SERVER_ERROR),\n                _ => http::StatusCode::INTERNAL_SERVER_ERROR,\n            },\n            ServerFnError::StreamError(_)\n            | ServerFnError::Registration(_)\n            | ServerFnError::UnsupportedRequestMethod(_)\n            | ServerFnError::MiddlewareError(_)\n            | ServerFnError::Deserialization(_)\n            | ServerFnError::Serialization(_)\n            | ServerFnError::Args(_)\n            | ServerFnError::MissingArg(_)\n            | ServerFnError::Response(_) => http::StatusCode::INTERNAL_SERVER_ERROR,\n        }\n    }\n}\n\nimpl From<RequestError> for ServerFnError {\n    fn from(value: RequestError) -> Self {\n        ServerFnError::Request(value)\n    }\n}\n\nimpl From<ServerFnError> for HttpError {\n    fn from(value: ServerFnError) -> Self {\n        let status = StatusCode::from_u16(match &value {\n            ServerFnError::ServerError { code, .. } => *code,\n            _ => 500,\n        })\n        .unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);\n        HttpError {\n            status,\n            message: Some(value.to_string()),\n        }\n    }\n}\n\nimpl From<HttpError> for ServerFnError {\n    fn from(value: HttpError) -> Self {\n        ServerFnError::ServerError {\n            message: value.message.unwrap_or_else(|| {\n                value\n                    .status\n                    .canonical_reason()\n                    .unwrap_or(\"Unknown error\")\n                    .to_string()\n            }),\n            code: value.status.as_u16(),\n            details: None,\n        }\n    }\n}\n\nimpl IntoResponse for ServerFnError {\n    fn into_response(self) -> axum_core::response::Response {\n        match self {\n            Self::ServerError {\n                message,\n                code,\n                details,\n            } => {\n                let status =\n                    StatusCode::from_u16(code).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);\n                let body = if let Some(details) = details {\n                    serde_json::json!({\n                        \"error\": message,\n                        \"details\": details,\n                    })\n                } else {\n                    serde_json::json!({\n                        \"error\": message,\n                    })\n                };\n                let body = axum_core::body::Body::from(\n                    serde_json::to_string(&body)\n                        .unwrap_or_else(|_| \"{\\\"error\\\":\\\"Internal Server Error\\\"}\".to_string()),\n                );\n                axum_core::response::Response::builder()\n                    .status(status)\n                    .header(\"Content-Type\", \"application/json\")\n                    .body(body)\n                    .unwrap_or_else(|_| {\n                        axum_core::response::Response::builder()\n                            .status(StatusCode::INTERNAL_SERVER_ERROR)\n                            .body(axum_core::body::Body::from(\n                                \"{\\\"error\\\":\\\"Internal Server Error\\\"}\",\n                            ))\n                            .unwrap()\n                    })\n            }\n            _ => {\n                let status: StatusCode = self.clone().into();\n                let body = axum_core::body::Body::from(\n                    serde_json::json!({\n                        \"error\": self.to_string(),\n                    })\n                    .to_string(),\n                );\n                axum_core::response::Response::builder()\n                    .status(status)\n                    .header(\"Content-Type\", \"application/json\")\n                    .body(body)\n                    .unwrap_or_else(|_| {\n                        axum_core::response::Response::builder()\n                            .status(StatusCode::INTERNAL_SERVER_ERROR)\n                            .body(axum_core::body::Body::from(\n                                \"{\\\"error\\\":\\\"Internal Server Error\\\"}\",\n                            ))\n                            .unwrap()\n                    })\n            }\n        }\n    }\n}\n\n/// An error type representing issues that can occur while making requests.\n///\n/// This is made to paper over the reqwest::Error type which we don't want to export here and\n/// is limited in many ways.\n#[derive(thiserror::Error, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]\npub enum RequestError {\n    /// An error occurred when building the request.\n    #[error(\"error building request: {0}\")]\n    Builder(String),\n\n    /// An error occurred when serializing the request body.\n    #[error(\"error serializing request body: {0}\")]\n    Serialization(String),\n\n    /// An error occurred when following a redirect.\n    #[error(\"error following redirect: {0}\")]\n    Redirect(String),\n\n    /// An error occurred when receiving a non-2xx status code.\n    #[error(\"error receiving status code: {0} ({1})\")]\n    Status(String, u16),\n\n    /// An error occurred when a request times out.\n    #[error(\"error timing out: {0}\")]\n    Timeout(String),\n\n    /// An error occurred when sending a request.\n    #[error(\"error sending request: {0}\")]\n    Request(String),\n\n    /// An error occurred when upgrading a connection.\n    #[error(\"error upgrading connection: {0}\")]\n    Connect(String),\n\n    /// An error occurred when there is a request or response body error.\n    #[error(\"request or response body error: {0}\")]\n    Body(String),\n\n    /// An error occurred when decoding the response body.\n    #[error(\"error decoding response body: {0}\")]\n    Decode(String),\n}\n\nimpl RequestError {\n    pub fn status(&self) -> Option<StatusCode> {\n        match self {\n            RequestError::Status(_, code) => Some(StatusCode::from_u16(*code).ok()?),\n            _ => None,\n        }\n    }\n\n    pub fn status_code(&self) -> Option<u16> {\n        match self {\n            RequestError::Status(_, code) => Some(*code),\n            _ => None,\n        }\n    }\n}\n"
  },
  {
    "path": "packages/fullstack-core/src/errors.rs",
    "content": "//! Contains a hydration compatible error boundary context.\n\nuse crate::serialize_context;\nuse dioxus_core::{spawn_isomorphic, CapturedError, ErrorContext, ReactiveContext};\nuse futures_util::StreamExt;\n\n/// Initializes an error boundary context that is compatible with hydration.\npub fn init_error_boundary() -> ErrorContext {\n    let initial_error = serialize_context().create_entry::<Option<CapturedError>>();\n    let (rx_context, mut rx) = ReactiveContext::new();\n    let errors = ErrorContext::new(None);\n    if let Ok(Some(err)) = initial_error.get() {\n        errors.insert_error(err);\n    }\n    rx_context.run_in(|| {\n        errors.error();\n    });\n    spawn_isomorphic({\n        let errors = errors.clone();\n        async move {\n            if rx.next().await.is_some() {\n                rx_context.run_in(|| {\n                    initial_error.insert(&errors.error(), std::panic::Location::caller())\n                });\n            }\n        }\n    });\n    errors\n}\n"
  },
  {
    "path": "packages/fullstack-core/src/history.rs",
    "content": "//! A history provider for fullstack apps that is compatible with hydration.\n\nuse std::{cell::RefCell, rc::Rc};\n\nuse crate::transport::{is_hydrating, SerializeContextEntry};\nuse dioxus_core::{provide_context, queue_effect, schedule_update, try_consume_context};\nuse dioxus_history::{history, provide_history_context, History};\n\n// If we are currently in a scope and this is the first run then queue a rerender\n// for after hydration\nfn match_hydration<O>(\n    during_hydration: impl FnOnce() -> O,\n    after_hydration: impl FnOnce() -> O,\n) -> O {\n    if is_hydrating() {\n        let update = schedule_update();\n        queue_effect(move || update());\n        during_hydration()\n    } else {\n        after_hydration()\n    }\n}\n\n#[derive(Debug, Clone, PartialEq)]\npub(crate) struct ResolvedRouteContext {\n    route: String,\n}\n\npub(crate) fn finalize_route() {\n    // This may run in tests without the full hydration context set up, if it does, then just\n    // return without modifying the context\n    let Some(entry) = try_consume_context::<RouteEntry>() else {\n        return;\n    };\n\n    let Some(entry) = entry.entry.borrow_mut().take() else {\n        // If it was already taken, then just return. This can happen if commit_initial_chunk is called twice\n        return;\n    };\n\n    if cfg!(feature = \"server\") {\n        let history = history();\n        let route = history.current_route();\n        entry.insert(&route, std::panic::Location::caller());\n        provide_context(ResolvedRouteContext { route });\n    } else if cfg!(feature = \"web\") {\n        let route = entry\n            .get()\n            .expect(\"Failed to get initial route from hydration context\");\n        provide_context(ResolvedRouteContext { route });\n    }\n}\n\n/// Provide the fullstack history context. This interacts with the hydration context so it must\n/// be called in the same order on the client and server after the hydration context is created\npub fn provide_fullstack_history_context<H: History + 'static>(history: H) {\n    let entry = crate::transport::serialize_context().create_entry();\n    provide_context(RouteEntry {\n        entry: Rc::new(RefCell::new(Some(entry.clone()))),\n    });\n    provide_history_context(Rc::new(FullstackHistory::new(history)));\n}\n\n#[derive(Clone)]\nstruct RouteEntry {\n    entry: Rc<RefCell<Option<SerializeContextEntry<String>>>>,\n}\n\n/// A history provider for fullstack apps that is compatible with hydration.\n#[derive(Clone)]\nstruct FullstackHistory<H> {\n    history: H,\n}\n\nimpl<H> FullstackHistory<H> {\n    /// Create a new `FullstackHistory` with the given history.\n    pub fn new(history: H) -> Self {\n        Self { history }\n    }\n\n    /// Get the initial route of the history.\n    fn initial_route(&self) -> String\n    where\n        H: History,\n    {\n        // If the route hydration entry is set, use that instead of the histories current route\n        // for better hydration behavior. The client may be rendering from a ssg route that was\n        // rendered at a different url\n        if let Some(entry) = try_consume_context::<RouteEntry>() {\n            let entry = entry.entry.borrow();\n            if let Some(entry) = &*entry {\n                if let Ok(initial_route) = entry.get() {\n                    return initial_route;\n                }\n            }\n        }\n\n        self.history.current_route()\n    }\n}\n\nimpl<H: History> History for FullstackHistory<H> {\n    fn current_prefix(&self) -> Option<String> {\n        self.history.current_prefix()\n    }\n\n    fn can_go_back(&self) -> bool {\n        match_hydration(|| false, || self.history.can_go_back())\n    }\n\n    fn can_go_forward(&self) -> bool {\n        match_hydration(|| false, || self.history.can_go_forward())\n    }\n\n    fn external(&self, url: String) -> bool {\n        self.history.external(url)\n    }\n\n    fn updater(&self, callback: std::sync::Arc<dyn Fn() + Send + Sync>) {\n        self.history.updater(callback)\n    }\n\n    fn include_prevent_default(&self) -> bool {\n        self.history.include_prevent_default()\n    }\n\n    fn current_route(&self) -> String {\n        match_hydration(|| self.initial_route(), || self.history.current_route())\n    }\n\n    fn go_back(&self) {\n        self.history.go_back();\n    }\n\n    fn go_forward(&self) {\n        self.history.go_forward();\n    }\n\n    fn push(&self, route: String) {\n        self.history.push(route);\n    }\n\n    fn replace(&self, path: String) {\n        self.history.replace(path);\n    }\n}\n"
  },
  {
    "path": "packages/fullstack-core/src/httperror.rs",
    "content": "use axum_core::response::IntoResponse;\nuse http::StatusCode;\nuse std::fmt;\n\n/// An error type that wraps an HTTP status code and optional message.\n#[derive(Debug, Clone, PartialEq)]\npub struct HttpError {\n    pub status: StatusCode,\n    pub message: Option<String>,\n}\n\nimpl HttpError {\n    pub fn new<M: Into<String>>(status: StatusCode, message: M) -> Self {\n        HttpError {\n            status,\n            message: Some(message.into()),\n        }\n    }\n\n    pub fn err<T>(status: StatusCode, message: impl Into<String>) -> Result<T, Self> {\n        Err(HttpError::new(status, message))\n    }\n\n    // --- 4xx Client Errors ---\n    pub fn bad_request<T>(message: impl Into<String>) -> Result<T, Self> {\n        Self::err(StatusCode::BAD_REQUEST, message)\n    }\n    pub fn unauthorized<T>(message: impl Into<String>) -> Result<T, Self> {\n        Self::err(StatusCode::UNAUTHORIZED, message)\n    }\n    pub fn payment_required<T>(message: impl Into<String>) -> Result<T, Self> {\n        Self::err(StatusCode::PAYMENT_REQUIRED, message)\n    }\n    pub fn forbidden<T>(message: impl Into<String>) -> Result<T, Self> {\n        Self::err(StatusCode::FORBIDDEN, message)\n    }\n    pub fn not_found<T>(message: impl Into<String>) -> Result<T, Self> {\n        Self::err(StatusCode::NOT_FOUND, message)\n    }\n    pub fn method_not_allowed<T>(message: impl Into<String>) -> Result<T, Self> {\n        Self::err(StatusCode::METHOD_NOT_ALLOWED, message)\n    }\n    pub fn not_acceptable<T>(message: impl Into<String>) -> Result<T, Self> {\n        Self::err(StatusCode::NOT_ACCEPTABLE, message)\n    }\n    pub fn proxy_auth_required<T>(message: impl Into<String>) -> Result<T, Self> {\n        Self::err(StatusCode::PROXY_AUTHENTICATION_REQUIRED, message)\n    }\n    pub fn request_timeout<T>(message: impl Into<String>) -> Result<T, Self> {\n        Self::err(StatusCode::REQUEST_TIMEOUT, message)\n    }\n    pub fn conflict<T>(message: impl Into<String>) -> Result<T, Self> {\n        Self::err(StatusCode::CONFLICT, message)\n    }\n    pub fn gone<T>(message: impl Into<String>) -> Result<T, Self> {\n        Self::err(StatusCode::GONE, message)\n    }\n    pub fn length_required<T>(message: impl Into<String>) -> Result<T, Self> {\n        Self::err(StatusCode::LENGTH_REQUIRED, message)\n    }\n    pub fn precondition_failed<T>(message: impl Into<String>) -> Result<T, Self> {\n        Self::err(StatusCode::PRECONDITION_FAILED, message)\n    }\n    pub fn payload_too_large<T>(message: impl Into<String>) -> Result<T, Self> {\n        Self::err(StatusCode::PAYLOAD_TOO_LARGE, message)\n    }\n    pub fn uri_too_long<T>(message: impl Into<String>) -> Result<T, Self> {\n        Self::err(StatusCode::URI_TOO_LONG, message)\n    }\n    pub fn unsupported_media_type<T>(message: impl Into<String>) -> Result<T, Self> {\n        Self::err(StatusCode::UNSUPPORTED_MEDIA_TYPE, message)\n    }\n    pub fn im_a_teapot<T>(message: impl Into<String>) -> Result<T, Self> {\n        Self::err(StatusCode::IM_A_TEAPOT, message)\n    }\n    pub fn too_many_requests<T>(message: impl Into<String>) -> Result<T, Self> {\n        Self::err(StatusCode::TOO_MANY_REQUESTS, message)\n    }\n\n    // --- 5xx Server Errors ---\n    pub fn internal_server_error<T>(message: impl Into<String>) -> Result<T, Self> {\n        Self::err(StatusCode::INTERNAL_SERVER_ERROR, message)\n    }\n    pub fn not_implemented<T>(message: impl Into<String>) -> Result<T, Self> {\n        Self::err(StatusCode::NOT_IMPLEMENTED, message)\n    }\n    pub fn bad_gateway<T>(message: impl Into<String>) -> Result<T, Self> {\n        Self::err(StatusCode::BAD_GATEWAY, message)\n    }\n    pub fn service_unavailable<T>(message: impl Into<String>) -> Result<T, Self> {\n        Self::err(StatusCode::SERVICE_UNAVAILABLE, message)\n    }\n    pub fn gateway_timeout<T>(message: impl Into<String>) -> Result<T, Self> {\n        Self::err(StatusCode::GATEWAY_TIMEOUT, message)\n    }\n    pub fn http_version_not_supported<T>(message: impl Into<String>) -> Result<T, Self> {\n        Self::err(StatusCode::HTTP_VERSION_NOT_SUPPORTED, message)\n    }\n\n    // --- 2xx/3xx (rare, but for completeness) ---\n    pub fn ok<T>(message: impl Into<String>) -> Result<T, Self> {\n        Self::err(StatusCode::OK, message)\n    }\n    pub fn created<T>(message: impl Into<String>) -> Result<T, Self> {\n        Self::err(StatusCode::CREATED, message)\n    }\n    pub fn accepted<T>(message: impl Into<String>) -> Result<T, Self> {\n        Self::err(StatusCode::ACCEPTED, message)\n    }\n    pub fn moved_permanently<T>(message: impl Into<String>) -> Result<T, Self> {\n        Self::err(StatusCode::MOVED_PERMANENTLY, message)\n    }\n    pub fn found<T>(message: impl Into<String>) -> Result<T, Self> {\n        Self::err(StatusCode::FOUND, message)\n    }\n    pub fn see_other<T>(message: impl Into<String>) -> Result<T, Self> {\n        Self::err(StatusCode::SEE_OTHER, message)\n    }\n    pub fn not_modified<T>(message: impl Into<String>) -> Result<T, Self> {\n        Self::err(StatusCode::NOT_MODIFIED, message)\n    }\n}\n\nimpl fmt::Display for HttpError {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        match &self.message {\n            Some(msg) => write!(f, \"{}: {}\", self.status, msg),\n            None => write!(f, \"{}\", self.status),\n        }\n    }\n}\n\nimpl std::error::Error for HttpError {}\n\n/// Trait to convert errors into HttpError with a given status code.\npub trait OrHttpError<T, M>: Sized {\n    fn or_http_error(self, status: StatusCode, message: impl Into<String>) -> Result<T, HttpError>;\n\n    // --- Most common user-facing status codes ---\n    fn or_bad_request(self, message: impl Into<String>) -> Result<T, HttpError> {\n        self.or_http_error(StatusCode::BAD_REQUEST, message)\n    }\n    fn or_unauthorized(self, message: impl Into<String>) -> Result<T, HttpError> {\n        self.or_http_error(StatusCode::UNAUTHORIZED, message)\n    }\n    fn or_forbidden(self, message: impl Into<String>) -> Result<T, HttpError> {\n        self.or_http_error(StatusCode::FORBIDDEN, message)\n    }\n    fn or_not_found(self, message: impl Into<String>) -> Result<T, HttpError> {\n        self.or_http_error(StatusCode::NOT_FOUND, message)\n    }\n    fn or_internal_server_error(self, message: impl Into<String>) -> Result<T, HttpError> {\n        self.or_http_error(StatusCode::INTERNAL_SERVER_ERROR, message)\n    }\n}\n\nimpl<T, E> OrHttpError<T, ()> for Result<T, E>\nwhere\n    E: std::error::Error + Send + Sync + 'static,\n{\n    fn or_http_error(self, status: StatusCode, message: impl Into<String>) -> Result<T, HttpError> {\n        self.map_err(|_| HttpError {\n            status,\n            message: Some(message.into()),\n        })\n    }\n}\n\nimpl<T> OrHttpError<T, ()> for Option<T> {\n    fn or_http_error(self, status: StatusCode, message: impl Into<String>) -> Result<T, HttpError> {\n        self.ok_or_else(|| HttpError {\n            status,\n            message: Some(message.into()),\n        })\n    }\n}\n\nimpl OrHttpError<(), ()> for bool {\n    fn or_http_error(\n        self,\n        status: StatusCode,\n        message: impl Into<String>,\n    ) -> Result<(), HttpError> {\n        if self {\n            Ok(())\n        } else {\n            Err(HttpError {\n                status,\n                message: Some(message.into()),\n            })\n        }\n    }\n}\n\npub struct AnyhowMarker;\nimpl<T> OrHttpError<T, AnyhowMarker> for Result<T, anyhow::Error> {\n    fn or_http_error(self, status: StatusCode, message: impl Into<String>) -> Result<T, HttpError> {\n        self.map_err(|_| HttpError {\n            status,\n            message: Some(message.into()),\n        })\n    }\n}\n\nimpl IntoResponse for HttpError {\n    fn into_response(self) -> axum_core::response::Response {\n        let body = match &self.message {\n            Some(msg) => msg.clone(),\n            None => self\n                .status\n                .canonical_reason()\n                .unwrap_or(\"Unknown error\")\n                .to_string(),\n        };\n        (self.status, body).into_response()\n    }\n}\n"
  },
  {
    "path": "packages/fullstack-core/src/lib.rs",
    "content": "// #![warn(missing_docs)]\n#![doc = include_str!(\"../README.md\")]\n\npub mod document;\npub mod history;\n\nmod errors;\nmod loader;\nmod server_cached;\nmod server_future;\nmod streaming;\nmod transport;\n\npub use crate::errors::*;\npub use crate::loader::*;\npub use crate::server_cached::*;\npub use crate::server_future::*;\npub use crate::streaming::*;\npub use crate::transport::*;\n\n/// Error types and utilities.\n#[macro_use]\npub mod error;\npub use error::*;\n\npub mod httperror;\npub use httperror::*;\n"
  },
  {
    "path": "packages/fullstack-core/src/loader.rs",
    "content": "use dioxus_core::{use_hook, IntoAttributeValue, IntoDynNode, Subscribers};\nuse dioxus_core::{CapturedError, RenderError, Result, SuspendedFuture};\nuse dioxus_hooks::{use_resource, use_signal, Resource};\nuse dioxus_signals::{\n    read_impls, ReadSignal, Readable, ReadableBoxExt, ReadableExt, ReadableRef, Signal, Writable,\n    WritableExt, WriteLock,\n};\nuse generational_box::{BorrowResult, UnsyncStorage};\nuse serde::{de::DeserializeOwned, Serialize};\nuse std::ops::Deref;\nuse std::{cmp::PartialEq, future::Future};\n\n/// A hook to create a resource that loads data asynchronously.\n///\n/// This hook takes a closure that returns a future. This future will be executed on both the client\n/// and the server. The loader will return `Loading` until the future resolves, at which point it will\n/// return a `Loader<T>`. If the future fails, it will return `Loading::Failed`.\n///\n/// After the loader has successfully loaded once, it will never suspend the component again, but will\n/// instead re-load the value in the background whenever any of its dependencies change.\n///\n/// If an error occurs while re-loading, `use_loader` will once again emit a `Loading::Failed` value.\n/// The `use_loader` hook will never return a suspended state after the initial load.\n///\n/// # On the server\n///\n/// On the server, this hook will block the rendering of the component (and therefore, the page) until\n/// the future resolves. Any server futures called by `use_loader` will receive the same request context\n/// as the component that called `use_loader`.\n#[allow(clippy::result_large_err)]\n#[track_caller]\npub fn use_loader<F, T, E>(mut future: impl FnMut() -> F + 'static) -> Result<Loader<T>, Loading>\nwhere\n    F: Future<Output = Result<T, E>> + 'static,\n    T: 'static + PartialEq + Serialize + DeserializeOwned,\n    E: Into<CapturedError> + 'static,\n{\n    let serialize_context = use_hook(crate::transport::serialize_context);\n\n    // We always create a storage entry, even if the data isn't ready yet to make it possible to deserialize pending server futures on the client\n    #[allow(unused)]\n    let storage_entry: crate::transport::SerializeContextEntry<Result<T, CapturedError>> =\n        use_hook(|| serialize_context.create_entry());\n\n    #[cfg(feature = \"server\")]\n    let caller = std::panic::Location::caller();\n\n    // If this is the first run and we are on the web client, the data might be cached\n    #[cfg(feature = \"web\")]\n    let initial_web_result =\n        use_hook(|| std::rc::Rc::new(std::cell::RefCell::new(Some(storage_entry.get()))));\n\n    let mut error = use_signal(|| None as Option<CapturedError>);\n    let mut value = use_signal(|| None as Option<T>);\n    let mut loader_state = use_signal(|| LoaderState::Pending);\n\n    let resource = use_resource(move || {\n        #[cfg(feature = \"server\")]\n        let storage_entry = storage_entry.clone();\n\n        let user_fut = future();\n\n        #[cfg(feature = \"web\")]\n        let initial_web_result = initial_web_result.clone();\n\n        #[allow(clippy::let_and_return)]\n        async move {\n            // If this is the first run and we are on the web client, the data might be cached\n            #[cfg(feature = \"web\")]\n            match initial_web_result.take() {\n                // The data was deserialized successfully from the server\n                Some(Ok(o)) => {\n                    match o {\n                        Ok(v) => {\n                            value.set(Some(v));\n                            loader_state.set(LoaderState::Ready);\n                        }\n                        Err(e) => {\n                            error.set(Some(e));\n                            loader_state.set(LoaderState::Failed);\n                        }\n                    };\n                    return;\n                }\n\n                // The data is still pending from the server. Don't try to resolve it on the client\n                Some(Err(crate::transport::TakeDataError::DataPending)) => {\n                    std::future::pending::<()>().await\n                }\n\n                // The data was not available on the server, rerun the future\n                Some(Err(_)) => {}\n\n                // This isn't the first run, so we don't need do anything\n                None => {}\n            }\n\n            // Otherwise just run the future itself\n            let out = user_fut.await;\n\n            // Remap the error to the captured error type so it's cheap to clone and pass out, just\n            // slightly more cumbersome to access the inner error.\n            let out = out.map_err(|e| {\n                let anyhow_err: CapturedError = e.into();\n                anyhow_err\n            });\n\n            // If this is the first run and we are on the server, cache the data in the slot we reserved for it\n            #[cfg(feature = \"server\")]\n            storage_entry.insert(&out, caller);\n\n            match out {\n                Ok(v) => {\n                    value.set(Some(v));\n                    loader_state.set(LoaderState::Ready);\n                }\n                Err(e) => {\n                    error.set(Some(e));\n                    loader_state.set(LoaderState::Failed);\n                }\n            };\n        }\n    });\n\n    // On the first run, force this task to be polled right away in case its value is ready\n    use_hook(|| {\n        let _ = resource.task().poll_now();\n    });\n\n    let read_value = use_hook(|| value.map(|f| f.as_ref().unwrap()).boxed());\n\n    let handle = LoaderHandle {\n        resource,\n        error,\n        state: loader_state,\n        _marker: std::marker::PhantomData,\n    };\n\n    match &*loader_state.read_unchecked() {\n        LoaderState::Pending => Err(Loading::Pending(handle)),\n        LoaderState::Failed => Err(Loading::Failed(handle)),\n        LoaderState::Ready => Ok(Loader {\n            real_value: value,\n            read_value,\n            error,\n            state: loader_state,\n            handle,\n        }),\n    }\n}\n\n/// A Loader is a signal that represents a value that is loaded asynchronously.\n///\n/// Once a `Loader<T>` has been successfully created from `use_loader`, it can be use like a normal signal of type `T`.\n///\n/// When the loader is re-reloading its values, it will no longer suspend its component, making it\n/// very useful for server-side-rendering.\npub struct Loader<T: 'static> {\n    /// This is a signal that unwraps the inner value. We can't give it out unless we know the inner value is Some(T)!\n    read_value: ReadSignal<T>,\n\n    /// This is the actual signal. We let the user set this value if they want to, but we can't let them set it to `None`.\n    real_value: Signal<Option<T>>,\n    error: Signal<Option<CapturedError>>,\n    state: Signal<LoaderState>,\n    handle: LoaderHandle,\n}\n\nimpl<T: 'static> Loader<T> {\n    /// Get the error that occurred during loading, if any.\n    ///\n    /// After initial load, this will return `None` until the next reload fails.\n    pub fn error(&self) -> Option<CapturedError> {\n        self.error.read().as_ref().cloned()\n    }\n\n    /// Restart the loading task.\n    ///\n    /// After initial load, this won't suspend the component, but will reload in the background.\n    pub fn restart(&mut self) {\n        self.handle.restart();\n    }\n\n    /// Check if the loader has failed.\n    pub fn is_error(&self) -> bool {\n        self.error.read().is_some() && matches!(*self.state.read(), LoaderState::Failed)\n    }\n\n    /// Cancel the current loading task.\n    pub fn cancel(&mut self) {\n        self.handle.resource.cancel();\n    }\n\n    pub fn loading(&self) -> bool {\n        !self.handle.resource.finished()\n    }\n}\n\nimpl<T: 'static> Writable for Loader<T> {\n    type WriteMetadata = <Signal<Option<T>> as Writable>::WriteMetadata;\n\n    fn try_write_unchecked(\n        &self,\n    ) -> std::result::Result<\n        dioxus_signals::WritableRef<'static, Self>,\n        generational_box::BorrowMutError,\n    >\n    where\n        Self::Target: 'static,\n    {\n        let writer = self.real_value.try_write_unchecked()?;\n        Ok(WriteLock::map(writer, |f: &mut Option<T>| {\n            f.as_mut()\n                .expect(\"Loader value should be set if the `Loader<T>` exists\")\n        }))\n    }\n}\n\nimpl<T> Readable for Loader<T> {\n    type Target = T;\n    type Storage = UnsyncStorage;\n\n    #[track_caller]\n    fn try_read_unchecked(\n        &self,\n    ) -> Result<ReadableRef<'static, Self>, generational_box::BorrowError>\n    where\n        T: 'static,\n    {\n        Ok(self.read_value.read_unchecked())\n    }\n\n    /// Get the current value of the signal. **Unlike read, this will not subscribe the current scope to the signal which can cause parts of your UI to not update.**\n    ///\n    /// If the signal has been dropped, this will panic.\n    #[track_caller]\n    fn try_peek_unchecked(&self) -> BorrowResult<ReadableRef<'static, Self>>\n    where\n        T: 'static,\n    {\n        Ok(self.read_value.peek_unchecked())\n    }\n\n    fn subscribers(&self) -> Subscribers\n    where\n        T: 'static,\n    {\n        self.read_value.subscribers()\n    }\n}\n\nimpl<T> IntoAttributeValue for Loader<T>\nwhere\n    T: Clone + IntoAttributeValue + PartialEq + 'static,\n{\n    fn into_value(self) -> dioxus_core::AttributeValue {\n        self.with(|f| f.clone().into_value())\n    }\n}\n\nimpl<T> IntoDynNode for Loader<T>\nwhere\n    T: Clone + IntoDynNode + PartialEq + 'static,\n{\n    fn into_dyn_node(self) -> dioxus_core::DynamicNode {\n        let t: T = self();\n        t.into_dyn_node()\n    }\n}\n\nimpl<T: 'static> PartialEq for Loader<T> {\n    fn eq(&self, other: &Self) -> bool {\n        self.read_value == other.read_value\n    }\n}\n\nimpl<T: Clone> Deref for Loader<T>\nwhere\n    T: PartialEq + 'static,\n{\n    type Target = dyn Fn() -> T;\n\n    fn deref(&self) -> &Self::Target {\n        unsafe { ReadableExt::deref_impl(self) }\n    }\n}\n\nread_impls!(Loader<T> where T: PartialEq);\n\nimpl<T> Clone for Loader<T> {\n    fn clone(&self) -> Self {\n        *self\n    }\n}\n\nimpl<T> Copy for Loader<T> {}\n\n#[derive(Clone, Copy, PartialEq, Hash, Eq, Debug)]\npub enum LoaderState {\n    /// The loader's future is still running\n    Pending,\n\n    /// The loader's future has completed successfully\n    Ready,\n\n    /// The loader's future has failed and now the loader is in an error state.\n    Failed,\n}\n\n#[derive(PartialEq)]\npub struct LoaderHandle<M = ()> {\n    resource: Resource<()>,\n    error: Signal<Option<CapturedError>>,\n    state: Signal<LoaderState>,\n    _marker: std::marker::PhantomData<M>,\n}\n\nimpl LoaderHandle {\n    /// Restart the loading task.\n    pub fn restart(&mut self) {\n        self.resource.restart();\n    }\n\n    /// Get the current state of the loader.\n    pub fn state(&self) -> LoaderState {\n        *self.state.read()\n    }\n\n    pub fn error(&self) -> Option<CapturedError> {\n        self.error.read().as_ref().cloned()\n    }\n}\n\nimpl Clone for LoaderHandle {\n    fn clone(&self) -> Self {\n        *self\n    }\n}\n\nimpl Copy for LoaderHandle {}\n\n#[derive(PartialEq)]\npub enum Loading {\n    /// The loader is still pending and the component should suspend.\n    Pending(LoaderHandle),\n\n    /// The loader has failed and an error will be returned up the tree.\n    Failed(LoaderHandle),\n}\n\nimpl std::fmt::Debug for Loading {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        match self {\n            Loading::Pending(_) => write!(f, \"Loading::Pending\"),\n            Loading::Failed(_) => write!(f, \"Loading::Failed\"),\n        }\n    }\n}\n\nimpl std::fmt::Display for Loading {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        match self {\n            Loading::Pending(_) => write!(f, \"Loading is still pending\"),\n            Loading::Failed(_) => write!(f, \"Loading has failed\"),\n        }\n    }\n}\n\n/// Convert a Loading into a RenderError for use with the `?` operator in components\nimpl From<Loading> for RenderError {\n    fn from(val: Loading) -> Self {\n        match val {\n            Loading::Pending(t) => RenderError::Suspended(SuspendedFuture::new(t.resource.task())),\n            Loading::Failed(err) => RenderError::Error(\n                err.error\n                    .cloned()\n                    .expect(\"LoaderHandle in Failed state should always have an error\"),\n            ),\n        }\n    }\n}\n"
  },
  {
    "path": "packages/fullstack-core/src/server_cached.rs",
    "content": "use crate::{transport::SerializeContextEntry, Transportable};\nuse dioxus_core::use_hook;\n\n/// This allows you to send data from the server to the client *during hydration*.\n/// - When compiled as server, the closure is ran and the resulting data is serialized on the server and sent to the client.\n/// - When compiled as web client, the data is deserialized from the server if already available, otherwise runs on the client. Data is usually only available if this hook exists in a component during hydration.\n/// - When otherwise compiled, the closure is run directly with no serialization.\n///\n/// The order this function is run on the client needs to be the same order initially run on the server.\n///\n/// If Dioxus fullstack cannot find the data on the client, it will run the closure again to get the data.\n///\n/// # Example\n/// ```rust\n/// use dioxus::prelude::*;\n///\n/// fn app() -> Element {\n///    let state1 = use_server_cached(|| {\n///       1234\n///    });\n///\n///    unimplemented!()\n/// }\n/// ```\n#[track_caller]\npub fn use_server_cached<O, M>(server_fn: impl Fn() -> O) -> O\nwhere\n    O: Transportable<M> + Clone,\n    M: 'static,\n{\n    let location = std::panic::Location::caller();\n    use_hook(|| server_cached(server_fn, location))\n}\n\npub(crate) fn server_cached<O, M>(\n    value: impl FnOnce() -> O,\n    #[allow(unused)] location: &'static std::panic::Location<'static>,\n) -> O\nwhere\n    O: Transportable<M> + Clone,\n    M: 'static,\n{\n    let serialize = crate::transport::serialize_context();\n\n    #[allow(unused)]\n    let entry: SerializeContextEntry<O> = serialize.create_entry();\n\n    #[cfg(feature = \"server\")]\n    {\n        let data = value();\n        entry.insert(&data, location);\n        data\n    }\n\n    #[cfg(all(not(feature = \"server\"), feature = \"web\"))]\n    {\n        match entry.get() {\n            Ok(value) => value,\n            Err(_) => value(),\n        }\n    }\n\n    #[cfg(not(any(feature = \"server\", feature = \"web\")))]\n    {\n        value()\n    }\n}\n"
  },
  {
    "path": "packages/fullstack-core/src/server_future.rs",
    "content": "use crate::Transportable;\nuse dioxus_core::{suspend, use_hook, RenderError};\nuse dioxus_hooks::*;\nuse dioxus_signals::ReadableExt;\nuse std::future::Future;\n\n/// Runs a future with a manual list of dependencies and returns a resource with the result if the future is finished or a suspended error if it is still running.\n///\n///\n/// On the server, this will wait until the future is resolved before continuing to render. When the future is resolved, the result will be serialized into the page and hydrated on the client without rerunning the future.\n///\n///\n/// <div class=\"warning\">\n///\n/// Unlike [`use_resource`] dependencies are only tracked inside the function that spawns the async block, not the async block itself.\n///\n/// ```rust, no_run\n/// # use dioxus::prelude::*;\n/// // ❌ The future inside of use_server_future is not reactive\n/// let id = use_signal(|| 0);\n/// use_server_future(move || {\n///     async move {\n///          // But the future is not reactive which means that the future will not subscribe to any reads here\n///          println!(\"{id}\");\n///     }\n/// });\n/// // ✅ The closure that creates the future for use_server_future is reactive\n/// let id = use_signal(|| 0);\n/// use_server_future(move || {\n///     // The closure itself is reactive which means the future will subscribe to any signals you read here\n///     let cloned_id = id();\n///     async move {\n///          // But the future is not reactive which means that the future will not subscribe to any reads here\n///          println!(\"{cloned_id}\");\n///     }\n/// });\n/// ```\n///\n/// </div>\n///\n/// # Example\n///\n/// ```rust, no_run\n/// # use dioxus::prelude::*;\n/// # async fn fetch_article(id: u32) -> String { unimplemented!() }\n/// use dioxus::prelude::*;\n///\n/// fn App() -> Element {\n///     let mut article_id = use_signal(|| 0);\n///     // `use_server_future` will spawn a task that runs on the server and serializes the result to send to the client.\n///     // The future will rerun any time the\n///     // Since we bubble up the suspense with `?`, the server will wait for the future to resolve before rendering\n///     let article = use_server_future(move || fetch_article(article_id()))?;\n///\n///     rsx! {\n///         \"{article().unwrap()}\"\n///     }\n/// }\n/// ```\n#[must_use = \"Consider using `cx.spawn` to run a future without reading its value\"]\n#[track_caller]\npub fn use_server_future<T, F, M>(\n    mut future: impl FnMut() -> F + 'static,\n) -> Result<Resource<T>, RenderError>\nwhere\n    F: Future<Output = T> + 'static,\n    T: Transportable<M>,\n    M: 'static,\n{\n    let serialize_context = use_hook(crate::transport::serialize_context);\n\n    // We always create a storage entry, even if the data isn't ready yet to make it possible to deserialize pending server futures on the client\n    #[allow(unused)]\n    let storage_entry: crate::transport::SerializeContextEntry<T> =\n        use_hook(|| serialize_context.create_entry());\n\n    #[cfg(feature = \"server\")]\n    let caller = std::panic::Location::caller();\n\n    // If this is the first run and we are on the web client, the data might be cached\n    #[cfg(feature = \"web\")]\n    let initial_web_result =\n        use_hook(|| std::rc::Rc::new(std::cell::RefCell::new(Some(storage_entry.get()))));\n\n    let resource = use_resource(move || {\n        #[cfg(feature = \"server\")]\n        let storage_entry = storage_entry.clone();\n\n        let user_fut = future();\n\n        #[cfg(feature = \"web\")]\n        let initial_web_result = initial_web_result.clone();\n\n        #[allow(clippy::let_and_return)]\n        async move {\n            // If this is the first run and we are on the web client, the data might be cached\n            #[cfg(feature = \"web\")]\n            match initial_web_result.take() {\n                // The data was deserialized successfully from the server\n                Some(Ok(o)) => return o,\n\n                // The data is still pending from the server. Don't try to resolve it on the client\n                Some(Err(crate::transport::TakeDataError::DataPending)) => {\n                    std::future::pending::<()>().await\n                }\n\n                // The data was not available on the server, rerun the future\n                Some(Err(_)) => {}\n\n                // This isn't the first run, so we don't need do anything\n                None => {}\n            }\n\n            // Otherwise just run the future itself\n            let out = user_fut.await;\n\n            // If this is the first run and we are on the server, cache the data in the slot we reserved for it\n            #[cfg(feature = \"server\")]\n            storage_entry.insert(&out, caller);\n\n            out\n        }\n    });\n\n    // On the first run, force this task to be polled right away in case its value is ready\n    use_hook(|| {\n        let _ = resource.task().poll_now();\n    });\n\n    // Suspend if the value isn't ready\n    if resource.state().cloned() == UseResourceState::Pending {\n        let task = resource.task();\n        if !task.paused() {\n            return Err(suspend(task).unwrap_err());\n        }\n    }\n\n    Ok(resource)\n}\n"
  },
  {
    "path": "packages/fullstack-core/src/streaming.rs",
    "content": "use crate::{HttpError, ServerFnError};\nuse axum_core::extract::FromRequest;\nuse axum_core::response::IntoResponse;\nuse dioxus_core::{CapturedError, ReactiveContext};\nuse http::StatusCode;\nuse http::{request::Parts, HeaderMap};\nuse parking_lot::RwLock;\nuse std::collections::HashSet;\nuse std::fmt::Debug;\nuse std::sync::Arc;\n\n/// The context provided by dioxus fullstack for server-side rendering.\n///\n/// This context will only be set on the server during the initial streaming response\n/// and inside server functions.\n#[derive(Clone, Debug)]\npub struct FullstackContext {\n    // We expose the lock for request headers directly so it needs to be in a separate lock\n    request_headers: Arc<RwLock<http::request::Parts>>,\n\n    // The rest of the fields are only held internally, so we can group them together\n    lock: Arc<RwLock<FullstackContextInner>>,\n}\n\n// `FullstackContext` is always set when either\n// 1. rendering the app via SSR\n// 2. handling a server function request\ntokio::task_local! {\n    static FULLSTACK_CONTEXT: FullstackContext;\n}\n\npub struct FullstackContextInner {\n    current_status: StreamingStatus,\n    current_status_subscribers: HashSet<ReactiveContext>,\n    response_headers: Option<HeaderMap>,\n    route_http_status: HttpError,\n    route_http_status_subscribers: HashSet<ReactiveContext>,\n}\n\nimpl Debug for FullstackContextInner {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_struct(\"FullstackContextInner\")\n            .field(\"current_status\", &self.current_status)\n            .field(\"response_headers\", &self.response_headers)\n            .field(\"route_http_status\", &self.route_http_status)\n            .finish()\n    }\n}\n\nimpl PartialEq for FullstackContext {\n    fn eq(&self, other: &Self) -> bool {\n        Arc::ptr_eq(&self.lock, &other.lock)\n            && Arc::ptr_eq(&self.request_headers, &other.request_headers)\n    }\n}\n\nimpl FullstackContext {\n    /// Create a new streaming context. You should not need to call this directly. Dioxus fullstack will\n    /// provide this context for you.\n    pub fn new(parts: Parts) -> Self {\n        Self {\n            request_headers: RwLock::new(parts).into(),\n            lock: RwLock::new(FullstackContextInner {\n                current_status: StreamingStatus::RenderingInitialChunk,\n                current_status_subscribers: Default::default(),\n                route_http_status: HttpError {\n                    status: http::StatusCode::OK,\n                    message: None,\n                },\n                route_http_status_subscribers: Default::default(),\n                response_headers: Some(HeaderMap::new()),\n            })\n            .into(),\n        }\n    }\n\n    /// Commit the initial chunk of the response. This will be called automatically if you are using the\n    /// dioxus router when the suspense boundary above the router is resolved. Otherwise, you will need\n    /// to call this manually to start the streaming part of the response.\n    ///\n    /// Once this method has been called, the http response parts can no longer be modified.\n    pub fn commit_initial_chunk(&mut self) {\n        let mut lock = self.lock.write();\n        lock.current_status = StreamingStatus::InitialChunkCommitted;\n\n        // The key type is mutable, but the hash is stable through mutations because we hash by pointer\n        #[allow(clippy::mutable_key_type)]\n        let subscribers = std::mem::take(&mut lock.current_status_subscribers);\n        for subscriber in subscribers {\n            subscriber.mark_dirty();\n        }\n    }\n\n    /// Get the current status of the streaming response. This method is reactive and will cause\n    /// the current reactive context to rerun when the status changes.\n    pub fn streaming_state(&self) -> StreamingStatus {\n        let mut lock = self.lock.write();\n        // Register the current reactive context as a subscriber to changes in the streaming status\n        if let Some(ctx) = ReactiveContext::current() {\n            lock.current_status_subscribers.insert(ctx);\n        }\n        lock.current_status\n    }\n\n    /// Access the http request parts mutably. This will allow you to modify headers and other parts of the request.\n    pub fn parts_mut(&self) -> parking_lot::RwLockWriteGuard<'_, http::request::Parts> {\n        self.request_headers.write()\n    }\n\n    /// Run a future within the scope of this FullstackContext.\n    pub async fn scope<F, R>(self, fut: F) -> R\n    where\n        F: std::future::Future<Output = R>,\n    {\n        FULLSTACK_CONTEXT.scope(self, fut).await\n    }\n\n    /// Extract an extension from the current request.\n    pub fn extension<T: Clone + Send + Sync + 'static>(&self) -> Option<T> {\n        let lock = self.request_headers.read();\n        lock.extensions.get::<T>().cloned()\n    }\n\n    /// Extract an axum extractor from the current request.\n    ///\n    /// The body of the request is always empty when using this method, as the body can only be consumed once in the server\n    /// function extractors.\n    pub async fn extract<T: FromRequest<Self, M>, M>() -> Result<T, ServerFnError> {\n        let this = Self::current().unwrap_or_else(|| {\n            // Create a dummy context if one doesn't exist, making the function usable outside of a request context\n            FullstackContext::new(\n                axum_core::extract::Request::builder()\n                    .method(\"GET\")\n                    .uri(\"/\")\n                    .header(\"X-Dummy-Header\", \"true\")\n                    .body(())\n                    .unwrap()\n                    .into_parts()\n                    .0,\n            )\n        });\n\n        let parts = this.request_headers.read().clone();\n        let request = axum_core::extract::Request::from_parts(parts, Default::default());\n        match T::from_request(request, &this).await {\n            Ok(res) => Ok(res),\n            Err(err) => {\n                let resp = err.into_response();\n                Err(ServerFnError::from_axum_response(resp).await)\n            }\n        }\n    }\n\n    /// Get the current `FullstackContext` if it exists. This will return `None` if called on the client\n    /// or outside of a streaming response on the server or server function.\n    pub fn current() -> Option<Self> {\n        // Try to get the context from the task local (for server functions)\n        if let Ok(context) = FULLSTACK_CONTEXT.try_get() {\n            return Some(context);\n        }\n\n        // Otherwise, try to get it from the dioxus runtime context (for streaming SSR)\n        if let Some(rt) = dioxus_core::Runtime::try_current() {\n            let id = rt.try_current_scope_id()?;\n            if let Some(ctx) = rt.consume_context::<FullstackContext>(id) {\n                return Some(ctx);\n            }\n        }\n\n        None\n    }\n\n    /// Get the current HTTP status for the route. This will default to 200 OK, but can be modified\n    /// by calling `FullstackContext::commit_error_status` with an error.\n    pub fn current_http_status(&self) -> HttpError {\n        let mut lock = self.lock.write();\n        // Register the current reactive context as a subscriber to changes in the http status\n        if let Some(ctx) = ReactiveContext::current() {\n            lock.route_http_status_subscribers.insert(ctx);\n        }\n        lock.route_http_status.clone()\n    }\n\n    pub fn set_current_http_status(&mut self, status: HttpError) {\n        let mut lock = self.lock.write();\n        lock.route_http_status = status;\n        // The key type is mutable, but the hash is stable through mutations because we hash by pointer\n        #[allow(clippy::mutable_key_type)]\n        let subscribers = std::mem::take(&mut lock.route_http_status_subscribers);\n        for subscriber in subscribers {\n            subscriber.mark_dirty();\n        }\n    }\n\n    /// Add a header to the response. This will be sent to the client when the response is committed.\n    pub fn add_response_header(\n        &self,\n        key: impl Into<http::header::HeaderName>,\n        value: impl Into<http::header::HeaderValue>,\n    ) {\n        let mut lock = self.lock.write();\n        if let Some(headers) = lock.response_headers.as_mut() {\n            headers.insert(key.into(), value.into());\n        }\n    }\n\n    /// Take the response headers out of the context. This will leave the context without any headers,\n    /// so it should only be called once when the response is being committed.\n    pub fn take_response_headers(&self) -> Option<HeaderMap> {\n        let mut lock = self.lock.write();\n        lock.response_headers.take()\n    }\n\n    /// Set the current HTTP status for the route. This will be used when committing the response\n    /// to the client.\n    pub fn commit_http_status(status: StatusCode, message: Option<String>) {\n        if let Some(mut ctx) = Self::current() {\n            ctx.set_current_http_status(HttpError { status, message });\n        }\n    }\n\n    /// Commit the CapturedError as the current HTTP status for the route.\n    /// This will attempt to downcast the error to known types and set the appropriate\n    /// status code. If the error type is unknown, it will default to\n    /// `StatusCode::INTERNAL_SERVER_ERROR`.\n    pub fn commit_error_status(error: impl Into<CapturedError>) -> HttpError {\n        let error = error.into();\n        let status = status_code_from_error(&error);\n        let http_error = HttpError {\n            status,\n            message: Some(error.to_string()),\n        };\n\n        if let Some(mut ctx) = Self::current() {\n            ctx.set_current_http_status(http_error.clone());\n        }\n\n        http_error\n    }\n}\n\n/// The status of the streaming response\n#[derive(Clone, Copy, Debug, PartialEq)]\npub enum StreamingStatus {\n    /// The initial chunk is still being rendered. The http response parts can still be modified at this point.\n    RenderingInitialChunk,\n\n    /// The initial chunk has been committed and the response is now streaming. The http response parts\n    /// have already been sent to the client and can no longer be modified.\n    InitialChunkCommitted,\n}\n\n/// Commit the initial chunk of the response. This will be called automatically if you are using the\n/// dioxus router when the suspense boundary above the router is resolved. Otherwise, you will need\n/// to call this manually to start the streaming part of the response.\n///\n/// On the client, this will do nothing.\n///\n/// # Example\n/// ```rust, no_run\n/// # use dioxus::prelude::*;\n/// # use dioxus_fullstack_core::*;\n/// # fn Children() -> Element { unimplemented!() }\n/// fn App() -> Element {\n///     // This will start streaming immediately after the current render is complete.\n///     use_hook(commit_initial_chunk);\n///\n///     rsx! { Children {} }\n/// }\n/// ```\npub fn commit_initial_chunk() {\n    crate::history::finalize_route();\n    if let Some(mut streaming) = FullstackContext::current() {\n        streaming.commit_initial_chunk();\n    }\n}\n\n/// Extract an axum extractor from the current request.\n#[deprecated(note = \"Use FullstackContext::extract instead\", since = \"0.7.0\")]\npub fn extract<T: FromRequest<FullstackContext, M>, M>(\n) -> impl std::future::Future<Output = Result<T, ServerFnError>> {\n    FullstackContext::extract::<T, M>()\n}\n\n/// Get the current status of the streaming response. This method is reactive and will cause\n/// the current reactive context to rerun when the status changes.\n///\n/// On the client, this will always return `StreamingStatus::InitialChunkCommitted`.\n///\n/// # Example\n/// ```rust, no_run\n/// # use dioxus::prelude::*;\n/// # use dioxus_fullstack_core::*;\n/// #[component]\n/// fn MetaTitle(title: String) -> Element {\n///     // If streaming has already started, warn the user that the meta tag will not show\n///     // up in the initial chunk.\n///     use_hook(|| {\n///         if current_status() == StreamingStatus::InitialChunkCommitted {\n///            dioxus::logger::tracing::warn!(\"Since `MetaTitle` was rendered after the initial chunk was committed, the meta tag will not show up in the head without javascript enabled.\");\n///         }\n///     });\n///\n///     rsx! { meta { property: \"og:title\", content: title } }\n/// }\n/// ```\npub fn current_status() -> StreamingStatus {\n    if let Some(streaming) = FullstackContext::current() {\n        streaming.streaming_state()\n    } else {\n        StreamingStatus::InitialChunkCommitted\n    }\n}\n\n/// Convert a `CapturedError` into an appropriate HTTP status code.\n///\n/// This will attempt to downcast the error to known types and return a corresponding status code.\n/// If the error type is unknown, it will default to `StatusCode::INTERNAL_SERVER_ERROR`.\npub fn status_code_from_error(error: &CapturedError) -> StatusCode {\n    if let Some(err) = error.downcast_ref::<ServerFnError>() {\n        match err {\n            ServerFnError::ServerError { code, .. } => {\n                return StatusCode::from_u16(*code).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR)\n            }\n            _ => return StatusCode::INTERNAL_SERVER_ERROR,\n        }\n    }\n\n    if let Some(err) = error.downcast_ref::<StatusCode>() {\n        return *err;\n    }\n\n    if let Some(err) = error.downcast_ref::<HttpError>() {\n        return err.status;\n    }\n\n    StatusCode::INTERNAL_SERVER_ERROR\n}\n"
  },
  {
    "path": "packages/fullstack-core/src/transport.rs",
    "content": "#![warn(missing_docs)]\n#![doc = include_str!(\"../README.md\")]\n\nuse base64::Engine;\nuse dioxus_core::CapturedError;\nuse serde::{de::DeserializeOwned, Deserialize, Serialize};\nuse std::{cell::RefCell, io::Cursor, rc::Rc};\n\n#[cfg(feature = \"web\")]\nthread_local! {\n    static CONTEXT: RefCell<Option<HydrationContext>> = const { RefCell::new(None) };\n}\n\n/// Data shared between the frontend and the backend for hydration\n/// of server functions.\n#[derive(Default, Clone)]\npub struct HydrationContext {\n    #[cfg(feature = \"web\")]\n    /// Is resolving suspense done on the client\n    suspense_finished: bool,\n    data: Rc<RefCell<HTMLData>>,\n}\n\nimpl HydrationContext {\n    /// Create a new serialize context from the serialized data\n    pub fn from_serialized(\n        data: &[u8],\n        debug_types: Option<Vec<String>>,\n        debug_locations: Option<Vec<String>>,\n    ) -> Self {\n        Self {\n            #[cfg(feature = \"web\")]\n            suspense_finished: false,\n            data: Rc::new(RefCell::new(HTMLData::from_serialized(\n                data,\n                debug_types,\n                debug_locations,\n            ))),\n        }\n    }\n\n    /// Serialize the data in the context to be sent to the client\n    pub fn serialized(&self) -> SerializedHydrationData {\n        self.data.borrow().serialized()\n    }\n\n    /// Create a new entry in the data that will be sent to the client without inserting any data. Returns an id that can be used to insert data into the entry once it is ready.\n    pub fn create_entry<T>(&self) -> SerializeContextEntry<T> {\n        let entry_index = self.data.borrow_mut().create_entry();\n\n        SerializeContextEntry {\n            index: entry_index,\n            context: self.clone(),\n            phantom: std::marker::PhantomData,\n        }\n    }\n\n    /// Get the entry for the error in the suspense boundary\n    pub fn error_entry(&self) -> SerializeContextEntry<Option<CapturedError>> {\n        // The first entry is reserved for the error\n        let entry_index = self.data.borrow_mut().create_entry_with_id(0);\n\n        SerializeContextEntry {\n            index: entry_index,\n            context: self.clone(),\n            phantom: std::marker::PhantomData,\n        }\n    }\n\n    /// Extend this data with the data from another [`HydrationContext`]\n    pub fn extend(&self, other: &Self) {\n        self.data.borrow_mut().extend(&other.data.borrow());\n    }\n\n    #[cfg(feature = \"web\")]\n    /// Run a closure inside of this context\n    pub fn in_context<T>(&self, f: impl FnOnce() -> T) -> T {\n        CONTEXT.with(|context| {\n            let old = context.borrow().clone();\n            *context.borrow_mut() = Some(self.clone());\n            let result = f();\n            *context.borrow_mut() = old;\n            result\n        })\n    }\n\n    pub(crate) fn insert<T: Transportable<M>, M: 'static>(\n        &self,\n        id: usize,\n        value: &T,\n        location: &'static std::panic::Location<'static>,\n    ) {\n        self.data.borrow_mut().insert(id, value, location);\n    }\n\n    pub(crate) fn get<T: Transportable<M>, M: 'static>(\n        &self,\n        id: usize,\n    ) -> Result<T, TakeDataError> {\n        // If suspense is finished on the client, we can assume that the data is available\n        #[cfg(feature = \"web\")]\n        if self.suspense_finished {\n            return Err(TakeDataError::DataNotAvailable);\n        }\n        self.data.borrow().get(id)\n    }\n}\n\n/// An entry into the serialized context. The order entries are created in must be consistent\n/// between the server and the client.\npub struct SerializeContextEntry<T> {\n    /// The index this context will be inserted into inside the serialize context\n    index: usize,\n    /// The context this entry is associated with\n    context: HydrationContext,\n    phantom: std::marker::PhantomData<T>,\n}\n\nimpl<T> Clone for SerializeContextEntry<T> {\n    fn clone(&self) -> Self {\n        Self {\n            index: self.index,\n            context: self.context.clone(),\n            phantom: std::marker::PhantomData,\n        }\n    }\n}\n\nimpl<T> SerializeContextEntry<T> {\n    /// Insert data into an entry that was created with [`HydrationContext::create_entry`]\n    pub fn insert<M: 'static>(self, value: &T, location: &'static std::panic::Location<'static>)\n    where\n        T: Transportable<M>,\n    {\n        self.context.insert(self.index, value, location);\n    }\n\n    /// Grab the data from the serialize context\n    pub fn get<M: 'static>(&self) -> Result<T, TakeDataError>\n    where\n        T: Transportable<M>,\n    {\n        self.context.get(self.index)\n    }\n}\n\n/// Check if the client is currently rendering a component for hydration. Always returns true on the server.\npub fn is_hydrating() -> bool {\n    #[cfg(feature = \"web\")]\n    {\n        // On the client, we can check if the context is set\n        CONTEXT.with(|context| context.borrow().is_some())\n    }\n    #[cfg(not(feature = \"web\"))]\n    {\n        true\n    }\n}\n\n/// Get or insert the current serialize context. On the client, the hydration context this returns\n/// will always return `TakeDataError::DataNotAvailable` if hydration of the current chunk is finished.\npub fn serialize_context() -> HydrationContext {\n    #[cfg(feature = \"web\")]\n    // On the client, the hydration logic provides the context in a global\n    if let Some(current_context) = CONTEXT.with(|context| context.borrow().clone()) {\n        current_context\n    } else {\n        // If the context is not set, then suspense is not active\n        HydrationContext {\n            suspense_finished: true,\n            ..Default::default()\n        }\n    }\n    #[cfg(not(feature = \"web\"))]\n    {\n        // On the server each scope creates the context lazily\n        dioxus_core::has_context()\n            .unwrap_or_else(|| dioxus_core::provide_context(HydrationContext::default()))\n    }\n}\n\npub(crate) struct HTMLData {\n    /// The position of the cursor in the data. This is only used on the client\n    pub(crate) cursor: usize,\n    /// The data required for hydration\n    pub data: Vec<Option<Vec<u8>>>,\n    /// The types of each serialized data\n    ///\n    /// NOTE: we don't store this in the main data vec because we don't want to include it in\n    /// release mode and we can't assume both the client and server are built with debug assertions\n    /// matching\n    #[cfg(debug_assertions)]\n    pub debug_types: Vec<Option<String>>,\n    /// The locations of each serialized data\n    #[cfg(debug_assertions)]\n    pub debug_locations: Vec<Option<String>>,\n}\n\nimpl Default for HTMLData {\n    fn default() -> Self {\n        Self {\n            cursor: 1,\n            data: Vec::new(),\n            #[cfg(debug_assertions)]\n            debug_types: Vec::new(),\n            #[cfg(debug_assertions)]\n            debug_locations: Vec::new(),\n        }\n    }\n}\n\nimpl HTMLData {\n    #[allow(unused)]\n    fn from_serialized(\n        data: &[u8],\n        debug_types: Option<Vec<String>>,\n        debug_locations: Option<Vec<String>>,\n    ) -> Self {\n        let data = ciborium::from_reader(Cursor::new(data)).unwrap();\n        Self {\n            cursor: 1,\n            data,\n            #[cfg(debug_assertions)]\n            debug_types: debug_types\n                .unwrap_or_default()\n                .into_iter()\n                .map(Some)\n                .collect(),\n            #[cfg(debug_assertions)]\n            debug_locations: debug_locations\n                .unwrap_or_default()\n                .into_iter()\n                .map(Some)\n                .collect(),\n        }\n    }\n\n    /// Create a new entry in the data that will be sent to the client without inserting any data. Returns an id that can be used to insert data into the entry once it is ready.\n    fn create_entry(&mut self) -> usize {\n        let id = self.cursor;\n        self.cursor += 1;\n        self.create_entry_with_id(id)\n    }\n\n    fn create_entry_with_id(&mut self, id: usize) -> usize {\n        while id + 1 > self.data.len() {\n            self.data.push(None);\n            #[cfg(debug_assertions)]\n            {\n                self.debug_types.push(None);\n                self.debug_locations.push(None);\n            }\n        }\n        id\n    }\n\n    /// Insert data into an entry that was created with [`Self::create_entry`]\n    fn insert<T: Transportable<M>, M: 'static>(\n        &mut self,\n        id: usize,\n        value: &T,\n        #[allow(unused)] location: &'static std::panic::Location<'static>,\n    ) {\n        let serialized = value.transport_to_bytes();\n        self.data[id] = Some(serialized);\n        #[cfg(debug_assertions)]\n        {\n            self.debug_types[id] = Some(std::any::type_name::<T>().to_string());\n            self.debug_locations[id] = Some(location.to_string());\n        }\n    }\n\n    /// Get the data from the serialize context\n    fn get<T: Transportable<M>, M: 'static>(&self, index: usize) -> Result<T, TakeDataError> {\n        if index >= self.data.len() {\n            tracing::trace!(\n                \"Tried to take more data than was available, len: {}, index: {}; This is normal if the server function was started on the client, but may indicate a bug if the server function result should be deserialized from the server\",\n                self.data.len(),\n                index\n            );\n            return Err(TakeDataError::DataNotAvailable);\n        }\n        let bytes = self.data[index].as_ref();\n        match bytes {\n            Some(bytes) => match T::transport_from_bytes(bytes) {\n                Ok(x) => Ok(x),\n                Err(err) => {\n                    #[cfg(debug_assertions)]\n                    {\n                        let debug_type = self.debug_types.get(index);\n                        let debug_locations = self.debug_locations.get(index);\n\n                        if let (Some(Some(debug_type)), Some(Some(debug_locations))) =\n                            (debug_type, debug_locations)\n                        {\n                            let client_type = std::any::type_name::<T>();\n                            let client_location = std::panic::Location::caller();\n                            // We we have debug types and a location, we can provide a more helpful error message\n                            tracing::error!(\n                                \"Error deserializing data: {err:?}\\n\\nThis type was serialized on the server at {debug_locations} with the type name {debug_type}. The client failed to deserialize the type {client_type} at {client_location}.\",\n                            );\n                            return Err(TakeDataError::DeserializationError(err));\n                        }\n                    }\n                    // Otherwise, just log the generic deserialization error\n                    tracing::error!(\"Error deserializing data: {:?}\", err);\n                    Err(TakeDataError::DeserializationError(err))\n                }\n            },\n            None => Err(TakeDataError::DataPending),\n        }\n    }\n\n    /// Extend this data with the data from another [`HTMLData`]\n    pub(crate) fn extend(&mut self, other: &Self) {\n        // Make sure this vectors error entry exists even if it is empty\n        if self.data.is_empty() {\n            self.data.push(None);\n            #[cfg(debug_assertions)]\n            {\n                self.debug_types.push(None);\n                self.debug_locations.push(None);\n            }\n        }\n\n        let mut other_data_iter = other.data.iter().cloned();\n        #[cfg(debug_assertions)]\n        let mut other_debug_types_iter = other.debug_types.iter().cloned();\n        #[cfg(debug_assertions)]\n        let mut other_debug_locations_iter = other.debug_locations.iter().cloned();\n\n        // Merge the error entry from the other context\n        if let Some(Some(other_error)) = other_data_iter.next() {\n            self.data[0] = Some(other_error.clone());\n            #[cfg(debug_assertions)]\n            {\n                self.debug_types[0] = other_debug_types_iter.next().unwrap_or(None);\n                self.debug_locations[0] = other_debug_locations_iter.next().unwrap_or(None);\n            }\n        }\n\n        // Don't copy the error from the other context\n        self.data.extend(other_data_iter);\n        #[cfg(debug_assertions)]\n        {\n            self.debug_types.extend(other_debug_types_iter);\n            self.debug_locations.extend(other_debug_locations_iter);\n        }\n    }\n\n    /// Encode data as base64. This is intended to be used in the server to send data to the client.\n    pub(crate) fn serialized(&self) -> SerializedHydrationData {\n        let mut serialized = Vec::new();\n        ciborium::into_writer(&self.data, &mut serialized).unwrap();\n\n        let data = base64::engine::general_purpose::STANDARD.encode(serialized);\n\n        #[cfg(debug_assertions)]\n        let format_js_list_of_strings = |list: &[Option<String>]| {\n            let body = list\n                .iter()\n                .map(|s| match s {\n                    Some(s) => {\n                        // Escape backslashes, quotes, and newlines\n                        let escaped = s\n                            .replace(r#\"\\\"#, r#\"\\\\\"#)\n                            .replace(\"\\n\", r#\"\\n\"#)\n                            .replace(r#\"\"\"#, r#\"\\\"\"#);\n\n                        format!(r#\"\"{escaped}\"\"#)\n                    }\n                    None => r#\"\"unknown\"\"#.to_string(),\n                })\n                .collect::<Vec<_>>()\n                .join(\",\");\n            format!(\"[{}]\", body)\n        };\n\n        SerializedHydrationData {\n            data,\n            #[cfg(debug_assertions)]\n            debug_types: format_js_list_of_strings(&self.debug_types),\n            #[cfg(debug_assertions)]\n            debug_locations: format_js_list_of_strings(&self.debug_locations),\n        }\n    }\n}\n\n/// Data that was serialized on the server for hydration on the client. This includes\n/// extra information about the types and sources of the serialized data in debug mode\npub struct SerializedHydrationData {\n    /// The base64 encoded serialized data\n    pub data: String,\n    /// A list of the types of each serialized data\n    #[cfg(debug_assertions)]\n    pub debug_types: String,\n    /// A list of the locations of each serialized data\n    #[cfg(debug_assertions)]\n    pub debug_locations: String,\n}\n\n/// An error that can occur when trying to take data from the server\n#[derive(Debug)]\npub enum TakeDataError {\n    /// Deserializing the data failed\n    DeserializationError(ciborium::de::Error<std::io::Error>),\n    /// No data was available\n    DataNotAvailable,\n    /// The server serialized a placeholder for the data, but it isn't available yet\n    DataPending,\n}\n\nimpl std::fmt::Display for TakeDataError {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        match self {\n            Self::DeserializationError(e) => write!(f, \"DeserializationError: {}\", e),\n            Self::DataNotAvailable => write!(f, \"DataNotAvailable\"),\n            Self::DataPending => write!(f, \"DataPending\"),\n        }\n    }\n}\n\nimpl std::error::Error for TakeDataError {}\n\n/// Create a new entry in the serialize context for the head element hydration\npub fn head_element_hydration_entry() -> SerializeContextEntry<bool> {\n    serialize_context().create_entry()\n}\n\n/// A `Transportable` type can be safely transported from the server to the client, and be used for\n/// hydration. Not all types can sensibly be transported, but many can. This trait makes it possible\n/// to customize how types are transported which helps for non-serializable types like `dioxus_core::CapturedError`.\n///\n/// By default, all types that implement `Serialize` and `DeserializeOwned` are transportable.\n///\n/// You can also implement `Transportable` for `Result<T, dioxus_core::CapturedError>` where `T` is\n/// `Serialize` and `DeserializeOwned` to allow transporting results that may contain errors.\n///\n/// Note that transporting a `Result<T, dioxus_core::CapturedError>` will lose various aspects of the original\n/// `dioxus_core::CapturedError` such as backtraces and source errors, but will preserve the error message.\npub trait Transportable<M = ()>: 'static {\n    /// Serialize the type to a byte vector for transport\n    fn transport_to_bytes(&self) -> Vec<u8>;\n\n    /// Deserialize the type from a byte slice\n    fn transport_from_bytes(bytes: &[u8]) -> Result<Self, ciborium::de::Error<std::io::Error>>\n    where\n        Self: Sized;\n}\n\nimpl<T> Transportable<()> for T\nwhere\n    T: Serialize + DeserializeOwned + 'static,\n{\n    fn transport_to_bytes(&self) -> Vec<u8> {\n        let mut serialized = Vec::new();\n        ciborium::into_writer(self, &mut serialized).unwrap();\n        serialized\n    }\n\n    fn transport_from_bytes(bytes: &[u8]) -> Result<Self, ciborium::de::Error<std::io::Error>>\n    where\n        Self: Sized,\n    {\n        ciborium::from_reader(Cursor::new(bytes))\n    }\n}\n\n#[derive(Serialize, Deserialize)]\nstruct TransportResultErr<T> {\n    error: Result<T, CapturedError>,\n}\n\n#[doc(hidden)]\npub struct TransportViaErrMarker;\n\nimpl<T> Transportable<TransportViaErrMarker> for Result<T, anyhow::Error>\nwhere\n    T: Serialize + DeserializeOwned + 'static,\n{\n    fn transport_to_bytes(&self) -> Vec<u8> {\n        let err = TransportResultErr {\n            error: self\n                .as_ref()\n                .map_err(|e| CapturedError::from_display(e.to_string())),\n        };\n\n        let mut serialized = Vec::new();\n        ciborium::into_writer(&err, &mut serialized).unwrap();\n        serialized\n    }\n\n    fn transport_from_bytes(bytes: &[u8]) -> Result<Self, ciborium::de::Error<std::io::Error>>\n    where\n        Self: Sized,\n    {\n        let err: TransportResultErr<T> = ciborium::from_reader(Cursor::new(bytes))?;\n        match err.error {\n            Ok(value) => Ok(Ok(value)),\n            Err(captured) => Ok(Err(anyhow::Error::msg(captured.to_string()))),\n        }\n    }\n}\n\n#[doc(hidden)]\npub struct TransportCapturedError;\n#[derive(Serialize, Deserialize)]\nstruct TransportError {\n    error: String,\n}\n\nimpl Transportable<TransportCapturedError> for CapturedError {\n    fn transport_to_bytes(&self) -> Vec<u8> {\n        let err = TransportError {\n            error: self.to_string(),\n        };\n\n        let mut serialized = Vec::new();\n        ciborium::into_writer(&err, &mut serialized).unwrap();\n        serialized\n    }\n\n    fn transport_from_bytes(bytes: &[u8]) -> Result<Self, ciborium::de::Error<std::io::Error>>\n    where\n        Self: Sized,\n    {\n        let err: TransportError = ciborium::from_reader(Cursor::new(bytes))?;\n        Ok(dioxus_core::CapturedError::msg::<String>(err.error))\n    }\n}\n"
  },
  {
    "path": "packages/fullstack-macro/Cargo.toml",
    "content": "[package]\nname = \"dioxus-fullstack-macro\"\nversion = { workspace = true }\nedition = \"2021\"\nrepository = \"https://github.com/DioxusLabs/dioxus/\"\nkeywords = [\"dom\", \"ui\", \"gui\", \"react\", \"liveview\"]\nauthors = [\"Jonathan Kelley\", \"Evan Almloff\"]\nlicense = \"MIT OR Apache-2.0\"\ndescription = \"Server function macros for Dioxus\"\n\n[dependencies]\nquote = { workspace = true, default-features = true }\nsyn = { features = [\n  \"full\",\n  \"parsing\",\n  \"extra-traits\",\n], workspace = true, default-features = true }\nproc-macro2 = { workspace = true, default-features = true }\nxxhash-rust = { features = [\"const_xxh64\" ], workspace = true, default-features = true }\nconst_format = { workspace = true, default-features = true }\nconvert_case = { workspace = true, default-features = true }\n\n[dev-dependencies]\ndioxus = { workspace = true, features = [\"fullstack\"] }\nserde = { workspace = true, features = [\"derive\"] }\ntower-http = { workspace = true, features = [\"timeout\"] }\naxum = { workspace = true }\n\n[lib]\nproc-macro = true\n"
  },
  {
    "path": "packages/fullstack-macro/src/lib.rs",
    "content": "// TODO: Create README, uncomment this: #![doc = include_str!(\"../README.md\")]\n#![doc(html_logo_url = \"https://avatars.githubusercontent.com/u/79236386\")]\n#![doc(html_favicon_url = \"https://avatars.githubusercontent.com/u/79236386\")]\n\nuse core::panic;\nuse proc_macro::TokenStream;\nuse proc_macro2::{Span, TokenStream as TokenStream2};\nuse quote::ToTokens;\nuse quote::{format_ident, quote};\nuse std::collections::HashMap;\nuse syn::{\n    braced, bracketed,\n    parse::ParseStream,\n    punctuated::Punctuated,\n    token::{Comma, Slash},\n    Error, ExprTuple, FnArg, Meta, PathArguments, PathSegment, Token, Type, TypePath,\n};\nuse syn::{parse::Parse, parse_quote, Ident, ItemFn, LitStr, Path};\nuse syn::{spanned::Spanned, LitBool, LitInt, Pat, PatType};\nuse syn::{\n    token::{Brace, Star},\n    Attribute, Expr, ExprClosure, Lit, Result,\n};\n\n/// ## Usage\n///\n/// ```rust,ignore\n/// # use dioxus::prelude::*;\n/// # #[derive(serde::Deserialize, serde::Serialize)]\n/// # struct BlogPost;\n/// # async fn load_posts(category: &str) -> Result<Vec<BlogPost>> { unimplemented!() }\n///\n/// #[server]\n/// async fn blog_posts(category: String) -> Result<Vec<BlogPost>> {\n///     let posts = load_posts(&category).await?;\n///     // maybe do some other work\n///     Ok(posts)\n/// }\n/// ```\n///\n/// ## Named Arguments\n///\n/// You can use any combination of the following named arguments:\n/// - `endpoint`: a prefix at which the server function handler will be mounted (defaults to `/api`).\n///   Example: `endpoint = \"/my_api/my_serverfn\"`.\n/// - `input`: the encoding for the arguments, defaults to `Json<T>`\n///     - You may customize the encoding of the arguments by specifying a different type for `input`.\n///     - Any axum `IntoRequest` extractor can be used here, and dioxus provides\n///       - `Json<T>`: The default axum `Json` extractor that decodes JSON-encoded request bodies.\n///       - `Cbor<T>`: A custom axum `Cbor` extractor that decodes CBOR-encoded request bodies.\n///       - `MessagePack<T>`: A custom axum `MessagePack` extractor that decodes MessagePack-encoded request bodies.\n/// - `output`: the encoding for the response (defaults to `Json`).\n///     - The `output` argument specifies how the server should encode the response data.\n///     - Acceptable values include:\n///       - `Json`: A response encoded as JSON (default). This is ideal for most web applications.\n///       - `Cbor`: A response encoded in the CBOR format for efficient, binary-encoded data.\n/// - `client`: a custom `Client` implementation that will be used for this server function. This allows\n///   customization of the client-side behavior if needed.\n///\n/// ## Advanced Usage of `input` and `output` Fields\n///\n/// The `input` and `output` fields allow you to customize how arguments and responses are encoded and decoded.\n/// These fields impose specific trait bounds on the types you use. Here are detailed examples for different scenarios:\n///\n/// ## Adding layers to server functions\n///\n/// Layers allow you to transform the request and response of a server function. You can use layers\n/// to add authentication, logging, or other functionality to your server functions. Server functions integrate\n/// with the tower ecosystem, so you can use any layer that is compatible with tower.\n///\n/// Common layers include:\n/// - [`tower_http::trace::TraceLayer`](https://docs.rs/tower-http/latest/tower_http/trace/struct.TraceLayer.html) for tracing requests and responses\n/// - [`tower_http::compression::CompressionLayer`](https://docs.rs/tower-http/latest/tower_http/compression/struct.CompressionLayer.html) for compressing large responses\n/// - [`tower_http::cors::CorsLayer`](https://docs.rs/tower-http/latest/tower_http/cors/struct.CorsLayer.html) for adding CORS headers to responses\n/// - [`tower_http::timeout::TimeoutLayer`](https://docs.rs/tower-http/latest/tower_http/timeout/struct.TimeoutLayer.html) for adding timeouts to requests\n/// - [`tower_sessions::service::SessionManagerLayer`](https://docs.rs/tower-sessions/0.13.0/tower_sessions/service/struct.SessionManagerLayer.html) for adding session management to requests\n///\n/// You can add a tower [`Layer`](https://docs.rs/tower/latest/tower/trait.Layer.html) to your server function with the middleware attribute:\n///\n/// ```rust,ignore\n/// # use dioxus::prelude::*;\n/// #[server]\n/// // The TraceLayer will log all requests to the console\n/// #[middleware(tower_http::timeout::TimeoutLayer::new(std::time::Duration::from_secs(5)))]\n/// pub async fn my_wacky_server_fn(input: Vec<String>) -> ServerFnResult<usize> {\n///     unimplemented!()\n/// }\n/// ```\n#[proc_macro_attribute]\npub fn server(attr: proc_macro::TokenStream, mut item: TokenStream) -> TokenStream {\n    // Parse the attribute list using the old server_fn arg parser.\n    let args = match syn::parse::<ServerFnArgs>(attr) {\n        Ok(args) => args,\n        Err(err) => {\n            let err: TokenStream = err.to_compile_error().into();\n            item.extend(err);\n            return item;\n        }\n    };\n\n    let method = Method::Post(Ident::new(\"POST\", proc_macro2::Span::call_site()));\n    let prefix = args\n        .prefix\n        .unwrap_or_else(|| LitStr::new(\"/api\", Span::call_site()));\n\n    let route: Route = Route {\n        method: None,\n        path_params: vec![],\n        query_params: vec![],\n        route_lit: args.fn_path,\n        oapi_options: None,\n        server_args: args.server_args,\n        prefix: Some(prefix),\n        _input_encoding: args.input,\n        _output_encoding: args.output,\n    };\n\n    match route_impl_with_route(route, item.clone(), Some(method)) {\n        Ok(mut tokens) => {\n            // Let's add some deprecated warnings to the various fields from `args` if the user is using them...\n            // We don't generate structs anymore, don't use various protocols, etc\n            if let Some(name) = args.struct_name {\n                tokens.extend(quote! {\n                    const _: () = {\n                        #[deprecated(note = \"Dioxus server functions no longer generate a struct for the server function. The function itself is used directly.\")]\n                        struct #name;\n                        fn ___assert_deprecated() {\n                            let _ = #name;\n                        }\n\n                        ()\n                    };\n                });\n            }\n\n            //\n            tokens.into()\n        }\n\n        // Retain the original function item and append the error to it. Better for autocomplete.\n        Err(err) => {\n            let err: TokenStream = err.to_compile_error().into();\n            item.extend(err);\n            item\n        }\n    }\n}\n\n#[proc_macro_attribute]\npub fn get(args: proc_macro::TokenStream, body: TokenStream) -> TokenStream {\n    wrapped_route_impl(args, body, Some(Method::new_from_string(\"GET\")))\n}\n\n#[proc_macro_attribute]\npub fn post(args: proc_macro::TokenStream, body: TokenStream) -> TokenStream {\n    wrapped_route_impl(args, body, Some(Method::new_from_string(\"POST\")))\n}\n\n#[proc_macro_attribute]\npub fn put(args: proc_macro::TokenStream, body: TokenStream) -> TokenStream {\n    wrapped_route_impl(args, body, Some(Method::new_from_string(\"PUT\")))\n}\n\n#[proc_macro_attribute]\npub fn delete(args: proc_macro::TokenStream, body: TokenStream) -> TokenStream {\n    wrapped_route_impl(args, body, Some(Method::new_from_string(\"DELETE\")))\n}\n\n#[proc_macro_attribute]\npub fn patch(args: proc_macro::TokenStream, body: TokenStream) -> TokenStream {\n    wrapped_route_impl(args, body, Some(Method::new_from_string(\"PATCH\")))\n}\n\nfn wrapped_route_impl(\n    attr: TokenStream,\n    mut item: TokenStream,\n    method: Option<Method>,\n) -> TokenStream {\n    match route_impl(attr, item.clone(), method) {\n        Ok(tokens) => tokens.into(),\n        Err(err) => {\n            let err: TokenStream = err.to_compile_error().into();\n            item.extend(err);\n            item\n        }\n    }\n}\n\nfn route_impl(\n    attr: TokenStream,\n    item: TokenStream,\n    method_from_macro: Option<Method>,\n) -> syn::Result<TokenStream2> {\n    let route = syn::parse::<Route>(attr)?;\n    route_impl_with_route(route, item, method_from_macro)\n}\n\nfn route_impl_with_route(\n    route: Route,\n    item: TokenStream,\n    method_from_macro: Option<Method>,\n) -> syn::Result<TokenStream2> {\n    // Parse the route and function\n    let mut function = syn::parse::<ItemFn>(item)?;\n\n    // Collect the middleware initializers\n    let middleware_layers = function\n        .attrs\n        .iter()\n        .filter(|attr| attr.path().is_ident(\"middleware\"))\n        .map(|f| match &f.meta {\n            Meta::List(meta_list) => Ok({\n                let tokens = &meta_list.tokens;\n                quote! { .layer(#tokens) }\n            }),\n            _ => Err(Error::new(\n                f.span(),\n                \"Expected middleware attribute to be a list, e.g. #[middleware(MyLayer::new())]\",\n            )),\n        })\n        .collect::<Result<Vec<_>>>()?;\n\n    // don't re-emit the middleware attribute on the inner\n    function\n        .attrs\n        .retain(|attr| !attr.path().is_ident(\"middleware\"));\n\n    // Attach `#[allow(unused_mut)]` to all original inputs to avoid warnings\n    let outer_inputs = function\n        .sig\n        .inputs\n        .iter()\n        .enumerate()\n        .map(|(i, arg)| match arg {\n            FnArg::Receiver(_receiver) => panic!(\"Self type is not supported\"),\n            FnArg::Typed(pat_type) => match pat_type.pat.as_ref() {\n                Pat::Ident(_) => {\n                    quote! { #[allow(unused_mut)] #pat_type }\n                }\n                _ => {\n                    let ident = format_ident!(\"___Arg{}\", i);\n                    let ty = &pat_type.ty;\n                    quote! { #[allow(unused_mut)] #ident: #ty }\n                }\n            },\n        })\n        .collect::<Punctuated<_, Token![,]>>();\n    // .collect::<Punctuated<_, Token![,]>>();\n\n    let route = CompiledRoute::from_route(route, &function, false, method_from_macro)?;\n    let query_params_struct = route.query_params_struct(false);\n    let method_ident = &route.method;\n    let body_json_args = route.remaining_pattypes_named(&function.sig.inputs);\n    let body_json_names = body_json_args\n        .iter()\n        .map(|(i, pat_type)| match &*pat_type.pat {\n            Pat::Ident(ref pat_ident) => pat_ident.ident.clone(),\n            _ => format_ident!(\"___Arg{}\", i),\n        })\n        .collect::<Vec<_>>();\n    let body_json_types = body_json_args\n        .iter()\n        .map(|pat_type| &pat_type.1.ty)\n        .collect::<Vec<_>>();\n    let route_docs = route.to_doc_comments();\n\n    // Get the variables we need for code generation\n    let fn_on_server_name = &function.sig.ident;\n    let vis = &function.vis;\n    let (impl_generics, ty_generics, where_clause) = &function.sig.generics.split_for_impl();\n    let ty_generics = ty_generics.as_turbofish();\n    let fn_docs = function\n        .attrs\n        .iter()\n        .filter(|attr| attr.path().is_ident(\"doc\"));\n\n    let __axum = quote! { dioxus_server::axum };\n\n    let output_type = match &function.sig.output {\n        syn::ReturnType::Default => parse_quote! { () },\n        syn::ReturnType::Type(_, ty) => (*ty).clone(),\n    };\n\n    let query_param_names = route\n        .query_params\n        .iter()\n        .filter(|c| !c.catch_all)\n        .map(|param| &param.binding);\n\n    let path_param_args = route.path_params.iter().map(|(_slash, param)| match param {\n        PathParam::Capture(_lit, _brace_1, ident, _ty, _brace_2) => {\n            Some(quote! { #ident = #ident, })\n        }\n        PathParam::WildCard(_lit, _brace_1, _star, ident, _ty, _brace_2) => {\n            Some(quote! { #ident = #ident, })\n        }\n        PathParam::Static(_lit) => None,\n    });\n\n    let out_ty = match output_type.as_ref() {\n        Type::Tuple(tuple) if tuple.elems.is_empty() => parse_quote! { () },\n        _ => output_type.clone(),\n    };\n\n    let mut function_on_server = function.clone();\n    function_on_server\n        .sig\n        .inputs\n        .extend(route.server_args.clone());\n\n    let server_names = route\n        .server_args\n        .iter()\n        .enumerate()\n        .map(|(i, pat_type)| match pat_type {\n            FnArg::Typed(_pat_type) => format_ident!(\"___sarg___{}\", i),\n            FnArg::Receiver(_) => panic!(\"Self type is not supported\"),\n        })\n        .collect::<Vec<_>>();\n\n    let server_types = route\n        .server_args\n        .iter()\n        .map(|pat_type| match pat_type {\n            FnArg::Receiver(_) => parse_quote! { () },\n            FnArg::Typed(pat_type) => (*pat_type.ty).clone(),\n        })\n        .collect::<Vec<_>>();\n\n    let body_struct_impl = {\n        let tys = body_json_types\n            .iter()\n            .enumerate()\n            .map(|(idx, _)| format_ident!(\"__Ty{}\", idx));\n\n        let names = body_json_names.iter().enumerate().map(|(idx, name)| {\n            let ty_name = format_ident!(\"__Ty{}\", idx);\n            quote! { #name: #ty_name }\n        });\n\n        quote! {\n            #[derive(serde::Serialize, serde::Deserialize)]\n            #[serde(crate = \"serde\")]\n            struct ___Body_Serialize___< #(#tys,)* > {\n                #(#names,)*\n            }\n        }\n    };\n\n    // This unpacks the body struct into the individual variables that get scoped\n    let unpack_closure = {\n        let unpack_args = body_json_names.iter().map(|name| quote! { data.#name });\n        quote! {\n            |data| { ( #(#unpack_args,)* ) }\n        }\n    };\n\n    let as_axum_path = route.to_axum_path_string();\n\n    let query_endpoint = if let Some(full_url) = route.url_without_queries_for_format() {\n        quote! { format!(#full_url, #( #path_param_args)*) }\n    } else {\n        quote! { __ENDPOINT_PATH.to_string() }\n    };\n\n    let endpoint_path = {\n        let prefix = route\n            .prefix\n            .as_ref()\n            .cloned()\n            .unwrap_or_else(|| LitStr::new(\"\", Span::call_site()));\n\n        let route_lit = if let Some(lit) = as_axum_path {\n            quote! { #lit }\n        } else {\n            let name =\n                route.route_lit.as_ref().cloned().unwrap_or_else(|| {\n                    LitStr::new(&fn_on_server_name.to_string(), Span::call_site())\n                });\n            quote! {\n                concat!(\n                    \"/\",\n                    #name\n                )\n            }\n        };\n\n        let hash = match route.prefix.as_ref() {\n            // Implicit route lit, we need to hash the function signature to avoid collisions\n            Some(_) if route.route_lit.is_none() => {\n                // let enable_hash = option_env!(\"DISABLE_SERVER_FN_HASH\").is_none();\n                let key_env_var = match option_env!(\"SERVER_FN_OVERRIDE_KEY\") {\n                    Some(_) => \"SERVER_FN_OVERRIDE_KEY\",\n                    None => \"CARGO_MANIFEST_DIR\",\n                };\n                quote! {\n                    dioxus_fullstack::xxhash_rust::const_xxh64::xxh64(\n                        concat!(env!(#key_env_var), \":\", module_path!()).as_bytes(),\n                        0\n                    )\n                }\n            }\n\n            // Explicit route lit, no need to hash\n            _ => quote! { \"\" },\n        };\n\n        quote! {\n            dioxus_fullstack::const_format::concatcp!(#prefix, #route_lit, #hash)\n        }\n    };\n\n    let extracted_idents = route.extracted_idents();\n\n    let query_tokens = if route.query_is_catchall() {\n        let query = route\n            .query_params\n            .iter()\n            .find(|param| param.catch_all)\n            .unwrap();\n        let input = &function.sig.inputs[query.arg_idx];\n        let name = match input {\n            FnArg::Typed(pat_type) => match pat_type.pat.as_ref() {\n                Pat::Ident(ref pat_ident) => pat_ident.ident.clone(),\n                _ => format_ident!(\"___Arg{}\", query.arg_idx),\n            },\n            FnArg::Receiver(_receiver) => panic!(),\n        };\n        quote! {\n            #name\n        }\n    } else {\n        quote! {\n            __QueryParams__ { #(#query_param_names,)* }\n        }\n    };\n\n    let extracted_as_server_headers = route.extracted_as_server_headers(query_tokens.clone());\n\n    Ok(quote! {\n        #(#fn_docs)*\n        #route_docs\n        #[deny(\n            unexpected_cfgs,\n            reason = \"\n==========================================================================================\n  Using Dioxus Server Functions requires a `server` feature flag in your `Cargo.toml`.\n  Please add the following to your `Cargo.toml`:\n\n  ```toml\n  [features]\n  server = [\\\"dioxus/server\\\"]\n  ```\n\n  To enable better Rust-Analyzer support, you can make `server` a default feature:\n  ```toml\n  [features]\n  default = [\\\"web\\\", \\\"server\\\"]\n  web = [\\\"dioxus/web\\\"]\n  server = [\\\"dioxus/server\\\"]\n  ```\n==========================================================================================\n        \"\n        )]\n        #vis async fn #fn_on_server_name #impl_generics( #outer_inputs ) -> #out_ty #where_clause {\n            use dioxus_fullstack::serde as serde;\n            use dioxus_fullstack::{\n                // concrete types\n                ServerFnEncoder, ServerFnDecoder, FullstackContext,\n\n                // \"magic\" traits for encoding/decoding on the client\n                ExtractRequest, EncodeRequest, RequestDecodeResult, RequestDecodeErr,\n\n                // \"magic\" traits for encoding/decoding on the server\n                MakeAxumResponse, MakeAxumError,\n            };\n\n            #query_params_struct\n\n            #body_struct_impl\n\n            const __ENDPOINT_PATH: &str = #endpoint_path;\n\n            {\n                _ = dioxus_fullstack::assert_is_result::<#out_ty>();\n\n                let verify_token = (&&&&&&&&&&&&&&ServerFnEncoder::<___Body_Serialize___<#(#body_json_types,)*>, (#(#body_json_types,)*)>::new())\n                    .verify_can_serialize();\n\n                dioxus_fullstack::assert_can_encode(verify_token);\n\n                let decode_token = (&&&&&ServerFnDecoder::<#out_ty>::new())\n                    .verify_can_deserialize();\n\n                dioxus_fullstack::assert_can_decode(decode_token);\n            };\n\n\n            // On the client, we make the request to the server\n            // We want to support extremely flexible error types and return types, making this more complex than it should\n            #[allow(clippy::unused_unit)]\n            #[cfg(not(feature = \"server\"))]\n            {\n                let client = dioxus_fullstack::ClientRequest::new(\n                    dioxus_fullstack::http::Method::#method_ident,\n                    #query_endpoint,\n                    &#query_tokens,\n                );\n\n                let response = (&&&&&&&&&&&&&&ServerFnEncoder::<___Body_Serialize___<#(#body_json_types,)*>, (#(#body_json_types,)*)>::new())\n                    .fetch_client(client, ___Body_Serialize___ { #(#body_json_names,)* }, #unpack_closure)\n                    .await;\n\n                let decoded = (&&&&&ServerFnDecoder::<#out_ty>::new())\n                    .decode_client_response(response)\n                    .await;\n\n                let result = (&&&&&ServerFnDecoder::<#out_ty>::new())\n                    .decode_client_err(decoded)\n                    .await;\n\n                return result;\n            }\n\n            // On the server, we expand the tokens and submit the function to inventory\n            #[cfg(feature = \"server\")] {\n                #function_on_server\n\n                #[allow(clippy::unused_unit)]\n                fn __inner__function__ #impl_generics(\n                    ___state: #__axum::extract::State<FullstackContext>,\n                    ___request: #__axum::extract::Request,\n                ) -> std::pin::Pin<Box<dyn std::future::Future<Output = #__axum::response::Response>>> #where_clause {\n                    Box::pin(async move {\n                         match (&&&&&&&&&&&&&&ServerFnEncoder::<___Body_Serialize___<#(#body_json_types,)*>, (#(#body_json_types,)*)>::new()).extract_axum(___state.0, ___request, #unpack_closure).await {\n                            Ok(((#(#body_json_names,)* ), (#(#extracted_as_server_headers,)* #(#server_names,)*) )) => {\n                                // Call the user function\n                                let res = #fn_on_server_name #ty_generics(#(#extracted_idents,)* #(#body_json_names,)* #(#server_names,)*).await;\n\n                                // Encode the response Into a `Result<T, E>`\n                                let encoded = (&&&&&&ServerFnDecoder::<#out_ty>::new()).make_axum_response(res);\n\n                                // And then encode `Result<T, E>` into `Response`\n                                (&&&&&ServerFnDecoder::<#out_ty>::new()).make_axum_error(encoded)\n                            },\n                            Err(res) => res,\n                        }\n                    })\n                }\n\n                dioxus_server::inventory::submit! {\n                    dioxus_server::ServerFunction::new(\n                        dioxus_server::http::Method::#method_ident,\n                        __ENDPOINT_PATH,\n                        || {\n                            dioxus_server::ServerFunction::make_handler(dioxus_server::http::Method::#method_ident, __inner__function__ #ty_generics)\n                                #(#middleware_layers)*\n                        }\n                    )\n                }\n\n                // Extract the server arguments from the context if needed.\n                let (#(#server_names,)*) = dioxus_fullstack::FullstackContext::extract::<(#(#server_types,)*), _>().await?;\n\n                // Call the function directly\n                return #fn_on_server_name #ty_generics(\n                    #(#extracted_idents,)*\n                    #(#body_json_names,)*\n                    #(#server_names,)*\n                ).await;\n            }\n\n            #[allow(unreachable_code)]\n            {\n                unreachable!()\n            }\n        }\n    })\n}\n\nstruct CompiledRoute {\n    method: Method,\n    #[allow(clippy::type_complexity)]\n    path_params: Vec<(Slash, PathParam)>,\n    query_params: Vec<QueryParam>,\n    route_lit: Option<LitStr>,\n    prefix: Option<LitStr>,\n    oapi_options: Option<OapiOptions>,\n    server_args: Punctuated<FnArg, Comma>,\n}\n\nstruct QueryParam {\n    arg_idx: usize,\n    name: String,\n    binding: Ident,\n    catch_all: bool,\n    ty: Box<Type>,\n}\n\nimpl CompiledRoute {\n    fn to_axum_path_string(&self) -> Option<String> {\n        if self.prefix.is_some() {\n            return None;\n        }\n\n        let mut path = String::new();\n\n        for (_slash, param) in &self.path_params {\n            path.push('/');\n            match param {\n                PathParam::Capture(lit, _brace_1, _, _, _brace_2) => {\n                    path.push('{');\n                    path.push_str(&lit.value());\n                    path.push('}');\n                }\n                PathParam::WildCard(lit, _brace_1, _, _, _, _brace_2) => {\n                    path.push('{');\n                    path.push('*');\n                    path.push_str(&lit.value());\n                    path.push('}');\n                }\n                PathParam::Static(lit) => path.push_str(&lit.value()),\n            }\n        }\n\n        Some(path)\n    }\n\n    /// Removes the arguments in `route` from `args`, and merges them in the output.\n    pub fn from_route(\n        mut route: Route,\n        function: &ItemFn,\n        with_aide: bool,\n        method_from_macro: Option<Method>,\n    ) -> syn::Result<Self> {\n        if !with_aide && route.oapi_options.is_some() {\n            return Err(syn::Error::new(\n                Span::call_site(),\n                \"Use `api_route` instead of `route` to use OpenAPI options\",\n            ));\n        } else if with_aide && route.oapi_options.is_none() {\n            route.oapi_options = Some(OapiOptions {\n                summary: None,\n                description: None,\n                id: None,\n                hidden: None,\n                tags: None,\n                security: None,\n                responses: None,\n                transform: None,\n            });\n        }\n\n        let sig = &function.sig;\n        let mut arg_map = sig\n            .inputs\n            .iter()\n            .enumerate()\n            .filter_map(|(i, item)| match item {\n                syn::FnArg::Receiver(_) => None,\n                syn::FnArg::Typed(pat_type) => Some((i, pat_type)),\n            })\n            .filter_map(|(i, pat_type)| match &*pat_type.pat {\n                syn::Pat::Ident(ident) => Some((ident.ident.clone(), (pat_type.ty.clone(), i))),\n                _ => None,\n            })\n            .collect::<HashMap<_, _>>();\n\n        for (_slash, path_param) in &mut route.path_params {\n            match path_param {\n                PathParam::Capture(_lit, _, ident, ty, _) => {\n                    let (new_ident, new_ty) = arg_map.remove_entry(ident).ok_or_else(|| {\n                        syn::Error::new(\n                            ident.span(),\n                            format!(\"path parameter `{}` not found in function arguments\", ident),\n                        )\n                    })?;\n                    *ident = new_ident;\n                    *ty = new_ty.0;\n                }\n                PathParam::WildCard(_lit, _, _star, ident, ty, _) => {\n                    let (new_ident, new_ty) = arg_map.remove_entry(ident).ok_or_else(|| {\n                        syn::Error::new(\n                            ident.span(),\n                            format!(\"path parameter `{}` not found in function arguments\", ident),\n                        )\n                    })?;\n                    *ident = new_ident;\n                    *ty = new_ty.0;\n                }\n                PathParam::Static(_lit) => {}\n            }\n        }\n\n        let mut query_params = Vec::new();\n        for param in route.query_params {\n            let (ident, ty) = arg_map.remove_entry(&param.binding).ok_or_else(|| {\n                syn::Error::new(\n                    param.binding.span(),\n                    format!(\n                        \"query parameter `{}` not found in function arguments\",\n                        param.binding\n                    ),\n                )\n            })?;\n            query_params.push(QueryParam {\n                binding: ident,\n                name: param.name,\n                catch_all: param.catch_all,\n                ty: ty.0,\n                arg_idx: ty.1,\n            });\n        }\n\n        // Disallow multiple query params if one is a catch-all\n        if query_params.iter().any(|param| param.catch_all) && query_params.len() > 1 {\n            return Err(syn::Error::new(\n                Span::call_site(),\n                \"Cannot have multiple query parameters when one is a catch-all\",\n            ));\n        }\n\n        if let Some(options) = route.oapi_options.as_mut() {\n            options.merge_with_fn(function)\n        }\n\n        let method = match (method_from_macro, route.method) {\n            (Some(method), None) => method,\n            (None, Some(method)) => method,\n            (Some(_), Some(_)) => {\n                return Err(syn::Error::new(\n                    Span::call_site(),\n                    \"HTTP method specified both in macro and in attribute\",\n                ))\n            }\n            (None, None) => {\n                return Err(syn::Error::new(\n                    Span::call_site(),\n                    \"HTTP method not specified in macro or in attribute\",\n                ))\n            }\n        };\n\n        Ok(Self {\n            method,\n            route_lit: route.route_lit,\n            path_params: route.path_params,\n            query_params,\n            oapi_options: route.oapi_options,\n            prefix: route.prefix,\n            server_args: route.server_args,\n        })\n    }\n\n    pub fn query_is_catchall(&self) -> bool {\n        self.query_params.iter().any(|param| param.catch_all)\n    }\n\n    pub fn extracted_as_server_headers(&self, query_tokens: TokenStream2) -> Vec<Pat> {\n        let mut out = vec![];\n\n        // Add the path extractor\n        out.push({\n            let path_iter = self\n                .path_params\n                .iter()\n                .filter_map(|(_slash, path_param)| path_param.capture());\n            let idents = path_iter.clone().map(|item| item.0);\n            parse_quote! {\n                dioxus_server::axum::extract::Path((#(#idents,)*))\n            }\n        });\n\n        out.push(parse_quote!(\n            dioxus_fullstack::payloads::Query(#query_tokens)\n        ));\n\n        out\n    }\n\n    pub fn query_params_struct(&self, with_aide: bool) -> TokenStream2 {\n        let fields = self.query_params.iter().map(|item| {\n            let name = &item.name;\n            let binding = &item.binding;\n            let ty = &item.ty;\n            if item.catch_all {\n                quote! {}\n            } else if item.binding != item.name {\n                quote! {\n                    #[serde(rename = #name)]\n                    #binding: #ty,\n                }\n            } else {\n                quote! { #binding: #ty, }\n            }\n        });\n        let derive = match with_aide {\n            true => quote! {\n                #[derive(serde::Deserialize, serde::Serialize, ::schemars::JsonSchema)]\n                #[serde(crate = \"serde\")]\n            },\n            false => quote! {\n                #[derive(serde::Deserialize, serde::Serialize)]\n                #[serde(crate = \"serde\")]\n            },\n        };\n        quote! {\n            #derive\n            struct __QueryParams__ {\n                #(#fields)*\n            }\n        }\n    }\n\n    pub fn extracted_idents(&self) -> Vec<Ident> {\n        let mut idents = Vec::new();\n        for (_slash, path_param) in &self.path_params {\n            if let Some((ident, _ty)) = path_param.capture() {\n                idents.push(ident.clone());\n            }\n        }\n        for param in &self.query_params {\n            idents.push(param.binding.clone());\n        }\n        idents\n    }\n\n    fn remaining_pattypes_named(&self, args: &Punctuated<FnArg, Comma>) -> Vec<(usize, PatType)> {\n        args.iter()\n            .enumerate()\n            .filter_map(|(i, item)| {\n                if let FnArg::Typed(pat_type) = item {\n                    if let syn::Pat::Ident(pat_ident) = &*pat_type.pat {\n                        if self.path_params.iter().any(|(_slash, path_param)| {\n                            if let Some((path_ident, _ty)) = path_param.capture() {\n                                path_ident == &pat_ident.ident\n                            } else {\n                                false\n                            }\n                        }) || self\n                            .query_params\n                            .iter()\n                            .any(|query| query.binding == pat_ident.ident)\n                        {\n                            return None;\n                        }\n                    }\n\n                    Some((i, pat_type.clone()))\n                } else {\n                    unimplemented!(\"Self type is not supported\")\n                }\n            })\n            .collect()\n    }\n\n    pub(crate) fn to_doc_comments(&self) -> TokenStream2 {\n        let mut doc = format!(\n            \"# Handler information\n- Method: `{}`\n- Path: `{}`\",\n            self.method.to_axum_method_name(),\n            self.route_lit\n                .as_ref()\n                .map(|lit| lit.value())\n                .unwrap_or_else(|| \"<auto>\".into()),\n        );\n\n        if let Some(options) = &self.oapi_options {\n            let summary = options\n                .summary\n                .as_ref()\n                .map(|(_, summary)| format!(\"\\\"{}\\\"\", summary.value()))\n                .unwrap_or(\"None\".to_string());\n            let description = options\n                .description\n                .as_ref()\n                .map(|(_, description)| format!(\"\\\"{}\\\"\", description.value()))\n                .unwrap_or(\"None\".to_string());\n            let id = options\n                .id\n                .as_ref()\n                .map(|(_, id)| format!(\"\\\"{}\\\"\", id.value()))\n                .unwrap_or(\"None\".to_string());\n            let hidden = options\n                .hidden\n                .as_ref()\n                .map(|(_, hidden)| hidden.value().to_string())\n                .unwrap_or(\"None\".to_string());\n            let tags = options\n                .tags\n                .as_ref()\n                .map(|(_, tags)| tags.to_string())\n                .unwrap_or(\"[]\".to_string());\n            let security = options\n                .security\n                .as_ref()\n                .map(|(_, security)| security.to_string())\n                .unwrap_or(\"{}\".to_string());\n\n            doc = format!(\n                \"{doc}\n\n## OpenAPI\n- Summary: `{summary}`\n- Description: `{description}`\n- Operation id: `{id}`\n- Tags: `{tags}`\n- Security: `{security}`\n- Hidden: `{hidden}`\n\"\n            );\n        }\n\n        quote!(\n            #[doc = #doc]\n        )\n    }\n\n    fn url_without_queries_for_format(&self) -> Option<String> {\n        // If there's a prefix, then it's an old-style route, and we can't generate a format string.\n        if self.prefix.is_some() {\n            return None;\n        }\n\n        // If there's no explicit route, we can't generate a format string this way.\n        let _lit = self.route_lit.as_ref()?;\n\n        let url_without_queries =\n            self.path_params\n                .iter()\n                .fold(String::new(), |mut acc, (_slash, param)| {\n                    acc.push('/');\n                    match param {\n                        PathParam::Capture(lit, _brace_1, _, _, _brace_2) => {\n                            acc.push_str(&format!(\"{{{}}}\", lit.value()));\n                        }\n                        PathParam::WildCard(lit, _brace_1, _, _, _, _brace_2) => {\n                            // no `*` since we want to use the argument *as the wildcard* when making requests\n                            // it's not super applicable to server functions, more for general route generation\n                            acc.push_str(&format!(\"{{{}}}\", lit.value()));\n                        }\n                        PathParam::Static(lit) => {\n                            acc.push_str(&lit.value());\n                        }\n                    }\n                    acc\n                });\n\n        let prefix = self\n            .prefix\n            .as_ref()\n            .cloned()\n            .unwrap_or_else(|| LitStr::new(\"\", Span::call_site()))\n            .value();\n        let full_url = format!(\n            \"{}{}{}\",\n            prefix,\n            if url_without_queries.starts_with(\"/\") {\n                \"\"\n            } else {\n                \"/\"\n            },\n            url_without_queries\n        );\n\n        Some(full_url)\n    }\n}\n\nstruct RouteParser {\n    path_params: Vec<(Slash, PathParam)>,\n    query_params: Vec<QueryParam>,\n}\n\nimpl RouteParser {\n    fn new(lit: LitStr) -> syn::Result<Self> {\n        let val = lit.value();\n        let span = lit.span();\n        let split_route = val.split('?').collect::<Vec<_>>();\n        if split_route.len() > 2 {\n            return Err(syn::Error::new(span, \"expected at most one '?'\"));\n        }\n\n        let path = split_route[0];\n        if !path.starts_with('/') {\n            return Err(syn::Error::new(span, \"expected path to start with '/'\"));\n        }\n        let path = path.strip_prefix('/').unwrap();\n\n        let mut path_params = Vec::new();\n\n        for path_param in path.split('/') {\n            path_params.push((\n                Slash(span),\n                PathParam::new(path_param, span, Box::new(parse_quote!(())))?,\n            ));\n        }\n\n        let path_param_len = path_params.len();\n        for (i, (_slash, path_param)) in path_params.iter().enumerate() {\n            match path_param {\n                PathParam::WildCard(_, _, _, _, _, _) => {\n                    if i != path_param_len - 1 {\n                        return Err(syn::Error::new(\n                            span,\n                            \"wildcard path param must be the last path param\",\n                        ));\n                    }\n                }\n                PathParam::Capture(_, _, _, _, _) => (),\n                PathParam::Static(lit) => {\n                    if lit.value() == \"*\" && i != path_param_len - 1 {\n                        return Err(syn::Error::new(\n                            span,\n                            \"wildcard path param must be the last path param\",\n                        ));\n                    }\n                }\n            }\n        }\n\n        let mut query_params = Vec::new();\n        if split_route.len() == 2 {\n            let query = split_route[1];\n            for query_param in query.split('&') {\n                if query_param.starts_with(\":\") {\n                    let ident = Ident::new(query_param.strip_prefix(\":\").unwrap(), span);\n\n                    query_params.push(QueryParam {\n                        name: ident.to_string(),\n                        binding: ident,\n                        catch_all: true,\n                        ty: parse_quote!(()),\n                        arg_idx: usize::MAX,\n                    });\n                } else if query_param.starts_with(\"{\") && query_param.ends_with(\"}\") {\n                    let ident = Ident::new(\n                        query_param\n                            .strip_prefix(\"{\")\n                            .unwrap()\n                            .strip_suffix(\"}\")\n                            .unwrap(),\n                        span,\n                    );\n\n                    query_params.push(QueryParam {\n                        name: ident.to_string(),\n                        binding: ident,\n                        catch_all: true,\n                        ty: parse_quote!(()),\n                        arg_idx: usize::MAX,\n                    });\n                } else {\n                    // if there's an `=` in the query param, we only take the left side as the name, and the right side is the binding\n                    let name;\n                    let binding;\n                    if let Some((n, b)) = query_param.split_once('=') {\n                        name = n;\n                        binding = Ident::new(b, span);\n                    } else {\n                        name = query_param;\n                        binding = Ident::new(query_param, span);\n                    }\n\n                    query_params.push(QueryParam {\n                        name: name.to_string(),\n                        binding,\n                        catch_all: false,\n                        ty: parse_quote!(()),\n                        arg_idx: usize::MAX,\n                    });\n                }\n            }\n        }\n\n        Ok(Self {\n            path_params,\n            query_params,\n        })\n    }\n}\n\nenum PathParam {\n    WildCard(LitStr, Brace, Star, Ident, Box<Type>, Brace),\n    Capture(LitStr, Brace, Ident, Box<Type>, Brace),\n    Static(LitStr),\n}\n\nimpl PathParam {\n    fn _captures(&self) -> bool {\n        matches!(self, Self::Capture(..) | Self::WildCard(..))\n    }\n\n    fn capture(&self) -> Option<(&Ident, &Type)> {\n        match self {\n            Self::Capture(_, _, ident, ty, _) => Some((ident, ty)),\n            Self::WildCard(_, _, _, ident, ty, _) => Some((ident, ty)),\n            _ => None,\n        }\n    }\n\n    fn new(str: &str, span: Span, ty: Box<Type>) -> syn::Result<Self> {\n        let ok = if str.starts_with('{') {\n            let str = str\n                .strip_prefix('{')\n                .unwrap()\n                .strip_suffix('}')\n                .ok_or_else(|| {\n                    syn::Error::new(span, \"expected path param to be wrapped in curly braces\")\n                })?;\n            Self::Capture(\n                LitStr::new(str, span),\n                Brace(span),\n                Ident::new(str, span),\n                ty,\n                Brace(span),\n            )\n        } else if str.starts_with('*') && str.len() > 1 {\n            let str = str.strip_prefix('*').unwrap();\n            Self::WildCard(\n                LitStr::new(str, span),\n                Brace(span),\n                Star(span),\n                Ident::new(str, span),\n                ty,\n                Brace(span),\n            )\n        } else if str.starts_with(':') && str.len() > 1 {\n            let str = str.strip_prefix(':').unwrap();\n            Self::Capture(\n                LitStr::new(str, span),\n                Brace(span),\n                Ident::new(str, span),\n                ty,\n                Brace(span),\n            )\n        } else {\n            Self::Static(LitStr::new(str, span))\n        };\n\n        Ok(ok)\n    }\n}\n\nstruct OapiOptions {\n    summary: Option<(Ident, LitStr)>,\n    description: Option<(Ident, LitStr)>,\n    id: Option<(Ident, LitStr)>,\n    hidden: Option<(Ident, LitBool)>,\n    tags: Option<(Ident, StrArray)>,\n    security: Option<(Ident, Security)>,\n    responses: Option<(Ident, Responses)>,\n    transform: Option<(Ident, ExprClosure)>,\n}\n\nstruct Security(Vec<(LitStr, StrArray)>);\nimpl Parse for Security {\n    fn parse(input: ParseStream) -> syn::Result<Self> {\n        let inner;\n        braced!(inner in input);\n\n        let mut arr = Vec::new();\n        while !inner.is_empty() {\n            let scheme = inner.parse::<LitStr>()?;\n            let _ = inner.parse::<Token![:]>()?;\n            let scopes = inner.parse::<StrArray>()?;\n            let _ = inner.parse::<Token![,]>().ok();\n            arr.push((scheme, scopes));\n        }\n\n        Ok(Self(arr))\n    }\n}\n\nimpl std::fmt::Display for Security {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(f, \"{{\")?;\n        for (i, (scheme, scopes)) in self.0.iter().enumerate() {\n            if i > 0 {\n                write!(f, \", \")?;\n            }\n            write!(f, \"{}: {}\", scheme.value(), scopes)?;\n        }\n        write!(f, \"}}\")\n    }\n}\n\nstruct Responses(Vec<(LitInt, Type)>);\nimpl Parse for Responses {\n    fn parse(input: ParseStream) -> syn::Result<Self> {\n        let inner;\n        braced!(inner in input);\n\n        let mut arr = Vec::new();\n        while !inner.is_empty() {\n            let status = inner.parse::<LitInt>()?;\n            let _ = inner.parse::<Token![:]>()?;\n            let ty = inner.parse::<Type>()?;\n            let _ = inner.parse::<Token![,]>().ok();\n            arr.push((status, ty));\n        }\n\n        Ok(Self(arr))\n    }\n}\n\nimpl std::fmt::Display for Responses {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(f, \"{{\")?;\n        for (i, (status, ty)) in self.0.iter().enumerate() {\n            if i > 0 {\n                write!(f, \", \")?;\n            }\n            write!(f, \"{}: {}\", status, ty.to_token_stream())?;\n        }\n        write!(f, \"}}\")\n    }\n}\n\n#[derive(Clone)]\nstruct StrArray(Vec<LitStr>);\nimpl Parse for StrArray {\n    fn parse(input: ParseStream) -> syn::Result<Self> {\n        let inner;\n        bracketed!(inner in input);\n        let mut arr = Vec::new();\n        while !inner.is_empty() {\n            arr.push(inner.parse::<LitStr>()?);\n            inner.parse::<Token![,]>().ok();\n        }\n        Ok(Self(arr))\n    }\n}\n\nimpl std::fmt::Display for StrArray {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(f, \"[\")?;\n        for (i, lit) in self.0.iter().enumerate() {\n            if i > 0 {\n                write!(f, \", \")?;\n            }\n            write!(f, \"\\\"{}\\\"\", lit.value())?;\n        }\n        write!(f, \"]\")\n    }\n}\n\nimpl Parse for OapiOptions {\n    fn parse(input: ParseStream) -> syn::Result<Self> {\n        let mut this = Self {\n            summary: None,\n            description: None,\n            id: None,\n            hidden: None,\n            tags: None,\n            security: None,\n            responses: None,\n            transform: None,\n        };\n\n        while !input.is_empty() {\n            let ident = input.parse::<Ident>()?;\n            let _ = input.parse::<Token![:]>()?;\n            match ident.to_string().as_str() {\n                \"summary\" => this.summary = Some((ident, input.parse()?)),\n                \"description\" => this.description = Some((ident, input.parse()?)),\n                \"id\" => this.id = Some((ident, input.parse()?)),\n                \"hidden\" => this.hidden = Some((ident, input.parse()?)),\n                \"tags\" => this.tags = Some((ident, input.parse()?)),\n                \"security\" => this.security = Some((ident, input.parse()?)),\n                \"responses\" => this.responses = Some((ident, input.parse()?)),\n                \"transform\" => this.transform = Some((ident, input.parse()?)),\n                _ => {\n                    return Err(syn::Error::new(\n                        ident.span(),\n                        \"unexpected field, expected one of (summary, description, id, hidden, tags, security, responses, transform)\",\n                    ))\n                }\n            }\n            let _ = input.parse::<Token![,]>().ok();\n        }\n\n        Ok(this)\n    }\n}\n\nimpl OapiOptions {\n    fn merge_with_fn(&mut self, function: &ItemFn) {\n        if self.description.is_none() {\n            self.description = doc_iter(&function.attrs)\n                .skip(2)\n                .map(|item| item.value())\n                .reduce(|mut acc, item| {\n                    acc.push('\\n');\n                    acc.push_str(&item);\n                    acc\n                })\n                .map(|item| (parse_quote!(description), parse_quote!(#item)))\n        }\n        if self.summary.is_none() {\n            self.summary = doc_iter(&function.attrs)\n                .next()\n                .map(|item| (parse_quote!(summary), item.clone()))\n        }\n        if self.id.is_none() {\n            let id = &function.sig.ident;\n            self.id = Some((parse_quote!(id), LitStr::new(&id.to_string(), id.span())));\n        }\n    }\n}\n\nfn doc_iter(attrs: &[Attribute]) -> impl Iterator<Item = &LitStr> + '_ {\n    attrs\n        .iter()\n        .filter(|attr| attr.path().is_ident(\"doc\"))\n        .map(|attr| {\n            let Meta::NameValue(meta) = &attr.meta else {\n                panic!(\"doc attribute is not a name-value attribute\");\n            };\n            let Expr::Lit(lit) = &meta.value else {\n                panic!(\"doc attribute is not a string literal\");\n            };\n            let Lit::Str(lit_str) = &lit.lit else {\n                panic!(\"doc attribute is not a string literal\");\n            };\n            lit_str\n        })\n}\n\nstruct Route {\n    method: Option<Method>,\n    path_params: Vec<(Slash, PathParam)>,\n    query_params: Vec<QueryParam>,\n    route_lit: Option<LitStr>,\n    prefix: Option<LitStr>,\n    oapi_options: Option<OapiOptions>,\n    server_args: Punctuated<FnArg, Comma>,\n\n    // todo: support these since `server_fn` had them\n    _input_encoding: Option<Type>,\n    _output_encoding: Option<Type>,\n}\n\nimpl Parse for Route {\n    fn parse(input: ParseStream) -> syn::Result<Self> {\n        let method = if input.peek(Ident) {\n            Some(input.parse::<Method>()?)\n        } else {\n            None\n        };\n\n        let route_lit = input.parse::<LitStr>()?;\n        let RouteParser {\n            path_params,\n            query_params,\n        } = RouteParser::new(route_lit.clone())?;\n\n        let oapi_options = input\n            .peek(Brace)\n            .then(|| {\n                let inner;\n                braced!(inner in input);\n                inner.parse::<OapiOptions>()\n            })\n            .transpose()?;\n\n        let server_args = if input.peek(Comma) {\n            let _ = input.parse::<Comma>()?;\n            input.parse_terminated(FnArg::parse, Comma)?\n        } else {\n            Punctuated::new()\n        };\n\n        Ok(Route {\n            method,\n            path_params,\n            query_params,\n            route_lit: Some(route_lit),\n            oapi_options,\n            server_args,\n            prefix: None,\n            _input_encoding: None,\n            _output_encoding: None,\n        })\n    }\n}\n\n#[derive(Clone)]\nenum Method {\n    Get(Ident),\n    Post(Ident),\n    Put(Ident),\n    Delete(Ident),\n    Head(Ident),\n    Connect(Ident),\n    Options(Ident),\n    Trace(Ident),\n    Patch(Ident),\n}\n\nimpl ToTokens for Method {\n    fn to_tokens(&self, tokens: &mut TokenStream2) {\n        match self {\n            Self::Get(ident)\n            | Self::Post(ident)\n            | Self::Put(ident)\n            | Self::Delete(ident)\n            | Self::Head(ident)\n            | Self::Connect(ident)\n            | Self::Options(ident)\n            | Self::Trace(ident)\n            | Self::Patch(ident) => {\n                ident.to_tokens(tokens);\n            }\n        }\n    }\n}\n\nimpl Parse for Method {\n    fn parse(input: ParseStream) -> syn::Result<Self> {\n        let ident = input.parse::<Ident>()?;\n        match ident.to_string().to_uppercase().as_str() {\n            \"GET\" => Ok(Self::Get(ident)),\n            \"POST\" => Ok(Self::Post(ident)),\n            \"PUT\" => Ok(Self::Put(ident)),\n            \"DELETE\" => Ok(Self::Delete(ident)),\n            \"HEAD\" => Ok(Self::Head(ident)),\n            \"CONNECT\" => Ok(Self::Connect(ident)),\n            \"OPTIONS\" => Ok(Self::Options(ident)),\n            \"TRACE\" => Ok(Self::Trace(ident)),\n            _ => Err(input\n                .error(\"expected one of (GET, POST, PUT, DELETE, HEAD, CONNECT, OPTIONS, TRACE)\")),\n        }\n    }\n}\n\nimpl Method {\n    fn to_axum_method_name(&self) -> Ident {\n        match self {\n            Self::Get(span) => Ident::new(\"get\", span.span()),\n            Self::Post(span) => Ident::new(\"post\", span.span()),\n            Self::Put(span) => Ident::new(\"put\", span.span()),\n            Self::Delete(span) => Ident::new(\"delete\", span.span()),\n            Self::Head(span) => Ident::new(\"head\", span.span()),\n            Self::Connect(span) => Ident::new(\"connect\", span.span()),\n            Self::Options(span) => Ident::new(\"options\", span.span()),\n            Self::Trace(span) => Ident::new(\"trace\", span.span()),\n            Self::Patch(span) => Ident::new(\"patch\", span.span()),\n        }\n    }\n\n    fn new_from_string(s: &str) -> Self {\n        match s.to_uppercase().as_str() {\n            \"GET\" => Self::Get(Ident::new(\"GET\", Span::call_site())),\n            \"POST\" => Self::Post(Ident::new(\"POST\", Span::call_site())),\n            \"PUT\" => Self::Put(Ident::new(\"PUT\", Span::call_site())),\n            \"DELETE\" => Self::Delete(Ident::new(\"DELETE\", Span::call_site())),\n            \"HEAD\" => Self::Head(Ident::new(\"HEAD\", Span::call_site())),\n            \"CONNECT\" => Self::Connect(Ident::new(\"CONNECT\", Span::call_site())),\n            \"OPTIONS\" => Self::Options(Ident::new(\"OPTIONS\", Span::call_site())),\n            \"TRACE\" => Self::Trace(Ident::new(\"TRACE\", Span::call_site())),\n            \"PATCH\" => Self::Patch(Ident::new(\"PATCH\", Span::call_site())),\n            _ => panic!(\"expected one of (GET, POST, PUT, DELETE, HEAD, CONNECT, OPTIONS, TRACE)\"),\n        }\n    }\n}\n\nmod kw {\n    syn::custom_keyword!(with);\n}\n\n/// The arguments to the `server` macro.\n///\n/// These originally came from the `server_fn` crate, but many no longer apply after the 0.7 fullstack\n/// overhaul. We keep the parser here for temporary backwards compatibility with existing code, but\n/// these arguments will be removed in a future release.\n#[derive(Debug)]\n#[non_exhaustive]\n#[allow(unused)]\nstruct ServerFnArgs {\n    /// The name of the struct that will implement the server function trait\n    /// and be submitted to inventory.\n    struct_name: Option<Ident>,\n    /// The prefix to use for the server function URL.\n    prefix: Option<LitStr>,\n    /// The input http encoding to use for the server function.\n    input: Option<Type>,\n    /// Additional traits to derive on the input struct for the server function.\n    input_derive: Option<ExprTuple>,\n    /// The output http encoding to use for the server function.\n    output: Option<Type>,\n    /// The path to the server function crate.\n    fn_path: Option<LitStr>,\n    /// The server type to use for the server function.\n    server: Option<Type>,\n    /// The client type to use for the server function.\n    client: Option<Type>,\n    /// The custom wrapper to use for the server function struct.\n    custom_wrapper: Option<syn::Path>,\n    /// If the generated input type should implement `From` the only field in the input\n    impl_from: Option<LitBool>,\n    /// If the generated input type should implement `Deref` to the only field in the input\n    impl_deref: Option<LitBool>,\n    /// The protocol to use for the server function implementation.\n    protocol: Option<Type>,\n    builtin_encoding: bool,\n    /// Server-only extractors (e.g., headers: HeaderMap, cookies: Cookies).\n    /// These are arguments that exist purely on the server side.\n    server_args: Punctuated<FnArg, Comma>,\n}\n\nimpl Parse for ServerFnArgs {\n    fn parse(stream: ParseStream) -> syn::Result<Self> {\n        // legacy 4-part arguments\n        let mut struct_name: Option<Ident> = None;\n        let mut prefix: Option<LitStr> = None;\n        let mut encoding: Option<LitStr> = None;\n        let mut fn_path: Option<LitStr> = None;\n\n        // new arguments: can only be keyed by name\n        let mut input: Option<Type> = None;\n        let mut input_derive: Option<ExprTuple> = None;\n        let mut output: Option<Type> = None;\n        let mut server: Option<Type> = None;\n        let mut client: Option<Type> = None;\n        let mut custom_wrapper: Option<syn::Path> = None;\n        let mut impl_from: Option<LitBool> = None;\n        let mut impl_deref: Option<LitBool> = None;\n        let mut protocol: Option<Type> = None;\n\n        let mut use_key_and_value = false;\n        let mut arg_pos = 0;\n\n        // Server-only extractors (key: Type pattern)\n        // These come after config options (key = value pattern)\n        // Example: #[server(endpoint = \"/api/chat\", headers: HeaderMap, cookies: Cookies)]\n        let mut server_args: Punctuated<FnArg, Comma> = Punctuated::new();\n\n        while !stream.is_empty() {\n            // Check if this looks like an extractor (Ident : Type)\n            // If so, break out to parse extractors - they must come last\n            if stream.peek(Ident) && stream.peek2(Token![:]) {\n                break;\n            }\n\n            arg_pos += 1;\n            let lookahead = stream.lookahead1();\n            if lookahead.peek(Ident) {\n                let key_or_value: Ident = stream.parse()?;\n\n                let lookahead = stream.lookahead1();\n                if lookahead.peek(Token![=]) {\n                    stream.parse::<Token![=]>()?;\n                    let key = key_or_value;\n                    use_key_and_value = true;\n                    if key == \"name\" {\n                        if struct_name.is_some() {\n                            return Err(syn::Error::new(\n                                key.span(),\n                                \"keyword argument repeated: `name`\",\n                            ));\n                        }\n                        struct_name = Some(stream.parse()?);\n                    } else if key == \"prefix\" {\n                        if prefix.is_some() {\n                            return Err(syn::Error::new(\n                                key.span(),\n                                \"keyword argument repeated: `prefix`\",\n                            ));\n                        }\n                        prefix = Some(stream.parse()?);\n                    } else if key == \"encoding\" {\n                        if encoding.is_some() {\n                            return Err(syn::Error::new(\n                                key.span(),\n                                \"keyword argument repeated: `encoding`\",\n                            ));\n                        }\n                        encoding = Some(stream.parse()?);\n                    } else if key == \"endpoint\" {\n                        if fn_path.is_some() {\n                            return Err(syn::Error::new(\n                                key.span(),\n                                \"keyword argument repeated: `endpoint`\",\n                            ));\n                        }\n                        fn_path = Some(stream.parse()?);\n                    } else if key == \"input\" {\n                        if encoding.is_some() {\n                            return Err(syn::Error::new(\n                                key.span(),\n                                \"`encoding` and `input` should not both be \\\n                                 specified\",\n                            ));\n                        } else if input.is_some() {\n                            return Err(syn::Error::new(\n                                key.span(),\n                                \"keyword argument repeated: `input`\",\n                            ));\n                        }\n                        input = Some(stream.parse()?);\n                    } else if key == \"input_derive\" {\n                        if input_derive.is_some() {\n                            return Err(syn::Error::new(\n                                key.span(),\n                                \"keyword argument repeated: `input_derive`\",\n                            ));\n                        }\n                        input_derive = Some(stream.parse()?);\n                    } else if key == \"output\" {\n                        if encoding.is_some() {\n                            return Err(syn::Error::new(\n                                key.span(),\n                                \"`encoding` and `output` should not both be \\\n                                 specified\",\n                            ));\n                        } else if output.is_some() {\n                            return Err(syn::Error::new(\n                                key.span(),\n                                \"keyword argument repeated: `output`\",\n                            ));\n                        }\n                        output = Some(stream.parse()?);\n                    } else if key == \"server\" {\n                        if server.is_some() {\n                            return Err(syn::Error::new(\n                                key.span(),\n                                \"keyword argument repeated: `server`\",\n                            ));\n                        }\n                        server = Some(stream.parse()?);\n                    } else if key == \"client\" {\n                        if client.is_some() {\n                            return Err(syn::Error::new(\n                                key.span(),\n                                \"keyword argument repeated: `client`\",\n                            ));\n                        }\n                        client = Some(stream.parse()?);\n                    } else if key == \"custom\" {\n                        if custom_wrapper.is_some() {\n                            return Err(syn::Error::new(\n                                key.span(),\n                                \"keyword argument repeated: `custom`\",\n                            ));\n                        }\n                        custom_wrapper = Some(stream.parse()?);\n                    } else if key == \"impl_from\" {\n                        if impl_from.is_some() {\n                            return Err(syn::Error::new(\n                                key.span(),\n                                \"keyword argument repeated: `impl_from`\",\n                            ));\n                        }\n                        impl_from = Some(stream.parse()?);\n                    } else if key == \"impl_deref\" {\n                        if impl_deref.is_some() {\n                            return Err(syn::Error::new(\n                                key.span(),\n                                \"keyword argument repeated: `impl_deref`\",\n                            ));\n                        }\n                        impl_deref = Some(stream.parse()?);\n                    } else if key == \"protocol\" {\n                        if protocol.is_some() {\n                            return Err(syn::Error::new(\n                                key.span(),\n                                \"keyword argument repeated: `protocol`\",\n                            ));\n                        }\n                        protocol = Some(stream.parse()?);\n                    } else {\n                        return Err(lookahead.error());\n                    }\n                } else {\n                    let value = key_or_value;\n                    if use_key_and_value {\n                        return Err(syn::Error::new(\n                            value.span(),\n                            \"positional argument follows keyword argument\",\n                        ));\n                    }\n                    if arg_pos == 1 {\n                        struct_name = Some(value)\n                    } else {\n                        return Err(syn::Error::new(value.span(), \"expected string literal\"));\n                    }\n                }\n            } else if lookahead.peek(LitStr) {\n                if use_key_and_value {\n                    return Err(syn::Error::new(\n                        stream.span(),\n                        \"If you use keyword arguments (e.g., `name` = \\\n                         Something), then you can no longer use arguments \\\n                         without a keyword.\",\n                    ));\n                }\n                match arg_pos {\n                    1 => return Err(lookahead.error()),\n                    2 => prefix = Some(stream.parse()?),\n                    3 => encoding = Some(stream.parse()?),\n                    4 => fn_path = Some(stream.parse()?),\n                    _ => return Err(syn::Error::new(stream.span(), \"unexpected extra argument\")),\n                }\n            } else {\n                return Err(lookahead.error());\n            }\n\n            if !stream.is_empty() {\n                stream.parse::<Token![,]>()?;\n            }\n        }\n\n        // Now parse any remaining extractors (key: Type pattern)\n        while !stream.is_empty() {\n            if stream.peek(Ident) && stream.peek2(Token![:]) {\n                server_args.push_value(stream.parse::<FnArg>()?);\n                if stream.peek(Comma) {\n                    server_args.push_punct(stream.parse::<Comma>()?);\n                } else {\n                    break;\n                }\n            } else {\n                break;\n            }\n        }\n\n        // parse legacy encoding into input/output\n        let mut builtin_encoding = false;\n        if let Some(encoding) = encoding {\n            match encoding.value().to_lowercase().as_str() {\n                \"url\" => {\n                    input = Some(type_from_ident(syn::parse_quote!(Url)));\n                    output = Some(type_from_ident(syn::parse_quote!(Json)));\n                    builtin_encoding = true;\n                }\n                \"cbor\" => {\n                    input = Some(type_from_ident(syn::parse_quote!(Cbor)));\n                    output = Some(type_from_ident(syn::parse_quote!(Cbor)));\n                    builtin_encoding = true;\n                }\n                \"getcbor\" => {\n                    input = Some(type_from_ident(syn::parse_quote!(GetUrl)));\n                    output = Some(type_from_ident(syn::parse_quote!(Cbor)));\n                    builtin_encoding = true;\n                }\n                \"getjson\" => {\n                    input = Some(type_from_ident(syn::parse_quote!(GetUrl)));\n                    output = Some(syn::parse_quote!(Json));\n                    builtin_encoding = true;\n                }\n                _ => return Err(syn::Error::new(encoding.span(), \"Encoding not found.\")),\n            }\n        }\n\n        Ok(Self {\n            struct_name,\n            prefix,\n            input,\n            input_derive,\n            output,\n            fn_path,\n            builtin_encoding,\n            server,\n            client,\n            custom_wrapper,\n            impl_from,\n            impl_deref,\n            protocol,\n            server_args,\n        })\n    }\n}\n\n/// An argument type in a server function.\n#[allow(unused)]\n// todo - we used to support a number of these attributes and pass them along to serde. bring them back.\n#[derive(Debug, Clone)]\nstruct ServerFnArg {\n    /// The attributes on the server function argument.\n    server_fn_attributes: Vec<Attribute>,\n    /// The type of the server function argument.\n    arg: syn::PatType,\n}\n\nimpl ToTokens for ServerFnArg {\n    fn to_tokens(&self, tokens: &mut TokenStream2) {\n        let ServerFnArg { arg, .. } = self;\n        tokens.extend(quote! {\n            #arg\n        });\n    }\n}\n\nimpl Parse for ServerFnArg {\n    fn parse(input: ParseStream) -> Result<Self> {\n        let arg: syn::FnArg = input.parse()?;\n        let mut arg = match arg {\n            FnArg::Receiver(_) => {\n                return Err(syn::Error::new(\n                    arg.span(),\n                    \"cannot use receiver types in server function macro\",\n                ))\n            }\n            FnArg::Typed(t) => t,\n        };\n\n        fn rename_path(path: Path, from_ident: Ident, to_ident: Ident) -> Path {\n            if path.is_ident(&from_ident) {\n                Path {\n                    leading_colon: None,\n                    segments: Punctuated::from_iter([PathSegment {\n                        ident: to_ident,\n                        arguments: PathArguments::None,\n                    }]),\n                }\n            } else {\n                path\n            }\n        }\n\n        let server_fn_attributes = arg\n            .attrs\n            .iter()\n            .cloned()\n            .map(|attr| {\n                if attr.path().is_ident(\"server\") {\n                    // Allow the following attributes:\n                    // - #[server(default)]\n                    // - #[server(rename = \"fieldName\")]\n\n                    // Rename `server` to `serde`\n                    let attr = Attribute {\n                        meta: match attr.meta {\n                            Meta::Path(path) => Meta::Path(rename_path(\n                                path,\n                                format_ident!(\"server\"),\n                                format_ident!(\"serde\"),\n                            )),\n                            Meta::List(mut list) => {\n                                list.path = rename_path(\n                                    list.path,\n                                    format_ident!(\"server\"),\n                                    format_ident!(\"serde\"),\n                                );\n                                Meta::List(list)\n                            }\n                            Meta::NameValue(mut name_value) => {\n                                name_value.path = rename_path(\n                                    name_value.path,\n                                    format_ident!(\"server\"),\n                                    format_ident!(\"serde\"),\n                                );\n                                Meta::NameValue(name_value)\n                            }\n                        },\n                        ..attr\n                    };\n\n                    let args = attr.parse_args::<Meta>()?;\n                    match args {\n                        // #[server(default)]\n                        Meta::Path(path) if path.is_ident(\"default\") => Ok(attr.clone()),\n                        // #[server(flatten)]\n                        Meta::Path(path) if path.is_ident(\"flatten\") => Ok(attr.clone()),\n                        // #[server(default = \"value\")]\n                        Meta::NameValue(name_value) if name_value.path.is_ident(\"default\") => {\n                            Ok(attr.clone())\n                        }\n                        // #[server(skip)]\n                        Meta::Path(path) if path.is_ident(\"skip\") => Ok(attr.clone()),\n                        // #[server(rename = \"value\")]\n                        Meta::NameValue(name_value) if name_value.path.is_ident(\"rename\") => {\n                            Ok(attr.clone())\n                        }\n                        _ => Err(Error::new(\n                            attr.span(),\n                            \"Unrecognized #[server] attribute, expected \\\n                             #[server(default)] or #[server(rename = \\\n                             \\\"fieldName\\\")]\",\n                        )),\n                    }\n                } else if attr.path().is_ident(\"doc\") {\n                    // Allow #[doc = \"documentation\"]\n                    Ok(attr.clone())\n                } else if attr.path().is_ident(\"allow\") {\n                    // Allow #[allow(...)]\n                    Ok(attr.clone())\n                } else if attr.path().is_ident(\"deny\") {\n                    // Allow #[deny(...)]\n                    Ok(attr.clone())\n                } else if attr.path().is_ident(\"ignore\") {\n                    // Allow #[ignore]\n                    Ok(attr.clone())\n                } else {\n                    Err(Error::new(\n                        attr.span(),\n                        \"Unrecognized attribute, expected #[server(...)]\",\n                    ))\n                }\n            })\n            .collect::<Result<Vec<_>>>()?;\n        arg.attrs = vec![];\n        Ok(ServerFnArg {\n            arg,\n            server_fn_attributes,\n        })\n    }\n}\n\nfn type_from_ident(ident: Ident) -> Type {\n    let mut segments = Punctuated::new();\n    segments.push(PathSegment {\n        ident,\n        arguments: PathArguments::None,\n    });\n    Type::Path(TypePath {\n        qself: None,\n        path: Path {\n            leading_colon: None,\n            segments,\n        },\n    })\n}\n"
  },
  {
    "path": "packages/fullstack-server/.gitignore",
    "content": "target"
  },
  {
    "path": "packages/fullstack-server/Cargo.toml",
    "content": "[package]\nname = \"dioxus-server\"\nauthors = [\"Jonathan Kelley\", \"Evan Almloff\"]\nversion = { workspace = true }\nedition = \"2021\"\ndescription = \"Fullstack utilities for Dioxus: Build fullstack web, desktop, and mobile apps with a single codebase.\"\nlicense = \"MIT OR Apache-2.0\"\nrepository = \"https://github.com/DioxusLabs/dioxus/\"\nhomepage = \"https://dioxuslabs.com\"\nkeywords = [\"web\", \"desktop\", \"mobile\", \"gui\", \"server\"]\nresolver = \"2\"\n\n[dependencies]\n\n# Dioxus + SSR\nsubsecond = { workspace =  true }\ndioxus-cli-config = { workspace = true }\ndioxus-devtools = { workspace = true, features = [\"serve\"] }\ndioxus-history = { workspace = true }\ndioxus-signals = { workspace = true }\ndioxus-hooks = { workspace = true }\ndioxus-logger = { workspace = true }\ndioxus-router = { workspace = true, features = [\"streaming\"], optional = true }\ndioxus-fullstack-core = { workspace = true, features = [\"server\"] }\ndioxus-core = { workspace = true }\ndioxus-core-macro = { workspace = true }\ndioxus-document = { workspace = true }\ndioxus-html = { workspace = true }\ngenerational-box = { workspace = true }\n\n\naxum = { workspace = true, features = [\"multipart\", \"ws\", \"json\", \"form\", \"tokio\", \"http1\", \"http2\", \"macros\"]}\nanyhow = { workspace = true }\ndashmap = \"6.1.0\"\ninventory = { workspace = true }\ndioxus-ssr = { workspace = true }\n\nhyper-util = { workspace = true, features = [\"full\"] }\nhyper = { workspace = true }\n\n# Web Integration\ndioxus-interpreter-js = { workspace = true }\n\ntracing = { workspace = true }\ntracing-futures = { workspace = true }\ntokio-util = { workspace = true, features = [\"rt\"] }\nasync-trait = { workspace = true }\n\nserde = { workspace = true }\nfutures-util = { workspace = true }\nfutures-channel = { workspace = true }\nciborium = { workspace = true }\nbase64 = { workspace = true }\nrustls = { workspace = true, optional = true }\nhyper-rustls = { workspace = true, optional = true }\n\nurl = { workspace = true, default-features = true }\nserde_json = { workspace = true }\n\nserde_qs = { workspace = true, default-features = true }\nmulter = { optional = true, workspace = true, default-features = true }\nrkyv = { optional = true,  default-features = true, version = \"0.8\" }\n\nfutures = { workspace = true, default-features = true }\npin-project = { workspace = true }\nthiserror = { workspace = true }\nbytes = {version = \"1.10.1\", features = [\"serde\"]}\ntower-http = { workspace = true, features = [\"fs\"] }\ntower = { workspace = true, features = [\"util\"] }\ntower-layer = { version = \"0.3.3\", optional = true }\nparking_lot = { workspace = true, features = [\"send_guard\"] }\n\ntokio-tungstenite = { workspace = true }\nhttp.workspace = true\nenumset = \"1.1.6\"\nhttp-body-util = \"0.1.3\"\n\nchrono = { workspace = true }\nrustc-hash = { workspace = true }\nlru  = { workspace = true }\nwalkdir = { workspace = true }\n\n[target.'cfg(target_arch = \"wasm32\")'.dependencies]\ntokio = { workspace = true, features = [\"rt\", \"sync\", \"macros\"], optional = true }\n\n[target.'cfg(not(target_arch = \"wasm32\"))'.dependencies]\ntokio = { workspace = true, features = [\"rt\", \"sync\", \"rt-multi-thread\", \"macros\", \"net\"] }\n\n[dev-dependencies]\ndioxus = { workspace = true, features = [\"fullstack\"] }\ntokio = { workspace = true, features = [\"full\"] }\n\n[features]\ndefault = [\"router\", \"document\"]\ndocument = []\nrouter = [\"dep:dioxus-router\"]\ndefault-tls = []\nrustls = [\"dep:rustls\", \"dep:hyper-rustls\"]\naxum-no-default = []\nrkyv = [\"dep:rkyv\"]\nserver = []\n\n[package.metadata.docs.rs]\ncargo-args = [\"-Zunstable-options\", \"-Zrustdoc-scrape-examples\"]\nfeatures = [\"axum\", \"web\", \"aws-lc-rs\"]\n"
  },
  {
    "path": "packages/fullstack-server/README.md",
    "content": "# Dioxus Fullstack\n\n[![Crates.io][crates-badge]][crates-url]\n[![MIT licensed][mit-badge]][mit-url]\n[![Build Status][actions-badge]][actions-url]\n[![Discord chat][discord-badge]][discord-url]\n\n[crates-badge]: https://img.shields.io/crates/v/dioxus-fullstack.svg\n[crates-url]: https://crates.io/crates/dioxus-fullstack\n[mit-badge]: https://img.shields.io/badge/license-MIT-blue.svg\n[mit-url]: https://github.com/dioxuslabs/dioxus/blob/main/LICENSE-MIT\n[actions-badge]: https://github.com/dioxuslabs/dioxus/actions/workflows/main.yml/badge.svg\n[actions-url]: https://github.com/dioxuslabs/dioxus/actions?query=workflow%3ACI+branch%3Amaster\n[discord-badge]: https://img.shields.io/discord/899851952891002890.svg?logo=discord&style=flat-square\n[discord-url]: https://discord.gg/XgGxMSkvUM\n\n[Website](https://dioxuslabs.com) |\n[Guides](https://dioxuslabs.com/learn/0.7/) |\n[API Docs](https://docs.rs/dioxus-fullstack/latest/dioxus_fullstack/) |\n[Chat](https://discord.gg/XgGxMSkvUM)\n\nFullstack utilities for the [`Dioxus`](https://dioxuslabs.com) framework.\n\n# Features\n\n- Integrates with the [Axum](./examples/axum-hello-world/src/main.rs) server framework with utilities for serving and rendering Dioxus applications.\n- Server functions allow you to call code on the server from the client as if it were a normal function.\n- Instant RSX Hot reloading with [`dioxus-hot-reload`](https://crates.io/crates/dioxus-hot-reload).\n- Passing root props from the server to the client.\n\n# Example\nQuickly build fullstack Rust apps with axum and dioxus.\n\n```rust, no_run\nuse dioxus::prelude::*;\n\nfn main() {\n    dioxus::launch(|| {\n        let mut meaning = use_action(|| get_meaning(\"life the universe and everything\".into()));\n\n        rsx! {\n            h1 { \"Meaning of life: {meaning:?}\" }\n            button { onclick: move |_| meaning.call(), \"Run a server function\" }\n        }\n    });\n}\n\n#[get(\"/meaning/?of\")]\nasync fn get_meaning(of: String) -> Result<Option<u32>> {\n    Ok(of.contains(\"life\").then(|| 42))\n}\n```\n\n## Axum Integration\n\nIf you have an existing Axum router or you need more control over the server, you can use the [`DioxusRouterExt`](https://docs.rs/dioxus-fullstack/0.6.0-alpha.2/dioxus_fullstack/prelude/trait.DioxusRouterExt.html) trait to integrate with your existing Axum router.\n\nFirst, make sure your `axum` dependency is optional and enabled by the server feature flag. Axum cannot be compiled to wasm, so if it is enabled by default, it will cause a compile error:\n\n```toml\n[dependencies]\ndioxus = { version = \"*\", features = [\"fullstack\"] }\naxum = { version = \"0.8.0\", optional = true }\ntokio = { version = \"1.0\", features = [\"full\"], optional = true }\ndioxus-cli-config = { version = \"*\", optional = true }\n\n[features]\nserver = [\"dioxus/server\", \"dep:axum\", \"dep:tokio\", \"dioxus-cli-config\"]\nweb = [\"dioxus/web\"]\n```\n\nThen we can set up dioxus with the axum server:\n\n```rust, no_run\n#![allow(non_snake_case)]\nuse dioxus::prelude::*;\n\n// The entry point for the server\n#[cfg(feature = \"server\")]\n#[tokio::main]\nasync fn main() {\n    use dioxus_server::DioxusRouterExt;\n\n    // Get the address the server should run on. If the CLI is running, the CLI proxies fullstack into the main address\n    // and we use the generated address the CLI gives us\n    let address = dioxus::cli_config::fullstack_address_or_localhost();\n\n    // Set up the axum router\n    let router = axum::Router::new()\n        // You can add a dioxus application to the router with the `serve_dioxus_application` method\n        // This will add a fallback route to the router that will serve your component and server functions\n        .serve_dioxus_application(dioxus_server::ServeConfig::new(), App);\n\n    // Finally, we can launch the server\n    let router = router.into_make_service();\n    let listener = tokio::net::TcpListener::bind(address).await.unwrap();\n    axum::serve(listener, router).await.unwrap();\n}\n\n// For any other platform, we just launch the app\n#[cfg(not(feature = \"server\"))]\nfn main() {\n    dioxus::launch(App);\n}\n\n#[component]\nfn App() -> Element {\n    let mut meaning = use_signal(|| None);\n\n    rsx! {\n        h1 { \"Meaning of life: {meaning:?}\" }\n        button {\n            onclick: move |_| async move {\n                if let Ok(data) = get_meaning(\"life the universe and everything\".into()).await {\n                    meaning.set(data);\n                }\n            },\n            \"Run a server function\"\n        }\n    }\n}\n\n#[server]\nasync fn get_meaning(of: String) -> ServerFnResult<Option<u32>> {\n    Ok(of.contains(\"life\").then(|| 42))\n}\n```\n\n## Getting Started\n\nTo get started with full stack Dioxus, check out our [getting started guide](https://dioxuslabs.com/learn/0.7/getting_started), or the [full stack examples](https://github.com/DioxusLabs/dioxus/tree/master/examples).\n\n## Contributing\n\n- Report issues on our [issue tracker](https://github.com/dioxuslabs/dioxus/issues).\n- Join the discord and ask questions!\n\n## License\n\nThis project is licensed under the [MIT license].\n\n[mit license]: https://github.com/dioxuslabs/dioxus/blob/main/LICENSE-MIT\n\nUnless you explicitly state otherwise, any contribution intentionally submitted\nfor inclusion in Dioxus by you shall be licensed as MIT without any additional\nterms or conditions.\n"
  },
  {
    "path": "packages/fullstack-server/src/config.rs",
    "content": "//! Configuration for how to serve a Dioxus application\n#![allow(non_snake_case)]\n\nuse dioxus_core::LaunchConfig;\nuse std::any::Any;\nuse std::sync::Arc;\n\nuse crate::{IncrementalRendererConfig, IndexHtml};\n\n#[allow(unused)]\npub(crate) type ContextProviders = Arc<Vec<Box<dyn Fn() -> Box<dyn Any> + Send + Sync + 'static>>>;\n\n/// A ServeConfig is used to configure how to serve a Dioxus application. It contains information about how to serve static assets, and what content to render with [`dioxus_ssr`].\n#[derive(Clone)]\npub struct ServeConfig {\n    pub(crate) index: IndexHtml,\n    pub(crate) incremental: Option<IncrementalRendererConfig>,\n    pub(crate) context_providers: Vec<Arc<dyn Fn() -> Box<dyn Any> + Send + Sync + 'static>>,\n    pub(crate) streaming_mode: StreamingMode,\n}\n\n/// The streaming mode to use while rendering the page\n#[derive(Clone, Copy, Default, PartialEq)]\npub enum StreamingMode {\n    /// Streaming is disabled; all server futures should be resolved before hydrating the page on the client\n    #[default]\n    Disabled,\n\n    /// Out of order streaming is enabled; server futures are resolved out of order and streamed to the client\n    /// as they resolve\n    OutOfOrder,\n}\n\nimpl LaunchConfig for ServeConfig {}\n\nimpl Default for ServeConfig {\n    fn default() -> Self {\n        Self::new()\n    }\n}\n\nimpl ServeConfig {\n    /// Create a new ServeConfig with incremental static generation disabled and the default index.html settings.\n    pub fn builder() -> Self {\n        Self::new()\n    }\n\n    /// Create a new ServeConfig with incremental static generation disabled and the default index.html settings\n    ///\n    /// This will automatically use the `index.html` file in the `/public` directory if it exists.\n    /// The `/public` folder is meant located next to the current executable. If no `index.html` file is found,\n    /// a default index.html will be used, which will not include any JavaScript or WASM initialization code.\n    ///\n    /// To provide an alternate `index.html`, you can use `with_index_html` method instead.\n    pub fn new() -> Self {\n        let index = if let Some(public_path) = crate::public_path() {\n            let index_html_path = public_path.join(\"index.html\");\n\n            if index_html_path.exists() {\n                let index_html = std::fs::read_to_string(index_html_path)\n                    .expect(\"Failed to read index.html from public directory\");\n\n                IndexHtml::new(&index_html, \"main\")\n                    .expect(\"Failed to parse index.html from public directory\")\n            } else {\n                tracing::warn!(\"No index.html found in public directory, using default index.html\");\n                IndexHtml::ssr_only()\n            }\n        } else {\n            tracing::warn!(\n                \"Cannot identify public directory, using default index.html. If you need client-side scripts (like JS + WASM), please provide an explicit public directory.\"\n            );\n            IndexHtml::ssr_only()\n        };\n\n        Self {\n            index,\n            incremental: None,\n            context_providers: Default::default(),\n            streaming_mode: StreamingMode::default(),\n        }\n    }\n\n    /// Create a new ServeConfig with the given parsed `IndexHtml` structure.\n    ///\n    /// You can create the `IndexHtml` structure by using `IndexHtml::new` method, or manually from\n    /// a string or file.\n    pub fn with_index_html(index: IndexHtml) -> Self {\n        Self {\n            index,\n            incremental: Default::default(),\n            context_providers: Default::default(),\n            streaming_mode: Default::default(),\n        }\n    }\n\n    /// Enable incremental static generation. Incremental static generation caches the\n    /// rendered html in memory and/or the file system. It can be used to improve performance of heavy routes.\n    ///\n    /// ```rust, no_run\n    /// # fn app() -> Element { unimplemented!() }\n    /// use dioxus::prelude::*;\n    ///\n    /// // Finally, launch the app with the config\n    /// LaunchBuilder::new()\n    ///     // Only set the server config if the server feature is enabled\n    ///     .with_cfg(server_only!(dioxus_server::ServeConfig::default().incremental(dioxus_server::IncrementalRendererConfig::default())))\n    ///     .launch(app);\n    /// ```\n    pub fn incremental(mut self, cfg: IncrementalRendererConfig) -> Self {\n        self.incremental = Some(cfg);\n        self\n    }\n\n    /// Provide context to the root and server functions. You can use this context while rendering with [`consume_context`](dioxus_core::consume_context).\n    ///\n    ///\n    /// The context providers passed into this method will be called when the context type is requested which may happen many times in the lifecycle of the application.\n    ///\n    ///\n    /// Context will be forwarded from the LaunchBuilder if it is provided.\n    ///\n    /// ```rust, no_run\n    /// #![allow(non_snake_case)]\n    /// use dioxus::prelude::*;\n    /// use std::sync::Arc;\n    /// use std::any::Any;\n    ///\n    /// fn main() {\n    ///     // Hydrate the application on the client\n    ///     #[cfg(not(feature = \"server\"))]\n    ///     dioxus::launch(app);\n    ///\n    ///    #[cfg(feature = \"server\")]\n    ///    dioxus_server::serve(|| async move {\n    ///        use dioxus_server::{axum, ServeConfig, DioxusRouterExt};\n    ///\n    ///        let config = ServeConfig::default()\n    ///            // You can provide context to your whole app on the server (including server functions) with the `context_provider` method on the launch builder\n    ///            .context_providers(Arc::new(vec![Box::new(|| Box::new(1234u32) as Box<dyn Any>) as Box<dyn Fn() -> Box<dyn Any> + Send + Sync>]));\n    ///\n    ///        Ok(\n    ///            axum::Router::new()\n    ///                .serve_dioxus_application(config, app)\n    ///        )\n    ///    })\n    /// }\n    ///\n    /// #[server]\n    /// async fn read_context() -> ServerFnResult<u32> {\n    ///     Ok(123)\n    /// }\n    ///\n    /// fn app() -> Element {\n    ///     let future = use_resource(read_context);\n    ///     rsx! {\n    ///         h1 { \"{future:?}\" }\n    ///     }\n    /// }\n    /// ```\n    pub fn context_providers(mut self, state: ContextProviders) -> Self {\n        // This API should probably accept Vec<Box<dyn Fn() -> Box<dyn Any> + Send + Sync + 'static>> instead of Arc so we can\n        // continue adding to the context list after calling this method. Changing the type is a breaking change so it cannot\n        // be done until 0.7 is released.\n        let context_providers = (0..state.len()).map(|i| {\n            let state = state.clone();\n            Arc::new(move || state[i]()) as Arc<dyn Fn() -> Box<dyn std::any::Any> + Send + Sync>\n        });\n        self.context_providers.extend(context_providers);\n        self\n    }\n\n    /// Provide context to the root and server functions. You can use this context\n    /// while rendering with [`consume_context`](dioxus_core::consume_context).\n    ///\n    ///\n    /// The context providers passed into this method will be called when the context type is requested which may happen many times in the lifecycle of the application.\n    ///\n    ///\n    /// Context will be forwarded from the LaunchBuilder if it is provided.\n    ///\n    /// ```rust, no_run\n    /// #![allow(non_snake_case)]\n    /// use dioxus::prelude::*;\n    ///\n    /// fn main() {\n    ///     #[cfg(not(feature = \"server\"))]\n    ///     // Hydrate the application on the client\n    ///     dioxus::launch(app);\n    ///\n    ///     #[cfg(feature = \"server\")]\n    ///     dioxus_server::serve(|| async move {\n    ///        use dioxus_server::{axum, ServeConfig, DioxusRouterExt};\n    ///\n    ///         let config = ServeConfig::default()\n    ///             // You can provide context to your whole app on the server (including server functions) with the `context_provider` method on the launch builder\n    ///             .context_provider(|| 1234u32);\n    ///\n    ///         Ok(\n    ///             axum::Router::new()\n    ///                 .serve_dioxus_application(config, app)\n    ///         )\n    ///     });\n    /// }\n    ///\n    /// #[server]\n    /// async fn read_context() -> ServerFnResult<u32> {\n    ///     Ok(123)\n    /// }\n    ///\n    /// fn app() -> Element {\n    ///     let future = use_resource(read_context);\n    ///     rsx! {\n    ///         h1 { \"{future:?}\" }\n    ///     }\n    /// }\n    /// ```\n    pub fn context_provider<C: 'static>(\n        mut self,\n        state: impl Fn() -> C + Send + Sync + 'static,\n    ) -> Self {\n        self.context_providers\n            .push(Arc::new(move || Box::new(state())));\n        self\n    }\n\n    /// Provide context to the root and server functions. You can use this context while rendering with [`consume_context`](dioxus_core::consume_context).\n    ///\n    /// Context will be forwarded from the LaunchBuilder if it is provided.\n    ///\n    /// ```rust, no_run\n    /// #![allow(non_snake_case)]\n    /// use dioxus::prelude::*;\n    ///\n    /// fn main() {\n    ///     // Hydrate the application on the client\n    ///     #[cfg(not(feature = \"server\"))]\n    ///     dioxus::launch(app);\n    ///\n    ///     // Run a custom server with axum on the server\n    ///     #[cfg(feature = \"server\")]\n    ///     dioxus_server::serve(|| async move {\n    ///         use dioxus_server::{axum, ServeConfig, DioxusRouterExt};\n    ///\n    ///         let config = ServeConfig::default()\n    ///             // You can provide context to your whole app on the server (including server functions) with the `context_provider` method on the launch builder\n    ///             .context(1234u32);\n    ///\n    ///         Ok(\n    ///             axum::Router::new()\n    ///                 .serve_dioxus_application(config, app)\n    ///         )\n    ///     });\n    /// }\n    ///\n    /// #[server]\n    /// async fn read_context() -> ServerFnResult<u32> {\n    ///     Ok(123)\n    /// }\n    ///\n    /// fn app() -> Element {\n    ///     let future = use_resource(read_context);\n    ///     rsx! {\n    ///         h1 { \"{future:?}\" }\n    ///     }\n    /// }\n    /// ```\n    pub fn context(mut self, state: impl Any + Clone + Send + Sync + 'static) -> Self {\n        self.context_providers\n            .push(Arc::new(move || Box::new(state.clone())));\n        self\n    }\n\n    /// Set the streaming mode for the server. By default, streaming is disabled.\n    ///\n    /// ```rust, no_run\n    /// # use dioxus::prelude::*;\n    /// # fn app() -> Element { unimplemented!() }\n    /// dioxus::LaunchBuilder::new()\n    ///     .with_context(server_only! {\n    ///         dioxus::server::ServeConfig::builder().streaming_mode(dioxus::server::StreamingMode::OutOfOrder)\n    ///     })\n    ///     .launch(app);\n    /// ```\n    pub fn streaming_mode(mut self, mode: StreamingMode) -> Self {\n        self.streaming_mode = mode;\n        self\n    }\n\n    /// Enable out of order streaming. This will cause server futures to be resolved out of order and streamed to the client as they resolve.\n    ///\n    /// It is equivalent to calling `streaming_mode(StreamingMode::OutOfOrder)`\n    ///\n    /// ```rust, no_run\n    /// # use dioxus::prelude::*;\n    /// # fn app() -> Element { unimplemented!() }\n    /// dioxus::LaunchBuilder::new()\n    ///     .with_context(server_only! {\n    ///         dioxus::server::ServeConfig::builder().enable_out_of_order_streaming()\n    ///     })\n    ///     .launch(app);\n    /// ```\n    pub fn enable_out_of_order_streaming(mut self) -> Self {\n        self.streaming_mode = StreamingMode::OutOfOrder;\n        self\n    }\n}\n"
  },
  {
    "path": "packages/fullstack-server/src/document.rs",
    "content": "//! On the server, we collect any elements that should be rendered into the head in the first frame of SSR.\n//! After the first frame, we have already sent down the head, so we can't modify it in place. The web client\n//! will hydrate the head with the correct contents once it loads.\n\nuse std::cell::RefCell;\n\nuse dioxus_core::Element;\nuse dioxus_core_macro::rsx;\nuse dioxus_document::{\n    Document, Eval, LinkProps, MetaProps, NoOpDocument, ScriptProps, StyleProps,\n};\nuse dioxus_html as dioxus_elements;\nuse dioxus_ssr::Renderer;\nuse parking_lot::RwLock;\nuse std::sync::LazyLock;\n\nstatic RENDERER: LazyLock<RwLock<Renderer>> = LazyLock::new(|| RwLock::new(Renderer::new()));\n\n/// Reset the static renderer to a fresh state, clearing its cache.\npub(crate) fn reset_renderer() {\n    RENDERER.write().clear();\n}\n\n#[derive(Default)]\nstruct ServerDocumentInner {\n    streaming: bool,\n    title: Option<String>,\n    meta: Vec<Element>,\n    link: Vec<Element>,\n    script: Vec<Element>,\n}\n\n/// A Document provider that collects all contents injected into the head for SSR rendering.\n#[derive(Default)]\npub struct ServerDocument(RefCell<ServerDocumentInner>);\n\nimpl ServerDocument {\n    pub(crate) fn title(&self) -> Option<String> {\n        let myself = self.0.borrow();\n        myself.title.as_ref().map(|title| {\n            RENDERER\n                .write()\n                .render_element(rsx! { title { \"{title}\" } })\n        })\n    }\n\n    pub(crate) fn render(&self, to: &mut impl std::fmt::Write) -> std::fmt::Result {\n        let myself = self.0.borrow();\n        let element = rsx! {\n            {myself.meta.iter().map(|m| rsx! { {m} })}\n            {myself.link.iter().map(|l| rsx! { {l} })}\n            {myself.script.iter().map(|s| rsx! { {s} })}\n        };\n\n        RENDERER.write().render_element_to(to, element)?;\n\n        Ok(())\n    }\n\n    pub(crate) fn start_streaming(&self) {\n        self.0.borrow_mut().streaming = true;\n    }\n\n    pub(crate) fn warn_if_streaming(&self) {\n        if self.0.borrow().streaming {\n            tracing::warn!(\"Attempted to insert content into the head after the initial streaming frame. Inserting content into the head only works during the initial render of SSR outside before resolving any suspense boundaries.\");\n        }\n    }\n\n    /// Write the head element into the serialized context for hydration\n    /// We write true if the head element was written to the DOM during server side rendering\n    #[track_caller]\n    pub(crate) fn serialize_for_hydration(&self) {\n        // We only serialize the head elements if the web document feature is enabled\n        #[cfg(feature = \"document\")]\n        {\n            dioxus_fullstack_core::head_element_hydration_entry()\n                .insert(&!self.0.borrow().streaming, std::panic::Location::caller());\n        }\n    }\n}\n\nimpl Document for ServerDocument {\n    fn eval(&self, js: String) -> Eval {\n        NoOpDocument.eval(js)\n    }\n\n    fn set_title(&self, title: String) {\n        self.warn_if_streaming();\n        self.0.borrow_mut().title = Some(title);\n    }\n\n    fn create_meta(&self, props: MetaProps) {\n        self.0.borrow_mut().meta.push(rsx! {\n            meta {\n                name: props.name,\n                charset: props.charset,\n                http_equiv: props.http_equiv,\n                content: props.content,\n                property: props.property,\n                \"data\": props.data,\n                ..props.additional_attributes,\n            }\n        });\n    }\n\n    fn create_script(&self, props: ScriptProps) {\n        let children = props.script_contents().ok();\n        self.0.borrow_mut().script.push(rsx! {\n            script {\n                src: props.src,\n                defer: props.defer,\n                crossorigin: props.crossorigin,\n                fetchpriority: props.fetchpriority,\n                integrity: props.integrity,\n                nomodule: props.nomodule,\n                nonce: props.nonce,\n                referrerpolicy: props.referrerpolicy,\n                r#type: props.r#type,\n                ..props.additional_attributes,\n                {children}\n            }\n        });\n    }\n\n    fn create_style(&self, props: StyleProps) {\n        let contents = props.style_contents().ok();\n        self.0.borrow_mut().script.push(rsx! {\n            style {\n                media: props.media,\n                nonce: props.nonce,\n                title: props.title,\n                ..props.additional_attributes,\n                {contents}\n            }\n        })\n    }\n\n    fn create_link(&self, props: LinkProps) {\n        self.0.borrow_mut().link.push(rsx! {\n            link {\n                rel: props.rel,\n                media: props.media,\n                title: props.title,\n                disabled: props.disabled,\n                r#as: props.r#as,\n                sizes: props.sizes,\n                href: props.href,\n                crossorigin: props.crossorigin,\n                referrerpolicy: props.referrerpolicy,\n                fetchpriority: props.fetchpriority,\n                hreflang: props.hreflang,\n                integrity: props.integrity,\n                r#type: props.r#type,\n                blocking: props.blocking,\n                ..props.additional_attributes,\n            }\n        })\n    }\n\n    fn create_head_component(&self) -> bool {\n        self.warn_if_streaming();\n        self.serialize_for_hydration();\n        true\n    }\n}\n"
  },
  {
    "path": "packages/fullstack-server/src/index_html.rs",
    "content": "use anyhow::Context;\nuse std::path::Path;\n\n/// An `IndexHtml` represents the contents of an `index.html` file used to serve a web application.\n///\n/// This defines the static portion of your web application, typically generated by a tool like `dx`\n/// in conjunction with wasm-bindgen.\n///\n/// This structure expects a well-formed HTML document split into several sections:\n/// - `head_before_title`: The portion of the `<head>` section before the `<title\n/// - `title`: The `<title>` tag and its contents.\n/// - `head_after_title`: The portion of the `<head>` section after the `<title>` tag.\n/// - `close_head`: The closing `</head>` tag and any content following it.\n/// - `post_main`: The content following the main application container (e.g., `<div id=\"main\">`).\n/// - `after_closing_body_tag`: The content following the closing `</body>` tag.\n///\n/// These fields are not explicitly exposed as part of the API, but are critical for dioxus-fullstack\n/// to properly inject server-rendered content and client-side bootstrapping scripts into the HTML.\n///\n/// The simplest HTML document that satisfies this structure is:\n///\n/// ```html\n/// <html>\n///     <head> </head>\n///     <body>\n///         <div id=\"main\"></div>\n///     </body>\n/// </html>\n/// ```\n#[derive(Clone, Debug)]\npub struct IndexHtml {\n    pub(crate) head_before_title: String,\n    pub(crate) head_after_title: String,\n    pub(crate) title: String,\n    pub(crate) close_head: String,\n    pub(crate) post_main: String,\n    pub(crate) after_closing_body_tag: String,\n}\n\nimpl IndexHtml {\n    /// Create a new `IndexHtml` from the raw contents of an `index.html` file.\n    ///\n    /// This function will parse the `index.html` and split it into sections for easier manipulation.\n    /// The `root_id` parameter specifies the id of the main application container (e.g., \"main\")\n    /// which your app will render into.\n    pub fn new(contents: &str, root_id: &str) -> Result<IndexHtml, anyhow::Error> {\n        let (pre_main, post_main) = contents.split_once(&format!(\"id=\\\"{root_id}\\\"\"))\n            .with_context(|| format!(\"Failed to find id=\\\"{root_id}\\\" in index.html. The id is used to inject the application into the page.\"))?;\n\n        let post_main = post_main.split_once('>').with_context(|| {\n            format!(\"Failed to find closing > after id=\\\"{root_id}\\\" in index.html.\")\n        })?;\n\n        let (pre_main, post_main) = (\n            pre_main.to_string() + &format!(\"id=\\\"{root_id}\\\"\") + post_main.0 + \">\",\n            post_main.1.to_string(),\n        );\n\n        let (head, close_head) = pre_main.split_once(\"</head>\").with_context(|| {\n            format!(\"Failed to find closing </head> tag after id=\\\"{root_id}\\\" in index.html.\")\n        })?;\n\n        let (head, close_head) = (head.to_string(), \"</head>\".to_string() + close_head);\n\n        let (post_main, after_closing_body_tag) =\n            post_main.split_once(\"</body>\").with_context(|| {\n                format!(\"Failed to find closing </body> tag after id=\\\"{root_id}\\\" in index.html.\")\n            })?;\n\n        // Strip out the head if it exists\n        let mut head_before_title = String::new();\n        let mut head_after_title = head;\n        let mut title = String::new();\n        if let Some((new_head_before_title, new_title)) = head_after_title.split_once(\"<title>\") {\n            let (new_title, new_head_after_title) = new_title\n                .split_once(\"</title>\")\n                .context(\"Failed to find closing </title> tag after <title> in index.html.\")?;\n            title = format!(\"<title>{new_title}</title>\");\n            head_before_title = new_head_before_title.to_string();\n            head_after_title = new_head_after_title.to_string();\n        }\n\n        Ok(IndexHtml {\n            head_before_title,\n            head_after_title,\n            title,\n            close_head,\n            post_main: post_main.to_string(),\n            after_closing_body_tag: \"</body>\".to_string() + after_closing_body_tag,\n        })\n    }\n\n    /// Load an `IndexHtml` from a file at the given path.\n    ///\n    /// This function reads the contents of the file and parses it into an `IndexHtml`.\n    /// The `id` parameter specifies the id of the main application container (e.g., \"main\").\n    pub fn from_file(path: &Path, id: &str) -> Result<IndexHtml, anyhow::Error> {\n        let contents = std::fs::read_to_string(path)\n            .with_context(|| format!(\"Failed to read index.html from {}\", path.display()))?;\n        IndexHtml::new(&contents, id)\n    }\n\n    /// Create a default `IndexHtml` suitable for server-side rendering (SSR) only applications.\n    /// This will not include any customizations to the HTML nor any JavaScript to bootstrap a client-side app.\n    pub fn ssr_only() -> Self {\n        const DEFAULT: &str = r#\"<!DOCTYPE html>\n        <html>\n            <head> </head>\n            <body>\n                <div id=\"main\"></div>\n            </body>\n        </html>\"#;\n\n        Self::new(DEFAULT, \"main\").expect(\"Failed to load default index.html\")\n    }\n}\n"
  },
  {
    "path": "packages/fullstack-server/src/isrg/config.rs",
    "content": "#![allow(non_snake_case)]\n\n#[cfg(not(target_arch = \"wasm32\"))]\nuse crate::isrg::fs_cache::PathMapFn;\n\nuse crate::isrg::memory_cache::InMemoryCache;\nuse crate::IncrementalRenderer;\n\nuse std::{\n    path::{Path, PathBuf},\n    time::Duration,\n};\n\n/// A configuration for the incremental renderer.\n#[derive(Clone)]\npub struct IncrementalRendererConfig {\n    static_dir: PathBuf,\n    memory_cache_limit: usize,\n    invalidate_after: Option<Duration>,\n    clear_cache: bool,\n    pre_render: bool,\n\n    #[cfg(not(target_arch = \"wasm32\"))]\n    map_path: Option<PathMapFn>,\n}\n\nimpl Default for IncrementalRendererConfig {\n    fn default() -> Self {\n        Self::new()\n    }\n}\n\nimpl IncrementalRendererConfig {\n    /// Create a new incremental renderer configuration.\n    pub fn new() -> Self {\n        Self {\n            static_dir: PathBuf::from(\"./static\"),\n            memory_cache_limit: 10000,\n            invalidate_after: None,\n            clear_cache: false,\n            pre_render: false,\n            #[cfg(not(target_arch = \"wasm32\"))]\n            map_path: None,\n        }\n    }\n\n    /// Clear the cache on startup (default: true)\n    pub fn clear_cache(mut self, clear_cache: bool) -> Self {\n        self.clear_cache = clear_cache;\n        self\n    }\n\n    /// Set a mapping from the route to the file path. This will override the default mapping configured with `static_dir`.\n    /// The function should return the path to the folder to store the index.html file in.\n    #[cfg(not(target_arch = \"wasm32\"))]\n    pub fn map_path<F: Fn(&str) -> PathBuf + Send + Sync + 'static>(mut self, map_path: F) -> Self {\n        self.map_path = Some(std::sync::Arc::new(map_path));\n        self\n    }\n\n    /// Set the static directory.\n    pub fn static_dir<P: AsRef<Path>>(mut self, static_dir: P) -> Self {\n        self.static_dir = static_dir.as_ref().to_path_buf();\n        self\n    }\n\n    /// Set the memory cache limit.\n    pub const fn memory_cache_limit(mut self, memory_cache_limit: usize) -> Self {\n        self.memory_cache_limit = memory_cache_limit;\n        self\n    }\n\n    /// Set the invalidation time.\n    pub fn invalidate_after(mut self, invalidate_after: Duration) -> Self {\n        self.invalidate_after = Some(invalidate_after);\n        self\n    }\n\n    /// Set whether to include hydration ids in the pre-rendered html.\n    pub fn pre_render(mut self, pre_render: bool) -> Self {\n        self.pre_render = pre_render;\n        self\n    }\n\n    /// Build the incremental renderer.\n    pub fn build(self) -> IncrementalRenderer {\n        let mut renderer = IncrementalRenderer {\n            #[cfg(not(target_arch = \"wasm32\"))]\n            file_system_cache: super::fs_cache::FileSystemCache::new(\n                self.static_dir.clone(),\n                self.map_path,\n                self.invalidate_after,\n            ),\n            memory_cache: InMemoryCache::new(self.memory_cache_limit, self.invalidate_after),\n            invalidate_after: self.invalidate_after,\n        };\n\n        if self.clear_cache {\n            renderer.invalidate_all();\n        }\n\n        renderer\n    }\n}\n"
  },
  {
    "path": "packages/fullstack-server/src/isrg/freshness.rs",
    "content": "use std::time::Duration;\n\nuse chrono::{DateTime, Utc};\n\n/// Information about the freshness of a rendered response\n#[derive(Debug, Clone, Copy)]\npub struct RenderFreshness {\n    /// The age of the rendered response\n    age: u64,\n    /// The maximum age of the rendered response\n    max_age: Option<u64>,\n    /// The time the response was rendered\n    timestamp: DateTime<Utc>,\n}\n\nimpl RenderFreshness {\n    /// Create new freshness information\n    pub(crate) fn new(age: u64, max_age: u64, timestamp: DateTime<Utc>) -> Self {\n        Self {\n            age,\n            max_age: Some(max_age),\n            timestamp,\n        }\n    }\n\n    /// Create new freshness information with only the age\n    pub(crate) fn new_age(age: u64, timestamp: DateTime<Utc>) -> Self {\n        Self {\n            age,\n            max_age: None,\n            timestamp,\n        }\n    }\n\n    /// Create new freshness information from a timestamp\n    pub(crate) fn created_at(timestamp: DateTime<Utc>, max_age: Option<Duration>) -> Self {\n        Self {\n            age: timestamp\n                .signed_duration_since(Utc::now())\n                .num_seconds()\n                .unsigned_abs(),\n            max_age: max_age.map(|d| d.as_secs()),\n            timestamp,\n        }\n    }\n\n    /// Create new freshness information at the current time\n    pub fn now(max_age: Option<Duration>) -> Self {\n        Self {\n            age: 0,\n            max_age: max_age.map(|d| d.as_secs()),\n            timestamp: Utc::now(),\n        }\n    }\n\n    /// Get the age of the rendered response in seconds\n    pub fn age(&self) -> u64 {\n        self.age\n    }\n\n    /// Get the maximum age of the rendered response in seconds\n    pub fn max_age(&self) -> Option<u64> {\n        self.max_age\n    }\n\n    /// Get the time the response was rendered\n    pub fn timestamp(&self) -> DateTime<Utc> {\n        self.timestamp\n    }\n\n    /// Write the freshness to the response headers.\n    pub fn write(&self, headers: &mut http::HeaderMap<http::HeaderValue>) {\n        let age = self.age();\n        headers.insert(http::header::AGE, age.into());\n        if let Some(max_age) = self.max_age() {\n            headers.insert(\n                http::header::CACHE_CONTROL,\n                http::HeaderValue::from_str(&format!(\"max-age={}\", max_age)).unwrap(),\n            );\n        }\n    }\n}\n"
  },
  {
    "path": "packages/fullstack-server/src/isrg/fs_cache.rs",
    "content": "#![allow(non_snake_case)]\n\nuse chrono::{DateTime, Utc};\n\nuse super::{IncrementalRendererError, RenderFreshness};\nuse std::{path::PathBuf, sync::Arc, time::SystemTime};\n\npub(crate) type PathMapFn = Arc<dyn Fn(&str) -> PathBuf + Send + Sync>;\n\npub(crate) struct FileSystemCache {\n    static_dir: PathBuf,\n    map_path: PathMapFn,\n    invalidate_after: Option<std::time::Duration>,\n}\n\nimpl FileSystemCache {\n    pub fn new(\n        static_dir: PathBuf,\n        map_path: Option<PathMapFn>,\n        invalidate_after: Option<std::time::Duration>,\n    ) -> Self {\n        Self {\n            static_dir: static_dir.clone(),\n            map_path: map_path.unwrap_or_else(move || {\n                Arc::new(move |route: &str| {\n                    let (before_query, _) = route.split_once('?').unwrap_or((route, \"\"));\n                    let mut path = static_dir.clone();\n                    for segment in before_query.split('/') {\n                        path.push(segment);\n                    }\n                    path\n                })\n            }),\n            invalidate_after,\n        }\n    }\n\n    pub fn put(\n        &mut self,\n        route: String,\n        timestamp: DateTime<Utc>,\n        data: Vec<u8>,\n    ) -> Result<(), IncrementalRendererError> {\n        use std::io::Write;\n        let file_path = self.route_as_path(&route, timestamp);\n        if let Some(parent) = file_path.parent() {\n            if !parent.exists() {\n                std::fs::create_dir_all(parent)?;\n            }\n        }\n        let file = std::fs::File::create(file_path)?;\n        let mut file = std::io::BufWriter::new(file);\n        file.write_all(&data)?;\n        Ok(())\n    }\n\n    pub fn clear(&mut self) {\n        // clear the static directory of index.html files contained within folders\n        for entry in std::fs::read_dir(&self.static_dir)\n            .into_iter()\n            .flatten()\n            .flatten()\n        {\n            if entry.file_type().map(|ft| ft.is_dir()).unwrap_or(false) {\n                for entry in walkdir::WalkDir::new(entry.path()).into_iter().flatten() {\n                    if entry.file_type().is_file() {\n                        if let Some(fnmae) = entry.file_name().to_str() {\n                            if fnmae.ends_with(\".html\") {\n                                if let Err(err) = std::fs::remove_file(entry.path()) {\n                                    tracing::error!(\"Failed to remove file: {}\", err);\n                                }\n                            }\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    pub fn invalidate(&mut self, route: &str) {\n        let file_path = self.find_file(route).unwrap().full_path;\n        if let Err(err) = std::fs::remove_file(file_path) {\n            tracing::error!(\"Failed to remove file: {}\", err);\n        }\n    }\n\n    pub fn get(\n        &self,\n        route: &str,\n    ) -> Result<Option<(RenderFreshness, Vec<u8>)>, IncrementalRendererError> {\n        if let Some(file_path) = self.find_file(route) {\n            if let Some(freshness) = file_path.freshness(self.invalidate_after) {\n                if let Ok(file) = std::fs::File::open(file_path.full_path) {\n                    let mut file = std::io::BufReader::new(file);\n                    let mut cache_hit = Vec::new();\n                    std::io::copy(&mut file, &mut cache_hit)?;\n                    tracing::trace!(\"file cache hit {:?}\", route);\n                    return Ok(Some((freshness, cache_hit)));\n                }\n            }\n        }\n\n        Ok(None)\n    }\n\n    fn find_file(&self, route: &str) -> Option<ValidCachedPath> {\n        let mut file_path = (self.map_path)(route);\n        if let Some(deadline) = self.invalidate_after {\n            // find the first file that matches the route and is a html file\n            file_path.push(\"index\");\n            if let Ok(dir) = std::fs::read_dir(file_path) {\n                for entry in dir.flatten() {\n                    if let Some(cached_path) = ValidCachedPath::try_from_path(entry.path()) {\n                        if let Ok(elapsed) = cached_path.timestamp.elapsed() {\n                            if elapsed < deadline {\n                                // The timestamp is valid, return the file\n                                return Some(cached_path);\n                            }\n                        }\n                        // if the timestamp is invalid or passed, delete the file\n                        if let Err(err) = std::fs::remove_file(entry.path()) {\n                            tracing::error!(\"Failed to remove file: {}\", err);\n                        }\n                    }\n                }\n                None\n            } else {\n                None\n            }\n        } else {\n            file_path.push(\"index.html\");\n            file_path.exists().then_some({\n                ValidCachedPath {\n                    full_path: file_path,\n                    timestamp: SystemTime::now(),\n                }\n            })\n        }\n    }\n\n    fn route_as_path(&self, route: &str, timestamp: DateTime<Utc>) -> PathBuf {\n        let mut file_path = (self.map_path)(route);\n        if self.track_timestamps() {\n            file_path.push(\"index\");\n            file_path.push(timestamp_to_string(timestamp));\n        } else {\n            file_path.push(\"index\");\n        }\n        file_path.set_extension(\"html\");\n        file_path\n    }\n\n    fn track_timestamps(&self) -> bool {\n        self.invalidate_after.is_some()\n    }\n}\n\npub(crate) struct ValidCachedPath {\n    pub(crate) full_path: PathBuf,\n    pub(crate) timestamp: std::time::SystemTime,\n}\n\nimpl ValidCachedPath {\n    pub fn try_from_path(value: PathBuf) -> Option<Self> {\n        if value.extension() != Some(std::ffi::OsStr::new(\"html\")) {\n            return None;\n        }\n        let timestamp = decode_timestamp(value.file_stem()?.to_str()?)?;\n        let full_path = value;\n        Some(Self {\n            full_path,\n            timestamp,\n        })\n    }\n\n    pub fn freshness(&self, max_age: Option<std::time::Duration>) -> Option<RenderFreshness> {\n        let age = self.timestamp.elapsed().ok()?.as_secs();\n        let max_age = max_age.map(|max_age| max_age.as_secs());\n        Some(RenderFreshness::new(age, max_age?, self.timestamp.into()))\n    }\n}\n\nfn decode_timestamp(timestamp: &str) -> Option<std::time::SystemTime> {\n    let timestamp = u64::from_str_radix(timestamp, 16).ok()?;\n    Some(std::time::UNIX_EPOCH + std::time::Duration::from_secs(timestamp))\n}\n\npub(crate) fn timestamp_to_string(timestamp: DateTime<Utc>) -> String {\n    let timestamp = timestamp\n        .signed_duration_since(DateTime::<Utc>::from(std::time::UNIX_EPOCH))\n        .num_seconds();\n    format!(\"{:x}\", timestamp)\n}\n"
  },
  {
    "path": "packages/fullstack-server/src/isrg/memory_cache.rs",
    "content": "//! Incremental file based incremental rendering\n\n#![allow(non_snake_case)]\n\nuse chrono::offset::Utc;\nuse chrono::DateTime;\nuse rustc_hash::FxHasher;\nuse std::{hash::BuildHasherDefault, num::NonZeroUsize};\n\nuse super::freshness::RenderFreshness;\n\npub(crate) struct InMemoryCache {\n    #[allow(clippy::type_complexity)]\n    lru: Option<lru::LruCache<String, (DateTime<Utc>, Vec<u8>), BuildHasherDefault<FxHasher>>>,\n    invalidate_after: Option<std::time::Duration>,\n}\n\nimpl InMemoryCache {\n    pub fn new(memory_cache_limit: usize, invalidate_after: Option<std::time::Duration>) -> Self {\n        Self {\n            lru: NonZeroUsize::new(memory_cache_limit)\n                .map(|limit| lru::LruCache::with_hasher(limit, Default::default())),\n            invalidate_after,\n        }\n    }\n\n    pub fn clear(&mut self) {\n        if let Some(cache) = &mut self.lru {\n            cache.clear();\n        }\n    }\n\n    pub fn put(&mut self, route: String, timestamp: DateTime<Utc>, data: Vec<u8>) {\n        if let Some(cache) = &mut self.lru {\n            cache.put(route, (timestamp, data));\n        }\n    }\n\n    pub fn invalidate(&mut self, route: &str) {\n        if let Some(cache) = &mut self.lru {\n            cache.pop(route);\n        }\n    }\n\n    pub fn try_get_or_insert<'a, F: FnOnce() -> Result<(DateTime<Utc>, Vec<u8>), E>, E>(\n        &'a mut self,\n        route: &str,\n        or_insert: F,\n    ) -> Result<Option<(RenderFreshness, &'a [u8])>, E> {\n        if let Some(memory_cache) = self.lru.as_mut() {\n            let (timestamp, _) = memory_cache.try_get_or_insert(route.to_string(), or_insert)?;\n\n            let now = Utc::now();\n            let elapsed = timestamp.signed_duration_since(now);\n            let age = elapsed.num_seconds();\n            // The cache entry is out of date, so we need to remove it.\n            if let Some(invalidate_after) = self.invalidate_after {\n                // If we can't convert to a std duration, the duration is negative and hasn't elapsed yet.\n                if let Ok(std_elapsed) = elapsed.to_std() {\n                    if std_elapsed > invalidate_after {\n                        tracing::trace!(\"memory cache out of date\");\n                        memory_cache.pop(route);\n                        return Ok(None);\n                    }\n                }\n            }\n\n            // We need to reborrow because we may have invalidated the lifetime if the route was removed.\n            // We know it wasn't because we returned... but rust doesn't understand that.\n            let (timestamp, cache_hit) = memory_cache.get(route).unwrap();\n\n            return match self.invalidate_after {\n                Some(invalidate_after) => {\n                    tracing::trace!(\"memory cache hit\");\n                    let max_age = invalidate_after.as_secs();\n                    let freshness = RenderFreshness::new(age as u64, max_age, *timestamp);\n                    Ok(Some((freshness, cache_hit)))\n                }\n                None => {\n                    tracing::trace!(\"memory cache hit\");\n                    let freshness = RenderFreshness::new_age(age as u64, *timestamp);\n                    Ok(Some((freshness, cache_hit)))\n                }\n            };\n        }\n\n        Ok(None)\n    }\n}\n"
  },
  {
    "path": "packages/fullstack-server/src/isrg/mod.rs",
    "content": "//! Incremental file based incremental rendering\n\n#![allow(non_snake_case)]\n\nmod config;\nmod freshness;\n#[cfg(not(target_arch = \"wasm32\"))]\nmod fs_cache;\nmod memory_cache;\n\nuse std::time::Duration;\n\nuse chrono::Utc;\npub use config::*;\npub use freshness::*;\n\nuse self::memory_cache::InMemoryCache;\n\n/// A render that was cached from a previous render.\npub struct CachedRender<'a> {\n    /// The route that was rendered\n    pub route: String,\n    /// The freshness information for the rendered response\n    pub freshness: RenderFreshness,\n    /// The rendered response\n    pub response: &'a [u8],\n}\n\n/// An incremental renderer.\npub struct IncrementalRenderer {\n    pub(crate) memory_cache: InMemoryCache,\n    #[cfg(not(target_arch = \"wasm32\"))]\n    pub(crate) file_system_cache: fs_cache::FileSystemCache,\n    invalidate_after: Option<Duration>,\n}\n\nimpl IncrementalRenderer {\n    /// Create a new incremental renderer builder.\n    pub fn builder() -> IncrementalRendererConfig {\n        IncrementalRendererConfig::new()\n    }\n\n    /// Remove a route from the cache.\n    pub fn invalidate(&mut self, route: &str) {\n        self.memory_cache.invalidate(route);\n        #[cfg(not(target_arch = \"wasm32\"))]\n        self.file_system_cache.invalidate(route);\n    }\n\n    /// Remove all routes from the cache.\n    pub fn invalidate_all(&mut self) {\n        self.memory_cache.clear();\n        #[cfg(not(target_arch = \"wasm32\"))]\n        self.file_system_cache.clear();\n    }\n\n    /// Cache a rendered response.\n    ///\n    /// ```rust\n    /// # use dioxus_server::IncrementalRenderer;\n    /// # let mut renderer = IncrementalRenderer::builder().build();\n    /// let route = \"/index\".to_string();\n    /// let response = b\"<html><body>Hello world</body></html>\";\n    /// renderer.cache(route, response).unwrap();\n    /// ```\n    pub fn cache(\n        &mut self,\n        route: String,\n        html: impl Into<Vec<u8>>,\n    ) -> Result<RenderFreshness, IncrementalRendererError> {\n        let timestamp = Utc::now();\n        let html = html.into();\n        #[cfg(not(target_arch = \"wasm32\"))]\n        self.file_system_cache\n            .put(route.clone(), timestamp, html.clone())?;\n        self.memory_cache.put(route, timestamp, html);\n        Ok(RenderFreshness::created_at(\n            timestamp,\n            self.invalidate_after,\n        ))\n    }\n\n    /// Try to get a cached response for a route.\n    ///\n    /// ```rust\n    /// # use dioxus_server::IncrementalRenderer;\n    /// # let mut renderer = IncrementalRenderer::builder().build();\n    /// # let route = \"/index\".to_string();\n    /// # let response = b\"<html><body>Hello world</body></html>\";\n    /// # renderer.cache(route, response).unwrap();\n    /// let route = \"/index\";\n    /// let response = renderer.get(route).unwrap();\n    /// assert_eq!(response.unwrap().response, b\"<html><body>Hello world</body></html>\");\n    /// ```\n    ///\n    /// If the route is not cached, `None` is returned.\n    ///\n    /// ```rust\n    /// # use dioxus_server::IncrementalRenderer;\n    /// # let mut renderer = IncrementalRenderer::builder().build();\n    /// let route = \"/index\";\n    /// let response = renderer.get(route).unwrap();\n    /// assert!(response.is_none());\n    /// ```\n    pub fn get<'a>(\n        &'a mut self,\n        route: &str,\n    ) -> Result<Option<CachedRender<'a>>, IncrementalRendererError> {\n        let Self {\n            memory_cache,\n            #[cfg(not(target_arch = \"wasm32\"))]\n            file_system_cache,\n            ..\n        } = self;\n\n        #[allow(unused)]\n        enum FsGetError {\n            NotPresent,\n            Error(IncrementalRendererError),\n        }\n\n        // The borrow checker prevents us from simply using a match/if and returning early. Instead we need to use the more complex closure API\n        // non lexical lifetimes will make this possible (it works with polonius)\n        let or_insert = || {\n            // check the file cache\n            #[cfg(not(target_arch = \"wasm32\"))]\n            return match file_system_cache.get(route) {\n                Ok(Some((freshness, bytes))) => Ok((freshness.timestamp(), bytes)),\n                Ok(None) => Err(FsGetError::NotPresent),\n                Err(e) => Err(FsGetError::Error(e)),\n            };\n\n            #[allow(unreachable_code)]\n            Err(FsGetError::NotPresent)\n        };\n\n        match memory_cache.try_get_or_insert(route, or_insert) {\n            Ok(Some((freshness, bytes))) => Ok(Some(CachedRender {\n                route: route.to_string(),\n                freshness,\n                response: bytes,\n            })),\n            Err(FsGetError::NotPresent) | Ok(None) => Ok(None),\n            Err(FsGetError::Error(e)) => Err(e),\n        }\n    }\n}\n\n/// An error that can occur while rendering a route or retrieving a cached route.\n#[derive(Debug, thiserror::Error)]\n#[non_exhaustive]\npub enum IncrementalRendererError {\n    /// An formatting error occurred while rendering a route.\n    #[error(\"RenderError: {0}\")]\n    RenderError(#[from] std::fmt::Error),\n\n    /// An IO error occurred while rendering a route.\n    #[error(\"IoError: {0}\")]\n    IoError(#[from] std::io::Error),\n\n    /// An error occurred while rendering a route.\n    #[error(\"Unknown error: {0}\")]\n    Other(#[from] dioxus_core::CapturedError),\n}\n"
  },
  {
    "path": "packages/fullstack-server/src/launch.rs",
    "content": "//! A launch function that creates an axum router for the LaunchBuilder\n\nuse crate::{server::DioxusRouterExt, FullstackState, ServeConfig};\nuse anyhow::Context;\nuse axum::{\n    body::Body,\n    extract::{Request, State},\n    routing::IntoMakeService,\n    Router,\n};\nuse dioxus_cli_config::base_path;\nuse dioxus_core::{ComponentFunction, Element};\n\nuse dioxus_devtools::{DevserverMsg, HotReloadMsg};\nuse futures_util::{stream::FusedStream, StreamExt};\nuse hyper::body::Incoming;\nuse hyper_util::server::conn::auto::Builder as HyperBuilder;\nuse hyper_util::{\n    rt::{TokioExecutor, TokioIo},\n    service::TowerToHyperService,\n};\nuse std::{any::Any, net::SocketAddr, prelude::rust_2024::Future};\nuse std::{pin::Pin, sync::Arc};\nuse subsecond::HotFn;\nuse tokio_util::either::Either;\nuse tower::{Service, ServiceExt as _};\n\n#[cfg(not(target_arch = \"wasm32\"))]\nuse {\n    dioxus_core::{RenderError, VNode},\n    tokio::net::TcpListener,\n};\n\ntype ContextList = Vec<Box<dyn Fn() -> Box<dyn Any> + Send + Sync>>;\ntype BaseComp = fn() -> Element;\n\n/// Launch a fullstack app with the given root component.\npub fn launch(root: BaseComp) -> ! {\n    launch_cfg(root, vec![], vec![])\n}\n\n/// Launch a fullstack app with the given root component, contexts, and config.\n#[allow(unused)]\npub fn launch_cfg(root: BaseComp, contexts: ContextList, platform_config: Vec<Box<dyn Any>>) -> ! {\n    #[cfg(not(target_arch = \"wasm32\"))]\n    tokio::runtime::Runtime::new()\n        .unwrap()\n        .block_on(async move { serve_server(root, contexts, platform_config).await });\n\n    unreachable!(\"Launching a fullstack app should never return\")\n}\n\n#[cfg(not(target_arch = \"wasm32\"))]\nasync fn serve_server(\n    original_root: fn() -> Result<VNode, RenderError>,\n    contexts: Vec<Box<dyn Fn() -> Box<dyn Any> + Send + Sync>>,\n    platform_config: Vec<Box<dyn Any>>,\n) {\n    let mut cfg = platform_config\n        .into_iter()\n        .find_map(|cfg| cfg.downcast::<ServeConfig>().ok().map(|b| *b))\n        .unwrap_or_else(ServeConfig::new);\n\n    // Extend the config's context providers with the context providers from the launch builder\n    for ctx in contexts {\n        let arced = Arc::new(ctx) as Arc<dyn Fn() -> Box<dyn Any> + Send + Sync>;\n        cfg.context_providers.push(arced);\n    }\n\n    let cb = move || {\n        let cfg = cfg.clone();\n        Box::pin(async move {\n            Ok(apply_base_path(\n                Router::new().serve_dioxus_application(cfg.clone(), original_root),\n                original_root,\n                cfg.clone(),\n                base_path().map(|s| s.to_string()),\n            ))\n        }) as _\n    };\n\n    serve_router(cb, dioxus_cli_config::fullstack_address_or_localhost()).await;\n}\n\n/// Create a router that serves the dioxus application at the appropriate base path.\n///\n/// This method automatically setups up:\n/// - Static asset serving\n/// - Mapping of base paths\n/// - Automatic registration of server functions\n/// - Handler to render the dioxus application\n/// - WebSocket handling for live reload and devtools\n/// - Hot-reloading\n/// - Async Runtime\n/// - Logging\npub fn router(app: fn() -> Element) -> Router {\n    let cfg = ServeConfig::new();\n    apply_base_path(\n        Router::new().serve_dioxus_application(cfg.clone(), app),\n        app,\n        cfg,\n        base_path().map(|s| s.to_string()),\n    )\n}\n\n/// Serve a fullstack dioxus application with a custom axum router.\n///\n/// This function sets up an async runtime, enables the default dioxus logger, runs the provided initializer,\n/// and then starts an axum server with the returned router.\n///\n/// The axum router will be bound to the address specified by the `IP` and `PORT` environment variables,\n/// defaulting to `127.0.0.1:8080` if not set.\n///\n/// This function uses axum to block on serving the application, and will not return.\npub fn serve<F>(mut serve_it: impl FnMut() -> F) -> !\nwhere\n    F: Future<Output = Result<Router, anyhow::Error>> + 'static,\n{\n    let cb = move || Box::pin(serve_it()) as _;\n\n    block_on(\n        async move { serve_router(cb, dioxus_cli_config::fullstack_address_or_localhost()).await },\n    );\n\n    unreachable!(\"Serving a fullstack app should never return\")\n}\n\n/// Serve a fullstack dioxus application with a custom axum router.\n///\n/// This function enables the dioxus logger and then serves the axum server with hot-reloading support.\n///\n/// To enable hot-reloading of the router, the provided `serve_callback` should return a new `Router`\n/// each time it is called.\npub async fn serve_router(\n    mut serve_callback: impl FnMut() -> Pin<Box<dyn Future<Output = Result<Router, anyhow::Error>>>>,\n    addr: SocketAddr,\n) {\n    dioxus_logger::initialize_default();\n\n    let listener = TcpListener::bind(addr)\n        .await\n        .with_context(|| format!(\"Failed to bind to address {addr}\"))\n        .unwrap();\n\n    // If we're not in debug mode, just serve the app normally\n    if !cfg!(debug_assertions) {\n        axum::serve(listener, serve_callback().await.unwrap())\n            .await\n            .unwrap();\n        return;\n    }\n\n    // Wire up the devtools connection. The sender only sends messages in dev.\n    let (devtools_tx, mut devtools_rx) = futures_channel::mpsc::unbounded();\n    dioxus_devtools::connect(move |msg| _ = devtools_tx.unbounded_send(msg));\n\n    let mut hot_serve_callback = HotFn::current(serve_callback);\n    let mut make_service = hot_serve_callback\n        .call(())\n        .await\n        .map(|router| router.into_make_service())\n        .unwrap();\n\n    let (shutdown_tx, _) = tokio::sync::broadcast::channel(1);\n    let our_build_id = Some(dioxus_cli_config::build_id());\n\n    // Manually loop on accepting connections so we can also respond to devtools messages\n    loop {\n        let res = tokio::select! {\n            res = listener.accept() => Either::Left(res),\n            Some(msg) = devtools_rx.next(), if !devtools_rx.is_terminated() => Either::Right(msg),\n            else => continue\n        };\n\n        match res {\n            Either::Left(Ok((tcp_stream, _remote_addr))) => {\n                let mut make_service = make_service.clone();\n                let mut shutdown_rx = shutdown_tx.subscribe();\n\n                tokio::task::spawn(async move {\n                    let tcp_stream = TokioIo::new(tcp_stream);\n\n                    std::future::poll_fn(|cx| {\n                        <IntoMakeService<Router> as tower::Service<Request>>::poll_ready(\n                            &mut make_service,\n                            cx,\n                        )\n                    })\n                    .await\n                    .expect(\"Infallible\");\n\n                    // upgrades needed for websockets\n                    let builder = HyperBuilder::new(TokioExecutor::new());\n                    let connection = builder.serve_connection_with_upgrades(\n                        tcp_stream,\n                        TowerToHyperService::new(\n                            make_service\n                                .call(())\n                                .await\n                                .unwrap()\n                                .map_request(|req: Request<Incoming>| req.map(Body::new)),\n                        ),\n                    );\n\n                    tokio::select! {\n                        res = connection => {\n                            if let Err(_err) = res {\n                                // This error only appears when the client doesn't send a request and\n                                // terminate the connection.\n                                //\n                                // If client sends one request then terminate connection whenever, it doesn't\n                                // appear.\n                            }\n                        }\n                        _res = shutdown_rx.recv() => {}\n                    }\n                });\n            }\n\n            // Handle just hot-patches for now.\n            // We don't do RSX hot-reload since usually the client handles that once the page is loaded.\n            //\n            // todo(jon): I *believe* SSR is resilient to RSX changes, but we should verify that...\n            Either::Right(DevserverMsg::HotReload(HotReloadMsg {\n                jump_table: Some(table),\n                for_build_id,\n                ..\n            })) if for_build_id == our_build_id => {\n                // Apply the hot-reload patch to the dioxus devtools first\n                unsafe { dioxus_devtools::subsecond::apply_patch(table).unwrap() };\n\n                // Now recreate the router\n                // We panic here because we don't want their app to continue in a maybe-corrupted state\n                make_service = hot_serve_callback\n                    .call(())\n                    .await\n                    .expect(\"Failed to create new router after hot-patch!\")\n                    .into_make_service();\n\n                // Make sure to wipe out the renderer state so we don't have stale elements\n                crate::document::reset_renderer();\n\n                _ = shutdown_tx.send(());\n            }\n\n            // Explicitly don't handle RSX hot-reloads on the server\n            // The client will handle that once the page is loaded. If we handled it here,\n            _ => {}\n        }\n    }\n}\n\nfn block_on<T>(app_future: impl Future<Output = T>) {\n    if let Ok(handle) = tokio::runtime::Handle::try_current() {\n        handle.block_on(app_future);\n    } else {\n        tokio::runtime::Builder::new_multi_thread()\n            .enable_all()\n            .build()\n            .unwrap()\n            .block_on(app_future);\n    }\n}\n\nfn apply_base_path<M: 'static>(\n    mut router: Router,\n    root: impl ComponentFunction<(), M> + Send + Sync,\n    cfg: ServeConfig,\n    base_path: Option<String>,\n) -> Router {\n    if let Some(base_path) = base_path {\n        let base_path = base_path.trim_matches('/');\n\n        // If there is a base path, nest the router under it and serve the root route manually\n        // Nesting a route in axum only serves /base_path or /base_path/ not both\n        router = Router::new().nest(&format!(\"/{base_path}/\"), router).route(\n            &format!(\"/{base_path}\"),\n            axum::routing::method_routing::get(\n                |state: State<FullstackState>, mut request: Request<Body>| async move {\n                    // The root of the base path always looks like the root from dioxus fullstack\n                    *request.uri_mut() = \"/\".parse().unwrap();\n                    FullstackState::render_handler(state, request).await\n                },\n            )\n            .with_state(FullstackState::new(cfg, root)),\n        )\n    }\n\n    router\n}\n"
  },
  {
    "path": "packages/fullstack-server/src/lib.rs",
    "content": "#![doc = include_str!(\"../README.md\")]\n#![doc(html_logo_url = \"https://avatars.githubusercontent.com/u/79236386\")]\n#![doc(html_favicon_url = \"https://avatars.githubusercontent.com/u/79236386\")]\n// #![warn(missing_docs)]\n#![cfg_attr(docsrs, feature(doc_cfg))]\n#![forbid(unexpected_cfgs)]\n\n// re-exported to make it possible to implement a custom Client without adding a separate\n// dependency on `bytes`\npub use bytes::Bytes;\npub use dioxus_fullstack_core::{ServerFnError, ServerFnResult};\n\npub use axum;\npub use config::ServeConfig;\npub use config::*;\npub use document::ServerDocument;\npub use http;\npub use inventory;\npub use server::*;\n\npub mod redirect;\n\n#[cfg(not(target_arch = \"wasm32\"))]\nmod launch;\n\n#[cfg(not(target_arch = \"wasm32\"))]\npub use launch::{launch, launch_cfg};\n\n/// Implementations of the server side of the server function call.\npub mod server;\n\n/// Types and traits for HTTP responses.\n// pub mod response;\npub mod config;\n\npub(crate) mod document;\npub(crate) mod ssr;\npub(crate) mod streaming;\n\npub use launch::router;\npub use launch::serve;\n\npub mod serverfn;\npub use serverfn::*;\n\npub mod isrg;\npub use isrg::*;\n\nmod index_html;\npub(crate) use index_html::IndexHtml;\n"
  },
  {
    "path": "packages/fullstack-server/src/redirect.rs",
    "content": "use std::sync::OnceLock;\n\n/// A custom header that can be set with any value to indicate\n/// that the server function client should redirect to a new route.\n///\n/// This is useful because it allows returning a value from the request,\n/// while also indicating that a redirect should follow. This cannot be\n/// done with an HTTP `3xx` status code, because the browser will follow\n/// that redirect rather than returning the desired data.\npub const REDIRECT_HEADER: &str = \"serverfnredirect\";\n\n/// A function that will be called if a server function returns a `3xx` status\n/// or the [`REDIRECT_HEADER`].\npub type RedirectHook = Box<dyn Fn(&str) + Send + Sync>;\n\n// allowed: not in a public API, and pretty straightforward\n#[allow(clippy::type_complexity)]\npub(crate) static REDIRECT_HOOK: OnceLock<RedirectHook> = OnceLock::new();\n\n/// Sets a function that will be called if a server function returns a `3xx` status\n/// or the [`REDIRECT_HEADER`]. Returns `Err(_)` if the hook has already been set.\npub fn set_redirect_hook(hook: impl Fn(&str) + Send + Sync + 'static) -> Result<(), RedirectHook> {\n    REDIRECT_HOOK.set(Box::new(hook))\n}\n\n/// Calls the hook that has been set by [`set_redirect_hook`] to redirect to `loc`.\npub fn call_redirect_hook(loc: &str) {\n    if let Some(hook) = REDIRECT_HOOK.get() {\n        hook(loc)\n    }\n}\n"
  },
  {
    "path": "packages/fullstack-server/src/server.rs",
    "content": "use crate::{\n    ssr::{SSRError, SsrRendererPool},\n    ServeConfig, ServerFunction,\n};\nuse axum::{\n    body::Body,\n    extract::State,\n    http::{Request, StatusCode},\n    response::{IntoResponse, Response},\n    routing::*,\n};\nuse dioxus_core::{ComponentFunction, VirtualDom};\nuse http::header::*;\nuse std::path::{Path, PathBuf};\nuse std::sync::Arc;\nuse tokio_util::task::LocalPoolHandle;\nuse tower::util::MapResponse;\nuse tower::ServiceExt;\nuse tower_http::services::fs::ServeFileSystemResponseBody;\n\n/// A extension trait with utilities for integrating Dioxus with your Axum router.\npub trait DioxusRouterExt {\n    /// Serves the static WASM for your Dioxus application (except the generated index.html).\n    ///\n    /// # Example\n    /// ```rust, no_run\n    /// # #![allow(non_snake_case)]\n    /// # use dioxus::prelude::*;\n    /// use dioxus_server::DioxusRouterExt;\n    ///\n    /// #[tokio::main]\n    /// async fn main() -> anyhow::Result<()> {\n    ///     let addr = dioxus::cli_config::fullstack_address_or_localhost();\n    ///     let router = axum::Router::new()\n    ///         // Server side render the application, serve static assets, and register server functions\n    ///         .serve_static_assets()\n    ///         // Server render the application\n    ///         // ...\n    ///         .with_state(dioxus_server::FullstackState::headless());\n    ///     let listener = tokio::net::TcpListener::bind(addr).await?;\n    ///     axum::serve(listener, router).await?;\n    ///     Ok(())\n    /// }\n    /// ```\n    fn serve_static_assets(self) -> Router<FullstackState>;\n\n    /// Serves the Dioxus application. This will serve a complete server side rendered application.\n    /// This will serve static assets, server render the application, register server functions, and integrate with hot reloading.\n    ///\n    /// # Example\n    /// ```rust, no_run\n    /// # #![allow(non_snake_case)]\n    /// # use dioxus::prelude::*;\n    /// use dioxus_server::{DioxusRouterExt, ServeConfig};\n    ///\n    /// #[tokio::main]\n    /// async fn main() {\n    ///     let addr = dioxus::cli_config::fullstack_address_or_localhost();\n    ///     let router = axum::Router::new()\n    ///         // Server side render the application, serve static assets, and register server functions\n    ///         .serve_dioxus_application(ServeConfig::new(), app);\n    ///     let listener = tokio::net::TcpListener::bind(addr).await.unwrap();\n    ///     axum::serve(listener, router).await.unwrap();\n    /// }\n    ///\n    /// fn app() -> Element {\n    ///     rsx! { \"Hello World\" }\n    /// }\n    /// ```\n    fn serve_dioxus_application<M: 'static>(\n        self,\n        cfg: ServeConfig,\n        app: impl ComponentFunction<(), M> + Send + Sync,\n    ) -> Router<()>;\n\n    /// Registers server functions with the default handler.\n    ///\n    /// # Example\n    /// ```rust, no_run\n    /// # use dioxus::prelude::*;\n    /// # use dioxus_server::DioxusRouterExt;\n    /// #[tokio::main]\n    /// async fn main() {\n    ///     let addr = dioxus::cli_config::fullstack_address_or_localhost();\n    ///     let router = axum::Router::new()\n    ///         // Register server functions routes with the default handler\n    ///         .register_server_functions()\n    ///         .with_state(dioxus_server::FullstackState::headless());\n    ///     let listener = tokio::net::TcpListener::bind(addr).await.unwrap();\n    ///     axum::serve(listener, router).await.unwrap();\n    /// }\n    /// ```\n    #[allow(dead_code)]\n    fn register_server_functions(self) -> Router<FullstackState>;\n\n    /// Serves a Dioxus application without static assets.\n    /// Sets up server function routes and rendering endpoints only.\n    ///\n    /// Useful for WebAssembly environments or when static assets\n    /// are served by another system.\n    ///\n    /// # Example\n    /// ```rust, no_run\n    /// # use dioxus::prelude::*;\n    /// # use dioxus_server::{DioxusRouterExt, ServeConfig};\n    /// #[tokio::main]\n    /// async fn main() {\n    ///     let router = axum::Router::new()\n    ///         .serve_api_application(ServeConfig::new(), app)\n    ///         .into_make_service();\n    ///     // ...\n    /// }\n    ///\n    /// fn app() -> Element {\n    ///     rsx! { \"Hello World\" }\n    /// }\n    /// ```\n    fn serve_api_application<M: 'static>(\n        self,\n        cfg: ServeConfig,\n        app: impl ComponentFunction<(), M> + Send + Sync,\n    ) -> Router<()>\n    where\n        Self: Sized;\n}\n\n#[cfg(not(target_arch = \"wasm32\"))]\nimpl DioxusRouterExt for Router<FullstackState> {\n    fn register_server_functions(mut self) -> Router<FullstackState> {\n        use std::collections::HashSet;\n\n        let mut seen = HashSet::new();\n\n        for func in ServerFunction::collect() {\n            if seen.insert(format!(\"{} {}\", func.method(), func.path())) {\n                tracing::info!(\"Registering: {} {}\", func.method(), func.path());\n\n                self = self.route(func.path(), func.method_router())\n            }\n        }\n\n        self\n    }\n\n    fn serve_static_assets(self) -> Router<FullstackState> {\n        let Some(public_path) = public_path() else {\n            return self;\n        };\n\n        // Serve all files in public folder except index.html\n        serve_dir_cached(self, &public_path, &public_path)\n    }\n\n    fn serve_api_application<M: 'static>(\n        self,\n        cfg: ServeConfig,\n        app: impl ComponentFunction<(), M> + Send + Sync,\n    ) -> Router<()> {\n        self.register_server_functions()\n            .fallback(get(FullstackState::render_handler))\n            .with_state(FullstackState::new(cfg, app))\n    }\n\n    fn serve_dioxus_application<M: 'static>(\n        self,\n        cfg: ServeConfig,\n        app: impl ComponentFunction<(), M> + Send + Sync,\n    ) -> Router<()> {\n        self.register_server_functions()\n            .serve_static_assets()\n            .fallback(get(FullstackState::render_handler))\n            .with_state(FullstackState::new(cfg, app))\n    }\n}\n\n/// SSR renderer handler for Axum with added context injection.\n///\n/// # Example\n/// ```rust,no_run\n/// #![allow(non_snake_case)]\n/// use std::sync::{Arc, Mutex};\n///\n/// use axum::routing::get;\n/// use dioxus::prelude::*;\n/// use dioxus_server::{FullstackState, render_handler, ServeConfig};\n///\n/// fn app() -> Element {\n///     rsx! {\n///         \"hello!\"\n///     }\n/// }\n///\n/// #[tokio::main]\n/// async fn main() {\n///     let addr = dioxus::cli_config::fullstack_address_or_localhost();\n///     let router = axum::Router::new()\n///         // Register server functions, etc.\n///         // Note you can use `register_server_functions_with_context`\n///         // to inject the context into server functions running outside\n///         // of an SSR render context.\n///         .fallback(get(render_handler))\n///         .with_state(FullstackState::new(ServeConfig::new(), app));\n///\n///     let listener = tokio::net::TcpListener::bind(addr).await.unwrap();\n///     axum::serve(listener, router).await.unwrap();\n/// }\n/// ```\npub async fn render_handler(\n    State(state): State<FullstackState>,\n    request: Request<Body>,\n) -> impl IntoResponse {\n    FullstackState::render_handler(State(state), request).await\n}\n\n/// State used by [`FullstackState::render_handler`] to render a dioxus component with axum\n#[derive(Clone)]\npub struct FullstackState {\n    config: ServeConfig,\n    build_virtual_dom: Arc<dyn Fn() -> VirtualDom + Send + Sync>,\n    renderers: Arc<SsrRendererPool>,\n    pub(crate) rt: LocalPoolHandle,\n}\n\nimpl FullstackState {\n    /// Create a headless [`FullstackState`] without a root component.\n    ///\n    /// This won't render pages, but can still be used to register server functions and serve static assets.\n    pub fn headless() -> Self {\n        let rt = LocalPoolHandle::new(\n            std::thread::available_parallelism()\n                .map(usize::from)\n                .unwrap_or(1),\n        );\n\n        Self {\n            renderers: Arc::new(SsrRendererPool::new(4, None)),\n            build_virtual_dom: Arc::new(|| {\n                panic!(\"No root component provided for headless FullstackState\")\n            }),\n            config: ServeConfig::new(),\n            rt,\n        }\n    }\n\n    /// Create a new [`FullstackState`]\n    pub fn new<M: 'static>(\n        config: ServeConfig,\n        root: impl ComponentFunction<(), M> + Send + Sync + 'static,\n    ) -> Self {\n        let rt = LocalPoolHandle::new(\n            std::thread::available_parallelism()\n                .map(usize::from)\n                .unwrap_or(1),\n        );\n\n        Self {\n            renderers: Arc::new(SsrRendererPool::new(4, config.incremental.clone())),\n            build_virtual_dom: Arc::new(move || VirtualDom::new_with_props(root.clone(), ())),\n            config,\n            rt,\n        }\n    }\n\n    /// Create a new [`FullstackState`] with a custom [`VirtualDom`] factory. This method can be\n    /// used to pass context into the root component of your application.\n    pub fn new_with_virtual_dom_factory(\n        config: ServeConfig,\n        build_virtual_dom: impl Fn() -> VirtualDom + Send + Sync + 'static,\n    ) -> Self {\n        let rt = LocalPoolHandle::new(\n            std::thread::available_parallelism()\n                .map(usize::from)\n                .unwrap_or(1),\n        );\n\n        Self {\n            renderers: Arc::new(SsrRendererPool::new(4, config.incremental.clone())),\n            config,\n            build_virtual_dom: Arc::new(build_virtual_dom),\n            rt,\n        }\n    }\n\n    /// Set the [`ServeConfig`] for this [`FullstackState`]\n    pub fn with_config(mut self, config: ServeConfig) -> Self {\n        self.config = config;\n        self\n    }\n\n    /// SSR renderer handler for Axum with added context injection.\n    ///\n    /// # Example\n    /// ```rust,no_run\n    /// #![allow(non_snake_case)]\n    /// use std::sync::{Arc, Mutex};\n    ///\n    /// use axum::routing::get;\n    /// use dioxus::prelude::*;\n    /// use dioxus_server::{FullstackState, render_handler, ServeConfig};\n    ///\n    /// fn app() -> Element {\n    ///     rsx! {\n    ///         \"hello!\"\n    ///     }\n    /// }\n    ///\n    /// #[tokio::main]\n    /// async fn main() {\n    ///     let addr = dioxus::cli_config::fullstack_address_or_localhost();\n    ///     let router = axum::Router::new()\n    ///         // Register server functions, etc.\n    ///         // Note you can use `register_server_functions_with_context`\n    ///         // to inject the context into server functions running outside\n    ///         // of an SSR render context.\n    ///         .fallback(get(render_handler))\n    ///         .with_state(FullstackState::new(ServeConfig::new(), app))\n    ///         .into_make_service();\n    ///\n    ///     let listener = tokio::net::TcpListener::bind(addr).await.unwrap();\n    ///     axum::serve(listener, router).await.unwrap();\n    /// }\n    /// ```\n    pub async fn render_handler(State(state): State<Self>, request: Request<Body>) -> Response {\n        let (parts, _) = request.into_parts();\n\n        let response = state\n            .renderers\n            .clone()\n            .render_to(parts, &state.config, &state.rt, {\n                let build_virtual_dom = state.build_virtual_dom.clone();\n                let context_providers = state.config.context_providers.clone();\n                move || {\n                    let mut vdom = build_virtual_dom();\n                    for state in context_providers.as_slice() {\n                        vdom.insert_any_root_context(state());\n                    }\n                    vdom\n                }\n            })\n            .await;\n\n        match response {\n            Ok((status, headers, freshness, rx)) => {\n                let mut response = Response::builder()\n                    .status(status.status)\n                    .header(CONTENT_TYPE, \"text/html; charset=utf-8\")\n                    .body(Body::from_stream(rx))\n                    .unwrap();\n\n                // Write our freshness header\n                freshness.write(response.headers_mut());\n\n                // write the other headers set by the user\n                for (key, value) in headers.into_iter() {\n                    if let Some(key) = key {\n                        response.headers_mut().insert(key, value);\n                    }\n                }\n\n                response\n            }\n\n            Err(SSRError::Incremental(e)) => {\n                tracing::error!(\"Failed to render page: {}\", e);\n\n                Response::builder()\n                    .status(StatusCode::INTERNAL_SERVER_ERROR)\n                    .body(e.to_string())\n                    .unwrap()\n                    .into_response()\n            }\n\n            Err(SSRError::HttpError { status, message }) => Response::builder()\n                .status(status)\n                .body(Body::from(message.unwrap_or_else(|| {\n                    status\n                        .canonical_reason()\n                        .unwrap_or(\"An unknown error occurred\")\n                        .to_string()\n                })))\n                .unwrap(),\n        }\n    }\n}\n\n/// Get the path to the public assets directory to serve static files from\npub(crate) fn public_path() -> Option<PathBuf> {\n    if let Ok(path) = std::env::var(\"DIOXUS_PUBLIC_PATH\") {\n        return Some(PathBuf::from(path));\n    }\n\n    // The CLI always bundles static assets into the exe/public directory\n    Some(\n        std::env::current_exe()\n            .ok()?\n            .parent()\n            .unwrap()\n            .join(\"public\"),\n    )\n}\n\nfn serve_dir_cached<S>(mut router: Router<S>, public_path: &Path, directory: &Path) -> Router<S>\nwhere\n    S: Send + Sync + Clone + 'static,\n{\n    use tower_http::services::{ServeDir, ServeFile};\n\n    let dir = std::fs::read_dir(directory)\n        .unwrap_or_else(|e| panic!(\"Couldn't read public directory at {:?}: {}\", &directory, e));\n\n    for entry in dir.flatten() {\n        let path = entry.path();\n        // Don't serve the index.html file. The SSR handler will generate it.\n        if path == public_path.join(\"index.html\") {\n            continue;\n        }\n\n        let route = format!(\n            \"/{}\",\n            path.strip_prefix(public_path)\n                .unwrap()\n                .iter()\n                .map(|segment| segment.to_string_lossy())\n                .collect::<Vec<_>>()\n                .join(\"/\")\n        );\n\n        if path.is_dir() {\n            // In debug builds, serve directories dynamically so new files (like\n            // wasm patch modules) are immediately visible without rebuilding\n            // the router. In release, keep the previous cached traversal\n            // behaviour.\n            #[cfg(debug_assertions)]\n            {\n                router = router.nest_service(&route, ServeDir::new(&path));\n            }\n\n            #[cfg(not(debug_assertions))]\n            {\n                router = serve_dir_cached(router, public_path, &path);\n            }\n        } else {\n            let serve_file = ServeFile::new(&path).precompressed_br();\n            // All cached assets are served at the root of the asset directory. If we know an asset\n            // is hashed for cache busting, we can cache the response on the client side forever. If\n            // the asset changes, the hash in the path will also change and the client will refetch it.\n            if file_name_looks_immutable(&route) {\n                router = router.nest_service(&route, cache_response_forever(serve_file))\n            } else {\n                router = router.nest_service(&route, serve_file)\n            }\n        }\n    }\n\n    router\n}\n\ntype MappedAxumService<S> = MapResponse<\n    S,\n    fn(Response<ServeFileSystemResponseBody>) -> Response<ServeFileSystemResponseBody>,\n>;\n\nfn cache_response_forever<S>(service: S) -> MappedAxumService<S>\nwhere\n    S: ServiceExt<Request<Body>, Response = Response<ServeFileSystemResponseBody>>,\n{\n    service.map_response(|mut response: Response<ServeFileSystemResponseBody>| {\n        response.headers_mut().insert(\n            CACHE_CONTROL,\n            HeaderValue::from_static(\"public, max-age=31536000, immutable\"),\n        );\n        response\n    })\n}\n\nfn file_name_looks_immutable(file_name: &str) -> bool {\n    // Check if the file name looks like a hash (e.g., \"main-dxh12345678.js\")\n    file_name.rsplit_once(\"-dxh\").is_some_and(|(_, hash)| {\n        hash.chars()\n            .take_while(|c| *c != '.')\n            .all(|c| c.is_ascii_hexdigit())\n    })\n}\n\n#[test]\nfn test_file_name_looks_immutable() {\n    assert!(file_name_looks_immutable(\"main-dxh12345678.js\"));\n    assert!(file_name_looks_immutable(\"style-dxhabcdef.css\"));\n    assert!(!file_name_looks_immutable(\"index.html\"));\n    assert!(!file_name_looks_immutable(\"script.js\"));\n    assert!(!file_name_looks_immutable(\"main-dxh1234wyz.js\"));\n    assert!(!file_name_looks_immutable(\"main-dxh12345678-invalid.js\"));\n}\n"
  },
  {
    "path": "packages/fullstack-server/src/serverfn.rs",
    "content": "use crate::FullstackState;\nuse axum::{\n    body::Body,\n    extract::{Request, State},\n    response::Response,\n    routing::MethodRouter,\n};\nuse dioxus_fullstack_core::FullstackContext;\nuse http::{Method, StatusCode};\nuse std::{pin::Pin, prelude::rust_2024::Future};\n\n/// A function endpoint that can be called from the client.\n#[derive(Clone)]\npub struct ServerFunction {\n    path: &'static str,\n    method: Method,\n    handler: fn() -> MethodRouter<FullstackState>,\n}\n\nimpl ServerFunction {\n    /// Create a new server function object from a MethodRouter\n    pub const fn new(\n        method: Method,\n        path: &'static str,\n        handler: fn() -> MethodRouter<FullstackState>,\n    ) -> Self {\n        Self {\n            path,\n            method,\n            handler,\n        }\n    }\n\n    /// The path of the server function.\n    pub fn path(&self) -> &'static str {\n        self.path\n    }\n\n    /// The HTTP method the server function expects.\n    pub fn method(&self) -> Method {\n        self.method.clone()\n    }\n\n    /// Collect all globally registered server functions\n    pub fn collect() -> Vec<&'static ServerFunction> {\n        inventory::iter::<ServerFunction>().collect()\n    }\n\n    /// Create a `MethodRouter` for this server function that can be mounted on an `axum::Router`.\n    ///\n    /// This runs the handler inside the required `FullstackContext` scope and populates\n    /// `FullstackContext` so that the handler can use those features.\n    ///\n    /// It also runs the server function inside a tokio `LocalPool` to allow !Send futures.\n    pub fn method_router(&self) -> MethodRouter<FullstackState> {\n        (self.handler)()\n    }\n\n    /// Creates a new `MethodRouter` for the given method and !Send handler.\n    ///\n    /// This is used internally by the `ServerFunction` to create the method router that this\n    /// server function uses.\n    #[allow(clippy::type_complexity)]\n    pub fn make_handler(\n        method: Method,\n        handler: fn(State<FullstackContext>, Request) -> Pin<Box<dyn Future<Output = Response>>>,\n    ) -> MethodRouter<FullstackState> {\n        axum::routing::method_routing::on(\n            method\n                .try_into()\n                .expect(\"MethodFilter only supports standard HTTP methods\"),\n            move |state: State<FullstackState>, request: Request| async move {\n                use tracing::Instrument;\n                let current_span = tracing::Span::current();\n                // Allow !Send futures by running in the render handlers pinned local pool\n                let result = state.rt.spawn_pinned(move || async move {\n                    use dioxus_fullstack_core::FullstackContext;\n                    use http::header::{ACCEPT, LOCATION, REFERER};\n                    use http::StatusCode;\n\n                    // todo: we're copying the parts here, but it'd be ideal if we didn't.\n                    // We can probably just pass the URI in so the matching logic can work and then\n                    // in the server function, do all extraction via FullstackContext. This ensures\n                    // calls to `.remove()` work as expected.\n                    let (parts, body) = request.into_parts();\n                    let server_context = FullstackContext::new(parts.clone());\n                    let request = axum::extract::Request::from_parts(parts, body);\n\n                    // store Accepts and Referrer in case we need them for redirect (below)\n                    let referrer = request.headers().get(REFERER).cloned();\n                    let accepts_html = request\n                        .headers()\n                        .get(ACCEPT)\n                        .and_then(|v| v.to_str().ok())\n                        .map(|v| v.contains(\"text/html\"))\n                        .unwrap_or(false);\n\n                    server_context\n                        .clone()\n                        .scope(async move {\n                            // Run the next middleware / handler inside the server context\n                            let mut response = handler(State(server_context), request)\n                                .instrument(current_span)\n                                .await;\n\n                            let server_context = FullstackContext::current().expect(\n                                \"Server context should be available inside the server context scope\",\n                            );\n\n                            // Get the extra response headers set during the handler and add them to the response\n                            let headers = server_context.take_response_headers();\n                            if let Some(headers) = headers {\n                                response.headers_mut().extend(headers);\n                            }\n\n                            // if it accepts text/html (i.e., is a plain form post) and doesn't already have a\n                            // Location set, then redirect to Referer\n                            if accepts_html {\n                                if let Some(referrer) = referrer {\n                                    let has_location = response.headers().get(LOCATION).is_some();\n                                    if !has_location {\n                                        *response.status_mut() = StatusCode::FOUND;\n                                        response.headers_mut().insert(LOCATION, referrer);\n                                    }\n                                }\n                            }\n\n                            response\n                        })\n                        .await\n                }).await;\n\n                match result {\n                    Ok(response) => response,\n                    Err(err) => Response::builder()\n                        .status(StatusCode::INTERNAL_SERVER_ERROR)\n                        .body(Body::new(if cfg!(debug_assertions) {\n                            format!(\"Server function panicked: {}\", err)\n                        } else {\n                            \"Internal Server Error\".to_string()\n                        }))\n                        .unwrap(),\n                }\n            },\n        )\n    }\n}\n\nimpl inventory::Collect for ServerFunction {\n    #[inline]\n    fn registry() -> &'static inventory::Registry {\n        static REGISTRY: inventory::Registry = inventory::Registry::new();\n        &REGISTRY\n    }\n}\n"
  },
  {
    "path": "packages/fullstack-server/src/ssr.rs",
    "content": "//! A shared pool of renderers for efficient server side rendering.\nuse crate::isrg::{\n    CachedRender, IncrementalRenderer, IncrementalRendererConfig, IncrementalRendererError,\n    RenderFreshness,\n};\nuse crate::streaming::{Mount, StreamingRenderer};\nuse crate::{document::ServerDocument, ServeConfig};\nuse dioxus_cli_config::base_path;\nuse dioxus_core::{\n    consume_context, has_context, try_consume_context, DynamicNode, ErrorContext, Runtime, ScopeId,\n    SuspenseContext, TemplateNode, VNode, VirtualDom,\n};\nuse dioxus_fullstack_core::{history::provide_fullstack_history_context, HttpError, ServerFnError};\nuse dioxus_fullstack_core::{FullstackContext, StreamingStatus};\nuse dioxus_fullstack_core::{HydrationContext, SerializedHydrationData};\nuse dioxus_router::ParseRouteError;\nuse dioxus_ssr::Renderer;\nuse futures_channel::mpsc::Sender;\nuse futures_util::{Stream, StreamExt};\nuse http::{request::Parts, HeaderMap, StatusCode};\nuse std::{\n    collections::HashMap,\n    fmt::Write,\n    iter::Peekable,\n    rc::Rc,\n    sync::{Arc, RwLock},\n};\nuse tokio_util::task::LocalPoolHandle;\n\nuse crate::StreamingMode;\n\n/// Errors that can occur during server side rendering before the initial chunk is sent down\npub enum SSRError {\n    /// An error from the incremental renderer. This should result in a 500 code\n    Incremental(IncrementalRendererError),\n\n    HttpError {\n        status: StatusCode,\n        message: Option<String>,\n    },\n}\n\n/// A suspense boundary that is pending with a placeholder in the client\nstruct PendingSuspenseBoundary {\n    mount: Mount,\n    children: Vec<ScopeId>,\n}\n\npub(crate) struct SsrRendererPool {\n    renderers: RwLock<Vec<Renderer>>,\n    incremental_cache: Option<RwLock<IncrementalRenderer>>,\n}\n\nimpl SsrRendererPool {\n    pub(crate) fn new(initial_size: usize, incremental: Option<IncrementalRendererConfig>) -> Self {\n        let renderers = RwLock::new((0..initial_size).map(|_| Self::pre_renderer()).collect());\n        Self {\n            renderers,\n            incremental_cache: incremental.map(|cache| RwLock::new(cache.build())),\n        }\n    }\n\n    /// Look for a cached route in the incremental cache and send it into the render channel if it exists\n    fn check_cached_route(\n        &self,\n        route: &str,\n        render_into: &mut Sender<Result<String, IncrementalRendererError>>,\n    ) -> Option<RenderFreshness> {\n        let incremental = self.incremental_cache.as_ref()?;\n\n        if let Ok(mut incremental) = incremental.write() {\n            match incremental.get(route) {\n                Ok(Some(cached_render)) => {\n                    let CachedRender {\n                        freshness,\n                        response,\n                        ..\n                    } = cached_render;\n                    _ = render_into.start_send(\n                        String::from_utf8(response.to_vec())\n                            .map_err(|err| IncrementalRendererError::Other(err.into())),\n                    );\n                    return Some(freshness);\n                }\n                Err(e) => {\n                    tracing::error!(\"Failed to get route \\\"{route}\\\" from incremental cache: {e}\");\n                }\n                _ => {}\n            }\n        }\n\n        None\n    }\n\n    /// Render a virtual dom into a stream. This method will return immediately and continue streaming the result in the background\n    /// The streaming is canceled when the stream the function returns is dropped\n    pub(crate) async fn render_to(\n        self: Arc<Self>,\n        parts: Parts,\n        cfg: &ServeConfig,\n        rt: &LocalPoolHandle,\n        virtual_dom_factory: impl FnOnce() -> VirtualDom + Send + Sync + 'static,\n    ) -> Result<\n        (\n            HttpError,\n            HeaderMap,\n            RenderFreshness,\n            impl Stream<Item = Result<String, IncrementalRendererError>>,\n        ),\n        SSRError,\n    > {\n        struct ReceiverWithDrop {\n            receiver: futures_channel::mpsc::Receiver<Result<String, IncrementalRendererError>>,\n            cancel_task: Option<tokio::task::JoinHandle<()>>,\n        }\n\n        impl Stream for ReceiverWithDrop {\n            type Item = Result<String, IncrementalRendererError>;\n\n            fn poll_next(\n                mut self: std::pin::Pin<&mut Self>,\n                cx: &mut std::task::Context<'_>,\n            ) -> std::task::Poll<Option<Self::Item>> {\n                self.receiver.poll_next_unpin(cx)\n            }\n        }\n\n        // When we drop the stream, we need to cancel the task that is feeding values to the stream\n        impl Drop for ReceiverWithDrop {\n            fn drop(&mut self) {\n                if let Some(cancel_task) = self.cancel_task.take() {\n                    cancel_task.abort();\n                }\n            }\n        }\n\n        let route = parts\n            .uri\n            .path_and_query()\n            .ok_or_else(|| SSRError::HttpError {\n                status: StatusCode::BAD_REQUEST,\n                message: None,\n            })?\n            .to_string();\n\n        let (mut into, rx) =\n            futures_channel::mpsc::channel::<Result<String, IncrementalRendererError>>(1000);\n\n        let (initial_result_tx, initial_result_rx) = futures_channel::oneshot::channel();\n\n        // before we even spawn anything, we can check synchronously if we have the route cached\n        if let Some(freshness) = self.check_cached_route(&route, &mut into) {\n            return Ok((\n                HttpError {\n                    status: StatusCode::OK,\n                    message: None,\n                },\n                HeaderMap::new(),\n                freshness,\n                ReceiverWithDrop {\n                    receiver: rx,\n                    cancel_task: None,\n                },\n            ));\n        }\n\n        let mut renderer = self\n            .renderers\n            .write()\n            .unwrap()\n            .pop()\n            .unwrap_or_else(Self::pre_renderer);\n\n        let myself = self.clone();\n        let streaming_mode = cfg.streaming_mode;\n\n        let cfg = cfg.clone();\n        let create_render_future = move || async move {\n            let mut virtual_dom = virtual_dom_factory();\n            let document = Rc::new(ServerDocument::default());\n            virtual_dom.provide_root_context(document.clone());\n\n            // If there is a base path, trim the base path from the route and add the base path formatting to the\n            // history provider\n            let history = if let Some(base_path) = base_path() {\n                let base_path = base_path.trim_matches('/');\n                let base_path = format!(\"/{base_path}\");\n                let route = route.strip_prefix(&base_path).unwrap_or(&route);\n                dioxus_history::MemoryHistory::with_initial_path(route).with_prefix(base_path)\n            } else {\n                dioxus_history::MemoryHistory::with_initial_path(&route)\n            };\n\n            // Provide the document and streaming context to the root of the app\n            let streaming_context =\n                virtual_dom.in_scope(ScopeId::ROOT, || FullstackContext::new(parts));\n            virtual_dom.provide_root_context(document.clone() as Rc<dyn dioxus_document::Document>);\n            virtual_dom.provide_root_context(streaming_context.clone());\n\n            virtual_dom.in_scope(ScopeId::ROOT, || {\n                // Wrap the memory history in a fullstack history provider to provide the initial route for hydration\n                provide_fullstack_history_context(history);\n\n                // Provide a hydration compatible error boundary that serializes errors for the client\n                dioxus_core::provide_create_error_boundary(\n                    dioxus_fullstack_core::init_error_boundary,\n                );\n            });\n\n            // rebuild the virtual dom\n            virtual_dom.rebuild_in_place();\n\n            // If streaming is disabled, wait for the virtual dom to finish all suspense work\n            // before rendering anything\n            if streaming_mode == StreamingMode::Disabled {\n                virtual_dom.wait_for_suspense().await;\n            } else {\n                // Otherwise, just wait for the streaming context to signal the initial chunk is ready\n                loop {\n                    // Check if the router has finished and set the streaming context to finished\n                    let streaming_context_finished = virtual_dom\n                        .in_scope(ScopeId::ROOT, || streaming_context.streaming_state())\n                        == StreamingStatus::InitialChunkCommitted;\n\n                    // Or if this app isn't using the router and has finished suspense\n                    let suspense_finished = !virtual_dom.suspended_tasks_remaining();\n                    if streaming_context_finished || suspense_finished {\n                        break;\n                    }\n\n                    // Wait for new async work that runs during suspense (mainly use_server_futures)\n                    virtual_dom.wait_for_suspense_work().await;\n\n                    // Do that async work\n                    virtual_dom.render_suspense_immediate().await;\n                }\n            }\n\n            // check if there are any errors from the root error boundary\n            let error = virtual_dom.in_scope(ScopeId::ROOT_ERROR_BOUNDARY, || {\n                consume_context::<ErrorContext>().error()\n            });\n\n            if let Some(error) = error {\n                let mut status_code = None;\n                let mut out_message = None;\n\n                // If the errors include an `HttpError` or `StatusCode` or `ServerFnError`, we need\n                // to try and return the appropriate status code\n                if let Some(error) = error.downcast_ref::<HttpError>() {\n                    status_code = Some(error.status);\n                    out_message = error.message.clone();\n                }\n\n                if let Some(error) = error.downcast_ref::<StatusCode>() {\n                    status_code = Some(*error);\n                }\n\n                // todo - the user is allowed to return anything that impls `From<ServerFnError>`\n                // we need to eventually be able to downcast that and get the status code from it\n                if let Some(ServerFnError::ServerError { message, code, .. }) = error.downcast_ref()\n                {\n                    status_code = Some(\n                        (*code)\n                            .try_into()\n                            .unwrap_or(StatusCode::INTERNAL_SERVER_ERROR),\n                    );\n\n                    out_message = Some(message.clone());\n                }\n\n                // If there was an error while routing, return the error with a 404 status\n                // Return a routing error if any of the errors were a routing error\n                if let Some(routing_error) = error.downcast_ref::<ParseRouteError>().cloned() {\n                    status_code = Some(StatusCode::NOT_FOUND);\n                    out_message = Some(routing_error.to_string());\n                }\n\n                // If we captured anything that produces a status code, we should return that status code.\n                if let Some(status_code) = status_code {\n                    _ = initial_result_tx.send(Err(SSRError::HttpError {\n                        status: status_code,\n                        message: out_message,\n                    }));\n                    return;\n                }\n\n                _ = initial_result_tx.send(Err(SSRError::Incremental(\n                    IncrementalRendererError::Other(error),\n                )));\n\n                return;\n            }\n\n            // Check the FullstackContext in case the user set the statuscode manually or via a layout.\n            let http_status = streaming_context.current_http_status();\n            let headers = streaming_context\n                .take_response_headers()\n                .unwrap_or_default();\n\n            // Now that we handled any errors from rendering, we can send the initial ok result\n            _ = initial_result_tx.send(Ok((http_status, headers)));\n\n            // Wait long enough to assemble the `<head>` of the document before starting to stream\n            let mut pre_body = String::new();\n            if let Err(err) = Self::render_head(&cfg, &mut pre_body, &virtual_dom) {\n                _ = into.start_send(Err(err));\n                return;\n            }\n\n            let stream = Arc::new(StreamingRenderer::new(pre_body, into));\n            let scope_to_mount_mapping = Arc::new(RwLock::new(HashMap::new()));\n\n            renderer.pre_render = true;\n            {\n                let scope_to_mount_mapping = scope_to_mount_mapping.clone();\n                let stream = stream.clone();\n                renderer.set_render_components(Self::streaming_render_component_callback(\n                    stream,\n                    scope_to_mount_mapping,\n                ));\n            }\n\n            macro_rules! throw_error {\n                ($e:expr) => {\n                    stream.close_with_error($e);\n                    return;\n                };\n            }\n\n            // Render the initial frame with loading placeholders\n            let mut initial_frame = renderer.render(&virtual_dom);\n\n            // Along with the initial frame, we render the html after the main element, but before the body tag closes. This should include the script that starts loading the wasm bundle.\n            if let Err(err) = Self::render_after_main(&cfg, &mut initial_frame, &virtual_dom) {\n                throw_error!(err);\n            }\n            stream.render(initial_frame);\n\n            // After the initial render, we need to resolve suspense\n            while virtual_dom.suspended_tasks_remaining() {\n                virtual_dom.wait_for_suspense_work().await;\n                let resolved_suspense_nodes = virtual_dom.render_suspense_immediate().await;\n\n                // Just rerender the resolved nodes\n                for scope in resolved_suspense_nodes {\n                    let pending_suspense_boundary = {\n                        let mut lock = scope_to_mount_mapping.write().unwrap();\n                        lock.remove(&scope)\n                    };\n                    // If the suspense boundary was immediately removed, it may not have a mount. We can just skip resolving it\n                    if let Some(pending_suspense_boundary) = pending_suspense_boundary {\n                        let mut resolved_chunk = String::new();\n                        // After we replace the placeholder in the dom with javascript, we need to send down the resolved data so that the client can hydrate the node\n                        let render_suspense = |into: &mut String| {\n                            renderer.reset_hydration();\n                            renderer.render_scope(into, &virtual_dom, scope)\n                        };\n                        let resolved_data = Self::serialize_server_data(&virtual_dom, scope);\n                        if let Err(err) = stream.replace_placeholder(\n                            pending_suspense_boundary.mount,\n                            render_suspense,\n                            resolved_data,\n                            &mut resolved_chunk,\n                        ) {\n                            throw_error!(IncrementalRendererError::RenderError(err));\n                        }\n\n                        stream.render(resolved_chunk);\n                        // Freeze the suspense boundary to prevent future reruns of any child nodes of the suspense boundary\n                        if let Some(suspense) =\n                            SuspenseContext::downcast_suspense_boundary_from_scope(\n                                &virtual_dom.runtime(),\n                                scope,\n                            )\n                        {\n                            suspense.freeze();\n                            // Go to every child suspense boundary and add an error boundary. Since we cannot rerun any nodes above the child suspense boundary,\n                            // we need to capture the errors and send them to the client as it resolves\n                            virtual_dom.in_runtime(|| {\n                                for &suspense_scope in pending_suspense_boundary.children.iter() {\n                                    Self::start_capturing_errors(suspense_scope);\n                                }\n                            });\n                        }\n                    }\n                }\n            }\n\n            // After suspense is done, we render the html after the body\n            let mut post_streaming = String::new();\n\n            if let Err(err) = Self::render_after_body(&cfg, &mut post_streaming) {\n                throw_error!(err);\n            }\n\n            // If incremental rendering is enabled, add the new render to the cache without the streaming bits\n            if let Some(incremental) = &self.incremental_cache {\n                let mut cached_render = String::new();\n                if let Err(err) = Self::render_head(&cfg, &mut cached_render, &virtual_dom) {\n                    throw_error!(err);\n                }\n                renderer.reset_hydration();\n                if let Err(err) = renderer.render_to(&mut cached_render, &virtual_dom) {\n                    throw_error!(IncrementalRendererError::RenderError(err));\n                }\n                if let Err(err) = Self::render_after_main(&cfg, &mut cached_render, &virtual_dom) {\n                    throw_error!(err);\n                }\n                cached_render.push_str(&post_streaming);\n\n                if let Ok(mut incremental) = incremental.write() {\n                    let _ = incremental.cache(route, cached_render);\n                }\n            }\n\n            stream.render(post_streaming);\n\n            renderer.reset_render_components();\n            myself.renderers.write().unwrap().push(renderer);\n        };\n\n        // Spawn the render future onto the local pool\n        let join_handle = rt.spawn_pinned(create_render_future);\n\n        // Wait for the initial result which determines the status code\n        let (status, headers) = initial_result_rx\n            .await\n            .map_err(|err| SSRError::Incremental(IncrementalRendererError::Other(err.into())))??;\n\n        Ok((\n            status,\n            headers,\n            RenderFreshness::now(None),\n            ReceiverWithDrop {\n                receiver: rx,\n                cancel_task: Some(join_handle),\n            },\n        ))\n    }\n\n    fn pre_renderer() -> Renderer {\n        let mut renderer = Renderer::default();\n        renderer.pre_render = true;\n        renderer\n    }\n\n    /// Create the streaming render component callback. It will keep track of what scopes are mounted to what pending\n    /// suspense boundaries in the DOM.\n    ///\n    /// This mapping is used to replace the DOM mount with the resolved contents once the suspense boundary is finished.\n    fn streaming_render_component_callback(\n        stream: Arc<StreamingRenderer<IncrementalRendererError>>,\n        scope_to_mount_mapping: Arc<RwLock<HashMap<ScopeId, PendingSuspenseBoundary>>>,\n    ) -> impl Fn(&mut Renderer, &mut dyn Write, &VirtualDom, ScopeId) -> std::fmt::Result\n           + Send\n           + Sync\n           + 'static {\n        // We use a stack to keep track of what suspense boundaries we are nested in to add children to the correct boundary\n        // The stack starts with the root scope because the root is a suspense boundary\n        let pending_suspense_boundaries_stack = RwLock::new(vec![]);\n        move |renderer, to, vdom, scope| {\n            let is_suspense_boundary =\n                SuspenseContext::downcast_suspense_boundary_from_scope(&vdom.runtime(), scope)\n                    .filter(|s| s.has_suspended_tasks())\n                    .is_some();\n            if is_suspense_boundary {\n                let mount = stream.render_placeholder(\n                    |to| {\n                        {\n                            pending_suspense_boundaries_stack\n                                .write()\n                                .unwrap()\n                                .push(scope);\n                        }\n                        let out = renderer.render_scope(to, vdom, scope);\n                        {\n                            pending_suspense_boundaries_stack.write().unwrap().pop();\n                        }\n                        out\n                    },\n                    &mut *to,\n                )?;\n                // Add the suspense boundary to the list of pending suspense boundaries\n                // We will replace the mount with the resolved contents later once the suspense boundary is resolved\n                let mut scope_to_mount_mapping_write = scope_to_mount_mapping.write().unwrap();\n                scope_to_mount_mapping_write.insert(\n                    scope,\n                    PendingSuspenseBoundary {\n                        mount,\n                        children: vec![],\n                    },\n                );\n                // Add the scope to the list of children of the parent suspense boundary\n                let pending_suspense_boundaries_stack =\n                    pending_suspense_boundaries_stack.read().unwrap();\n                // If there is a parent suspense boundary, add the scope to the list of children\n                // This suspense boundary will start capturing errors when the parent is resolved\n                if let Some(parent) = pending_suspense_boundaries_stack.last() {\n                    let parent = scope_to_mount_mapping_write.get_mut(parent).unwrap();\n                    parent.children.push(scope);\n                }\n                // Otherwise this is a root suspense boundary, so we need to start capturing errors immediately\n                else {\n                    vdom.in_runtime(|| {\n                        Self::start_capturing_errors(scope);\n                    });\n                }\n            } else {\n                renderer.render_scope(to, vdom, scope)?\n            }\n            Ok(())\n        }\n    }\n\n    /// Start capturing errors at a suspense boundary. If the parent suspense boundary is frozen, we need to capture the errors in the suspense boundary\n    /// and send them to the client to continue bubbling up\n    fn start_capturing_errors(suspense_scope: ScopeId) {\n        // Add an error boundary to the scope. We serialize the suspense error boundary separately so we can use\n        // the normal in memory ErrorContext here\n        Runtime::current().in_scope(suspense_scope, || {\n            dioxus_core::provide_context(ErrorContext::new(None))\n        });\n    }\n\n    fn serialize_server_data(virtual_dom: &VirtualDom, scope: ScopeId) -> SerializedHydrationData {\n        // After we replace the placeholder in the dom with javascript, we need to send down the resolved data so that the client can hydrate the node\n        // Extract any data we serialized for hydration (from server futures)\n        let html_data = Self::extract_from_suspense_boundary(virtual_dom, scope);\n\n        // serialize the server state into a base64 string\n        html_data.serialized()\n    }\n\n    /// Walks through the suspense boundary in a depth first order and extracts the data from the context API.\n    /// We use depth first order instead of relying on the order the hooks are called in because during suspense on the server, the order that futures are run in may be non deterministic.\n    pub(crate) fn extract_from_suspense_boundary(\n        vdom: &VirtualDom,\n        scope: ScopeId,\n    ) -> HydrationContext {\n        let data = HydrationContext::default();\n        Self::serialize_errors(&data, vdom, scope);\n        Self::take_from_scope(&data, vdom, scope);\n        data\n    }\n\n    /// Get the errors from the suspense boundary\n    fn serialize_errors(context: &HydrationContext, vdom: &VirtualDom, scope: ScopeId) {\n        // If there is an error boundary on the suspense boundary, grab the error from the context API\n        // and throw it on the client so that it bubbles up to the nearest error boundary\n        let error = vdom.in_scope(scope, || {\n            try_consume_context::<ErrorContext>().and_then(|error_context| error_context.error())\n        });\n        context\n            .error_entry()\n            .insert(&error, std::panic::Location::caller());\n    }\n\n    fn take_from_scope(context: &HydrationContext, vdom: &VirtualDom, scope: ScopeId) {\n        vdom.in_scope(scope, || {\n            // Grab any serializable server context from this scope\n            let other: Option<HydrationContext> = has_context();\n            if let Some(other) = other {\n                context.extend(&other);\n            }\n        });\n\n        // then continue to any children\n        if let Some(scope) = vdom.get_scope(scope) {\n            // If this is a suspense boundary, move into the children first (even if they are suspended) because that will be run first on the client\n            if let Some(suspense_boundary) =\n                SuspenseContext::downcast_suspense_boundary_from_scope(&vdom.runtime(), scope.id())\n            {\n                if let Some(node) = suspense_boundary.suspended_nodes() {\n                    Self::take_from_vnode(context, vdom, &node);\n                }\n            }\n            if let Some(node) = scope.try_root_node() {\n                Self::take_from_vnode(context, vdom, node);\n            }\n        }\n    }\n\n    fn take_from_vnode(context: &HydrationContext, vdom: &VirtualDom, vnode: &VNode) {\n        let template = &vnode.template;\n        let mut dynamic_nodes_iter = template.node_paths.iter().copied().enumerate().peekable();\n        for (root_idx, node) in template.roots.iter().enumerate() {\n            match node {\n                TemplateNode::Element { .. } => {\n                    // dioxus core runs nodes in an odd order to not mess up template order. We need to match\n                    // that order here\n                    let (start, end) =\n                        match Self::collect_dyn_node_range(&mut dynamic_nodes_iter, root_idx as u8)\n                        {\n                            Some((a, b)) => (a, b),\n                            None => continue,\n                        };\n\n                    let reversed_iter = (start..=end).rev();\n\n                    for dynamic_node_id in reversed_iter {\n                        let dynamic_node = &vnode.dynamic_nodes[dynamic_node_id];\n                        Self::take_from_dynamic_node(\n                            context,\n                            vdom,\n                            vnode,\n                            dynamic_node,\n                            dynamic_node_id,\n                        );\n                    }\n                }\n                TemplateNode::Dynamic { id } => {\n                    // Take a dynamic node off the depth first iterator\n                    _ = dynamic_nodes_iter.next().unwrap();\n                    let dynamic_node = &vnode.dynamic_nodes[*id];\n                    Self::take_from_dynamic_node(context, vdom, vnode, dynamic_node, *id);\n                }\n                _ => {}\n            }\n        }\n    }\n\n    fn take_from_dynamic_node(\n        context: &HydrationContext,\n        vdom: &VirtualDom,\n        vnode: &VNode,\n        dyn_node: &DynamicNode,\n        dynamic_node_index: usize,\n    ) {\n        match dyn_node {\n            DynamicNode::Component(comp) => {\n                if let Some(scope) = comp.mounted_scope(dynamic_node_index, vnode, vdom) {\n                    Self::take_from_scope(context, vdom, scope.id());\n                }\n            }\n            DynamicNode::Fragment(nodes) => {\n                for node in nodes {\n                    Self::take_from_vnode(context, vdom, node);\n                }\n            }\n            _ => {}\n        }\n    }\n\n    // This should have the same behavior as the collect_dyn_node_range method in core\n    // Find the index of the first and last dynamic node under a root index\n    fn collect_dyn_node_range(\n        dynamic_nodes: &mut Peekable<impl Iterator<Item = (usize, &'static [u8])>>,\n        root_idx: u8,\n    ) -> Option<(usize, usize)> {\n        let start = match dynamic_nodes.peek() {\n            Some((idx, [first, ..])) if *first == root_idx => *idx,\n            _ => return None,\n        };\n\n        let mut end = start;\n\n        while let Some((idx, p)) =\n            dynamic_nodes.next_if(|(_, p)| matches!(p, [idx, ..] if *idx == root_idx))\n        {\n            if p.len() == 1 {\n                continue;\n            }\n\n            end = idx;\n        }\n\n        Some((start, end))\n    }\n\n    /// Render any content before the head of the page.\n    pub fn render_head<R: std::fmt::Write>(\n        cfg: &ServeConfig,\n        to: &mut R,\n        virtual_dom: &VirtualDom,\n    ) -> Result<(), IncrementalRendererError> {\n        let title = {\n            let document: Option<Rc<ServerDocument>> =\n                virtual_dom.in_scope(ScopeId::ROOT, dioxus_core::try_consume_context);\n            // Collect any head content from the document provider and inject that into the head\n            document.and_then(|document| document.title())\n        };\n\n        to.write_str(&cfg.index.head_before_title)?;\n        if let Some(title) = title {\n            to.write_str(&title)?;\n        } else {\n            to.write_str(&cfg.index.title)?;\n        }\n        to.write_str(&cfg.index.head_after_title)?;\n\n        let document =\n            virtual_dom.in_scope(ScopeId::ROOT, try_consume_context::<Rc<ServerDocument>>);\n        if let Some(document) = document {\n            // Collect any head content from the document provider and inject that into the head\n            document.render(to)?;\n\n            // Enable a warning when inserting contents into the head during streaming\n            document.start_streaming();\n        }\n\n        Self::render_before_body(cfg, to)?;\n\n        Ok(())\n    }\n\n    /// Render any content before the body of the page.\n    fn render_before_body<R: std::fmt::Write>(\n        cfg: &ServeConfig,\n        to: &mut R,\n    ) -> Result<(), IncrementalRendererError> {\n        to.write_str(&cfg.index.close_head)?;\n\n        // // #[cfg(feature = \"document\")]\n        // {\n        use dioxus_interpreter_js::INITIALIZE_STREAMING_JS;\n        write!(to, \"<script>{INITIALIZE_STREAMING_JS}</script>\")?;\n        // }\n\n        Ok(())\n    }\n\n    /// Render all content after the main element of the page.\n    pub fn render_after_main<R: std::fmt::Write>(\n        cfg: &ServeConfig,\n        to: &mut R,\n        virtual_dom: &VirtualDom,\n    ) -> Result<(), IncrementalRendererError> {\n        // Collect the initial server data from the root node. For most apps, no use_server_futures will be resolved initially, so this will be full on `None`s.\n        // Sending down those Nones are still important to tell the client not to run the use_server_futures that are already running on the backend\n        let resolved_data = SsrRendererPool::serialize_server_data(virtual_dom, ScopeId::ROOT);\n        // We always send down the data required to hydrate components on the client\n        let raw_data = resolved_data.data;\n        write!(\n            to,\n            r#\"<script>window.initial_dioxus_hydration_data=\"{raw_data}\";\"#,\n        )?;\n        #[cfg(debug_assertions)]\n        {\n            // In debug mode, we also send down the type names and locations of the serialized data\n            let debug_types = &resolved_data.debug_types;\n            let debug_locations = &resolved_data.debug_locations;\n            write!(\n                to,\n                r#\"window.initial_dioxus_hydration_debug_types={debug_types};\"#,\n            )?;\n            write!(\n                to,\n                r#\"window.initial_dioxus_hydration_debug_locations={debug_locations};\"#,\n            )?;\n        }\n        write!(to, r#\"</script>\"#,)?;\n        to.write_str(&cfg.index.post_main)?;\n\n        Ok(())\n    }\n\n    /// Render all content after the body of the page.\n    pub fn render_after_body<R: std::fmt::Write>(\n        cfg: &ServeConfig,\n        to: &mut R,\n    ) -> Result<(), IncrementalRendererError> {\n        to.write_str(&cfg.index.after_closing_body_tag)?;\n\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "packages/fullstack-server/src/streaming.rs",
    "content": "//! There are two common ways to render suspense:\n//! 1. Stream the HTML in order - this will work even if javascript is disabled, but if there is something slow at the top of your page, and fast at the bottom, nothing will render until the slow part is done\n//! 2. Render placeholders and stream the HTML out of order - this will only work if javascript is enabled. This lets you render any parts of your page that resolve quickly, and then render the rest of the page as it becomes available\n//!\n//! Dioxus currently uses a the second out of order streaming approach which requires javascript. The rendering structure is as follows:\n//! ```html\n//! // Initial content is sent down with placeholders\n//! <div>\n//!     Header\n//!     <div class=\"flex flex-col\">\n//!         // If we reach a suspense placeholder that may be replaced later, we insert a template node with a unique id to replace later\n//!         <div>Loading user info...</div>\n//!     </div>\n//!     Footer\n//! </div>\n//! // After the initial render is done, we insert divs that are hidden with new content.\n//! // We use divs instead of templates for better SEO\n//! <script>\n//!     // Code to hook up hydration replacement\n//! </script>\n//! <div hidden id=\"ds-1-r\">\n//!     <div>Final HTML</div>\n//! </div>\n//! <script>\n//!     window.dx_hydrate(2, \"suspenseboundarydata\");\n//! </script>\n//! ```\n\nuse dioxus_fullstack_core::SerializedHydrationData;\nuse futures_channel::mpsc::Sender;\n\nuse std::{\n    fmt::{Display, Write},\n    sync::{Arc, RwLock},\n};\n\n/// Sections are identified by a unique id based on the suspense path. We only track the path of suspense boundaries because the client may render different components than the server.\n#[derive(Clone, Debug, Default)]\nstruct MountPath {\n    parent: Option<Arc<MountPath>>,\n    id: usize,\n}\n\nimpl MountPath {\n    fn child(&self) -> Self {\n        Self {\n            parent: Some(Arc::new(self.clone())),\n            id: 0,\n        }\n    }\n}\n\nimpl Display for MountPath {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        if let Some(parent) = &self.parent {\n            write!(f, \"{},\", parent)?;\n        }\n        write!(f, \"{}\", self.id)\n    }\n}\n\npub(crate) struct StreamingRenderer<E = std::convert::Infallible> {\n    channel: RwLock<Sender<Result<String, E>>>,\n    current_path: RwLock<MountPath>,\n}\n\nimpl<E> StreamingRenderer<E> {\n    /// Create a new streaming renderer with the given head that renders into a channel\n    pub(crate) fn new(\n        before_body: impl Display,\n        mut render_into: Sender<Result<String, E>>,\n    ) -> Self {\n        let start_html = before_body.to_string();\n        _ = render_into.start_send(Ok(start_html));\n\n        Self {\n            channel: render_into.into(),\n            current_path: Default::default(),\n        }\n    }\n\n    /// Render a new chunk of html that will never change\n    pub(crate) fn render(&self, html: impl Display) {\n        _ = self\n            .channel\n            .write()\n            .unwrap()\n            .start_send(Ok(html.to_string()));\n    }\n\n    /// Render a new chunk of html that may change\n    pub(crate) fn render_placeholder<W: Write + ?Sized>(\n        &self,\n        html: impl FnOnce(&mut W) -> std::fmt::Result,\n        into: &mut W,\n    ) -> Result<Mount, std::fmt::Error> {\n        let id = self.current_path.read().unwrap().clone();\n        // Increment the id for the next placeholder\n        self.current_path.write().unwrap().id += 1;\n        // While we are inside the placeholder, set the suspense path to the suspense boundary that we are rendering\n        let old_path = std::mem::replace(&mut *self.current_path.write().unwrap(), id.child());\n        html(into)?;\n        // Restore the old path\n        *self.current_path.write().unwrap() = old_path;\n        Ok(Mount { id })\n    }\n\n    /// Replace a placeholder that was rendered previously\n    pub(crate) fn replace_placeholder<W: Write + ?Sized>(\n        &self,\n        id: Mount,\n        html: impl FnOnce(&mut W) -> std::fmt::Result,\n        resolved_data: SerializedHydrationData,\n        into: &mut W,\n    ) -> std::fmt::Result {\n        // Then replace the suspense placeholder with the new content\n        write!(into, r#\"<div id=\"ds-{id}-r\" hidden>\"#)?;\n        // While we are inside the placeholder, set the suspense path to the suspense boundary that we are rendering\n        let old_path = std::mem::replace(&mut *self.current_path.write().unwrap(), id.id.child());\n        html(into)?;\n        // Restore the old path\n        *self.current_path.write().unwrap() = old_path;\n        // dx_hydrate accepts 2-4 arguments. The first two are required, the rest are optional\n        // The arguments are:\n        // 1. The id of the nodes we are hydrating under\n        // 2. The serialized data required to hydrate those components\n        // 3. (in debug mode) The type names of the serialized data\n        // 4. (in debug mode) The locations of the serialized data\n        write!(\n            into,\n            r#\"</div><script>window.dx_hydrate([{id}], \"{}\"\"#,\n            resolved_data.data\n        )?;\n        #[cfg(debug_assertions)]\n        {\n            // In debug mode, we also send down the type names and locations of the serialized data\n            let debug_types = &resolved_data.debug_types;\n            let debug_locations = &resolved_data.debug_locations;\n            write!(into, r#\", {debug_types}, {debug_locations}\"#,)?;\n        }\n        write!(into, r#\")</script>\"#)?;\n\n        Ok(())\n    }\n\n    /// Close the stream with an error\n    pub(crate) fn close_with_error(&self, error: E) {\n        _ = self.channel.write().unwrap().start_send(Err(error));\n    }\n}\n\n/// A mounted placeholder in the dom that may change in the future\n#[derive(Clone, Debug)]\npub(crate) struct Mount {\n    id: MountPath,\n}\n\nimpl Display for Mount {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(f, \"{}\", self.id)\n    }\n}\n"
  },
  {
    "path": "packages/generational-box/Cargo.toml",
    "content": "[package]\nname = \"generational-box\"\nauthors = [\"Evan Almloff\"]\nversion = { workspace = true }\nedition = \"2021\"\ndescription = \"A box backed by a generational runtime\"\nlicense = \"MIT OR Apache-2.0\"\nrepository = \"https://github.com/DioxusLabs/dioxus/\"\nkeywords = [\"generational\", \"box\", \"memory\", \"allocator\"]\nrust-version = \"1.83.0\"\n\n[dependencies]\nparking_lot = { workspace = true }\ntracing = { workspace = true }\n\n[dev-dependencies]\nrand = { workspace = true }\ncriterion = { workspace = true }\n\n[features]\ndebug_borrows = []\ndebug_ownership = []\n\n[[bench]]\nname = \"lock\"\nharness = false\n\n[package.metadata.docs.rs]\ncargo-args = [\"-Zunstable-options\", \"-Zrustdoc-scrape-examples\"]\n"
  },
  {
    "path": "packages/generational-box/README.md",
    "content": "# Generational Box\n\nGenerational Box is a runtime for Rust that allows any static type to implement `Copy`. It can be combined with a global runtime to create an ergonomic state solution like `dioxus-signals`. This crate doesn't have any `unsafe` code.\n\nThree main types manage state in Generational Box:\n\n- `Store`: Handles recycling generational boxes that have been dropped. Your application should have one store or one store per thread.\n- `Owner`: Handles dropping generational boxes. The owner acts like a runtime lifetime guard. Any states that you create with an owner will be dropped when that owner is dropped.\n- `GenerationalBox`: The core Copy state type. The generational box will be dropped when the owner is dropped.\n\nExample:\n\n```rust\nuse generational_box::{UnsyncStorage, AnyStorage};\n\n// Create an owner for some state for a scope\nlet owner = UnsyncStorage::owner();\n\n// Create some non-copy data, move it into a owner, and work with copy data\nlet data: String = \"hello world\".to_string();\nlet key = owner.insert(data);\n\n// The generational box can be read from and written to like a RefCell\nlet value = key.read();\nassert_eq!(*value, \"hello world\");\n```\n\n## How it works\n\nInternally, `generational-box` creates an arena of generational `RefCell`s that are recycled when the owner is dropped. You can think of the cells as something like `&'static RefCell<Box<dyn Any>>` with a generational check to make recycling a cell easier to debug. Then `GenerationalBox`es are `Copy` because the `&'static` pointer is `Copy`.\n"
  },
  {
    "path": "packages/generational-box/benches/lock.rs",
    "content": "#![allow(unused)]\nuse generational_box::*;\n\nuse criterion::{criterion_group, criterion_main, Criterion};\nuse std::hint::black_box;\n\nfn create<S: Storage<u32>>(owner: &Owner<S>) -> GenerationalBox<u32, S> {\n    owner.insert(0)\n}\n\nfn set_read<S: Storage<u32>>(signal: GenerationalBox<u32, S>) -> u32 {\n    signal.set(1);\n    *signal.read()\n}\n\nfn bench_fib(c: &mut Criterion) {\n    {\n        let owner = UnsyncStorage::owner();\n        c.bench_function(\"create_unsync\", |b| b.iter(|| create(black_box(&owner))));\n        let signal = create(&owner);\n        c.bench_function(\"set_read_unsync\", |b| {\n            b.iter(|| set_read(black_box(signal)))\n        });\n    }\n    {\n        let owner = SyncStorage::owner();\n        c.bench_function(\"create_sync\", |b| b.iter(|| create(black_box(&owner))));\n        let signal = create(&owner);\n        c.bench_function(\"set_read_sync\", |b| b.iter(|| set_read(black_box(signal))));\n    }\n}\n\ncriterion_group!(benches, bench_fib);\ncriterion_main!(benches);\n"
  },
  {
    "path": "packages/generational-box/src/entry.rs",
    "content": "use crate::{\n    BorrowError, BorrowMutError, GenerationalLocation, GenerationalRefBorrowGuard,\n    GenerationalRefBorrowMutGuard,\n};\nuse std::{\n    num::NonZeroU64,\n    sync::atomic::{AtomicU64, Ordering},\n};\n\npub(crate) struct RcStorageEntry<T> {\n    ref_count: AtomicU64,\n    pub data: T,\n}\n\nimpl<T> RcStorageEntry<T> {\n    pub const fn new(data: T) -> Self {\n        Self {\n            ref_count: AtomicU64::new(0),\n            data,\n        }\n    }\n\n    pub fn add_ref(&self) {\n        self.ref_count.fetch_add(1, Ordering::SeqCst);\n    }\n\n    pub fn drop_ref(&self) -> bool {\n        let new_ref_count = self.ref_count.fetch_sub(1, Ordering::SeqCst);\n        new_ref_count == 0\n    }\n}\n\npub(crate) struct StorageEntry<T> {\n    generation: NonZeroU64,\n    pub(crate) data: T,\n}\n\nimpl<T> StorageEntry<T> {\n    pub const fn new(data: T) -> Self {\n        Self {\n            generation: NonZeroU64::MIN,\n            data,\n        }\n    }\n\n    pub fn valid(&self, location: &GenerationalLocation) -> bool {\n        self.generation == location.generation\n    }\n\n    pub fn increment_generation(&mut self) {\n        self.generation = self.generation.checked_add(1).unwrap();\n    }\n\n    pub fn generation(&self) -> NonZeroU64 {\n        self.generation\n    }\n}\n\nimpl<T: Default + 'static> Default for StorageEntry<T> {\n    fn default() -> Self {\n        Self::new(T::default())\n    }\n}\n\n#[derive(Default)]\npub(crate) struct MemoryLocationBorrowInfo(\n    #[cfg(any(debug_assertions, feature = \"debug_borrows\"))]\n    parking_lot::RwLock<MemoryLocationBorrowInfoInner>,\n);\n\nimpl MemoryLocationBorrowInfo {\n    pub(crate) fn borrow_mut_error(&self) -> BorrowMutError {\n        #[cfg(any(debug_assertions, feature = \"debug_borrows\"))]\n        {\n            let borrow = self.0.read();\n            if let Some(borrowed_mut_at) = borrow.borrowed_mut_at.as_ref() {\n                BorrowMutError::AlreadyBorrowedMut(crate::error::AlreadyBorrowedMutError {\n                    borrowed_mut_at,\n                })\n            } else {\n                BorrowMutError::AlreadyBorrowed(crate::error::AlreadyBorrowedError {\n                    borrowed_at: borrow.borrowed_at.clone(),\n                })\n            }\n        }\n        #[cfg(not(any(debug_assertions, feature = \"debug_borrows\")))]\n        {\n            BorrowMutError::AlreadyBorrowed(crate::error::AlreadyBorrowedError {})\n        }\n    }\n\n    pub(crate) fn borrow_error(&self) -> BorrowError {\n        BorrowError::AlreadyBorrowedMut(crate::error::AlreadyBorrowedMutError {\n            #[cfg(any(debug_assertions, feature = \"debug_ownership\"))]\n            borrowed_mut_at: self.0.read().borrowed_mut_at.unwrap(),\n        })\n    }\n\n    /// Start a new borrow\n    #[track_caller]\n    pub(crate) fn borrow_guard(&'static self) -> GenerationalRefBorrowGuard {\n        #[cfg(any(debug_assertions, feature = \"debug_borrows\"))]\n        let borrowed_at = std::panic::Location::caller();\n        #[cfg(any(debug_assertions, feature = \"debug_borrows\"))]\n        {\n            let mut borrow = self.0.write();\n            borrow.borrowed_at.push(borrowed_at);\n        }\n\n        GenerationalRefBorrowGuard {\n            #[cfg(any(debug_assertions, feature = \"debug_borrows\"))]\n            borrowed_at,\n            #[cfg(any(debug_assertions, feature = \"debug_borrows\"))]\n            borrowed_from: self,\n        }\n    }\n\n    /// Start a new mutable borrow\n    #[track_caller]\n    pub(crate) fn borrow_mut_guard(&'static self) -> GenerationalRefBorrowMutGuard {\n        #[cfg(any(debug_assertions, feature = \"debug_borrows\"))]\n        let borrowed_mut_at = std::panic::Location::caller();\n        #[cfg(any(debug_assertions, feature = \"debug_borrows\"))]\n        {\n            let mut borrow = self.0.write();\n            borrow.borrowed_mut_at = Some(borrowed_mut_at);\n        }\n\n        GenerationalRefBorrowMutGuard {\n            #[cfg(any(debug_assertions, feature = \"debug_borrows\"))]\n            borrowed_mut_at,\n            #[cfg(any(debug_assertions, feature = \"debug_borrows\"))]\n            borrowed_from: self,\n        }\n    }\n\n    #[allow(unused)]\n    pub(crate) fn drop_borrow(&self, borrowed_at: &'static std::panic::Location<'static>) {\n        #[cfg(any(debug_assertions, feature = \"debug_borrows\"))]\n        {\n            let mut borrow = self.0.write();\n            borrow\n                .borrowed_at\n                .retain(|location| *location != borrowed_at);\n        }\n    }\n\n    #[allow(unused)]\n    pub(crate) fn drop_borrow_mut(&self, borrowed_mut_at: &'static std::panic::Location<'static>) {\n        #[cfg(any(debug_assertions, feature = \"debug_borrows\"))]\n        {\n            let mut borrow = self.0.write();\n            if borrow.borrowed_mut_at == Some(borrowed_mut_at) {\n                borrow.borrowed_mut_at = None;\n            }\n        }\n    }\n}\n\n#[cfg(any(debug_assertions, feature = \"debug_borrows\"))]\n#[derive(Default)]\nstruct MemoryLocationBorrowInfoInner {\n    borrowed_at: Vec<&'static std::panic::Location<'static>>,\n    borrowed_mut_at: Option<&'static std::panic::Location<'static>>,\n}\n"
  },
  {
    "path": "packages/generational-box/src/error.rs",
    "content": "//! Generational box errors\n#![allow(clippy::uninlined_format_args, reason = \"causes compile error\")]\n\nuse std::error::Error;\nuse std::fmt::Debug;\nuse std::fmt::Display;\n\nuse crate::GenerationalLocation;\n\n/// A result that can be returned from a borrow operation.\npub type BorrowResult<T = ()> = std::result::Result<T, BorrowError>;\n\n/// A result that can be returned from a borrow mut operation.\npub type BorrowMutResult<T = ()> = std::result::Result<T, BorrowMutError>;\n\n#[derive(Debug, Clone, PartialEq)]\n/// An error that can occur when trying to borrow a value.\npub enum BorrowError {\n    /// The value was dropped.\n    Dropped(ValueDroppedError),\n    /// The value was already borrowed mutably.\n    AlreadyBorrowedMut(AlreadyBorrowedMutError),\n}\n\nimpl Display for BorrowError {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        match self {\n            BorrowError::Dropped(error) => Display::fmt(error, f),\n            BorrowError::AlreadyBorrowedMut(error) => Display::fmt(error, f),\n        }\n    }\n}\n\nimpl Error for BorrowError {}\n\n#[derive(Debug, Clone, PartialEq)]\n/// An error that can occur when trying to borrow a value mutably.\npub enum BorrowMutError {\n    /// The value was dropped.\n    Dropped(ValueDroppedError),\n    /// The value was already borrowed.\n    AlreadyBorrowed(AlreadyBorrowedError),\n    /// The value was already borrowed mutably.\n    AlreadyBorrowedMut(AlreadyBorrowedMutError),\n}\n\nimpl Display for BorrowMutError {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        match self {\n            BorrowMutError::Dropped(error) => Display::fmt(error, f),\n            BorrowMutError::AlreadyBorrowedMut(error) => Display::fmt(error, f),\n            BorrowMutError::AlreadyBorrowed(error) => Display::fmt(error, f),\n        }\n    }\n}\n\nimpl Error for BorrowMutError {}\n\nimpl From<BorrowError> for BorrowMutError {\n    fn from(error: BorrowError) -> Self {\n        match error {\n            BorrowError::Dropped(error) => BorrowMutError::Dropped(error),\n            BorrowError::AlreadyBorrowedMut(error) => BorrowMutError::AlreadyBorrowedMut(error),\n        }\n    }\n}\n\n/// An error that can occur when trying to use a value that has been dropped.\n#[derive(Debug, Copy, Clone, PartialEq)]\npub struct ValueDroppedError {\n    #[cfg(any(debug_assertions, feature = \"debug_ownership\"))]\n    pub(crate) created_at: &'static std::panic::Location<'static>,\n}\n\nimpl ValueDroppedError {\n    /// Create a new `ValueDroppedError`.\n    #[allow(unused)]\n    pub fn new(created_at: &'static std::panic::Location<'static>) -> Self {\n        Self {\n            #[cfg(any(debug_assertions, feature = \"debug_ownership\"))]\n            created_at,\n        }\n    }\n\n    /// Create a new `ValueDroppedError` for a [`GenerationalLocation`].\n    #[allow(unused)]\n    pub(crate) fn new_for_location(location: GenerationalLocation) -> Self {\n        Self {\n            #[cfg(any(debug_assertions, feature = \"debug_borrows\"))]\n            created_at: location.created_at,\n        }\n    }\n}\n\nimpl Display for ValueDroppedError {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.write_str(\"Failed to borrow because the value was dropped.\")?;\n        #[cfg(any(debug_assertions, feature = \"debug_ownership\"))]\n        f.write_fmt(format_args!(\"created_at: {}\", self.created_at))?;\n        Ok(())\n    }\n}\n\nimpl std::error::Error for ValueDroppedError {}\n\n/// An error that can occur when trying to borrow a value that has already been borrowed mutably.\n#[derive(Debug, Copy, Clone, PartialEq)]\npub struct AlreadyBorrowedMutError {\n    #[cfg(any(debug_assertions, feature = \"debug_borrows\"))]\n    pub(crate) borrowed_mut_at: &'static std::panic::Location<'static>,\n}\n\nimpl AlreadyBorrowedMutError {\n    /// Create a new `AlreadyBorrowedMutError`.\n    #[allow(unused)]\n    pub fn new(borrowed_mut_at: &'static std::panic::Location<'static>) -> Self {\n        Self {\n            #[cfg(any(debug_assertions, feature = \"debug_borrows\"))]\n            borrowed_mut_at,\n        }\n    }\n}\n\nimpl Display for AlreadyBorrowedMutError {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.write_str(\"Failed to borrow because the value was already borrowed mutably.\")?;\n        #[cfg(any(debug_assertions, feature = \"debug_borrows\"))]\n        f.write_fmt(format_args!(\"borrowed_mut_at: {}\", self.borrowed_mut_at))?;\n        Ok(())\n    }\n}\n\nimpl std::error::Error for AlreadyBorrowedMutError {}\n\n/// An error that can occur when trying to borrow a value mutably that has already been borrowed immutably.\n#[derive(Debug, Clone, PartialEq)]\npub struct AlreadyBorrowedError {\n    #[cfg(any(debug_assertions, feature = \"debug_borrows\"))]\n    pub(crate) borrowed_at: Vec<&'static std::panic::Location<'static>>,\n}\n\nimpl AlreadyBorrowedError {\n    /// Create a new `AlreadyBorrowedError`.\n    #[allow(unused)]\n    pub fn new(borrowed_at: Vec<&'static std::panic::Location<'static>>) -> Self {\n        Self {\n            #[cfg(any(debug_assertions, feature = \"debug_borrows\"))]\n            borrowed_at,\n        }\n    }\n}\n\nimpl Display for AlreadyBorrowedError {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.write_str(\"Failed to borrow mutably because the value was already borrowed immutably.\")?;\n        #[cfg(any(debug_assertions, feature = \"debug_borrows\"))]\n        f.write_str(\"borrowed_at:\")?;\n        #[cfg(any(debug_assertions, feature = \"debug_borrows\"))]\n        for location in self.borrowed_at.iter() {\n            f.write_fmt(format_args!(\"\\t{}\", location))?;\n        }\n        Ok(())\n    }\n}\n\nimpl std::error::Error for AlreadyBorrowedError {}\n"
  },
  {
    "path": "packages/generational-box/src/lib.rs",
    "content": "#![doc = include_str!(\"../README.md\")]\n#![warn(missing_docs)]\n\nuse parking_lot::Mutex;\nuse std::{\n    fmt::Debug,\n    marker::PhantomData,\n    num::NonZeroU64,\n    ops::{Deref, DerefMut},\n    sync::Arc,\n};\n\npub use error::*;\npub use references::*;\npub use sync::SyncStorage;\npub use unsync::UnsyncStorage;\n\nmod entry;\nmod error;\nmod references;\nmod sync;\nmod unsync;\n\n/// The type erased id of a generational box.\n#[derive(Clone, Copy, PartialEq, Eq, Hash)]\npub struct GenerationalBoxId {\n    data_ptr: *const (),\n    generation: NonZeroU64,\n}\n\n// Safety: GenerationalBoxId is Send and Sync because there is no way to access the pointer.\nunsafe impl Send for GenerationalBoxId {}\nunsafe impl Sync for GenerationalBoxId {}\n\nimpl Debug for GenerationalBoxId {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.write_fmt(format_args!(\"{:?}@{:?}\", self.data_ptr, self.generation))?;\n        Ok(())\n    }\n}\n\n/// The core Copy state type. The generational box will be dropped when the [Owner] is dropped.\npub struct GenerationalBox<T, S: 'static = UnsyncStorage> {\n    raw: GenerationalPointer<S>,\n    _marker: PhantomData<T>,\n}\n\nimpl<T, S: AnyStorage> Debug for GenerationalBox<T, S> {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        self.raw.fmt(f)\n    }\n}\n\nimpl<T, S: Storage<T>> GenerationalBox<T, S> {\n    /// Create a new generational box by leaking a value into the storage. This is useful for creating\n    /// a box that needs to be manually dropped with no owners.\n    #[track_caller]\n    pub fn leak(value: T, location: &'static std::panic::Location<'static>) -> Self {\n        let location = S::new(value, location);\n        Self {\n            raw: location,\n            _marker: PhantomData,\n        }\n    }\n\n    /// Create a new reference counted generational box by leaking a value into the storage. This is useful for creating\n    /// a box that needs to be manually dropped with no owners.\n    #[track_caller]\n    pub fn leak_rc(value: T, location: &'static std::panic::Location<'static>) -> Self {\n        let location = S::new_rc(value, location);\n        Self {\n            raw: location,\n            _marker: PhantomData,\n        }\n    }\n\n    /// Get the raw pointer to the value.\n    pub fn raw_ptr(&self) -> *const () {\n        self.raw.storage.data_ptr()\n    }\n\n    /// Get the id of the generational box.\n    pub fn id(&self) -> GenerationalBoxId {\n        self.raw.id()\n    }\n\n    /// Try to read the value. Returns an error if the value is no longer valid.\n    #[track_caller]\n    pub fn try_read(&self) -> Result<S::Ref<'static, T>, BorrowError> {\n        self.raw.try_read()\n    }\n\n    /// Read the value. Panics if the value is no longer valid.\n    #[track_caller]\n    pub fn read(&self) -> S::Ref<'static, T> {\n        self.try_read().unwrap()\n    }\n\n    /// Try to write the value. Returns None if the value is no longer valid.\n    #[track_caller]\n    pub fn try_write(&self) -> Result<S::Mut<'static, T>, BorrowMutError> {\n        self.raw.try_write()\n    }\n\n    /// Write the value. Panics if the value is no longer valid.\n    #[track_caller]\n    pub fn write(&self) -> S::Mut<'static, T> {\n        self.try_write().unwrap()\n    }\n\n    /// Set the value. Panics if the value is no longer valid.\n    #[track_caller]\n    pub fn set(&self, value: T)\n    where\n        T: 'static,\n    {\n        *self.write() = value;\n    }\n\n    /// Drop the value out of the generational box and invalidate the generational box.\n    pub fn manually_drop(&self)\n    where\n        T: 'static,\n    {\n        self.raw.recycle();\n    }\n\n    /// Get a reference to the value\n    #[track_caller]\n    pub fn leak_reference(&self) -> BorrowResult<GenerationalBox<T, S>> {\n        Ok(Self {\n            raw: S::new_reference(self.raw)?,\n            _marker: std::marker::PhantomData,\n        })\n    }\n\n    /// Change this box to point to another generational box\n    pub fn point_to(&self, other: GenerationalBox<T, S>) -> BorrowResult {\n        S::change_reference(self.raw, other.raw)\n    }\n}\n\nimpl<T, S> GenerationalBox<T, S> {\n    /// Returns true if the pointer is equal to the other pointer.\n    pub fn ptr_eq(&self, other: &Self) -> bool\n    where\n        S: AnyStorage,\n    {\n        self.raw == other.raw\n    }\n\n    /// Try to get the location the generational box was created at. In release mode this will always return None.\n    pub fn created_at(&self) -> Option<&'static std::panic::Location<'static>> {\n        self.raw.location.created_at()\n    }\n}\n\nimpl<T, S> Copy for GenerationalBox<T, S> {}\n\nimpl<T, S> Clone for GenerationalBox<T, S> {\n    fn clone(&self) -> Self {\n        *self\n    }\n}\n\n/// A trait for a storage backing type. (RefCell, RwLock, etc.)\npub trait Storage<Data = ()>: AnyStorage {\n    /// Try to read the value. Returns None if the value is no longer valid.\n    fn try_read(pointer: GenerationalPointer<Self>) -> BorrowResult<Self::Ref<'static, Data>>;\n\n    /// Try to write the value. Returns None if the value is no longer valid.\n    fn try_write(pointer: GenerationalPointer<Self>) -> BorrowMutResult<Self::Mut<'static, Data>>;\n\n    /// Create a new memory location. This will either create a new memory location or recycle an old one.\n    fn new(\n        value: Data,\n        caller: &'static std::panic::Location<'static>,\n    ) -> GenerationalPointer<Self>;\n\n    /// Create a new reference counted memory location. This will either create a new memory location or recycle an old one.\n    fn new_rc(\n        value: Data,\n        caller: &'static std::panic::Location<'static>,\n    ) -> GenerationalPointer<Self>;\n\n    /// Reference another location if the location is valid\n    ///\n    /// This method may return an error if the other box is no longer valid or it is already borrowed mutably.\n    fn new_reference(inner: GenerationalPointer<Self>) -> BorrowResult<GenerationalPointer<Self>>;\n\n    /// Change the reference a signal is pointing to\n    ///\n    /// This method may return an error if the other box is no longer valid or it is already borrowed mutably.\n    fn change_reference(\n        pointer: GenerationalPointer<Self>,\n        rc_pointer: GenerationalPointer<Self>,\n    ) -> BorrowResult;\n}\n\n/// A trait for any storage backing type.\npub trait AnyStorage: Default {\n    /// The reference this storage type returns.\n    type Ref<'a, T: ?Sized + 'a>: Deref<Target = T>;\n    /// The mutable reference this storage type returns.\n    type Mut<'a, T: ?Sized + 'a>: DerefMut<Target = T>;\n\n    /// Downcast a reference in a Ref to a more specific lifetime\n    ///\n    /// This function enforces the variance of the lifetime parameter `'a` in Ref. Rust will typically infer this cast with a concrete type, but it cannot with a generic type.\n    fn downcast_lifetime_ref<'a: 'b, 'b, T: ?Sized + 'a>(\n        ref_: Self::Ref<'a, T>,\n    ) -> Self::Ref<'b, T>;\n\n    /// Downcast a mutable reference in a RefMut to a more specific lifetime\n    ///\n    /// This function enforces the variance of the lifetime parameter `'a` in Mut.  Rust will typically infer this cast with a concrete type, but it cannot with a generic type.\n    fn downcast_lifetime_mut<'a: 'b, 'b, T: ?Sized + 'a>(\n        mut_: Self::Mut<'a, T>,\n    ) -> Self::Mut<'b, T>;\n\n    /// Try to map the mutable ref.\n    fn try_map_mut<T: ?Sized, U: ?Sized>(\n        mut_ref: Self::Mut<'_, T>,\n        f: impl FnOnce(&mut T) -> Option<&mut U>,\n    ) -> Option<Self::Mut<'_, U>>;\n\n    /// Map the mutable ref.\n    fn map_mut<T: ?Sized, U: ?Sized>(\n        mut_ref: Self::Mut<'_, T>,\n        f: impl FnOnce(&mut T) -> &mut U,\n    ) -> Self::Mut<'_, U> {\n        Self::try_map_mut(mut_ref, |v| Some(f(v))).unwrap()\n    }\n\n    /// Try to map the ref.\n    fn try_map<T: ?Sized, U: ?Sized>(\n        ref_: Self::Ref<'_, T>,\n        f: impl FnOnce(&T) -> Option<&U>,\n    ) -> Option<Self::Ref<'_, U>>;\n\n    /// Map the ref.\n    fn map<T: ?Sized, U: ?Sized>(\n        ref_: Self::Ref<'_, T>,\n        f: impl FnOnce(&T) -> &U,\n    ) -> Self::Ref<'_, U> {\n        Self::try_map(ref_, |v| Some(f(v))).unwrap()\n    }\n\n    /// Get the data pointer. No guarantees are made about the data pointer. It should only be used for debugging.\n    fn data_ptr(&self) -> *const ();\n\n    /// Recycle a memory location. This will drop the memory location and return it to the runtime.\n    fn recycle(location: GenerationalPointer<Self>);\n\n    /// Create a new owner. The owner will be responsible for dropping all of the generational boxes that it creates.\n    fn owner() -> Owner<Self>\n    where\n        Self: 'static,\n    {\n        Owner(Arc::new(Mutex::new(OwnerInner {\n            owned: Default::default(),\n        })))\n    }\n}\n\n#[derive(Debug, Clone, Copy)]\npub(crate) struct GenerationalLocation {\n    /// The generation this location is associated with. Using the location after this generation is invalidated will return errors.\n    generation: NonZeroU64,\n    #[cfg(any(debug_assertions, feature = \"debug_ownership\"))]\n    created_at: &'static std::panic::Location<'static>,\n}\n\nimpl GenerationalLocation {\n    pub(crate) fn created_at(&self) -> Option<&'static std::panic::Location<'static>> {\n        #[cfg(debug_assertions)]\n        {\n            Some(self.created_at)\n        }\n        #[cfg(not(debug_assertions))]\n        {\n            None\n        }\n    }\n}\n\n/// A pointer to a specific generational box and generation in that box.\npub struct GenerationalPointer<S: 'static = UnsyncStorage> {\n    /// The storage that is backing this location\n    storage: &'static S,\n    /// The location of the data\n    location: GenerationalLocation,\n}\n\nimpl<S: AnyStorage + 'static> PartialEq for GenerationalPointer<S> {\n    fn eq(&self, other: &Self) -> bool {\n        self.storage.data_ptr() == other.storage.data_ptr()\n            && self.location.generation == other.location.generation\n    }\n}\n\nimpl<S: AnyStorage + 'static> Debug for GenerationalPointer<S> {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.write_fmt(format_args!(\n            \"{:?}@{:?}\",\n            self.storage.data_ptr(),\n            self.location.generation\n        ))\n    }\n}\n\nimpl<S: 'static> Clone for GenerationalPointer<S> {\n    fn clone(&self) -> Self {\n        *self\n    }\n}\n\nimpl<S: 'static> Copy for GenerationalPointer<S> {}\n\nimpl<S> GenerationalPointer<S> {\n    #[track_caller]\n    fn try_read<T>(self) -> Result<S::Ref<'static, T>, BorrowError>\n    where\n        S: Storage<T>,\n    {\n        S::try_read(self)\n    }\n\n    #[track_caller]\n    fn try_write<T>(self) -> Result<S::Mut<'static, T>, BorrowMutError>\n    where\n        S: Storage<T>,\n    {\n        S::try_write(self)\n    }\n\n    fn recycle(self)\n    where\n        S: AnyStorage,\n    {\n        S::recycle(self);\n    }\n\n    fn id(&self) -> GenerationalBoxId\n    where\n        S: AnyStorage,\n    {\n        GenerationalBoxId {\n            data_ptr: self.storage.data_ptr(),\n            generation: self.location.generation,\n        }\n    }\n}\n\nstruct OwnerInner<S: AnyStorage + 'static> {\n    owned: Vec<GenerationalPointer<S>>,\n}\n\nimpl<S: AnyStorage> Drop for OwnerInner<S> {\n    fn drop(&mut self) {\n        for location in self.owned.drain(..) {\n            location.recycle();\n        }\n    }\n}\n\n/// Owner: Handles dropping generational boxes. The owner acts like a runtime lifetime guard. Any states that you create with an owner will be dropped when that owner is dropped.\npub struct Owner<S: AnyStorage + 'static = UnsyncStorage>(Arc<Mutex<OwnerInner<S>>>);\n\nimpl<S: AnyStorage> Default for Owner<S> {\n    fn default() -> Self {\n        S::owner()\n    }\n}\n\nimpl<S: AnyStorage> Clone for Owner<S> {\n    fn clone(&self) -> Self {\n        Self(self.0.clone())\n    }\n}\n\nimpl<S: AnyStorage> Owner<S> {\n    /// Insert a value into the store. The value will be dropped when the owner is dropped.\n    #[track_caller]\n    pub fn insert<T>(&self, value: T) -> GenerationalBox<T, S>\n    where\n        S: Storage<T>,\n    {\n        self.insert_with_caller(value, std::panic::Location::caller())\n    }\n\n    /// Create a new reference counted box. The box will be dropped when all references are dropped.\n    #[track_caller]\n    pub fn insert_rc<T>(&self, value: T) -> GenerationalBox<T, S>\n    where\n        S: Storage<T>,\n    {\n        self.insert_rc_with_caller(value, std::panic::Location::caller())\n    }\n\n    /// Insert a value into the store with a specific location blamed for creating the value. The value will be dropped when the owner is dropped.\n    pub fn insert_rc_with_caller<T>(\n        &self,\n        value: T,\n        caller: &'static std::panic::Location<'static>,\n    ) -> GenerationalBox<T, S>\n    where\n        S: Storage<T>,\n    {\n        let location = S::new_rc(value, caller);\n        self.0.lock().owned.push(location);\n        GenerationalBox {\n            raw: location,\n            _marker: std::marker::PhantomData,\n        }\n    }\n\n    /// Insert a value into the store with a specific location blamed for creating the value. The value will be dropped when the owner is dropped.\n    pub fn insert_with_caller<T>(\n        &self,\n        value: T,\n        caller: &'static std::panic::Location<'static>,\n    ) -> GenerationalBox<T, S>\n    where\n        S: Storage<T>,\n    {\n        let location = S::new(value, caller);\n        self.0.lock().owned.push(location);\n        GenerationalBox {\n            raw: location,\n            _marker: PhantomData,\n        }\n    }\n\n    /// Create a new reference to an existing box. The reference will be dropped when the owner is dropped.\n    ///\n    /// This method may return an error if the other box is no longer valid or it is already borrowed mutably.\n    #[track_caller]\n    pub fn insert_reference<T>(\n        &self,\n        other: GenerationalBox<T, S>,\n    ) -> BorrowResult<GenerationalBox<T, S>>\n    where\n        S: Storage<T>,\n    {\n        let location = other.leak_reference()?;\n        self.0.lock().owned.push(location.raw);\n        Ok(location)\n    }\n}\n"
  },
  {
    "path": "packages/generational-box/src/references.rs",
    "content": "use std::{\n    fmt::{Debug, Display},\n    ops::{Deref, DerefMut},\n};\n\n/// A reference to a value in a generational box. This reference acts similarly to [`std::cell::Ref`], but has extra debug information\n/// to track when all references to the value are created and dropped.\n///\n/// [`GenerationalRef`] implements [`Deref`] which means you can call methods on the inner value just like you would on a reference to the\n/// inner value. If you need to get the inner reference directly, you can call [`GenerationalRef::deref`].\n///\n/// # Example\n/// ```rust\n/// # use generational_box::{Owner, UnsyncStorage, AnyStorage};\n/// let owner = UnsyncStorage::owner();\n/// let value = owner.insert(String::from(\"hello\"));\n/// let reference = value.read();\n///\n/// // You call methods like `as_str` on the reference just like you would with the inner String\n/// assert_eq!(reference.as_str(), \"hello\");\n/// ```\n///\n/// ## Matching on GenerationalRef\n///\n/// You need to get the inner reference with [`GenerationalRef::deref`] before you match the inner value. If you try to match without\n/// calling [`GenerationalRef::deref`], you will get an error like this:\n///\n/// ```compile_fail\n/// # use generational_box::{Owner, UnsyncStorage, AnyStorage};\n/// enum Colors {\n///     Red,\n///     Green\n/// }\n/// let owner = UnsyncStorage::owner();\n/// let value = owner.insert(Colors::Red);\n/// let reference = value.read();\n///\n/// match reference {\n///     // Since we are matching on the `GenerationalRef` type instead of &Colors, we can't match on the enum directly\n///     Colors::Red => {}\n///     Colors::Green => {}\n/// }\n/// ```\n///\n/// ```text\n/// error[E0308]: mismatched types\n///   --> packages/generational-box/tests/basic.rs:25:9\n///   |\n/// 2 |         Red,\n///   |         --- unit variant defined here\n/// ...\n/// 3 |     match reference {\n///   |           --------- this expression has type `GenerationalRef<Ref<'_, Colors>>`\n/// 4 |         // Since we are matching on the `GenerationalRef` type instead of &Colors, we can't match on the enum directly\n/// 5 |         Colors::Red => {}\n///   |         ^^^^^^^^^^^ expected `GenerationalRef<Ref<'_, Colors>>`, found `Colors`\n///   |\n///   = note: expected struct `GenerationalRef<Ref<'_, Colors>>`\n///                found enum `Colors`\n/// ```\n///\n/// Instead, you need to deref the reference to get the inner value **before** you match on it:\n///\n/// ```rust\n/// use std::ops::Deref;\n/// # use generational_box::{AnyStorage, Owner, UnsyncStorage};\n/// enum Colors {\n///     Red,\n///     Green\n/// }\n/// let owner = UnsyncStorage::owner();\n/// let value = owner.insert(Colors::Red);\n/// let reference = value.read();\n///\n/// // Deref converts the `GenerationalRef` into a `&Colors`\n/// match reference.deref() {\n///     // Now we can match on the inner value\n///     Colors::Red => {}\n///     Colors::Green => {}\n/// }\n/// ```\npub struct GenerationalRef<R> {\n    pub(crate) inner: R,\n    guard: GenerationalRefBorrowGuard,\n}\n\nimpl<T: ?Sized, R: Deref<Target = T>> GenerationalRef<R> {\n    pub(crate) fn new(inner: R, guard: GenerationalRefBorrowGuard) -> Self {\n        Self { inner, guard }\n    }\n\n    /// Map the inner value to a new type\n    pub fn map<R2, F: FnOnce(R) -> R2>(self, f: F) -> GenerationalRef<R2> {\n        GenerationalRef {\n            inner: f(self.inner),\n            guard: self.guard,\n        }\n    }\n\n    /// Try to map the inner value to a new type\n    pub fn try_map<R2, F: FnOnce(R) -> Option<R2>>(self, f: F) -> Option<GenerationalRef<R2>> {\n        f(self.inner).map(|inner| GenerationalRef {\n            inner,\n            guard: self.guard,\n        })\n    }\n\n    /// Clone the inner value. This requires that the inner value implements [`Clone`].\n    pub fn cloned(&self) -> T\n    where\n        T: Clone,\n    {\n        self.inner.deref().clone()\n    }\n}\n\nimpl<T: ?Sized + Debug, R: Deref<Target = T>> Debug for GenerationalRef<R> {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        self.inner.deref().fmt(f)\n    }\n}\n\nimpl<T: ?Sized + Display, R: Deref<Target = T>> Display for GenerationalRef<R> {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        self.inner.deref().fmt(f)\n    }\n}\n\nimpl<T: ?Sized, R: Deref<Target = T>> Deref for GenerationalRef<R> {\n    type Target = T;\n\n    fn deref(&self) -> &Self::Target {\n        self.inner.deref()\n    }\n}\n\npub(crate) struct GenerationalRefBorrowGuard {\n    #[cfg(any(debug_assertions, feature = \"debug_borrows\"))]\n    pub(crate) borrowed_at: &'static std::panic::Location<'static>,\n    #[cfg(any(debug_assertions, feature = \"debug_borrows\"))]\n    pub(crate) borrowed_from: &'static crate::entry::MemoryLocationBorrowInfo,\n}\n\n#[cfg(any(debug_assertions, feature = \"debug_borrows\"))]\nimpl Drop for GenerationalRefBorrowGuard {\n    fn drop(&mut self) {\n        self.borrowed_from.drop_borrow(self.borrowed_at);\n    }\n}\n\n/// A mutable reference to a value in a generational box. This reference acts similarly to [`std::cell::RefMut`], but has extra debug information\n/// to track when all references to the value are created and dropped.\n///\n/// [`GenerationalRefMut`] implements [`DerefMut`] which means you can call methods on the inner value just like you would on a mutable reference\n/// to the inner value. If you need to get the inner reference directly, you can call [`GenerationalRefMut::deref_mut`].\n///\n/// # Example\n/// ```rust\n/// # use generational_box::{Owner, UnsyncStorage, AnyStorage};\n/// let owner = UnsyncStorage::owner();\n/// let mut value = owner.insert(String::from(\"hello\"));\n/// let mut mutable_reference = value.write();\n///\n/// // You call methods like `push_str` on the reference just like you would with the inner String\n/// mutable_reference.push_str(\"world\");\n/// ```\n///\n/// ## Matching on GenerationalMut\n///\n/// You need to get the inner mutable reference with [`GenerationalRefMut::deref_mut`] before you match the inner value. If you try to match\n/// without calling [`GenerationalRefMut::deref_mut`], you will get an error like this:\n///\n/// ```compile_fail\n/// # use generational_box::{Owner, UnsyncStorage, AnyStorage};\n/// enum Colors {\n///     Red(u32),\n///     Green\n/// }\n/// let owner = UnsyncStorage::owner();\n/// let mut value = owner.insert(Colors::Red(0));\n/// let mut mutable_reference = value.write();\n///\n/// match mutable_reference {\n///     // Since we are matching on the `GenerationalRefMut` type instead of &mut Colors, we can't match on the enum directly\n///     Colors::Red(brightness) => *brightness += 1,\n///     Colors::Green => {}\n/// }\n/// ```\n///\n/// ```text\n/// error[E0308]: mismatched types\n///   --> packages/generational-box/tests/basic.rs:25:9\n///    |\n/// 9  |     match mutable_reference {\n///    |           ----------------- this expression has type `GenerationalRefMut<RefMut<'_, fn(u32) -> Colors {Colors::Red}>>`\n/// 10 |         // Since we are matching on the `GenerationalRefMut` type instead of &mut Colors, we can't match on the enum directly\n/// 11 |         Colors::Red(brightness) => *brightness += 1,\n///    |         ^^^^^^^^^^^^^^^^^^^^^^^ expected `GenerationalRefMut<RefMut<'_, ...>>`, found `Colors`\n///    |\n///    = note: expected struct `GenerationalRefMut<RefMut<'_, fn(u32) -> Colors {Colors::Red}>>`\n///                found enum `Colors`\n/// ```\n///\n/// Instead, you need to call deref mut on the reference to get the inner value **before** you match on it:\n///\n/// ```rust\n/// use std::ops::DerefMut;\n/// # use generational_box::{AnyStorage, Owner, UnsyncStorage};\n/// enum Colors {\n///     Red(u32),\n///     Green\n/// }\n/// let owner = UnsyncStorage::owner();\n/// let mut value = owner.insert(Colors::Red(0));\n/// let mut mutable_reference = value.write();\n///\n/// // DerefMut converts the `GenerationalRefMut` into a `&mut Colors`\n/// match mutable_reference.deref_mut() {\n///     // Now we can match on the inner value\n///     Colors::Red(brightness) => *brightness += 1,\n///     Colors::Green => {}\n/// }\n/// ```\npub struct GenerationalRefMut<W> {\n    pub(crate) inner: W,\n    pub(crate) borrow: GenerationalRefBorrowMutGuard,\n}\n\nimpl<T: ?Sized, R: DerefMut<Target = T>> GenerationalRefMut<R> {\n    pub(crate) fn new(inner: R, borrow: GenerationalRefBorrowMutGuard) -> Self {\n        Self { inner, borrow }\n    }\n\n    /// Map the inner value to a new type\n    pub fn map<R2, F: FnOnce(R) -> R2>(self, f: F) -> GenerationalRefMut<R2> {\n        GenerationalRefMut {\n            inner: f(self.inner),\n            borrow: self.borrow,\n        }\n    }\n\n    /// Try to map the inner value to a new type\n    pub fn try_map<R2, F: FnOnce(R) -> Option<R2>>(self, f: F) -> Option<GenerationalRefMut<R2>> {\n        f(self.inner).map(|inner| GenerationalRefMut {\n            inner,\n            borrow: self.borrow,\n        })\n    }\n}\n\nimpl<T: ?Sized, W: DerefMut<Target = T>> Deref for GenerationalRefMut<W> {\n    type Target = T;\n\n    fn deref(&self) -> &Self::Target {\n        self.inner.deref()\n    }\n}\n\nimpl<T: ?Sized, W: DerefMut<Target = T>> DerefMut for GenerationalRefMut<W> {\n    fn deref_mut(&mut self) -> &mut Self::Target {\n        self.inner.deref_mut()\n    }\n}\n\npub(crate) struct GenerationalRefBorrowMutGuard {\n    #[cfg(any(debug_assertions, feature = \"debug_borrows\"))]\n    /// The location where the borrow occurred.\n    pub(crate) borrowed_from: &'static crate::entry::MemoryLocationBorrowInfo,\n    #[cfg(any(debug_assertions, feature = \"debug_borrows\"))]\n    pub(crate) borrowed_mut_at: &'static std::panic::Location<'static>,\n}\n\n#[cfg(any(debug_assertions, feature = \"debug_borrows\"))]\nimpl Drop for GenerationalRefBorrowMutGuard {\n    fn drop(&mut self) {\n        self.borrowed_from.drop_borrow_mut(self.borrowed_mut_at);\n    }\n}\n"
  },
  {
    "path": "packages/generational-box/src/sync.rs",
    "content": "use parking_lot::{\n    MappedRwLockReadGuard, MappedRwLockWriteGuard, Mutex, RwLock, RwLockReadGuard, RwLockWriteGuard,\n};\nuse std::{\n    any::Any,\n    fmt::Debug,\n    num::NonZeroU64,\n    sync::{Arc, OnceLock},\n};\n\nuse crate::{\n    entry::{MemoryLocationBorrowInfo, RcStorageEntry, StorageEntry},\n    error::{self, ValueDroppedError},\n    references::{GenerationalRef, GenerationalRefMut},\n    AnyStorage, BorrowError, BorrowMutError, BorrowMutResult, BorrowResult, GenerationalLocation,\n    GenerationalPointer, Storage,\n};\n\ntype RwLockStorageEntryRef = RwLockReadGuard<'static, StorageEntry<RwLockStorageEntryData>>;\ntype RwLockStorageEntryMut = RwLockWriteGuard<'static, StorageEntry<RwLockStorageEntryData>>;\n\ntype AnyRef = MappedRwLockReadGuard<'static, Box<dyn Any + Send + Sync + 'static>>;\ntype AnyRefMut = MappedRwLockWriteGuard<'static, Box<dyn Any + Send + Sync + 'static>>;\n\n#[derive(Default)]\npub(crate) enum RwLockStorageEntryData {\n    Reference(GenerationalPointer<SyncStorage>),\n    Rc(RcStorageEntry<Box<dyn Any + Send + Sync>>),\n    Data(Box<dyn Any + Send + Sync>),\n    #[default]\n    Empty,\n}\n\nimpl Debug for RwLockStorageEntryData {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        match self {\n            Self::Reference(location) => write!(f, \"Reference({location:?})\"),\n            Self::Rc(_) => write!(f, \"Rc\"),\n            Self::Data(_) => write!(f, \"Data\"),\n            Self::Empty => write!(f, \"Empty\"),\n        }\n    }\n}\n\nimpl RwLockStorageEntryData {\n    pub const fn new_full(data: Box<dyn Any + Send + Sync>) -> Self {\n        Self::Data(data)\n    }\n}\n\n/// A thread safe storage. This is slower than the unsync storage, but allows you to share the value between threads.\n#[derive(Default)]\npub struct SyncStorage {\n    borrow_info: MemoryLocationBorrowInfo,\n    data: RwLock<StorageEntry<RwLockStorageEntryData>>,\n}\n\nimpl SyncStorage {\n    pub(crate) fn read(\n        pointer: GenerationalPointer<Self>,\n    ) -> BorrowResult<(AnyRef, GenerationalPointer<Self>)> {\n        Self::get_split_ref(pointer).map(|(resolved, guard)| {\n            (\n                RwLockReadGuard::map(guard, |data| match &data.data {\n                    RwLockStorageEntryData::Data(data) => data,\n                    RwLockStorageEntryData::Rc(data) => &data.data,\n                    _ => unreachable!(),\n                }),\n                resolved,\n            )\n        })\n    }\n\n    pub(crate) fn get_split_ref(\n        mut pointer: GenerationalPointer<Self>,\n    ) -> BorrowResult<(GenerationalPointer<Self>, RwLockStorageEntryRef)> {\n        loop {\n            let borrow = pointer.storage.data.read();\n            if !borrow.valid(&pointer.location) {\n                return Err(BorrowError::Dropped(ValueDroppedError::new_for_location(\n                    pointer.location,\n                )));\n            }\n            match &borrow.data {\n                // If this is a reference, keep traversing the pointers\n                RwLockStorageEntryData::Reference(data) => {\n                    pointer = *data;\n                }\n                // Otherwise return the value\n                RwLockStorageEntryData::Data(_) | RwLockStorageEntryData::Rc(_) => {\n                    return Ok((pointer, borrow));\n                }\n                RwLockStorageEntryData::Empty => {\n                    return Err(BorrowError::Dropped(ValueDroppedError::new_for_location(\n                        pointer.location,\n                    )));\n                }\n            }\n        }\n    }\n\n    pub(crate) fn write(\n        pointer: GenerationalPointer<Self>,\n    ) -> BorrowMutResult<(AnyRefMut, GenerationalPointer<Self>)> {\n        Self::get_split_mut(pointer).map(|(resolved, guard)| {\n            (\n                RwLockWriteGuard::map(guard, |data| match &mut data.data {\n                    RwLockStorageEntryData::Data(data) => data,\n                    RwLockStorageEntryData::Rc(data) => &mut data.data,\n                    _ => unreachable!(),\n                }),\n                resolved,\n            )\n        })\n    }\n\n    pub(crate) fn get_split_mut(\n        mut pointer: GenerationalPointer<Self>,\n    ) -> BorrowMutResult<(GenerationalPointer<Self>, RwLockStorageEntryMut)> {\n        loop {\n            let borrow = pointer.storage.data.write();\n            if !borrow.valid(&pointer.location) {\n                return Err(BorrowMutError::Dropped(\n                    ValueDroppedError::new_for_location(pointer.location),\n                ));\n            }\n            match &borrow.data {\n                // If this is a reference, keep traversing the pointers\n                RwLockStorageEntryData::Reference(data) => {\n                    pointer = *data;\n                }\n                // Otherwise return the value\n                RwLockStorageEntryData::Data(_) | RwLockStorageEntryData::Rc(_) => {\n                    return Ok((pointer, borrow));\n                }\n                RwLockStorageEntryData::Empty => {\n                    return Err(BorrowMutError::Dropped(\n                        ValueDroppedError::new_for_location(pointer.location),\n                    ));\n                }\n            }\n        }\n    }\n\n    fn create_new(\n        value: RwLockStorageEntryData,\n        #[allow(unused)] caller: &'static std::panic::Location<'static>,\n    ) -> GenerationalPointer<Self> {\n        match sync_runtime().lock().pop() {\n            Some(storage) => {\n                let mut write = storage.data.write();\n                let location = GenerationalLocation {\n                    generation: write.generation(),\n                    #[cfg(any(debug_assertions, feature = \"debug_borrows\"))]\n                    created_at: caller,\n                };\n                write.data = value;\n                GenerationalPointer { storage, location }\n            }\n            None => {\n                let storage: &'static Self = &*Box::leak(Box::new(Self {\n                    borrow_info: Default::default(),\n                    data: RwLock::new(StorageEntry::new(value)),\n                }));\n\n                let location = GenerationalLocation {\n                    generation: NonZeroU64::MIN,\n                    #[cfg(any(debug_assertions, feature = \"debug_borrows\"))]\n                    created_at: caller,\n                };\n\n                GenerationalPointer { storage, location }\n            }\n        }\n    }\n}\n\nstatic SYNC_RUNTIME: OnceLock<Arc<Mutex<Vec<&'static SyncStorage>>>> = OnceLock::new();\n\nfn sync_runtime() -> &'static Arc<Mutex<Vec<&'static SyncStorage>>> {\n    SYNC_RUNTIME.get_or_init(|| Arc::new(Mutex::new(Vec::new())))\n}\n\nimpl AnyStorage for SyncStorage {\n    type Ref<'a, R: ?Sized + 'a> = GenerationalRef<MappedRwLockReadGuard<'a, R>>;\n    type Mut<'a, W: ?Sized + 'a> = GenerationalRefMut<MappedRwLockWriteGuard<'a, W>>;\n\n    fn downcast_lifetime_ref<'a: 'b, 'b, T: ?Sized + 'b>(\n        ref_: Self::Ref<'a, T>,\n    ) -> Self::Ref<'b, T> {\n        ref_\n    }\n\n    fn downcast_lifetime_mut<'a: 'b, 'b, T: ?Sized + 'a>(\n        mut_: Self::Mut<'a, T>,\n    ) -> Self::Mut<'b, T> {\n        mut_\n    }\n\n    fn map<T: ?Sized, U: ?Sized>(\n        ref_: Self::Ref<'_, T>,\n        f: impl FnOnce(&T) -> &U,\n    ) -> Self::Ref<'_, U> {\n        ref_.map(|inner| MappedRwLockReadGuard::map(inner, f))\n    }\n\n    fn map_mut<T: ?Sized, U: ?Sized>(\n        mut_ref: Self::Mut<'_, T>,\n        f: impl FnOnce(&mut T) -> &mut U,\n    ) -> Self::Mut<'_, U> {\n        mut_ref.map(|inner| MappedRwLockWriteGuard::map(inner, f))\n    }\n\n    fn try_map<I: ?Sized, U: ?Sized>(\n        ref_: Self::Ref<'_, I>,\n        f: impl FnOnce(&I) -> Option<&U>,\n    ) -> Option<Self::Ref<'_, U>> {\n        ref_.try_map(|inner| MappedRwLockReadGuard::try_map(inner, f).ok())\n    }\n\n    fn try_map_mut<I: ?Sized, U: ?Sized>(\n        mut_ref: Self::Mut<'_, I>,\n        f: impl FnOnce(&mut I) -> Option<&mut U>,\n    ) -> Option<Self::Mut<'_, U>> {\n        mut_ref.try_map(|inner| MappedRwLockWriteGuard::try_map(inner, f).ok())\n    }\n\n    fn data_ptr(&self) -> *const () {\n        self.data.data_ptr() as *const ()\n    }\n\n    fn recycle(pointer: GenerationalPointer<Self>) {\n        let mut borrow_mut = pointer.storage.data.write();\n\n        // First check if the generation is still valid\n        if !borrow_mut.valid(&pointer.location) {\n            return;\n        }\n\n        borrow_mut.increment_generation();\n\n        // Then decrement the reference count or drop the value if it's the last reference\n        match &mut borrow_mut.data {\n            // If this is the original reference, drop the value\n            RwLockStorageEntryData::Data(_) => borrow_mut.data = RwLockStorageEntryData::Empty,\n            // If this is a rc, just ignore the drop\n            RwLockStorageEntryData::Rc(_) => {}\n            // If this is a reference, decrement the reference count\n            RwLockStorageEntryData::Reference(reference) => {\n                drop_ref(*reference);\n            }\n            RwLockStorageEntryData::Empty => {}\n        }\n\n        sync_runtime().lock().push(pointer.storage);\n    }\n}\n\nfn drop_ref(pointer: GenerationalPointer<SyncStorage>) {\n    let mut borrow_mut = pointer.storage.data.write();\n\n    // First check if the generation is still valid\n    if !borrow_mut.valid(&pointer.location) {\n        return;\n    }\n\n    if let RwLockStorageEntryData::Rc(entry) = &mut borrow_mut.data {\n        // Decrement the reference count\n        if entry.drop_ref() {\n            // If the reference count is now zero, drop the value\n            borrow_mut.data = RwLockStorageEntryData::Empty;\n            sync_runtime().lock().push(pointer.storage);\n        }\n    } else {\n        unreachable!(\"References should always point to a data entry directly\");\n    }\n}\n\nimpl<T: Sync + Send + 'static> Storage<T> for SyncStorage {\n    #[track_caller]\n    fn try_read(\n        pointer: GenerationalPointer<Self>,\n    ) -> Result<Self::Ref<'static, T>, error::BorrowError> {\n        let (read, pointer) = Self::read(pointer)?;\n\n        let read = MappedRwLockReadGuard::try_map(read, |any| {\n            // Then try to downcast\n            any.downcast_ref()\n        });\n        match read {\n            Ok(guard) => Ok(GenerationalRef::new(\n                guard,\n                pointer.storage.borrow_info.borrow_guard(),\n            )),\n            Err(_) => Err(error::BorrowError::Dropped(\n                ValueDroppedError::new_for_location(pointer.location),\n            )),\n        }\n    }\n\n    #[track_caller]\n    fn try_write(\n        pointer: GenerationalPointer<Self>,\n    ) -> Result<Self::Mut<'static, T>, error::BorrowMutError> {\n        let (write, pointer) = Self::write(pointer)?;\n\n        let write = MappedRwLockWriteGuard::try_map(write, |any| {\n            // Then try to downcast\n            any.downcast_mut()\n        });\n        match write {\n            Ok(guard) => Ok(GenerationalRefMut::new(\n                guard,\n                pointer.storage.borrow_info.borrow_mut_guard(),\n            )),\n            Err(_) => Err(error::BorrowMutError::Dropped(\n                ValueDroppedError::new_for_location(pointer.location),\n            )),\n        }\n    }\n\n    fn new(value: T, caller: &'static std::panic::Location<'static>) -> GenerationalPointer<Self> {\n        Self::create_new(RwLockStorageEntryData::new_full(Box::new(value)), caller)\n    }\n\n    fn new_rc(\n        value: T,\n        caller: &'static std::panic::Location<'static>,\n    ) -> GenerationalPointer<Self> {\n        // Create the data that the rc points to\n        let data = Self::create_new(\n            RwLockStorageEntryData::Rc(RcStorageEntry::new(Box::new(value))),\n            caller,\n        );\n        Self::create_new(RwLockStorageEntryData::Reference(data), caller)\n    }\n\n    fn new_reference(\n        location: GenerationalPointer<Self>,\n    ) -> BorrowResult<GenerationalPointer<Self>> {\n        // Chase the reference to get the final location\n        let (location, value) = Self::get_split_ref(location)?;\n        if let RwLockStorageEntryData::Rc(data) = &value.data {\n            data.add_ref();\n        } else {\n            unreachable!()\n        }\n        Ok(Self::create_new(\n            RwLockStorageEntryData::Reference(location),\n            location\n                .location\n                .created_at()\n                .unwrap_or(std::panic::Location::caller()),\n        ))\n    }\n\n    fn change_reference(\n        location: GenerationalPointer<Self>,\n        other: GenerationalPointer<Self>,\n    ) -> BorrowResult {\n        if location == other {\n            return Ok(());\n        }\n\n        let (other_final, other_write) = Self::get_split_ref(other)?;\n\n        let mut write = location.storage.data.write();\n        // First check if the generation is still valid\n        if !write.valid(&location.location) {\n            return Err(BorrowError::Dropped(ValueDroppedError::new_for_location(\n                location.location,\n            )));\n        }\n\n        if let (RwLockStorageEntryData::Reference(reference), RwLockStorageEntryData::Rc(data)) =\n            (&mut write.data, &other_write.data)\n        {\n            if reference == &other_final {\n                return Ok(());\n            }\n            drop_ref(*reference);\n            *reference = other_final;\n            data.add_ref();\n        } else {\n            tracing::trace!(\n                \"References should always point to a data entry directly found {:?} instead\",\n                other_write.data\n            );\n            return Err(BorrowError::Dropped(ValueDroppedError::new_for_location(\n                other_final.location,\n            )));\n        }\n\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "packages/generational-box/src/unsync.rs",
    "content": "use crate::{\n    entry::{MemoryLocationBorrowInfo, RcStorageEntry, StorageEntry},\n    error,\n    references::{GenerationalRef, GenerationalRefMut},\n    AnyStorage, BorrowError, BorrowMutError, BorrowMutResult, BorrowResult, GenerationalLocation,\n    GenerationalPointer, Storage, ValueDroppedError,\n};\nuse std::{\n    any::Any,\n    cell::{Ref, RefCell, RefMut},\n    fmt::Debug,\n    num::NonZeroU64,\n};\n\ntype RefCellStorageEntryRef = Ref<'static, StorageEntry<RefCellStorageEntryData>>;\ntype RefCellStorageEntryMut = RefMut<'static, StorageEntry<RefCellStorageEntryData>>;\ntype AnyRef = Ref<'static, Box<dyn Any>>;\ntype AnyRefMut = RefMut<'static, Box<dyn Any>>;\n\nthread_local! {\n    static UNSYNC_RUNTIME: RefCell<Vec<&'static UnsyncStorage>> = const { RefCell::new(Vec::new()) };\n}\n\n#[derive(Default)]\npub(crate) enum RefCellStorageEntryData {\n    Reference(GenerationalPointer<UnsyncStorage>),\n    Rc(RcStorageEntry<Box<dyn Any>>),\n    Data(Box<dyn Any>),\n    #[default]\n    Empty,\n}\n\nimpl Debug for RefCellStorageEntryData {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        match self {\n            Self::Reference(pointer) => write!(f, \"Reference({:?})\", pointer.location),\n            Self::Rc(_) => write!(f, \"Rc\"),\n            Self::Data(_) => write!(f, \"Data\"),\n            Self::Empty => write!(f, \"Empty\"),\n        }\n    }\n}\n\n/// A unsync storage. This is the default storage type.\n#[derive(Default)]\npub struct UnsyncStorage {\n    borrow_info: MemoryLocationBorrowInfo,\n    data: RefCell<StorageEntry<RefCellStorageEntryData>>,\n}\n\nimpl UnsyncStorage {\n    pub(crate) fn read(\n        pointer: GenerationalPointer<Self>,\n    ) -> BorrowResult<(AnyRef, GenerationalPointer<Self>)> {\n        Self::get_split_ref(pointer).map(|(resolved, guard)| {\n            (\n                Ref::map(guard, |data| match &data.data {\n                    RefCellStorageEntryData::Data(data) => data,\n                    RefCellStorageEntryData::Rc(data) => &data.data,\n                    _ => unreachable!(),\n                }),\n                resolved,\n            )\n        })\n    }\n\n    pub(crate) fn get_split_ref(\n        mut pointer: GenerationalPointer<Self>,\n    ) -> BorrowResult<(GenerationalPointer<Self>, RefCellStorageEntryRef)> {\n        loop {\n            let borrow = pointer\n                .storage\n                .data\n                .try_borrow()\n                .map_err(|_| pointer.storage.borrow_info.borrow_error())?;\n            if !borrow.valid(&pointer.location) {\n                return Err(BorrowError::Dropped(ValueDroppedError::new_for_location(\n                    pointer.location,\n                )));\n            }\n            match &borrow.data {\n                // If this is a reference, keep traversing the pointers\n                RefCellStorageEntryData::Reference(data) => {\n                    pointer = *data;\n                }\n                // Otherwise return the value\n                RefCellStorageEntryData::Rc(_) | RefCellStorageEntryData::Data(_) => {\n                    return Ok((pointer, borrow));\n                }\n                RefCellStorageEntryData::Empty => {\n                    return Err(BorrowError::Dropped(ValueDroppedError::new_for_location(\n                        pointer.location,\n                    )));\n                }\n            }\n        }\n    }\n\n    pub(crate) fn write(\n        pointer: GenerationalPointer<Self>,\n    ) -> BorrowMutResult<(AnyRefMut, GenerationalPointer<Self>)> {\n        Self::get_split_mut(pointer).map(|(resolved, guard)| {\n            (\n                RefMut::map(guard, |data| match &mut data.data {\n                    RefCellStorageEntryData::Data(data) => data,\n                    RefCellStorageEntryData::Rc(data) => &mut data.data,\n                    _ => unreachable!(),\n                }),\n                resolved,\n            )\n        })\n    }\n\n    pub(crate) fn get_split_mut(\n        mut pointer: GenerationalPointer<Self>,\n    ) -> BorrowMutResult<(GenerationalPointer<Self>, RefCellStorageEntryMut)> {\n        loop {\n            let borrow = pointer\n                .storage\n                .data\n                .try_borrow_mut()\n                .map_err(|_| pointer.storage.borrow_info.borrow_mut_error())?;\n            if !borrow.valid(&pointer.location) {\n                return Err(BorrowMutError::Dropped(\n                    ValueDroppedError::new_for_location(pointer.location),\n                ));\n            }\n            match &borrow.data {\n                // If this is a reference, keep traversing the pointers\n                RefCellStorageEntryData::Reference(data) => {\n                    pointer = *data;\n                }\n                // Otherwise return the value\n                RefCellStorageEntryData::Data(_) | RefCellStorageEntryData::Rc(_) => {\n                    return Ok((pointer, borrow));\n                }\n                RefCellStorageEntryData::Empty => {\n                    return Err(BorrowMutError::Dropped(\n                        ValueDroppedError::new_for_location(pointer.location),\n                    ));\n                }\n            }\n        }\n    }\n\n    fn create_new(\n        value: RefCellStorageEntryData,\n        #[allow(unused)] caller: &'static std::panic::Location<'static>,\n    ) -> GenerationalPointer<Self> {\n        UNSYNC_RUNTIME.with(|runtime| match runtime.borrow_mut().pop() {\n            Some(storage) => {\n                let mut write = storage.data.borrow_mut();\n                let location = GenerationalLocation {\n                    generation: write.generation(),\n                    #[cfg(any(debug_assertions, feature = \"debug_borrows\"))]\n                    created_at: caller,\n                };\n                write.data = value;\n                GenerationalPointer { storage, location }\n            }\n            None => {\n                let storage: &'static Self = &*Box::leak(Box::new(Self {\n                    borrow_info: Default::default(),\n                    data: RefCell::new(StorageEntry::new(value)),\n                }));\n\n                let location = GenerationalLocation {\n                    generation: NonZeroU64::MIN,\n                    #[cfg(any(debug_assertions, feature = \"debug_borrows\"))]\n                    created_at: caller,\n                };\n\n                GenerationalPointer { storage, location }\n            }\n        })\n    }\n}\n\nimpl AnyStorage for UnsyncStorage {\n    type Ref<'a, R: ?Sized + 'a> = GenerationalRef<Ref<'a, R>>;\n    type Mut<'a, W: ?Sized + 'a> = GenerationalRefMut<RefMut<'a, W>>;\n\n    fn downcast_lifetime_ref<'a: 'b, 'b, T: ?Sized + 'a>(\n        ref_: Self::Ref<'a, T>,\n    ) -> Self::Ref<'b, T> {\n        ref_\n    }\n\n    fn downcast_lifetime_mut<'a: 'b, 'b, T: ?Sized + 'a>(\n        mut_: Self::Mut<'a, T>,\n    ) -> Self::Mut<'b, T> {\n        mut_\n    }\n\n    fn map<T: ?Sized, U: ?Sized>(\n        ref_: Self::Ref<'_, T>,\n        f: impl FnOnce(&T) -> &U,\n    ) -> Self::Ref<'_, U> {\n        ref_.map(|inner| Ref::map(inner, f))\n    }\n\n    fn map_mut<T: ?Sized, U: ?Sized>(\n        mut_ref: Self::Mut<'_, T>,\n        f: impl FnOnce(&mut T) -> &mut U,\n    ) -> Self::Mut<'_, U> {\n        mut_ref.map(|inner| RefMut::map(inner, f))\n    }\n\n    fn try_map<I: ?Sized, U: ?Sized>(\n        _self: Self::Ref<'_, I>,\n        f: impl FnOnce(&I) -> Option<&U>,\n    ) -> Option<Self::Ref<'_, U>> {\n        _self.try_map(|inner| Ref::filter_map(inner, f).ok())\n    }\n\n    fn try_map_mut<I: ?Sized, U: ?Sized>(\n        mut_ref: Self::Mut<'_, I>,\n        f: impl FnOnce(&mut I) -> Option<&mut U>,\n    ) -> Option<Self::Mut<'_, U>> {\n        mut_ref.try_map(|inner| RefMut::filter_map(inner, f).ok())\n    }\n\n    fn data_ptr(&self) -> *const () {\n        self.data.as_ptr() as *const ()\n    }\n\n    fn recycle(pointer: GenerationalPointer<Self>) {\n        let mut borrow_mut = pointer.storage.data.borrow_mut();\n\n        // First check if the generation is still valid\n        if !borrow_mut.valid(&pointer.location) {\n            return;\n        }\n\n        borrow_mut.increment_generation();\n        // Then decrement the reference count or drop the value if it's the last reference\n        match &mut borrow_mut.data {\n            // If this is the original reference, drop the value\n            RefCellStorageEntryData::Data(_) => borrow_mut.data = RefCellStorageEntryData::Empty,\n            // If this is a rc, just ignore the drop\n            RefCellStorageEntryData::Rc(_) => {}\n            // If this is a reference, decrement the reference count\n            RefCellStorageEntryData::Reference(reference) => {\n                let reference = *reference;\n                drop(borrow_mut);\n                drop_ref(reference);\n            }\n            RefCellStorageEntryData::Empty => {}\n        }\n\n        UNSYNC_RUNTIME.with(|runtime| runtime.borrow_mut().push(pointer.storage));\n    }\n}\n\nfn drop_ref(pointer: GenerationalPointer<UnsyncStorage>) {\n    let mut borrow_mut = pointer.storage.data.borrow_mut();\n\n    // First check if the generation is still valid\n    if !borrow_mut.valid(&pointer.location) {\n        return;\n    }\n\n    if let RefCellStorageEntryData::Rc(entry) = &mut borrow_mut.data {\n        // Decrement the reference count\n        if entry.drop_ref() {\n            // If the reference count is now zero, drop the value\n            borrow_mut.data = RefCellStorageEntryData::Empty;\n            UNSYNC_RUNTIME.with(|runtime| runtime.borrow_mut().push(pointer.storage));\n        }\n    } else {\n        unreachable!(\"References should always point to a data entry directly\",);\n    }\n}\n\nimpl<T: 'static> Storage<T> for UnsyncStorage {\n    #[track_caller]\n    fn try_read(\n        pointer: GenerationalPointer<Self>,\n    ) -> Result<Self::Ref<'static, T>, error::BorrowError> {\n        let (read, pointer) = Self::read(pointer)?;\n\n        let ref_ = Ref::filter_map(read, |any| {\n            // Then try to downcast\n            any.downcast_ref()\n        });\n        match ref_ {\n            Ok(guard) => Ok(GenerationalRef::new(\n                guard,\n                pointer.storage.borrow_info.borrow_guard(),\n            )),\n            Err(_) => Err(error::BorrowError::Dropped(\n                error::ValueDroppedError::new_for_location(pointer.location),\n            )),\n        }\n    }\n\n    #[track_caller]\n    fn try_write(\n        pointer: GenerationalPointer<Self>,\n    ) -> Result<Self::Mut<'static, T>, error::BorrowMutError> {\n        let (write, pointer) = Self::write(pointer)?;\n\n        let ref_mut = RefMut::filter_map(write, |any| {\n            // Then try to downcast\n            any.downcast_mut()\n        });\n        match ref_mut {\n            Ok(guard) => Ok(GenerationalRefMut::new(\n                guard,\n                pointer.storage.borrow_info.borrow_mut_guard(),\n            )),\n            Err(_) => Err(error::BorrowMutError::Dropped(\n                error::ValueDroppedError::new_for_location(pointer.location),\n            )),\n        }\n    }\n\n    fn new(value: T, caller: &'static std::panic::Location<'static>) -> GenerationalPointer<Self> {\n        Self::create_new(RefCellStorageEntryData::Data(Box::new(value)), caller)\n    }\n\n    fn new_rc(\n        value: T,\n        caller: &'static std::panic::Location<'static>,\n    ) -> GenerationalPointer<Self> {\n        // Create the data that the rc points to\n        let data = Self::create_new(\n            RefCellStorageEntryData::Rc(RcStorageEntry::new(Box::new(value))),\n            caller,\n        );\n        Self::create_new(RefCellStorageEntryData::Reference(data), caller)\n    }\n\n    fn new_reference(\n        pointer: GenerationalPointer<Self>,\n    ) -> BorrowResult<GenerationalPointer<Self>> {\n        // Chase the reference to get the final location\n        let (pointer, value) = Self::get_split_ref(pointer)?;\n        if let RefCellStorageEntryData::Rc(data) = &value.data {\n            data.add_ref();\n        } else {\n            unreachable!()\n        }\n        Ok(Self::create_new(\n            RefCellStorageEntryData::Reference(pointer),\n            pointer\n                .location\n                .created_at()\n                .unwrap_or(std::panic::Location::caller()),\n        ))\n    }\n\n    fn change_reference(\n        location: GenerationalPointer<Self>,\n        other: GenerationalPointer<Self>,\n    ) -> BorrowResult {\n        if location == other {\n            return Ok(());\n        }\n\n        let (other_final, other_write) = Self::get_split_ref(other)?;\n\n        let mut write = location.storage.data.borrow_mut();\n        // First check if the generation is still valid\n        if !write.valid(&location.location) {\n            return Err(BorrowError::Dropped(ValueDroppedError::new_for_location(\n                location.location,\n            )));\n        }\n\n        if let (RefCellStorageEntryData::Reference(reference), RefCellStorageEntryData::Rc(data)) =\n            (&mut write.data, &other_write.data)\n        {\n            if reference == &other_final {\n                return Ok(());\n            }\n            drop_ref(*reference);\n            *reference = other_final;\n            data.add_ref();\n        } else {\n            tracing::trace!(\n                \"References should always point to a data entry directly found {:?} instead\",\n                other_write.data\n            );\n            return Err(BorrowError::Dropped(ValueDroppedError::new_for_location(\n                other_final.location,\n            )));\n        }\n\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "packages/generational-box/tests/basic.rs",
    "content": "use generational_box::{GenerationalBox, Storage, SyncStorage, UnsyncStorage};\n\n/// # Example\n///\n/// ```compile_fail\n/// let data = String::from(\"hello world\");\n/// let owner = UnsyncStorage::owner();\n/// let key = owner.insert(&data);\n/// drop(data);\n/// assert_eq!(*key.read(), \"hello world\");\n/// ```\n#[allow(unused)]\nfn compile_fail() {}\n\n#[test]\nfn leaking_is_ok() {\n    fn leaking_is_ok_test<S: Storage<String> + 'static>() {\n        let data = String::from(\"hello world\");\n        let key;\n        {\n            // create an owner\n            let owner = S::owner();\n            // insert data into the store\n            key = owner.insert(data);\n            // don't drop the owner\n            std::mem::forget(owner);\n        }\n        assert_eq!(\n            key.try_read().as_deref().unwrap(),\n            &\"hello world\".to_string()\n        );\n    }\n\n    leaking_is_ok_test::<UnsyncStorage>();\n    leaking_is_ok_test::<SyncStorage>();\n}\n\n#[test]\nfn drops() {\n    fn drops_test<S: Storage<String> + 'static>() {\n        let data = String::from(\"hello world\");\n        let key;\n        {\n            // create an owner\n            let owner = S::owner();\n            // insert data into the store\n            key = owner.insert(data);\n            // drop the owner\n        }\n        assert!(key.try_read().is_err());\n    }\n\n    drops_test::<UnsyncStorage>();\n    drops_test::<SyncStorage>();\n}\n\n#[test]\nfn works() {\n    fn works_test<S: Storage<i32> + 'static>() {\n        let owner = S::owner();\n        let key = owner.insert(1);\n\n        assert_eq!(*key.read(), 1);\n    }\n\n    works_test::<UnsyncStorage>();\n    works_test::<SyncStorage>();\n}\n\n#[test]\nfn insert_while_reading() {\n    fn insert_while_reading_test<S: Storage<String> + Storage<&'static i32> + 'static>() {\n        let owner = S::owner();\n        let key;\n        {\n            let data: String = \"hello world\".to_string();\n            key = owner.insert(data);\n        }\n        let value = key.read();\n        owner.insert(&1);\n        assert_eq!(*value, \"hello world\");\n    }\n\n    insert_while_reading_test::<UnsyncStorage>();\n    insert_while_reading_test::<SyncStorage>();\n}\n\n#[test]\n#[should_panic]\nfn panics() {\n    fn panics_test<S: Storage<i32> + 'static>() {\n        let owner = S::owner();\n\n        let key = owner.insert(1);\n        drop(owner);\n\n        assert_eq!(*key.read(), 1);\n    }\n\n    panics_test::<UnsyncStorage>();\n    panics_test::<SyncStorage>();\n}\n\n#[test]\nfn fuzz() {\n    fn maybe_owner_scope<S: Storage<String> + 'static>(\n        valid_keys: &mut Vec<GenerationalBox<String, S>>,\n        invalid_keys: &mut Vec<GenerationalBox<String, S>>,\n        path: &mut Vec<u8>,\n    ) {\n        let branch_cutoff = 5;\n        let children = if path.len() < branch_cutoff {\n            rand::random::<u8>() % 4\n        } else {\n            rand::random::<u8>() % 2\n        };\n\n        for i in 0..children {\n            let owner = S::owner();\n            let value = format!(\"hello world {path:?}\");\n            let key = owner.insert(value.clone());\n            println!(\"created new box {key:?}\");\n            valid_keys.push(key);\n            path.push(i);\n            // read all keys\n            println!(\"{:?}\", path);\n            for key in valid_keys.iter() {\n                println!(\"reading {key:?}\");\n                let value = key.read();\n                println!(\"{:?}\", &*value);\n                assert!(value.starts_with(\"hello world\"));\n            }\n            for key in invalid_keys.iter() {\n                println!(\"reading invalid {key:?}\");\n                assert!(key.try_read().is_err());\n            }\n            maybe_owner_scope(valid_keys, invalid_keys, path);\n\n            // After all the children run, we should still have our data\n            let key_value = &*key.read();\n            println!(\"{:?}\", key_value);\n            assert_eq!(key_value, &value);\n\n            let invalid = valid_keys.pop().unwrap();\n            println!(\"popping {invalid:?}\");\n            invalid_keys.push(invalid);\n            path.pop();\n        }\n    }\n\n    for _ in 0..10 {\n        maybe_owner_scope::<UnsyncStorage>(&mut Vec::new(), &mut Vec::new(), &mut Vec::new());\n    }\n\n    for _ in 0..10 {\n        maybe_owner_scope::<SyncStorage>(&mut Vec::new(), &mut Vec::new(), &mut Vec::new());\n    }\n}\n\n#[test]\nfn fuzz_rc() {\n    fn maybe_owner_scope<S: Storage<String>>(\n        valid_keys: &mut Vec<Vec<GenerationalBox<String, S>>>,\n        invalid_keys: &mut Vec<GenerationalBox<String, S>>,\n        path: &mut Vec<u8>,\n    ) {\n        let branch_cutoff = 5;\n        let children = if path.len() < branch_cutoff {\n            rand::random::<u8>() % 4\n        } else {\n            rand::random::<u8>() % 2\n        };\n\n        for i in 0..children {\n            let owner = S::owner();\n            let value = format!(\"hello world {path:?}\");\n            let key = owner.insert_rc(value.clone());\n            println!(\"created new box {key:?}\");\n            let mut keys = vec![key];\n            for _ in 0..rand::random::<u8>() % 10 {\n                if rand::random::<u8>() % 2 == 0 {\n                    let owner = S::owner();\n                    let key = owner.insert_reference(key).unwrap();\n                    println!(\"created new reference {key:?}\");\n                    invalid_keys.push(key);\n                }\n                let key = owner.insert_reference(key).unwrap();\n                println!(\"created new reference {key:?}\");\n                keys.push(key);\n            }\n            valid_keys.push(keys.clone());\n            path.push(i);\n            // read all keys\n            println!(\"{:?}\", path);\n            for keys in valid_keys.iter() {\n                for key in keys {\n                    println!(\"reading {key:?}\");\n                    let value = key.read();\n                    println!(\"{:?}\", &*value);\n                    assert!(value.starts_with(\"hello world\"));\n                }\n            }\n            for key in invalid_keys.iter() {\n                println!(\"reading invalid {key:?}\");\n                assert!(key.try_read().is_err());\n            }\n            maybe_owner_scope(valid_keys, invalid_keys, path);\n\n            // After all the children run, we should still have our data\n            for key in keys {\n                let key_value = &*key.read();\n                println!(\"{:?}\", key_value);\n                assert_eq!(key_value, &value);\n            }\n\n            let invalid = valid_keys.pop().unwrap();\n            println!(\"popping {invalid:?}\");\n            invalid_keys.extend(invalid);\n            path.pop();\n        }\n    }\n\n    for _ in 0..10 {\n        maybe_owner_scope::<UnsyncStorage>(&mut Vec::new(), &mut Vec::new(), &mut Vec::new());\n    }\n\n    for _ in 0..10 {\n        maybe_owner_scope::<SyncStorage>(&mut Vec::new(), &mut Vec::new(), &mut Vec::new());\n    }\n}\n"
  },
  {
    "path": "packages/generational-box/tests/errors.rs",
    "content": "use generational_box::{\n    AlreadyBorrowedError, AlreadyBorrowedMutError, BorrowError, BorrowMutError, GenerationalBox,\n    Owner, Storage, SyncStorage, UnsyncStorage, ValueDroppedError,\n};\n\n#[track_caller]\nfn read_at_location<S: Storage<i32>>(\n    value: GenerationalBox<i32, S>,\n) -> (S::Ref<'static, i32>, &'static std::panic::Location<'static>) {\n    let location = std::panic::Location::caller();\n    let read = value.read();\n    (read, location)\n}\n\n#[track_caller]\nfn write_at_location<S: Storage<i32>>(\n    value: GenerationalBox<i32, S>,\n) -> (S::Mut<'static, i32>, &'static std::panic::Location<'static>) {\n    let location = std::panic::Location::caller();\n    let write = value.write();\n    (write, location)\n}\n\n#[track_caller]\nfn create_at_location<S: Storage<i32>>(\n    owner: &Owner<S>,\n) -> (\n    GenerationalBox<i32, S>,\n    &'static std::panic::Location<'static>,\n) {\n    let location = std::panic::Location::caller();\n    let value = owner.insert(1);\n    (value, location)\n}\n\n#[track_caller]\nfn create_at_location_rc<S: Storage<i32>>(\n    owner: &Owner<S>,\n) -> (\n    GenerationalBox<i32, S>,\n    &'static std::panic::Location<'static>,\n) {\n    let location = std::panic::Location::caller();\n    let value = owner.insert_rc(1);\n    (value, location)\n}\n\n#[test]\nfn read_while_writing_error() {\n    fn read_while_writing_error_test<S: Storage<i32> + 'static>() {\n        let owner = S::owner();\n        let value = owner.insert(1);\n\n        let (write, location) = write_at_location(value);\n\n        assert_eq!(\n            value.try_read().err(),\n            Some(BorrowError::AlreadyBorrowedMut(\n                AlreadyBorrowedMutError::new(location)\n            ))\n        );\n        drop(write);\n    }\n\n    // For sync storage this will deadlock\n    read_while_writing_error_test::<UnsyncStorage>();\n}\n\n#[test]\nfn read_while_writing_error_rc() {\n    fn read_while_writing_error_test<S: Storage<i32> + 'static>() {\n        let owner = S::owner();\n        let value = owner.insert_rc(1);\n\n        let (write, location) = write_at_location(value);\n\n        assert_eq!(\n            value.try_read().err(),\n            Some(BorrowError::AlreadyBorrowedMut(\n                AlreadyBorrowedMutError::new(location)\n            ))\n        );\n        drop(write);\n    }\n\n    // For sync storage this will deadlock\n    read_while_writing_error_test::<UnsyncStorage>();\n}\n\n#[test]\nfn read_after_dropped_error() {\n    fn read_after_dropped_error_test<S: Storage<i32> + 'static>() {\n        let owner = S::owner();\n        let (value, location) = create_at_location(&owner);\n        drop(owner);\n        assert_eq!(\n            value.try_read().err(),\n            Some(BorrowError::Dropped(ValueDroppedError::new(location)))\n        );\n    }\n\n    read_after_dropped_error_test::<UnsyncStorage>();\n    read_after_dropped_error_test::<SyncStorage>();\n}\n\n#[test]\nfn read_after_dropped_error_rc() {\n    fn read_after_dropped_error_test<S: Storage<i32> + 'static>() {\n        let owner = S::owner();\n        let (value, location) = create_at_location_rc(&owner);\n        drop(owner);\n        assert_eq!(\n            value.try_read().err(),\n            Some(BorrowError::Dropped(ValueDroppedError::new(location)))\n        );\n    }\n\n    read_after_dropped_error_test::<UnsyncStorage>();\n    read_after_dropped_error_test::<SyncStorage>();\n}\n\n#[test]\nfn write_while_writing_error() {\n    fn write_while_writing_error_test<S: Storage<i32> + 'static>() {\n        let owner = S::owner();\n        let value = owner.insert(1);\n\n        #[allow(unused)]\n        let (write, location) = write_at_location(value);\n\n        let write_result = value.try_write();\n        assert!(write_result.is_err());\n        #[cfg(any(debug_assertions, feature = \"debug_borrows\"))]\n        assert_eq!(\n            write_result.err(),\n            Some(BorrowMutError::AlreadyBorrowedMut(\n                AlreadyBorrowedMutError::new(location)\n            ))\n        );\n\n        drop(write);\n    }\n\n    // For sync storage this will deadlock\n    write_while_writing_error_test::<UnsyncStorage>();\n}\n\n#[test]\nfn write_while_writing_error_rc() {\n    fn write_while_writing_error_test<S: Storage<i32> + 'static>() {\n        let owner = S::owner();\n        let value = owner.insert_rc(1);\n\n        #[allow(unused)]\n        let (write, location) = write_at_location(value);\n\n        let write_result = value.try_write();\n        assert!(write_result.is_err());\n        #[cfg(any(debug_assertions, feature = \"debug_borrows\"))]\n        assert_eq!(\n            write_result.err(),\n            Some(BorrowMutError::AlreadyBorrowedMut(\n                AlreadyBorrowedMutError::new(location)\n            ))\n        );\n\n        drop(write);\n    }\n\n    // For sync storage this will deadlock\n    write_while_writing_error_test::<UnsyncStorage>();\n}\n\n#[test]\nfn write_while_reading_error() {\n    fn write_while_reading_error_test<S: Storage<i32> + 'static>() {\n        let owner = S::owner();\n        let value = owner.insert(1);\n\n        let (read, location) = read_at_location(value);\n\n        assert_eq!(\n            value.try_write().err(),\n            Some(BorrowMutError::AlreadyBorrowed(AlreadyBorrowedError::new(\n                vec![location]\n            )))\n        );\n\n        drop(read);\n    }\n\n    // For sync storage this will deadlock\n    write_while_reading_error_test::<UnsyncStorage>();\n}\n\n#[test]\nfn write_while_reading_error_rc() {\n    fn write_while_reading_error_test<S: Storage<i32> + 'static>() {\n        let owner = S::owner();\n        let value = owner.insert_rc(1);\n\n        let (read, location) = read_at_location(value);\n\n        assert_eq!(\n            value.try_write().err(),\n            Some(BorrowMutError::AlreadyBorrowed(AlreadyBorrowedError::new(\n                vec![location]\n            )))\n        );\n\n        drop(read);\n    }\n\n    // For sync storage this will deadlock\n    write_while_reading_error_test::<UnsyncStorage>();\n}\n"
  },
  {
    "path": "packages/generational-box/tests/reference_counting.rs",
    "content": "use generational_box::{Storage, SyncStorage, UnsyncStorage};\n\n#[test]\nfn reference_counting() {\n    fn reference_counting<S: Storage<String> + 'static>() {\n        let data = String::from(\"hello world\");\n        let reference;\n        {\n            let outer_owner = S::owner();\n            {\n                // create an owner\n                let owner = S::owner();\n                // insert data into the store\n                let original = owner.insert_rc(data);\n                reference = outer_owner.insert_reference(original).unwrap();\n                // The reference should point to the value immediately\n                assert_eq!(&*reference.read(), \"hello world\");\n                // Original is dropped\n            }\n            // The reference should still point to the value\n            assert_eq!(&*reference.read(), \"hello world\");\n        }\n        // Now that all references are dropped, the value should be dropped\n        assert!(reference.try_read().is_err());\n    }\n\n    reference_counting::<UnsyncStorage>();\n    reference_counting::<SyncStorage>();\n}\n\n#[test]\nfn move_reference_in_place() {\n    fn move_reference_in_place<S: Storage<String> + 'static>() {\n        let data1 = String::from(\"hello world\");\n        let data2 = String::from(\"hello world 2\");\n\n        // create an owner\n        let original_owner = S::owner();\n        // insert data into the store\n        let original = original_owner.insert_rc(data1.clone());\n        let reference = original_owner.insert_reference(original).unwrap();\n        // The reference should point to the original value\n        assert_eq!(&*reference.read(), &data1);\n\n        let new_owner = S::owner();\n        // Move the reference in place\n        let new = new_owner.insert_rc(data2.clone());\n        reference.point_to(new).unwrap();\n        // The reference should point to the new value\n        assert_eq!(&*reference.read(), &data2);\n\n        // make sure both got dropped\n        drop(original_owner);\n        drop(new_owner);\n        assert!(original.try_read().is_err());\n        assert!(new.try_read().is_err());\n    }\n\n    move_reference_in_place::<UnsyncStorage>();\n    move_reference_in_place::<SyncStorage>();\n}\n"
  },
  {
    "path": "packages/generational-box/tests/reused.rs",
    "content": "//! This test needs to be in its own file such that it doesn't share\n//! an address space with the other tests.\n//!\n//! That will cause random failures on CI.\n\nuse generational_box::{Storage, SyncStorage, UnsyncStorage};\n\n#[test]\nfn reused() {\n    fn reused_test<S: Storage<i32> + 'static>() {\n        let first_ptr;\n        {\n            let owner = S::owner();\n            first_ptr = owner.insert(1).raw_ptr();\n            drop(owner);\n        }\n        {\n            let owner = S::owner();\n            let second_ptr = owner.insert(1234).raw_ptr();\n            assert_eq!(first_ptr, second_ptr);\n            drop(owner);\n        }\n    }\n\n    reused_test::<UnsyncStorage>();\n    reused_test::<SyncStorage>();\n}\n"
  },
  {
    "path": "packages/generational-box/tests/sync.rs",
    "content": "// Regression test for https://github.com/DioxusLabs/dioxus/issues/2636\n\nuse std::time::Duration;\n\nuse generational_box::{AnyStorage, GenerationalBox, SyncStorage};\n\n#[test]\nfn race_condition_regression() {\n    for _ in 0..100 {\n        let handle = {\n            let owner = SyncStorage::owner();\n            let key = owner.insert(1u64);\n            let handle = std::thread::spawn(move || reader(key));\n\n            std::thread::sleep(Duration::from_millis(10));\n            handle\n            // owner is dropped now\n        };\n        // owner is *recycled*\n        let owner = SyncStorage::owner();\n        let _key = owner.insert(2u64);\n        let _ = handle.join();\n    }\n}\n\nfn reader(key: GenerationalBox<u64, SyncStorage>) {\n    for _ in 0..1000000 {\n        match key.try_read() {\n            Ok(value) => {\n                if *value == 2 {\n                    panic!(\"Read a new value with the old generation\");\n                } else {\n                    // fine\n                }\n            }\n            Err(err) => {\n                eprintln!(\"bailing out - {err:?}\");\n                break;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "packages/history/Cargo.toml",
    "content": "[package]\nname = \"dioxus-history\"\nedition = \"2021\"\nversion = { workspace = true }\nauthors = [\"Jonathan Kelley\"]\ndescription = \"History provider for dioxus\"\nlicense = \"MIT OR Apache-2.0\"\nrepository = \"https://github.com/DioxusLabs/dioxus/\"\nhomepage = \"https://dioxuslabs.com\"\nkeywords = [\"dom\", \"ui\", \"gui\", \"react\"]\n\n\n[dependencies]\ndioxus-core = { workspace = true }\ntracing = { workspace = true }\n\n[dev-dependencies]\ndioxus = { workspace = true, features = [\"router\"] }\n"
  },
  {
    "path": "packages/history/src/lib.rs",
    "content": "use dioxus_core::{provide_context, provide_root_context};\nuse std::{rc::Rc, sync::Arc};\n\nmod memory;\npub use memory::*;\n\n/// Get the history provider for the current platform if the platform doesn't implement a history functionality.\npub fn history() -> Rc<dyn History> {\n    match dioxus_core::try_consume_context::<Rc<dyn History>>() {\n        Some(history) => history,\n        None => {\n            tracing::error!(\"Unable to find a history provider in the renderer. Make sure your renderer supports the Router. Falling back to the in-memory history provider.\");\n            provide_root_context(Rc::new(MemoryHistory::default()))\n        }\n    }\n}\n\n/// Provide a history context to the current component.\npub fn provide_history_context(history: Rc<dyn History>) {\n    provide_context(history);\n}\n\npub trait History {\n    /// Get the path of the current URL.\n    ///\n    /// **Must start** with `/`. **Must _not_ contain** the prefix.\n    ///\n    /// ```rust\n    /// # use dioxus::prelude::*;\n    /// # #[component]\n    /// # fn Index() -> Element { VNode::empty() }\n    /// # #[component]\n    /// # fn OtherPage() -> Element { VNode::empty() }\n    /// #[derive(Clone, Routable, Debug, PartialEq)]\n    /// enum Route {\n    ///     #[route(\"/\")]\n    ///     Index {},\n    ///     #[route(\"/some-other-page\")]\n    ///     OtherPage {},\n    /// }\n    /// let mut history = dioxus::history::MemoryHistory::default();\n    /// assert_eq!(history.current_route(), \"/\");\n    ///\n    /// history.push(Route::OtherPage {}.to_string());\n    /// assert_eq!(history.current_route(), \"/some-other-page\");\n    /// ```\n    #[must_use]\n    fn current_route(&self) -> String;\n\n    /// Get the current path prefix of the URL.\n    ///\n    /// Not all [`History`]s need a prefix feature. It is meant for environments where a\n    /// dioxus-router-core-routed application is not running on `/`. The [`History`] is responsible\n    /// for removing the prefix from the dioxus-router-core-internal path, and also for adding it back in\n    /// during navigation. This functions value is only used for creating `href`s (e.g. for SSR or\n    /// display (but not navigation) in a web app).\n    fn current_prefix(&self) -> Option<String> {\n        None\n    }\n\n    /// Check whether there is a previous page to navigate back to.\n    ///\n    /// If a [`History`] cannot know this, it should return [`true`].\n    ///\n    /// ```rust\n    /// # use dioxus::prelude::*;\n    /// # #[component]\n    /// # fn Index() -> Element { VNode::empty() }\n    /// # fn Other() -> Element { VNode::empty() }\n    /// #[derive(Clone, Routable, Debug, PartialEq)]\n    /// enum Route {\n    ///     #[route(\"/\")]\n    ///     Index {},\n    ///     #[route(\"/other\")]\n    ///     Other {},\n    /// }\n    /// let mut history = dioxus::history::MemoryHistory::default();\n    /// assert_eq!(history.can_go_back(), false);\n    ///\n    /// history.push(Route::Other {}.to_string());\n    /// assert_eq!(history.can_go_back(), true);\n    /// ```\n    #[must_use]\n    fn can_go_back(&self) -> bool {\n        true\n    }\n\n    /// Go back to a previous page.\n    ///\n    /// If a [`History`] cannot go to a previous page, it should do nothing. This method\n    /// might be called, even if `can_go_back` returns [`false`].\n    ///\n    /// ```rust\n    /// # use dioxus::prelude::*;\n    /// # #[component]\n    /// # fn Index() -> Element { VNode::empty() }\n    /// # #[component]\n    /// # fn OtherPage() -> Element { VNode::empty() }\n    /// #[derive(Clone, Routable, Debug, PartialEq)]\n    /// enum Route {\n    ///     #[route(\"/\")]\n    ///     Index {},\n    ///     #[route(\"/some-other-page\")]\n    ///     OtherPage {},\n    /// }\n    /// let mut history = dioxus::history::MemoryHistory::default();\n    /// assert_eq!(history.current_route(), \"/\");\n    ///\n    /// history.go_back();\n    /// assert_eq!(history.current_route(), \"/\");\n    ///\n    /// history.push(Route::OtherPage {}.to_string());\n    /// assert_eq!(history.current_route(), \"/some-other-page\");\n    ///\n    /// history.go_back();\n    /// assert_eq!(history.current_route(), \"/\");\n    /// ```\n    fn go_back(&self);\n\n    /// Check whether there is a future page to navigate forward to.\n    ///\n    /// If a [`History`] cannot know this, it should return [`true`].\n    ///\n    /// ```rust\n    /// # use dioxus::prelude::*;\n    /// # #[component]\n    /// # fn Index() -> Element { VNode::empty() }\n    /// # #[component]\n    /// # fn OtherPage() -> Element { VNode::empty() }\n    /// #[derive(Clone, Routable, Debug, PartialEq)]\n    /// enum Route {\n    ///     #[route(\"/\")]\n    ///     Index {},\n    ///     #[route(\"/some-other-page\")]\n    ///     OtherPage {},\n    /// }\n    /// let mut history = dioxus::history::MemoryHistory::default();\n    /// assert_eq!(history.can_go_forward(), false);\n    ///\n    /// history.push(Route::OtherPage {}.to_string());\n    /// assert_eq!(history.can_go_forward(), false);\n    ///\n    /// history.go_back();\n    /// assert_eq!(history.can_go_forward(), true);\n    /// ```\n    #[must_use]\n    fn can_go_forward(&self) -> bool {\n        true\n    }\n\n    /// Go forward to a future page.\n    ///\n    /// If a [`History`] cannot go to a previous page, it should do nothing. This method\n    /// might be called, even if `can_go_forward` returns [`false`].\n    ///\n    /// ```rust\n    /// # use dioxus::prelude::*;\n    /// # #[component]\n    /// # fn Index() -> Element { VNode::empty() }\n    /// # #[component]\n    /// # fn OtherPage() -> Element { VNode::empty() }\n    /// #[derive(Clone, Routable, Debug, PartialEq)]\n    /// enum Route {\n    ///     #[route(\"/\")]\n    ///     Index {},\n    ///     #[route(\"/some-other-page\")]\n    ///     OtherPage {},\n    /// }\n    /// let mut history = dioxus::history::MemoryHistory::default();\n    /// history.push(Route::OtherPage {}.to_string());\n    /// assert_eq!(history.current_route(), Route::OtherPage {}.to_string());\n    ///\n    /// history.go_back();\n    /// assert_eq!(history.current_route(), Route::Index {}.to_string());\n    ///\n    /// history.go_forward();\n    /// assert_eq!(history.current_route(), Route::OtherPage {}.to_string());\n    /// ```\n    fn go_forward(&self);\n\n    /// Go to another page.\n    ///\n    /// This should do three things:\n    /// 1. Merge the current URL with the `path` parameter (which may also include a query part).\n    /// 2. Remove the previous URL to the navigation history.\n    /// 3. Clear the navigation future.\n    ///\n    /// ```rust\n    /// # use dioxus::prelude::*;\n    /// # #[component]\n    /// # fn Index() -> Element { VNode::empty() }\n    /// # #[component]\n    /// # fn OtherPage() -> Element { VNode::empty() }\n    /// #[derive(Clone, Routable, Debug, PartialEq)]\n    /// enum Route {\n    ///     #[route(\"/\")]\n    ///     Index {},\n    ///     #[route(\"/some-other-page\")]\n    ///     OtherPage {},\n    /// }\n    /// let mut history = dioxus::history::MemoryHistory::default();\n    /// assert_eq!(history.current_route(), Route::Index {}.to_string());\n    ///\n    /// history.push(Route::OtherPage {}.to_string());\n    /// assert_eq!(history.current_route(), Route::OtherPage {}.to_string());\n    /// assert!(history.can_go_back());\n    /// ```\n    fn push(&self, route: String);\n\n    /// Replace the current page with another one.\n    ///\n    /// This should merge the current URL with the `path` parameter (which may also include a query\n    /// part). In contrast to the `push` function, the navigation history and future should stay\n    /// untouched.\n    ///\n    /// ```rust\n    /// # use dioxus::prelude::*;\n    /// # #[component]\n    /// # fn Index() -> Element { VNode::empty() }\n    /// # #[component]\n    /// # fn OtherPage() -> Element { VNode::empty() }\n    /// #[derive(Clone, Routable, Debug, PartialEq)]\n    /// enum Route {\n    ///     #[route(\"/\")]\n    ///     Index {},\n    ///     #[route(\"/some-other-page\")]\n    ///     OtherPage {},\n    /// }\n    /// let mut history = dioxus::history::MemoryHistory::default();\n    /// assert_eq!(history.current_route(), Route::Index {}.to_string());\n    ///\n    /// history.replace(Route::OtherPage {}.to_string());\n    /// assert_eq!(history.current_route(), Route::OtherPage {}.to_string());\n    /// assert!(!history.can_go_back());\n    /// ```\n    fn replace(&self, path: String);\n\n    /// Navigate to an external URL.\n    ///\n    /// This should navigate to an external URL, which isn't controlled by the router. If a\n    /// [`History`] cannot do that, it should return [`false`], otherwise [`true`].\n    ///\n    /// Returning [`false`] will cause the router to handle the external navigation failure.\n    #[allow(unused_variables)]\n    fn external(&self, url: String) -> bool {\n        false\n    }\n\n    /// Provide the [`History`] with an update callback.\n    ///\n    /// Some [`History`]s may receive URL updates from outside the router. When such\n    /// updates are received, they should call `callback`, which will cause the router to update.\n    #[allow(unused_variables)]\n    fn updater(&self, callback: Arc<dyn Fn() + Send + Sync>) {}\n\n    /// Whether the router should include the legacy prevent default attribute instead of the new\n    /// prevent default method. This should only be used by liveview.\n    fn include_prevent_default(&self) -> bool {\n        false\n    }\n}\n"
  },
  {
    "path": "packages/history/src/memory.rs",
    "content": "use std::cell::RefCell;\n\nuse crate::History;\n\nstruct MemoryHistoryState {\n    current: String,\n    history: Vec<String>,\n    future: Vec<String>,\n}\n\n/// A [`History`] provider that stores all navigation information in memory.\npub struct MemoryHistory {\n    state: RefCell<MemoryHistoryState>,\n    base_path: Option<String>,\n}\n\nimpl Default for MemoryHistory {\n    fn default() -> Self {\n        Self::with_initial_path(\"/\")\n    }\n}\n\nimpl MemoryHistory {\n    /// Create a [`MemoryHistory`] starting at `path`.\n    ///\n    /// ```rust\n    /// # use dioxus::prelude::*;\n    /// # #[component]\n    /// # fn Index() -> Element { VNode::empty() }\n    /// # #[component]\n    /// # fn OtherPage() -> Element { VNode::empty() }\n    /// #[derive(Clone, Routable, Debug, PartialEq)]\n    /// enum Route {\n    ///     #[route(\"/\")]\n    ///     Index {},\n    ///     #[route(\"/some-other-page\")]\n    ///     OtherPage {},\n    /// }\n    ///\n    /// let mut history = dioxus_history::MemoryHistory::with_initial_path(Route::Index {});\n    /// assert_eq!(history.current_route(), Route::Index {}.to_string());\n    /// assert_eq!(history.can_go_back(), false);\n    /// ```\n    pub fn with_initial_path(path: impl ToString) -> Self {\n        Self {\n            state: MemoryHistoryState{\n                current: path.to_string().parse().unwrap_or_else(|err| {\n                    panic!(\"index route does not exist:\\n{err}\\n use MemoryHistory::with_initial_path to set a custom path\")\n                }),\n                history: Vec::new(),\n                future: Vec::new(),\n            }.into(),\n            base_path: None,\n        }\n    }\n\n    /// Set the base path for the history. All routes will be prefixed with this path when rendered.\n    ///\n    /// ```rust\n    /// # use dioxus_history::*;\n    /// let mut history = MemoryHistory::default().with_prefix(\"/my-app\");\n    ///\n    /// // The base path is set to \"/my-app\"\n    /// assert_eq!(history.current_prefix(), Some(\"/my-app\".to_string()));\n    /// ```\n    pub fn with_prefix(mut self, prefix: impl ToString) -> Self {\n        self.base_path = Some(prefix.to_string());\n        self\n    }\n}\n\nimpl History for MemoryHistory {\n    fn current_prefix(&self) -> Option<String> {\n        self.base_path.clone()\n    }\n\n    fn current_route(&self) -> String {\n        self.state.borrow().current.clone()\n    }\n\n    fn can_go_back(&self) -> bool {\n        !self.state.borrow().history.is_empty()\n    }\n\n    fn go_back(&self) {\n        let mut write = self.state.borrow_mut();\n        if let Some(last) = write.history.pop() {\n            let old = std::mem::replace(&mut write.current, last);\n            write.future.push(old);\n        }\n    }\n\n    fn can_go_forward(&self) -> bool {\n        !self.state.borrow().future.is_empty()\n    }\n\n    fn go_forward(&self) {\n        let mut write = self.state.borrow_mut();\n        if let Some(next) = write.future.pop() {\n            let old = std::mem::replace(&mut write.current, next);\n            write.history.push(old);\n        }\n    }\n\n    fn push(&self, new: String) {\n        let mut write = self.state.borrow_mut();\n        // don't push the same route twice\n        if write.current == new {\n            return;\n        }\n        let old = std::mem::replace(&mut write.current, new);\n        write.history.push(old);\n        write.future.clear();\n    }\n\n    fn replace(&self, path: String) {\n        let mut write = self.state.borrow_mut();\n        write.current = path;\n    }\n}\n"
  },
  {
    "path": "packages/hooks/Cargo.toml",
    "content": "[package]\nname = \"dioxus-hooks\"\nversion = { workspace = true }\nauthors = [\"Jonathan Kelley\"]\nedition = \"2021\"\ndescription = \"Basic useful hooks for Dioxus.\"\nlicense = \"MIT OR Apache-2.0\"\nrepository = \"https://github.com/DioxusLabs/dioxus/\"\nhomepage = \"https://dioxuslabs.com\"\nkeywords = [\"dom\", \"ui\", \"gui\", \"react\"]\n\n[features]\ndefault = []\nnightly-features = []\n\n[dependencies]\ndioxus-core = { workspace = true }\ndioxus-signals = { workspace = true }\nfutures-channel = { workspace = true }\ntracing = { workspace = true }\nslab = { workspace = true }\nfutures-util = { workspace = true, features = [\"std\"] }\ngenerational-box = { workspace = true }\nrustversion = { workspace = true }\n\n[dev-dependencies]\nfutures-util = { workspace = true, default-features = false }\ndioxus-core = { workspace = true }\ndioxus = { workspace = true }\nweb-sys = { workspace = true, features = [\"Document\", \"Window\", \"Element\"] }\ntokio = { workspace = true, features = [\"full\"] }\nreqwest = { workspace = true }\n\n[package.metadata.docs.rs]\ncargo-args = [\"-Zunstable-options\", \"-Zrustdoc-scrape-examples\"]\n"
  },
  {
    "path": "packages/hooks/README.md",
    "content": "# Dioxus Hooks\n\n[![Crates.io][crates-badge]][crates-url]\n[![MIT licensed][mit-badge]][mit-url]\n[![Build Status][actions-badge]][actions-url]\n[![Discord chat][discord-badge]][discord-url]\n\n[crates-badge]: https://img.shields.io/crates/v/dioxus-hooks.svg\n[crates-url]: https://crates.io/crates/dioxus-hooks\n[mit-badge]: https://img.shields.io/badge/license-MIT-blue.svg\n[mit-url]: https://github.com/dioxuslabs/dioxus/blob/main/LICENSE-MIT\n[actions-badge]: https://github.com/dioxuslabs/dioxus/actions/workflows/main.yml/badge.svg\n[actions-url]: https://github.com/dioxuslabs/dioxus/actions?query=workflow%3ACI+branch%3Amaster\n[discord-badge]: https://img.shields.io/discord/899851952891002890.svg?logo=discord&style=flat-square\n[discord-url]: https://discord.gg/XgGxMSkvUM\n\n[Website](https://dioxuslabs.com) |\n[Guides](https://dioxuslabs.com/learn/0.7/) |\n[API Docs](https://docs.rs/dioxus-hooks/latest/dioxus_hooks) |\n[Chat](https://discord.gg/XgGxMSkvUM)\n\n## Overview\n\n`dioxus-hooks` includes some basic useful hooks for Dioxus such as:\n\n- use_signal\n- use_effect\n- use_resource\n- use_memo\n- use_coroutine\n\nUnlike React, none of these hooks are foundational since they all build off the primitive `use_hook`. You can extend these hooks with [custom hooks](https://dioxuslabs.com/learn/0.7/essentials/advanced/custom_hooks) in your own code. If you think they would be useful for the broader community, you can open a PR to add your hook to the [Dioxus Awesome](https://github.com/DioxusLabs/awesome-dioxus) list.\n\n## State Cheat Sheet\n\nIf you aren't sure what hook to use, you can use this cheat sheet to help you decide:\n\n### State Location\n\nDepending on where you need to access the state, you can put your state in one of three places:\n\n| Location                                                                                 | Where can you access the state? | Recommended for Libraries? | Examples                                                                    |\n| ---------------------------------------------------------------------------------------- | ------------------------------- | -------------------------- | --------------------------------------------------------------------------- |\n| [Hooks](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_signal.html)             | Any components you pass it to   | ✅                          | `use_signal(\\|\\| 0)`, `use_memo(\\|\\| state() * 2)`                          |\n| [Context](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_context_provider.html) | Any child components            | ✅                          | `use_context_provider(\\|\\| Signal::new(0))`, `use_context::<Signal<i32>>()` |\n| [Global](https://docs.rs/dioxus/latest/dioxus/prelude/struct.Signal.html#method.global)  | Anything in your app            | ❌                          | `Signal::global(\\|\\| 0)`                                                    |\n\n### Derived State\n\nIf you don't have an initial value for your state, you can derive your state from other states with a closure or asynchronous function:\n\n| Hook                                                                                | Reactive (reruns when dependencies change) | Async | Memorizes Output | Example                                                                             |\n| ----------------------------------------------------------------------------------- | ------------------------------------------ | ----- | ---------------- | ----------------------------------------------------------------------------------- |\n| [`use_memo`](https://docs.rs/dioxus/latest/dioxus/prelude/fn.use_memo.html)         | ✅                                          | ❌     | ✅                | `use_memo(move \\|\\| count() * 2)`                                                   |\n| [`use_resource`](https://docs.rs/dioxus/latest/dioxus/prelude/fn.use_resource.html) | ✅                                          | ✅     | ❌                | `use_resource(move \\|\\| reqwest::get(format!(\"/users/{user_id}\")))`                 |\n| [`use_future`](https://docs.rs/dioxus/latest/dioxus/prelude/fn.use_future.html)     | ❌                                          | ✅     | ❌                | `use_future(move \\|\\| println!(\"{:?}\", reqwest::get(format!(\"/users/{user_id}\"))))` |\n\n### Persistent State\n\nThe core hooks library doesn't provide hooks for persistent state, but you can extend the core hooks with hooks from [dioxus-sdk](https://crates.io/crates/dioxus-sdk) and the [dioxus-router](https://crates.io/crates/dioxus-router) to provide persistent state management.\n\n| State                                                                              | Sharable | Example                                                                                           |\n| ---------------------------------------------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------- |\n| [`use_persistent`](https://github.com/DioxusLabs/sdk/tree/master/examples/storage) | ❌        | `use_persistent(\"unique_key\", move \\|\\| initial_state)`                                           |\n| [`Router<Route> {}`](https://dioxuslabs.com/learn/0.7/essentials/router/)          | ✅        | `#[derive(Routable, Clone, PartialEq)] enum Route { #[route(\"/user/:id\")] Homepage { id: u32 } }` |\n\n## Contributing\n\n- Report issues on our [issue tracker](https://github.com/dioxuslabs/dioxus/issues).\n- Join the discord and ask questions!\n\n## License\n\nThis project is licensed under the [MIT license].\n\n[mit license]: https://github.com/dioxuslabs/dioxus/blob/main/LICENSE-MIT\n\nUnless you explicitly state otherwise, any contribution intentionally submitted\nfor inclusion in Dioxus by you shall be licensed as MIT without any additional\nterms or conditions.\n"
  },
  {
    "path": "packages/hooks/docs/derived_state.md",
    "content": "Creates a new Memo. The memo will be run immediately and whenever any signal it reads is written to. Memos can be used to efficiently compute derived data from signals.\n\n```rust\nuse dioxus::prelude::*;\nuse dioxus_signals::*;\n\nfn App() -> Element {\n    let mut count = use_signal(|| 0);\n    // the double memo will always be equal to two times the value of count, even after count changes\n    let double = use_memo(move || count * 2);\n\n    rsx! {\n        \"{double}\"\n        button {\n            // When count changes, the memo will rerun and double will be updated\n            // memos rerun any time you write to a signal they read. They will only rerun values/component that depend on them if the value of the memo changes\n            onclick: move |_| count += 1,\n            \"Increment\"\n        }\n    }\n}\n```\n\nThe closure you pass into memos will be called whenever the state you read inside the memo is written to even if the value hasn't actually changed, but the memo you get will not rerun other parts of your app unless the output changes (`PartialEq` returns false).\n\nLets dig into some examples to see how this works:\n\n```rust, no_run\n# use dioxus::prelude::*;\nlet mut count = use_signal(|| 1);\n// double_count will rerun when state we read inside the memo changes (count)\nlet double_count = use_memo(move || count() * 2);\n\n// memos act a lot like a read only version of a signal. You can read them, display them, and move them around like any other signal\nprintln!(\"{}\", double_count); // Prints \"2\"\n\n// But you can't write to them directly\n// Instead, any time you write to a value the memo reads, the memo will rerun\ncount += 1;\n\nprintln!(\"{}\", double_count); // Prints \"4\"\n\n// Lets create another memo that reads the value of double_count\nlet double_count_plus_one = use_memo(move || double_count() + 1);\n\nprintln!(\"{}\", double_count_plus_one); // Prints \"5\"\n\n// Now if we write to count the double_count memo will rerun\n// If that the output of double_count changes, then it will cause double_count_plus_one to rerun\ncount += 1;\n\nprintln!(\"{}\", double_count); // Prints \"6\"\nprintln!(\"{}\", double_count_plus_one); // Prints \"7\"\n\n// However if the value of double_count doesn't change after a write, then it won't trigger double_count_plus_one to rerun\n// Since we just write the same value, the doubled value is still 6 and we don't rerun double_count_plus_one\n*count.write() = 3;\n\nprintln!(\"{}\", double_count); // Prints \"6\"\nprintln!(\"{}\", double_count_plus_one); // Prints \"7\"\n```\n\n## With non-reactive dependencies\n\nTo add non-reactive dependencies, you can use the [`crate::use_reactive()`] hook.\n\nSignals will automatically be added as dependencies, so you don't need to call this method for them.\n\n```rust\n# use dioxus::prelude::*;\n#[component]\nfn Comp(count: u32) -> Element {\n// Because the memo subscribes to `count` by adding it as a dependency, the memo will rerun every time `count` changes.\nlet new_count = use_memo(use_reactive((&count,), |(count,)| count + 1));\n    todo!()\n}\n```\n"
  },
  {
    "path": "packages/hooks/docs/moving_state_around.md",
    "content": "<details>\n<summary>How should I move around state? (Click here to learn more about moving around and sharing state in Dioxus)</summary>\n\nYou will often need to move state around between your components. Dioxus provides three different ways to pass around state:\n\n1. Just pass your values as [props](https://dioxuslabs.com/learn/0.7/essentials/ui/components#component-properties):\n\n```rust\n# use dioxus::prelude::*;\nfn MyComponent() -> Element {\n    let count = use_signal(|| 0);\n\n    rsx! {\n        IncrementButton {\n            count\n        }\n    }\n}\n\n#[component]\nfn IncrementButton(mut count: Signal<i32>) -> Element {\n    rsx! {\n        button {\n            onclick: move |_| count += 1,\n            \"Increment\"\n        }\n    }\n}\n```\n\nThis is the most common way to pass state around. It is the most explicit and local to your component. Use this when it isn't overly annoying to pass around a value.\n\n2. Use [use_context](https://dioxuslabs.com/learn/0.7/essentials/basics/context) to pass state from a parent component to all children:\n\n```rust\n# use dioxus::prelude::*;\n#[derive(Clone, Copy)]\nstruct MyState {\n    count: Signal<i32>\n}\n\nfn ParentComponent() -> Element {\n    // Use context provider provides an unique type to all children of this component\n    use_context_provider(|| MyState { count: Signal::new(0) });\n\n    rsx! {\n        // IncrementButton will have access to the count without explicitly passing it through props\n        IncrementButton {}\n    }\n}\n\n#[component]\nfn IncrementButton() -> Element {\n    // Use context gets the value from a parent component\n    let mut count = use_context::<MyState>().count;\n\n    rsx! {\n        button {\n            onclick: move |_| count += 1,\n            \"Increment\"\n        }\n    }\n}\n```\n\nThis is slightly less explicit than passing it as a prop, but it is still local to the component. This is really great if you want state that is global to part of your app. It lets you create multiple global-ish states while still making state different when you reuse components. If I create a new `ParentComponent`, it will have a new `MyState`.\n\n3. Globals let you share state with your whole app with rust statics:\n\n```rust\n# use dioxus::prelude::*;\n// Count will be created the first time you access it with the closure you pass to Signal::global\nstatic COUNT: GlobalSignal<i32> = Signal::global(|| 0);\n\nfn ParentComponent() -> Element {\n    rsx! {\n        IncrementButton {}\n    }\n}\n\nfn IncrementButton() -> Element {\n    rsx! {\n        button {\n            // You don't need to pass anything around or get anything out of the context because COUNT is global\n            onclick: move |_| *COUNT.write() += 1,\n            \"Increment\"\n        }\n    }\n}\n```\n\nGlobal state can be very ergonomic if your state is truly global, but you shouldn't use it if you need state to be different for different instances of your component. If I create another `IncrementButton` it will use the same `COUNT`. Libraries should generally avoid this to make components more reusable.\n\n> Note: Even though it is in a static, `COUNT` will be different for each app instance (this is generally only reliant on the server).\n\n</details>\n"
  },
  {
    "path": "packages/hooks/docs/rules_of_hooks.md",
    "content": "## Additional Information that may be useful\n\n<details>\n<summary>This function is a hook which means you need to <b>follow the rules of hooks</b> when you call it. You can click here to learn more about the rules of hooks.</summary>\n\nHooks in dioxus need to run in the same order every time you run the component. To make sure you run hooks in a consistent order, you should follow the rules of hooks:\n\n1. Hooks should only be called from the root of a component or another hook\n\n```rust\n# use dioxus::prelude::*;\nfn App() -> Element {\n    // ✅ You can call hooks from the body of a component\n    let number = use_signal(|| 1);\n    if number() == 1 {\n        // ❌ You can run into issues if you can hooks inside other expressions inside your component\n        // If number changes from 0 to 1, the order of the hooks will be different and your app may panic\n        let string = use_signal(|| \"hello world\".to_string());\n    }\n\n    todo!()\n}\n\nfn use_my_hook() -> Signal<i32> {\n    // ✅ You can call hooks from the body of other hooks\n    let number = use_signal(|| 1);\n    // ❌ Again, creating hooks inside expressions inside other hooks can cause issues\n    if number() == 1 {\n        let string = use_signal(|| \"hello world\".to_string());\n    }\n\n    number\n}\n```\n\n2. Hooks should always start with `use_` to make it clear that you need to call them in a consistent order\n\nBecause hooks should only be called from the root of a component or another hook, you shouldn't call hooks inside of:\n\n- ❌ Conditionals\n\n```rust\n# use dioxus::prelude::*;\nfn App() -> Element {\n    let number = use_signal(|| 1);\n    // ❌ Changing the condition will change the order of the hooks\n    if number() == 1 {\n        let string = use_signal(|| \"hello world\".to_string());\n    }\n\n    // ❌ Changing the value you are matching will change the order of the hooks\n    match number() {\n        1 => {\n            let string = use_signal(|| \"hello world\".to_string());\n        },\n        _ => (),\n    }\n\n    todo!()\n}\n```\n\n- ❌ Loops\n\n```rust\n# use dioxus::prelude::*;\nfn App() -> Element {\n    let number = use_signal(|| 1);\n    // ❌ Changing the loop will change the order of the hooks\n    for i in 0..number() {\n        let string = use_signal(|| \"hello world\".to_string());\n    }\n\n    todo!()\n}\n```\n\n- ❌ Event Handlers\n\n```rust\n# use dioxus::prelude::*;\nfn App() -> Element {\n    rsx! {\n        button {\n            onclick: move |_| {\n                // ❌ Calling the event handler will change the order of the hooks\n                use_signal(|| \"hello world\".to_string());\n            },\n            \"Click me\"\n        }\n    }\n}\n```\n\n- ❌ Initialization closures in other hooks\n\n```rust\n# use dioxus::prelude::*;\nfn App() -> Element {\n    let number = use_signal(|| {\n        // ❌ This closure will only be called when the component is first created. Running the component will change the order of the hooks\n        let string = use_signal(|| \"hello world\".to_string());\n        string()\n    });\n\n    todo!()\n}\n```\n\n<details>\n<summary>Why do hooks need to run in a consistent order?</summary>\n\nHooks need to be run in a consistent order because dioxus stores hooks in a list and uses the order you run hooks in to determine what part of the state belongs to which hook.\n\nLets look at an example component:\n\n```rust\n# use dioxus::prelude::*;\nfn App() -> Element {\n    let number = use_signal(|| 1); // Hook 1\n    let string = use_signal(|| \"hello world\".to_string()); // Hook 2\n    let doubled = use_memo(move || number() * 2); // Hook 3\n\n    todo!()\n}\n```\n\nWhen we first create the component, we run the hooks in the order they are defined and store the state in the component in a list.\n\n```rust, ignore\n[\n    Box::new(1),\n    Box::new(\"hello world\".to_string()),\n    Box::new(2),\n]\n```\n\nNext time we run the component, we return items from the state list instead of creating state again.\n\n```rust, ignore\n[\n    Box::new(1), // Hook 1 returns 1\n    Box::new(\"hello world\".to_string()), // Hook 2 returns \"hello world\"\n    Box::new(2), // Hook 3 returns 2\n]\n```\n\nThe order the hooks are run it must be the same because the order determines which hook gets what state! If you run the hooks in a different order, dioxus may panic because it can't turn the state back into the right type or you may just get the wrong state for your hook.\n\n</details>\n\n</details>\n"
  },
  {
    "path": "packages/hooks/docs/side_effects.md",
    "content": "Effects are reactive closures that run **after the component has finished rendering**. Effects are useful for things like manually updating the DOM after it is rendered with web-sys or javascript. Or reading a value from the rendered DOM.\n\n**Effects are specifically created for side effects. If you are trying to derive state, use a [memo](#derived-state), or [resource](#derived-async-state) instead.**\n\nIf you are trying to update the DOM, you can use the [`use_effect`](https://docs.rs/dioxus/latest/dioxus/prelude/fn.use_effect.html) hook to run an effect after the component has finished rendering.\n\n`use_effect` will subscribe to any changes in the signal values it captures effects will always run after first mount and then whenever the signal values change. If the use_effect call was skipped due to an early return, the effect will no longer activate.\n\n```rust\n# use dioxus::prelude::*;\nfn MyComponent() -> Element {\n    let mut count = use_signal(|| 0);\n\n    use_effect(move || {\n        // Effects are reactive like memos, and resources. If you read a value inside the effect, the effect will rerun when that value changes\n        let count = count.read();\n\n        // You can use the count value to update the DOM manually\n        document::eval(&format!(\n            r#\"var c = document.getElementById(\"dioxus-canvas\");\nvar ctx = c.getContext(\"2d\");\nctx.font = \"30px Arial\";\nctx.fillText(\"{count}\", 10, 50);\"#\n        ));\n    });\n\n    rsx! {\n        button {\n            // When you click the button, count will be incremented and the effect will rerun\n            onclick: move |_| count += 1,\n            \"Increment\"\n        }\n        canvas {\n            id: \"dioxus-canvas\",\n        }\n    }\n}\n```\n\n## With non-reactive dependencies\n\nTo add non-reactive dependencies, you can use the [`crate::use_reactive()`] hook.\n\nSignals will automatically be added as dependencies, so you don't need to call this method for them.\n\n```rust\n# use dioxus::prelude::*;\n# async fn sleep(delay: u32) {}\n\n#[component]\nfn Comp(count: u32) -> Element {\n    // Because the memo subscribes to `count` by adding it as a dependency, the memo will rerun every time `count` changes.\n    use_effect(use_reactive((&count,), |(count,)| println!(\"Manually manipulate the dom\") ));\n\n    todo!()\n}\n```\n\n## Modifying mounted nodes\n\nOne of the most common use cases for effects is modifying or reading something from the rendered DOM. Dioxus provides access to the DOM with the [`onmounted`](https://docs.rs/dioxus/latest/dioxus/events/fn.onmounted.html) event.\n\nYou can combine `use_effect` with `onmounted` to run an effect with access to a DOM element after all rendering is finished:\n\n```rust\n# use dioxus::prelude::*;\nfn MyComponent() -> Element {\n    let mut current_text = use_signal(String::new);\n    let mut mounted_text_div: Signal<Option<MountedEvent>> = use_signal(|| None);\n    let mut rendered_size = use_signal(String::new);\n\n    use_effect(move || {\n        // If we have mounted the text div, we can read the width of the div\n        if let Some(div) = mounted_text_div() {\n            // We read the current text here inside of the effect instead of the spawn so the effect subscribes to the signal\n            let text = current_text();\n            spawn(async move {\n                let bounding_box = div.get_client_rect().await;\n                rendered_size.set(format!(\"{text} is {bounding_box:?}\"));\n            });\n        }\n    });\n\n    rsx! {\n        input {\n            // When you enter text into the input, the effect will rerun because it subscribes to the current_text signal\n            oninput: move |evt| current_text.set(evt.value()),\n            placeholder: \"Enter text here\",\n            value: \"{current_text}\"\n        }\n        // When text changes, it will change the size of this div\n        div {\n            onmounted: move |element| {\n                mounted_text_div.set(Some(element.clone()));\n            },\n            \"{current_text}\"\n        }\n\n        \"{rendered_size}\"\n    }\n}\n```\n"
  },
  {
    "path": "packages/hooks/docs/use_resource.md",
    "content": "[`use_resource()`] is a reactive hook that resolves to the result of a future. It will rerun when you write to any signals you read inside the future.\n\n## Example\n\n```rust\nuse dioxus::prelude::*;\n\nasync fn get_weather(location: &WeatherLocation) -> Result<String, String> {\n    Ok(\"Sunny\".to_string())\n}\n\nfn app() -> Element {\n    let country = use_signal(|| WeatherLocation {\n        city: \"Berlin\".to_string(),\n        country: \"Germany\".to_string(),\n        coordinates: (52.5244, 13.4105),\n    });\n\n    // Because the resource's future subscribes to `country` by reading it (`country.read()`),\n    // every time `country` changes the resource's future will run again and thus provide a new value.\n    let current_weather = use_resource(move || async move { get_weather(&country()).await });\n\n    rsx! {\n        // the value of the resource can be polled to\n        // conditionally render elements based off if it's future\n        // finished (Some(Ok(_)), errored Some(Err(_)),\n        // or is still running (None)\n        match &*current_weather.read_unchecked() {\n            Some(Ok(weather)) => rsx! { WeatherElement { weather } },\n            Some(Err(e)) => rsx! { p { \"Loading weather failed, {e}\" } },\n            None =>  rsx! { p { \"Loading...\" } }\n        }\n    }\n}\n\n#[derive(Clone)]\nstruct WeatherLocation {\n    city: String,\n    country: String,\n    coordinates: (f64, f64),\n}\n\n#[component]\nfn WeatherElement(weather: String) -> Element {\n    rsx! { p { \"The weather is {weather}\" } }\n}\n```\n\n## Reactivity\n\n`use_resource` is reactive which just means that it will rerun when you write to any signals you read inside the future. This means that any time you change something the future depends on, the resource automatically knows to rerun. Lets take a look at some examples:\n\n```rust, no_run\n# use dioxus::prelude::*;\n// Create a new count signal\nlet mut count = use_signal(|| 1);\n// Create a new resource that doubles the value of count\nlet double_count = use_resource(move || async move {\n    // Start a request to the server. We are reading the value of count in the format macro\n    // Reading the value of count makes the resource \"subscribe\" to changes to count (when count changes, the resource will rerun)\n    let response = reqwest::get(format!(\"https://myserver.com/doubleme?count={count}\")).await.unwrap();\n    response.text().await.unwrap()\n});\n\n// Resource can be read in a way that is similar to signals, but they have a bit of extra information about the state of the resource future.\n\n// Calling .state() on a resource will return a Signal<UseResourceState> with information about the current status of the resource\nprintln!(\"{:?}\", double_count.state().read()); // Prints \"UseResourceState::Pending\"\n\n// You can also try to get the last resolved value of the resource with the .value() method\nprintln!(\"{:?}\", double_count.read()); // Prints \"None\"\n\n// Wait for the resource to finish and get the value\nstd::thread::sleep(std::time::Duration::from_secs(1));\n\n// Now if we read the state, we will see that it is done\nprintln!(\"{:?}\", double_count.state().read()); // Prints \"UseResourceState::Done\"\n\n// And we can get the value\nprintln!(\"{:?}\", double_count.read()); // Prints \"Some(2)\"\n\n// Now if we write to count, the resource will rerun\ncount += 1; // count is now 2\n\n// Wait for the resource to finish and get the value\nstd::thread::sleep(std::time::Duration::from_secs(1));\n\n// Now if we read the state, we will see that it is done\nprintln!(\"{:?}\", double_count.state().read()); // Prints \"UseResourceState::Done\"\n\n// And we can get the value\nprintln!(\"{:?}\", double_count.read()); // Prints \"Some(4)\"\n\n// One more case, what happens if we write to the resource while it is in progress?\n// The resource will rerun and the value will be None\ncount += 1; // count is now 3\n\n// If we write to a value the resource subscribes to again, it will cancel the current future and start a new one\ncount += 1; // count is now 4\n\nprintln!(\"{:?}\", double_count.state().read()); // Prints \"UseResourceState::Stopped\"\nprintln!(\"{:?}\", double_count.read()); // Prints the last resolved value \"Some(4)\"\n\n// After we wait for the resource to finish, we will get the value of only the latest future\nstd::thread::sleep(std::time::Duration::from_secs(1));\n\nprintln!(\"{:?}\", double_count.state().read()); // Prints \"UseResourceState::Done\"\n\nprintln!(\"{:?}\", double_count.read()); // Prints \"Some(8)\"\n```\n\n## With non-reactive dependencies\n\n`use_resource` can determine dependencies automatically with any reactive value ([`Signal`]s, [`ReadSignal`]s, [`Memo`]s, [`Resource`]s, etc). If you need to rerun the future when a normal rust value changes, you can add it as a dependency with the [`crate::use_reactive()`] hook:\n\n```rust\n# use dioxus::prelude::*;\n# async fn sleep(delay: u32) {}\n#[component]\nfn Comp(count: u32) -> Element {\n    // We manually add the resource to the dependencies list with the `use_reactive` hook\n    // Any time `count` changes, the resource will rerun\n    let new_count = use_resource(use_reactive!(|(count,)| async move {\n        sleep(100).await;\n        count + 1\n    }));\n    rsx! { \"{new_count:?}\" }\n}\n\n// If your value is already reactive, you never need to call `use_reactive` manually\n// Instead of manually adding count to the dependencies list, you can make your prop reactive by wrapping it in `ReadSignal`\n#[component]\nfn ReactiveComp(count: ReadSignal<u32>) -> Element {\n    // Because `count` is reactive, the resource knows to rerun when `count` changes automatically\n    let new_count = use_resource(move || async move {\n        sleep(100).await;\n        count() + 1\n    });\n    rsx! { \"{new_count:?}\" }\n}\n```\n\n## Differences from `use_future` and `use_memo`\n\nJust like [`crate::use_future()`], `use_resource` spawns an async task in a component. However, unlike [`crate::use_future()`], `use_resource` returns the result of the future and will rerun when any dependencies change.\n\nResources return a value based on some existing state just like memos, but unlike memos, resources do not memorize the output of the closure. They will always rerun any parts of your app that read the value of the resource when the future resolves even if the output doesn't change.\n\nSee also: [`Resource`]\n"
  },
  {
    "path": "packages/hooks/src/lib.rs",
    "content": "#![doc = include_str!(\"../README.md\")]\n#![doc(html_logo_url = \"https://avatars.githubusercontent.com/u/79236386\")]\n#![doc(html_favicon_url = \"https://avatars.githubusercontent.com/u/79236386\")]\n\n#[macro_export]\n/// A helper macro for cloning multiple values at once\n///\n/// # Usage\n///\n///\n/// ```\n/// # use dioxus::prelude::*;\n/// #\n/// # #[derive(Props, PartialEq, Clone)]\n/// # struct Props {\n/// #    prop: String,\n/// # }\n/// # fn Component(props: Props) -> Element {\n///\n/// let (data) = use_signal(|| {});\n///\n/// let handle_thing = move |_| {\n///     to_owned![data, props.prop];\n///     spawn(async move {\n///         // do stuff\n///     });\n/// };\n/// # handle_thing(());\n/// # VNode::empty() }\n/// ```\nmacro_rules! to_owned {\n    // Rule matching simple symbols without a path\n    ($es:ident $(, $($rest:tt)*)?) => {\n        #[allow(unused_mut)]\n        let mut $es = $es.to_owned();\n        $( to_owned![$($rest)*] )?\n    };\n\n    // We need to find the last element in a path, for this we need to unstack the path part by\n    // part using, separating what we have with a '@'\n    ($($deref:ident).* $(, $($rest:tt)*)?) => {\n        to_owned![@ $($deref).* $(, $($rest)*)?]\n    };\n\n    // Take the head of the path and add it to the list of $deref\n    ($($deref:ident)* @ $head:ident $( . $tail:ident)+ $(, $($rest:tt)*)?) => {\n        to_owned![$($deref)* $head @ $($tail).+ $(, $($rest)*)?]\n    };\n    // We have exhausted the path, use the last as a name\n    ($($deref:ident)* @ $last:ident $(, $($rest:tt)*)? ) => {\n        #[allow(unused_mut)]\n        let mut $last = $($deref .)* $last .to_owned();\n        $(to_owned![$($rest)*])?\n    };\n}\n\nmod use_callback;\npub use use_callback::*;\n\nmod use_on_destroy;\npub use use_on_destroy::*;\n\nmod use_context;\npub use use_context::*;\n\nmod use_coroutine;\npub use use_coroutine::*;\n\nmod use_future;\npub use use_future::*;\n\nmod use_reactive;\npub use use_reactive::*;\n\n// mod use_sorted;\n// pub use use_sorted::*;\n\nmod use_resource;\npub use use_resource::*;\n\nmod use_effect;\npub use use_effect::*;\n\nmod use_memo;\npub use use_memo::*;\n\nmod use_root_context;\npub use use_root_context::*;\n\nmod use_hook_did_run;\npub use use_hook_did_run::*;\n\nmod use_signal;\npub use use_signal::*;\n\nmod use_set_compare;\npub use use_set_compare::*;\n\nmod use_after_suspense_resolved;\npub use use_after_suspense_resolved::*;\n\nmod use_action;\npub use use_action::*;\n\nmod use_waker;\npub use use_waker::*;\n"
  },
  {
    "path": "packages/hooks/src/use_action.rs",
    "content": "use crate::{use_callback, use_signal};\nuse dioxus_core::{use_hook, Callback, CapturedError, Result, Task};\nuse dioxus_signals::{ReadSignal, ReadableBoxExt, ReadableExt, Signal, WritableExt};\nuse futures_channel::oneshot::Receiver;\nuse futures_util::{future::Shared, FutureExt};\nuse std::{marker::PhantomData, pin::Pin, prelude::rust_2024::Future, task::Poll};\n\npub fn use_action<E, C, M>(mut user_fn: C) -> Action<C::Input, C::Output>\nwhere\n    E: Into<CapturedError> + 'static,\n    C: ActionCallback<M, E>,\n    M: 'static,\n    C::Input: 'static,\n    C::Output: 'static,\n    C: 'static,\n{\n    let mut value = use_signal(|| None as Option<C::Output>);\n    let mut error = use_signal(|| None as Option<CapturedError>);\n    let mut task = use_signal(|| None as Option<Task>);\n    let mut state = use_signal(|| ActionState::Unset);\n    let callback = use_callback(move |input: C::Input| {\n        // Cancel any existing task\n        if let Some(task) = task.take() {\n            task.cancel();\n        }\n\n        let (tx, rx) = futures_channel::oneshot::channel();\n        let rx = rx.shared();\n\n        // Spawn a new task, and *then* fire off the async\n        let result = user_fn.call(input);\n        let new_task = dioxus_core::spawn(async move {\n            // Set the state to pending\n            state.set(ActionState::Pending);\n\n            // Create a new task\n            let result = result.await;\n            match result {\n                Ok(res) => {\n                    error.set(None);\n                    value.set(Some(res));\n                    state.set(ActionState::Ready);\n                }\n                Err(err) => {\n                    error.set(Some(err.into()));\n                    value.set(None);\n                    state.set(ActionState::Errored);\n                }\n            }\n\n            tx.send(()).ok();\n        });\n\n        task.set(Some(new_task));\n\n        rx\n    });\n\n    // Create a reader that maps the Option<T> to T, unwrapping the Option\n    // This should only be handed out if we know the value is Some. We never set the value back to None, only modify the state of the action\n    let reader = use_hook(|| value.boxed().map(|v| v.as_ref().unwrap()).boxed());\n\n    Action {\n        value,\n        error,\n        task,\n        callback,\n        reader,\n        _phantom: PhantomData,\n        state,\n    }\n}\n\npub struct Action<I, T: 'static> {\n    reader: ReadSignal<T>,\n    error: Signal<Option<CapturedError>>,\n    value: Signal<Option<T>>,\n    task: Signal<Option<Task>>,\n    callback: Callback<I, Shared<Receiver<()>>>,\n    state: Signal<ActionState>,\n    _phantom: PhantomData<*const I>,\n}\n\nimpl<I: 'static, O: 'static> Action<I, O> {\n    pub fn value(&self) -> Option<Result<ReadSignal<O>, CapturedError>> {\n        if !matches!(\n            *self.state.read(),\n            ActionState::Ready | ActionState::Errored\n        ) {\n            return None;\n        }\n\n        if let Some(err) = self.error.cloned() {\n            return Some(Err(err));\n        }\n\n        if self.value.read().is_none() {\n            return None;\n        }\n\n        Some(Ok(self.reader))\n    }\n\n    pub fn pending(&self) -> bool {\n        *self.state.read() == ActionState::Pending\n    }\n\n    /// Clear the current value and error, setting the state to Reset\n    pub fn reset(&mut self) {\n        self.state.set(ActionState::Reset);\n        if let Some(t) = self.task.take() {\n            t.cancel()\n        }\n    }\n\n    pub fn cancel(&mut self) {\n        if let Some(t) = self.task.take() {\n            t.cancel()\n        }\n        self.state.set(ActionState::Reset);\n    }\n}\n\nimpl<I, T> std::fmt::Debug for Action<I, T>\nwhere\n    T: std::fmt::Debug + 'static,\n{\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        if f.alternate() {\n            f.debug_struct(\"Action\")\n                .field(\"state\", &self.state.read())\n                .field(\"value\", &self.value.read())\n                .field(\"error\", &self.error.read())\n                .finish()\n        } else {\n            std::fmt::Debug::fmt(&self.value.read().as_ref(), f)\n        }\n    }\n}\npub struct Dispatching<I> {\n    _phantom: PhantomData<*const I>,\n    receiver: Shared<Receiver<()>>,\n}\n\nimpl<T> Dispatching<T> {\n    pub(crate) fn new(receiver: Shared<Receiver<()>>) -> Self {\n        Self {\n            _phantom: PhantomData,\n            receiver,\n        }\n    }\n}\n\nimpl<T> std::future::Future for Dispatching<T> {\n    type Output = ();\n\n    fn poll(mut self: Pin<&mut Self>, _cx: &mut std::task::Context<'_>) -> Poll<Self::Output> {\n        match self.receiver.poll_unpin(_cx) {\n            Poll::Ready(_) => Poll::Ready(()),\n            Poll::Pending => Poll::Pending,\n        }\n    }\n}\n\nimpl<I, T> Copy for Action<I, T> {}\nimpl<I, T> Clone for Action<I, T> {\n    fn clone(&self) -> Self {\n        *self\n    }\n}\n\n/// The state of an action\n///\n/// We can never reset the state to Unset, only to Reset, otherwise the value reader would panic.\n#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]\nenum ActionState {\n    Unset,\n    Pending,\n    Ready,\n    Errored,\n    Reset,\n}\n\npub trait ActionCallback<M, E> {\n    type Input;\n    type Output;\n    fn call(\n        &mut self,\n        input: Self::Input,\n    ) -> impl Future<Output = Result<Self::Output, E>> + 'static;\n}\n\nimpl<F, O, G, E> ActionCallback<(O,), E> for F\nwhere\n    F: FnMut() -> G,\n    G: Future<Output = Result<O, E>> + 'static,\n{\n    type Input = ();\n    type Output = O;\n    fn call(\n        &mut self,\n        _input: Self::Input,\n    ) -> impl Future<Output = Result<Self::Output, E>> + 'static {\n        (self)()\n    }\n}\n\nimpl<F, O, A, G, E> ActionCallback<(A, O), E> for F\nwhere\n    F: FnMut(A) -> G,\n    G: Future<Output = Result<O, E>> + 'static,\n{\n    type Input = (A,);\n    type Output = O;\n    fn call(\n        &mut self,\n        input: Self::Input,\n    ) -> impl Future<Output = Result<Self::Output, E>> + 'static {\n        let (a,) = input;\n        (self)(a)\n    }\n}\n\nimpl<O, A, B, F, G, E> ActionCallback<(A, B, O), E> for F\nwhere\n    F: FnMut(A, B) -> G,\n    G: Future<Output = Result<O, E>> + 'static,\n{\n    type Input = (A, B);\n    type Output = O;\n    fn call(\n        &mut self,\n        input: Self::Input,\n    ) -> impl Future<Output = Result<Self::Output, E>> + 'static {\n        let (a, b) = input;\n        (self)(a, b)\n    }\n}\n\nimpl<O, A, B, C, F, G, E> ActionCallback<(A, B, C, O), E> for F\nwhere\n    F: FnMut(A, B, C) -> G,\n    G: Future<Output = Result<O, E>> + 'static,\n{\n    type Input = (A, B, C);\n    type Output = O;\n    fn call(\n        &mut self,\n        input: Self::Input,\n    ) -> impl Future<Output = Result<Self::Output, E>> + 'static {\n        let (a, b, c) = input;\n        (self)(a, b, c)\n    }\n}\n\nimpl<O> Action<(), O> {\n    pub fn call(&mut self) -> Dispatching<()> {\n        Dispatching::new((self.callback).call(()))\n    }\n}\n\nimpl<A: 'static, O> Action<(A,), O> {\n    pub fn call(&mut self, _a: A) -> Dispatching<()> {\n        Dispatching::new((self.callback).call((_a,)))\n    }\n}\n\nimpl<A: 'static, B: 'static, O> Action<(A, B), O> {\n    pub fn call(&mut self, _a: A, _b: B) -> Dispatching<()> {\n        Dispatching::new((self.callback).call((_a, _b)))\n    }\n}\n\nimpl<A: 'static, B: 'static, C: 'static, O> Action<(A, B, C), O> {\n    pub fn call(&mut self, _a: A, _b: B, _c: C) -> Dispatching<()> {\n        Dispatching::new((self.callback).call((_a, _b, _c)))\n    }\n}\n"
  },
  {
    "path": "packages/hooks/src/use_after_suspense_resolved.rs",
    "content": "use dioxus_core::{use_hook, Runtime};\n\n/// Run a closure after the suspense boundary this is under is resolved. The\n/// closure will be run immediately if the suspense boundary is already resolved\n/// or the scope is not under a suspense boundary.\npub fn use_after_suspense_resolved(suspense_resolved: impl FnOnce() + 'static) {\n    use_hook(|| {\n        // If this is under a suspense boundary, we need to check if it is resolved\n        match Runtime::current().suspense_context() {\n            Some(context) => {\n                // If it is suspended, run the closure after the suspense is resolved\n                context.after_suspense_resolved(suspense_resolved)\n            }\n            None => {\n                // Otherwise, just run the resolved closure immediately\n                suspense_resolved();\n            }\n        }\n    })\n}\n"
  },
  {
    "path": "packages/hooks/src/use_callback.rs",
    "content": "use dioxus_core::{use_hook, Callback};\n\n/// Create a callback that's always up to date. Whenever this hook is called the inner callback will be replaced with the new callback but the handle will remain.\n///\n/// There is *currently* no signal tracking on the Callback so anything reading from it will not be updated.\n///\n/// This API is in flux and might not remain.\n#[doc = include_str!(\"../docs/rules_of_hooks.md\")]\npub fn use_callback<T: 'static, O: 'static>(f: impl FnMut(T) -> O + 'static) -> Callback<T, O> {\n    let mut callback = Some(f);\n\n    // Create a copyvalue with no contents\n    // This copyvalue is generic over F so that it can be sized properly\n    let mut inner = use_hook(|| {\n        Callback::new(\n            callback\n                .take()\n                .expect(\"Callback cannot be None on first call\"),\n        )\n    });\n\n    if let Some(callback) = callback.take() {\n        // Every time this hook is called replace the inner callback with the new callback\n        inner.replace(Box::new(callback));\n    }\n\n    inner\n}\n"
  },
  {
    "path": "packages/hooks/src/use_collection.rs",
    "content": "/*\na form of use_signal explicitly for map-style collections (BTreeMap, HashMap, etc).\n\nWhy?\n---\nTraditionally, it's possible to use the \"use_state\" hook for collections in the React world.\nAdding a new entry would look something similar to:\n\n```js\nlet (map, set_map) = useState({});\nset_map({ ...map, [key]: value });\n```\nThe new value then causes the appropriate update when passed into children.\n\nThis is moderately efficient because the fields of the map are moved, but the data itself is not cloned.\nHowever, if you used similar approach with Dioxus:\n\n```rust\nlet (map, set_map) = use_signal(|| HashMap::new());\nset_map({\n    let mut newmap = map.clone();\n    newmap.set(key, value);\n    newmap\n})\n```\nUnfortunately, you'd be cloning the entire state every time a value is changed. The obvious solution is to\nwrap every element in the HashMap with an Rc. That way, cloning the HashMap is on par with its JS equivalent.\n\nFortunately, we can make this operation even more efficient in Dioxus, leveraging the borrow rules of Rust.\n\nThis hook provides a memoized collection, memoized setters, and memoized getters. This particular hook is\nextremely powerful for implementing lists and supporting core state management needs for small apps.\n\nIf you need something even more powerful, check out the dedicated atomic state management Dioxus Dataflow, which\nuses the same memoization on top of the use_context API.\n\nHere's a fully-functional todo app using the use_map API:\n```rust\nstatic TodoList: Component = |cx| {\n    let todos = use_map(|| HashMap::new());\n    let input = use_signal(|| None);\n\n    rsx! {\n        div {\n            button {\n                \"Add todo\"\n                onclick: move |_| {\n                    let new_todo = TodoItem::new(input.contents());\n                    todos.insert(new_todo.id.clone(), new_todo);\n                    input.clear();\n                }\n            }\n            button {\n                \"Clear todos\"\n                onclick: move |_| todos.clear()\n            }\n            input {\n                placeholder: \"What needs to be done?\"\n                ref: input\n            }\n            ul {\n                {todos.iter().map(|todo| rsx!(\n                    li {\n                        key: todo.id\n                        span { \"{todo.content}\" }\n                        button {\"x\", onclick: move |_| todos.remove(todo.key.clone())}\n                    }\n                ))}\n            }\n        }\n    })\n}\n\n```\n\n*/\n"
  },
  {
    "path": "packages/hooks/src/use_context.rs",
    "content": "use dioxus_core::{consume_context, provide_context, try_consume_context, use_hook};\n\n/// Consume some context in the tree, providing a sharable handle to the value\n///\n/// Does not regenerate the value if the value is changed at the parent.\n#[doc = include_str!(\"../docs/rules_of_hooks.md\")]\n#[doc = include_str!(\"../docs/moving_state_around.md\")]\n#[must_use]\npub fn try_use_context<T: 'static + Clone>() -> Option<T> {\n    use_hook(|| try_consume_context::<T>())\n}\n\n/// Consume some context in the tree, providing a sharable handle to the value\n///\n/// Does not regenerate the value if the value is changed at the parent.\n/// ```rust\n/// # use dioxus::prelude::*;\n/// # #[derive(Clone, Copy, PartialEq, Debug)]\n/// # enum Theme { Dark, Light }\n/// fn Parent() -> Element {\n///     use_context_provider(|| Theme::Dark);\n///     rsx! { Child {} }\n/// }\n/// #[component]\n/// fn Child() -> Element {\n///     //gets context provided by parent element with use_context_provider\n///     let user_theme = use_context::<Theme>();\n///     rsx! { \"user using dark mode: {user_theme == Theme::Dark}\" }\n/// }\n/// ```\n#[doc = include_str!(\"../docs/rules_of_hooks.md\")]\n#[doc = include_str!(\"../docs/moving_state_around.md\")]\n#[must_use]\npub fn use_context<T: 'static + Clone>() -> T {\n    use_hook(|| consume_context::<T>())\n}\n\n/// Provide some context via the tree and return a reference to it\n///\n/// Once the context has been provided, it is immutable. Mutations should be done via interior mutability.\n/// Context can be read by any child components of the context provider, and is a solution to prop\n/// drilling, using a context provider with a Signal inside is a good way to provide global/shared\n/// state in your app:\n/// ```rust\n/// # use dioxus::prelude::*;\n///fn app() -> Element {\n///    use_context_provider(|| Signal::new(0));\n///    rsx! { Child {} }\n///}\n/// // This component does read from the signal, so when the signal changes it will rerun\n///#[component]\n///fn Child() -> Element {\n///     let mut signal: Signal<i32> = use_context();\n///     rsx! {\n///         button { onclick: move |_| signal += 1, \"increment context\" }\n///         p {\"{signal}\"}\n///     }\n///}\n/// ```\n#[doc = include_str!(\"../docs/rules_of_hooks.md\")]\n#[doc = include_str!(\"../docs/moving_state_around.md\")]\npub fn use_context_provider<T: 'static + Clone>(f: impl FnOnce() -> T) -> T {\n    use_hook(|| provide_context(f()))\n}\n"
  },
  {
    "path": "packages/hooks/src/use_coroutine.rs",
    "content": "use crate::{use_context_provider, use_future, UseFuture};\nuse dioxus_core::Task;\nuse dioxus_core::{consume_context, use_hook};\nuse dioxus_signals::*;\npub use futures_channel::mpsc::{UnboundedReceiver, UnboundedSender};\nuse std::future::Future;\n\n/// Maintain a handle over a future that can be paused, resumed, and canceled.\n///\n/// This is an upgraded form of [`crate::use_future()`] with an integrated channel system.\n/// Specifically, the coroutine generated here comes with an [`futures_channel::mpsc::UnboundedSender`]\n/// built into it - saving you the hassle of building your own.\n///\n/// Additionally, coroutines are automatically injected as shared contexts, so\n/// downstream components can tap into a coroutine's channel and send messages\n/// into a singular async event loop.\n///\n/// This makes it effective for apps that need to interact with an event loop or\n/// some asynchronous code without thinking too hard about state.\n///\n/// ## Global State\n///\n/// Typically, writing apps that handle concurrency properly can be difficult,\n/// so the intention of this hook is to make it easy to join and poll async tasks\n/// concurrently in a centralized place. You'll find that you can have much better\n/// control over your app's state if you centralize your async actions, even under\n/// the same concurrent context. This makes it easier to prevent undeseriable\n/// states in your UI while various async tasks are already running.\n///\n/// This hook is especially powerful when combined with Fermi. We can store important\n/// global data in a coroutine, and then access display-level values from the rest\n/// of our app through atoms.\n///\n/// ## UseCallback instead\n///\n/// However, you must plan out your own concurrency and synchronization. If you\n/// don't care about actions in your app being synchronized, you can use [`crate::use_callback()`]\n/// hook to spawn multiple tasks and run them concurrently.\n///\n/// ### Notice\n/// In order to use ``rx.next().await``, you will need to extend the ``Stream`` trait (used by ``UnboundedReceiver``)\n/// by adding the ``futures-util`` crate as a dependency and adding ``StreamExt`` into scope via ``use futures_util::stream::StreamExt;``\n///\n/// ## Example\n///\n/// ```rust, no_run\n/// # use dioxus::prelude::*;\n/// use futures_util::StreamExt;\n/// enum Action {\n///     Start,\n///     Stop,\n/// }\n///\n/// let chat_client = use_coroutine(|mut rx: UnboundedReceiver<Action>| async move {\n///     while let Some(action) = rx.next().await {\n///         match action {\n///             Action::Start => {}\n///             Action::Stop => {},\n///         }\n///     }\n/// });\n///\n///\n/// rsx! {\n///     button {\n///         onclick: move |_| chat_client.send(Action::Start),\n///         \"Start Chat Service\"\n///     }\n/// };\n/// ```\n#[doc = include_str!(\"../docs/rules_of_hooks.md\")]\npub fn use_coroutine<M, G, F>(mut init: G) -> Coroutine<M>\nwhere\n    M: 'static,\n    G: FnMut(UnboundedReceiver<M>) -> F + 'static,\n    F: Future<Output = ()> + 'static,\n{\n    let mut tx_copy_value = use_hook(|| CopyValue::new(None));\n\n    let future = use_future(move || {\n        let (tx, rx) = futures_channel::mpsc::unbounded();\n        tx_copy_value.set(Some(tx));\n        init(rx)\n    });\n\n    use_context_provider(|| Coroutine {\n        tx: tx_copy_value,\n        future,\n    })\n}\n\n/// Get a handle to a coroutine higher in the tree\n/// Analogous to use_context_provider and use_context,\n/// but used for coroutines specifically\n/// See the docs for [`use_coroutine`] for more details.\n#[doc = include_str!(\"../docs/rules_of_hooks.md\")]\n#[must_use]\npub fn use_coroutine_handle<M: 'static>() -> Coroutine<M> {\n    use_hook(consume_context::<Coroutine<M>>)\n}\n\npub struct Coroutine<T: 'static> {\n    tx: CopyValue<Option<UnboundedSender<T>>>,\n    future: UseFuture,\n}\n\nimpl<T> Coroutine<T> {\n    /// Get the underlying task handle\n    pub fn task(&self) -> Task {\n        self.future.task()\n    }\n\n    /// Send a message to the coroutine\n    pub fn send(&self, msg: T) {\n        let _ = self.tx.read().as_ref().unwrap().unbounded_send(msg);\n    }\n\n    pub fn tx(&self) -> UnboundedSender<T> {\n        self.tx.read().as_ref().unwrap().clone()\n    }\n\n    /// Restart this coroutine\n    pub fn restart(&mut self) {\n        self.future.restart();\n    }\n}\n\n// manual impl since deriving doesn't work with generics\nimpl<T> Copy for Coroutine<T> {}\n\nimpl<T> Clone for Coroutine<T> {\n    fn clone(&self) -> Self {\n        *self\n    }\n}\n\nimpl<T> PartialEq for Coroutine<T> {\n    fn eq(&self, other: &Self) -> bool {\n        self.tx == other.tx && self.future == other.future\n    }\n}\n"
  },
  {
    "path": "packages/hooks/src/use_effect.rs",
    "content": "use std::{cell::Cell, rc::Rc};\n\nuse dioxus_core::*;\nuse futures_util::StreamExt;\n\nuse crate::use_callback;\n\n#[doc = include_str!(\"../docs/side_effects.md\")]\n#[doc = include_str!(\"../docs/rules_of_hooks.md\")]\n#[track_caller]\npub fn use_effect(mut callback: impl FnMut() + 'static) -> Effect {\n    let callback = use_callback(move |_| callback());\n\n    let location = std::panic::Location::caller();\n\n    use_hook(|| {\n        // Inside the effect, we track any reads so that we can rerun the effect if a value the effect reads changes\n        let (rc, mut changed) = ReactiveContext::new_with_origin(location);\n\n        // Deduplicate queued effects\n        let effect_queued = Rc::new(Cell::new(false));\n\n        // Spawn a task that will run the effect when:\n        // 1) The component is first run\n        // 2) The effect is rerun due to an async read at any time\n        // 3) The effect is rerun in the same tick that the component is rerun: we need to wait for the component to rerun before we can run the effect again\n        let queue_effect_for_next_render = move || {\n            if effect_queued.get() {\n                return;\n            }\n            effect_queued.set(true);\n            let effect_queued = effect_queued.clone();\n            queue_effect(move || {\n                rc.reset_and_run_in(|| callback(()));\n                effect_queued.set(false);\n            });\n        };\n\n        queue_effect_for_next_render();\n        spawn(async move {\n            loop {\n                // Wait for context to change\n                let _ = changed.next().await;\n\n                // Run the effect\n                queue_effect_for_next_render();\n            }\n        });\n        Effect { rc }\n    })\n}\n\n/// A handle to an effect.\n#[derive(Clone, Copy)]\npub struct Effect {\n    rc: ReactiveContext,\n}\n\nimpl Effect {\n    /// Marks the effect as dirty, causing it to rerun on the next render.\n    pub fn mark_dirty(&mut self) {\n        self.rc.mark_dirty();\n    }\n}\n"
  },
  {
    "path": "packages/hooks/src/use_future.rs",
    "content": "#![allow(missing_docs)]\nuse crate::{use_callback, use_hook_did_run, use_signal};\nuse dioxus_core::{use_hook, Callback, Subscribers, Task};\nuse dioxus_signals::*;\nuse std::future::Future;\nuse std::ops::Deref;\n\n/// A hook that allows you to spawn a future the first time you render a component.\n///\n///\n/// This future will **not** run on the server. To run a future on the server, you should use [`dioxus_core::spawn_isomorphic`] directly.\n///\n///\n/// `use_future` **won't return a value**. If you want to return a value from a future, use [`crate::use_resource()`] instead.\n///\n/// ## Example\n///\n/// ```rust\n/// # use dioxus::prelude::*;\n/// # use std::time::Duration;\n/// fn app() -> Element {\n///     let mut count = use_signal(|| 0);\n///     let mut running = use_signal(|| true);\n///     // `use_future` will spawn an infinitely running future that can be started and stopped\n///     use_future(move || async move {\n///         loop {\n///            if running() {\n///                count += 1;\n///            }\n///            tokio::time::sleep(Duration::from_millis(400)).await;\n///        }\n///     });\n///     rsx! {\n///         div {\n///             h1 { \"Current count: {count}\" }\n///             button { onclick: move |_| running.toggle(), \"Start/Stop the count\"}\n///             button { onclick: move |_| count.set(0), \"Reset the count\" }\n///         }\n///     }\n/// }\n/// ```\n#[doc = include_str!(\"../docs/rules_of_hooks.md\")]\n#[doc = include_str!(\"../docs/moving_state_around.md\")]\n#[doc(alias = \"use_async\")]\npub fn use_future<F>(mut future: impl FnMut() -> F + 'static) -> UseFuture\nwhere\n    F: Future + 'static,\n{\n    let mut state = use_signal(|| UseFutureState::Pending);\n\n    let callback = use_callback(move |_| {\n        let fut = future();\n        dioxus_core::spawn(async move {\n            state.set(UseFutureState::Pending);\n            fut.await;\n            state.set(UseFutureState::Ready);\n        })\n    });\n\n    // Create the task inside a CopyValue so we can reset it in-place later\n    let task = use_hook(|| CopyValue::new(callback(())));\n\n    // Early returns in dioxus have consequences for use_memo, use_resource, and use_future, etc\n    // We *don't* want futures to be running if the component early returns. It's a rather weird behavior to have\n    // use_memo running in the background even if the component isn't hitting those hooks anymore.\n    //\n    // React solves this by simply not having early returns interleave with hooks.\n    // However, since dioxus allows early returns (since we use them for suspense), we need to solve this problem\n    use_hook_did_run(move |did_run| match did_run {\n        true => task.peek().resume(),\n        false => task.peek().pause(),\n    });\n\n    UseFuture {\n        task,\n        state,\n        callback,\n    }\n}\n\n#[derive(Clone, Copy, PartialEq)]\npub struct UseFuture {\n    task: CopyValue<Task>,\n    state: Signal<UseFutureState>,\n    callback: Callback<(), Task>,\n}\n\n/// A signal that represents the state of a future\n// we might add more states (panicked, etc)\n#[derive(Clone, Copy, PartialEq, Hash, Eq, Debug)]\npub enum UseFutureState {\n    /// The future is still running\n    Pending,\n\n    /// The future has been forcefully stopped\n    Stopped,\n\n    /// The future has been paused, tempoarily\n    Paused,\n\n    /// The future has completed\n    Ready,\n}\n\nimpl UseFuture {\n    /// Restart the future with new dependencies.\n    ///\n    /// Will not cancel the previous future, but will ignore any values that it\n    /// generates.\n    pub fn restart(&mut self) {\n        self.task.write().cancel();\n        let new_task = self.callback.call(());\n        self.task.set(new_task);\n    }\n\n    /// Forcefully cancel a future\n    pub fn cancel(&mut self) {\n        self.state.set(UseFutureState::Stopped);\n        self.task.write().cancel();\n    }\n\n    /// Pause the future\n    pub fn pause(&mut self) {\n        self.state.set(UseFutureState::Paused);\n        self.task.write().pause();\n    }\n\n    /// Resume the future\n    pub fn resume(&mut self) {\n        if self.finished() {\n            return;\n        }\n\n        self.state.set(UseFutureState::Pending);\n        self.task.write().resume();\n    }\n\n    /// Get a handle to the inner task backing this future\n    /// Modify the task through this handle will cause inconsistent state\n    pub fn task(&self) -> Task {\n        self.task.cloned()\n    }\n\n    /// Is the future currently finished running?\n    ///\n    /// Reading this does not subscribe to the future's state\n    pub fn finished(&self) -> bool {\n        matches!(\n            *self.state.peek(),\n            UseFutureState::Ready | UseFutureState::Stopped\n        )\n    }\n\n    /// Get the current state of the future.\n    pub fn state(&self) -> ReadSignal<UseFutureState> {\n        self.state.into()\n    }\n}\n\nimpl From<UseFuture> for ReadSignal<UseFutureState> {\n    fn from(val: UseFuture) -> Self {\n        val.state.into()\n    }\n}\n\nimpl Readable for UseFuture {\n    type Target = UseFutureState;\n    type Storage = UnsyncStorage;\n\n    #[track_caller]\n    fn try_read_unchecked(\n        &self,\n    ) -> Result<ReadableRef<'static, Self>, generational_box::BorrowError> {\n        self.state.try_read_unchecked()\n    }\n\n    #[track_caller]\n    fn try_peek_unchecked(\n        &self,\n    ) -> Result<ReadableRef<'static, Self>, generational_box::BorrowError> {\n        self.state.try_peek_unchecked()\n    }\n\n    fn subscribers(&self) -> Subscribers {\n        self.state.subscribers()\n    }\n}\n\n/// Allow calling a signal with signal() syntax\n///\n/// Currently only limited to copy types, though could probably specialize for string/arc/rc\nimpl Deref for UseFuture {\n    type Target = dyn Fn() -> UseFutureState;\n\n    fn deref(&self) -> &Self::Target {\n        unsafe { ReadableExt::deref_impl(self) }\n    }\n}\n"
  },
  {
    "path": "packages/hooks/src/use_hook_did_run.rs",
    "content": "use dioxus_core::{use_after_render, use_before_render, use_hook};\nuse dioxus_signals::{CopyValue, WritableExt};\n\n/// A utility lifecycle hook that is intended to be used inside other hooks to determine if the outer hook has ran this render.\n/// The provided callback is executed after each render.\n/// The value will only be true if the containing outer hook is executed.\n#[doc = include_str!(\"../docs/rules_of_hooks.md\")]\n#[doc = include_str!(\"../docs/moving_state_around.md\")]\npub fn use_hook_did_run(mut handler: impl FnMut(bool) + 'static) {\n    let mut did_run_ = use_hook(|| CopyValue::new(false));\n\n    // Before render always set the value to false\n    use_before_render(move || did_run_.set(false));\n\n    // Only when the outer hook is run do we want to set the value to true\n    did_run_.set(true);\n\n    // After render, we can check if the outer hook was run\n    use_after_render(move || handler(did_run_()));\n}\n"
  },
  {
    "path": "packages/hooks/src/use_memo.rs",
    "content": "use crate::use_callback;\nuse dioxus_core::use_hook;\nuse dioxus_signals::Memo;\n\n#[doc = include_str!(\"../docs/derived_state.md\")]\n#[doc = include_str!(\"../docs/rules_of_hooks.md\")]\n#[doc = include_str!(\"../docs/moving_state_around.md\")]\n#[track_caller]\npub fn use_memo<R: PartialEq + 'static>(mut f: impl FnMut() -> R + 'static) -> Memo<R> {\n    let callback = use_callback(move |_| f());\n    let caller = std::panic::Location::caller();\n    #[allow(clippy::redundant_closure)]\n    use_hook(|| Memo::new_with_location(move || callback(()), caller))\n}\n"
  },
  {
    "path": "packages/hooks/src/use_on_destroy.rs",
    "content": "use dioxus_core::use_drop;\n\n#[deprecated(note = \"Use `use_drop` instead, which has the same functionality.\")]\npub fn use_on_unmount<D: FnOnce() + 'static>(destroy: D) {\n    use_drop(destroy);\n}\n"
  },
  {
    "path": "packages/hooks/src/use_reactive.rs",
    "content": "use dioxus_signals::{ReadableExt, WritableExt};\n\nuse crate::use_signal;\n\n/// A dependency is a trait that can be used to determine if a effect or selector should be re-run.\n#[rustversion::attr(\n    since(1.78.0),\n    diagnostic::on_unimplemented(\n        message = \"`Dependency` is not implemented for `{Self}`\",\n        label = \"Dependency\",\n        note = \"Dependency is automatically implemented for all tuples with less than 8 references to element that implement `PartialEq` and `Clone`. For example, `(&A, &B, &C)` implements `Dependency` automatically as long as `A`, `B`, and `C` implement `PartialEq` and `Clone`.\",\n    )\n)]\npub trait Dependency: Sized + Clone {\n    /// The output of the dependency\n    type Out: Clone + PartialEq + 'static;\n    /// Returns the output of the dependency.\n    fn out(&self) -> Self::Out;\n    /// Returns true if the dependency has changed.\n    fn changed(&self, other: &Self::Out) -> bool {\n        self.out() != *other\n    }\n}\n\nimpl Dependency for () {\n    type Out = ();\n    fn out(&self) -> Self::Out {}\n}\n\n/// A dependency is a trait that can be used to determine if a effect or selector should be re-run.\n#[rustversion::attr(\n    since(1.78.0),\n    diagnostic::on_unimplemented(\n        message = \"`DependencyElement` is not implemented for `{Self}`\",\n        label = \"dependency element\",\n        note = \"DependencyElement is automatically implemented for types that implement `PartialEq` and `Clone`\",\n    )\n)]\npub trait DependencyElement: 'static + PartialEq + Clone {}\nimpl<T> DependencyElement for T where T: 'static + PartialEq + Clone {}\n\nimpl<A: DependencyElement> Dependency for &A {\n    type Out = A;\n    fn out(&self) -> Self::Out {\n        (*self).clone()\n    }\n}\n\nmacro_rules! impl_dep {\n    (\n        $($el:ident=$name:ident $other:ident,)*\n    ) => {\n        impl< $($el),* > Dependency for ($(&$el,)*)\n        where\n            $(\n                $el: DependencyElement\n            ),*\n        {\n            type Out = ($($el,)*);\n\n            fn out(&self) -> Self::Out {\n                let ($($name,)*) = self;\n                ($((*$name).clone(),)*)\n            }\n\n            fn changed(&self, other: &Self::Out) -> bool {\n                let ($($name,)*) = self;\n                let ($($other,)*) = other;\n                $(\n                    if *$name != $other {\n                        return true;\n                    }\n                )*\n                false\n            }\n        }\n    };\n}\n\nimpl_dep!(A = a1 a2,);\nimpl_dep!(A = a1 a2, B = b1 b2,);\nimpl_dep!(A = a1 a2, B = b1 b2, C = c1 c2,);\nimpl_dep!(A = a1 a2, B = b1 b2, C = c1 c2, D = d1 d2,);\nimpl_dep!(A = a1 a2, B = b1 b2, C = c1 c2, D = d1 d2, E = e1 e2,);\nimpl_dep!(A = a1 a2, B = b1 b2, C = c1 c2, D = d1 d2, E = e1 e2, F = f1 f2,);\nimpl_dep!(A = a1 a2, B = b1 b2, C = c1 c2, D = d1 d2, E = e1 e2, F = f1 f2, G = g1 g2,);\nimpl_dep!(A = a1 a2, B = b1 b2, C = c1 c2, D = d1 d2, E = e1 e2, F = f1 f2, G = g1 g2, H = h1 h2,);\n\n/// Takes some non-reactive data, and a closure and returns a closure that will subscribe to that non-reactive data as if it were reactive.\n///\n/// # Example\n///\n/// ```rust, no_run\n/// use dioxus::prelude::*;\n///\n/// let data = 5;\n///\n/// use_effect(use_reactive((&data,), |(data,)| {\n///     println!(\"Data changed: {}\", data);\n/// }));\n/// ```\n#[doc = include_str!(\"../docs/rules_of_hooks.md\")]\npub fn use_reactive<O, D: Dependency>(\n    non_reactive_data: D,\n    mut closure: impl FnMut(D::Out) -> O + 'static,\n) -> impl FnMut() -> O + 'static {\n    let mut first_run = false;\n    let mut last_state = use_signal(|| {\n        first_run = true;\n        non_reactive_data.out()\n    });\n    if !first_run && non_reactive_data.changed(&*last_state.peek()) {\n        last_state.set(non_reactive_data.out())\n    }\n    move || closure(last_state())\n}\n\n/// A helper macro for `use_reactive` that merges uses the closure syntax to elaborate the dependency array\n///\n/// Takes some non-reactive data, and a closure and returns a closure that will subscribe to that non-reactive data as if it were reactive.\n///\n/// # Example\n///\n/// ```rust, no_run\n/// use dioxus::prelude::*;\n///\n/// let data = 5;\n///\n/// use_effect(use_reactive!(|data| {\n///     println!(\"Data changed: {}\", data);\n/// }));\n/// ```\n#[doc = include_str!(\"../docs/rules_of_hooks.md\")]\n#[macro_export]\nmacro_rules! use_reactive {\n    (|| $($rest:tt)*) => { use_reactive( (), move |_| $($rest)* ) };\n    (| $($args:tt),* | $($rest:tt)*) => {\n        use_reactive(\n            ($(&$args),*),\n            move |($($args),*)| $($rest)*\n        )\n    };\n}\n"
  },
  {
    "path": "packages/hooks/src/use_resource.rs",
    "content": "#![allow(missing_docs)]\n\nuse crate::{use_callback, use_signal, use_waker, UseWaker};\n\nuse dioxus_core::{\n    spawn, use_hook, Callback, IntoAttributeValue, IntoDynNode, ReactiveContext, RenderError,\n    Subscribers, SuspendedFuture, Task,\n};\nuse dioxus_signals::*;\nuse futures_util::{\n    future::{self},\n    pin_mut, FutureExt, StreamExt,\n};\nuse std::{cell::Cell, future::Future, rc::Rc};\nuse std::{fmt::Debug, ops::Deref};\n\n#[doc = include_str!(\"../docs/use_resource.md\")]\n#[doc = include_str!(\"../docs/rules_of_hooks.md\")]\n#[doc = include_str!(\"../docs/moving_state_around.md\")]\n#[doc(alias = \"use_async_memo\")]\n#[doc(alias = \"use_memo_async\")]\n#[track_caller]\npub fn use_resource<T, F>(mut future: impl FnMut() -> F + 'static) -> Resource<T>\nwhere\n    T: 'static,\n    F: Future<Output = T> + 'static,\n{\n    let location = std::panic::Location::caller();\n\n    let mut value = use_signal(|| None);\n    let mut state = use_signal(|| UseResourceState::Pending);\n    let (rc, changed) = use_hook(|| {\n        let (rc, changed) = ReactiveContext::new_with_origin(location);\n        (rc, Rc::new(Cell::new(Some(changed))))\n    });\n\n    let mut waker = use_waker::<()>();\n\n    let cb = use_callback(move |_| {\n        // Set the state to Pending when the task is restarted\n        state.set(UseResourceState::Pending);\n\n        // Create the user's task\n        let fut = rc.reset_and_run_in(&mut future);\n\n        // Spawn a wrapper task that polls the inner future and watches its dependencies\n        spawn(async move {\n            // Move the future here and pin it so we can poll it\n            let fut = fut;\n            pin_mut!(fut);\n\n            // Run each poll in the context of the reactive scope\n            // This ensures the scope is properly subscribed to the future's dependencies\n            let res = future::poll_fn(|cx| {\n                rc.run_in(|| {\n                    tracing::trace_span!(\"polling resource\", location = %location)\n                        .in_scope(|| fut.poll_unpin(cx))\n                })\n            })\n            .await;\n\n            // Set the value and state\n            state.set(UseResourceState::Ready);\n            value.set(Some(res));\n\n            // Notify that the value has changed\n            waker.wake(());\n        })\n    });\n\n    let mut task = use_hook(|| Signal::new(cb(())));\n\n    use_hook(|| {\n        let mut changed = changed.take().unwrap();\n        spawn(async move {\n            loop {\n                // Wait for the dependencies to change\n                let _ = changed.next().await;\n\n                // Stop the old task\n                task.write().cancel();\n\n                // Start a new task\n                task.set(cb(()));\n            }\n        })\n    });\n\n    Resource {\n        task,\n        value,\n        state,\n        waker,\n        callback: cb,\n    }\n}\n\n/// A handle to a reactive future spawned with [`use_resource`] that can be used to modify or read the result of the future.\n///\n/// ## Example\n///\n/// Reading the result of a resource:\n/// ```rust, no_run\n/// # use dioxus::prelude::*;\n/// # use std::time::Duration;\n/// fn App() -> Element {\n///     let mut revision = use_signal(|| \"1d03b42\");\n///     let mut resource = use_resource(move || async move {\n///         // This will run every time the revision signal changes because we read the count inside the future\n///         reqwest::get(format!(\"https://github.com/DioxusLabs/awesome-dioxus/blob/{revision}/awesome.json\")).await\n///     });\n///\n///     // Since our resource may not be ready yet, the value is an Option. Our request may also fail, so the get function returns a Result\n///     // The complete type we need to match is `Option<Result<String, reqwest::Error>>`\n///     // We can use `read_unchecked` to keep our matching code in one statement while avoiding a temporary variable error (this is still completely safe because dioxus checks the borrows at runtime)\n///     match &*resource.read_unchecked() {\n///         Some(Ok(value)) => rsx! { \"{value:?}\" },\n///         Some(Err(err)) => rsx! { \"Error: {err}\" },\n///         None => rsx! { \"Loading...\" },\n///     }\n/// }\n/// ```\n#[derive(Debug)]\npub struct Resource<T: 'static> {\n    waker: UseWaker<()>,\n    value: Signal<Option<T>>,\n    task: Signal<Task>,\n    state: Signal<UseResourceState>,\n    callback: Callback<(), Task>,\n}\n\nimpl<T> PartialEq for Resource<T> {\n    fn eq(&self, other: &Self) -> bool {\n        self.value == other.value\n            && self.state == other.state\n            && self.task == other.task\n            && self.callback == other.callback\n    }\n}\n\nimpl<T> Clone for Resource<T> {\n    fn clone(&self) -> Self {\n        *self\n    }\n}\nimpl<T> Copy for Resource<T> {}\n\n/// A signal that represents the state of the resource\n// we might add more states (panicked, etc)\n#[derive(Clone, Copy, PartialEq, Hash, Eq, Debug)]\npub enum UseResourceState {\n    /// The resource's future is still running\n    Pending,\n\n    /// The resource's future has been forcefully stopped\n    Stopped,\n\n    /// The resource's future has been paused, tempoarily\n    Paused,\n\n    /// The resource's future has completed\n    Ready,\n}\n\nimpl<T> Resource<T> {\n    /// Restart the resource's future.\n    ///\n    /// This will cancel the current future and start a new one.\n    ///\n    /// ## Example\n    /// ```rust, no_run\n    /// # use dioxus::prelude::*;\n    /// # use std::time::Duration;\n    /// fn App() -> Element {\n    ///     let mut revision = use_signal(|| \"1d03b42\");\n    ///     let mut resource = use_resource(move || async move {\n    ///         // This will run every time the revision signal changes because we read the count inside the future\n    ///         reqwest::get(format!(\"https://github.com/DioxusLabs/awesome-dioxus/blob/{revision}/awesome.json\")).await\n    ///     });\n    ///\n    ///     rsx! {\n    ///         button {\n    ///             // We can get a signal with the value of the resource with the `value` method\n    ///             onclick: move |_| resource.restart(),\n    ///             \"Restart resource\"\n    ///         }\n    ///         \"{resource:?}\"\n    ///     }\n    /// }\n    /// ```\n    pub fn restart(&mut self) {\n        self.task.write().cancel();\n        let new_task = self.callback.call(());\n        self.task.set(new_task);\n    }\n\n    /// Forcefully cancel the resource's future.\n    ///\n    /// ## Example\n    /// ```rust, no_run\n    /// # use dioxus::prelude::*;\n    /// # use std::time::Duration;\n    /// fn App() -> Element {\n    ///     let mut revision = use_signal(|| \"1d03b42\");\n    ///     let mut resource = use_resource(move || async move {\n    ///         reqwest::get(format!(\"https://github.com/DioxusLabs/awesome-dioxus/blob/{revision}/awesome.json\")).await\n    ///     });\n    ///\n    ///     rsx! {\n    ///         button {\n    ///             // We can cancel the resource before it finishes with the `cancel` method\n    ///             onclick: move |_| resource.cancel(),\n    ///             \"Cancel resource\"\n    ///         }\n    ///         \"{resource:?}\"\n    ///     }\n    /// }\n    /// ```\n    pub fn cancel(&mut self) {\n        self.state.set(UseResourceState::Stopped);\n        self.task.write().cancel();\n    }\n\n    /// Pause the resource's future.\n    ///\n    /// ## Example\n    /// ```rust, no_run\n    /// # use dioxus::prelude::*;\n    /// # use std::time::Duration;\n    /// fn App() -> Element {\n    ///     let mut revision = use_signal(|| \"1d03b42\");\n    ///     let mut resource = use_resource(move || async move {\n    ///         // This will run every time the revision signal changes because we read the count inside the future\n    ///         reqwest::get(format!(\"https://github.com/DioxusLabs/awesome-dioxus/blob/{revision}/awesome.json\")).await\n    ///     });\n    ///\n    ///     rsx! {\n    ///         button {\n    ///             // We can pause the future with the `pause` method\n    ///             onclick: move |_| resource.pause(),\n    ///             \"Pause\"\n    ///         }\n    ///         button {\n    ///             // And resume it with the `resume` method\n    ///             onclick: move |_| resource.resume(),\n    ///             \"Resume\"\n    ///         }\n    ///         \"{resource:?}\"\n    ///     }\n    /// }\n    /// ```\n    pub fn pause(&mut self) {\n        self.state.set(UseResourceState::Paused);\n        self.task.write().pause();\n    }\n\n    /// Resume the resource's future.\n    ///\n    /// ## Example\n    /// ```rust, no_run\n    /// # use dioxus::prelude::*;\n    /// # use std::time::Duration;\n    /// fn App() -> Element {\n    ///     let mut revision = use_signal(|| \"1d03b42\");\n    ///     let mut resource = use_resource(move || async move {\n    ///         // This will run every time the revision signal changes because we read the count inside the future\n    ///         reqwest::get(format!(\"https://github.com/DioxusLabs/awesome-dioxus/blob/{revision}/awesome.json\")).await\n    ///     });\n    ///\n    ///     rsx! {\n    ///         button {\n    ///             // We can pause the future with the `pause` method\n    ///             onclick: move |_| resource.pause(),\n    ///             \"Pause\"\n    ///         }\n    ///         button {\n    ///             // And resume it with the `resume` method\n    ///             onclick: move |_| resource.resume(),\n    ///             \"Resume\"\n    ///         }\n    ///         \"{resource:?}\"\n    ///     }\n    /// }\n    /// ```\n    pub fn resume(&mut self) {\n        if self.finished() {\n            return;\n        }\n\n        self.state.set(UseResourceState::Pending);\n        self.task.write().resume();\n    }\n\n    /// Clear the resource's value. This will just reset the value. It will not modify any running tasks.\n    ///\n    /// ## Example\n    /// ```rust, no_run\n    /// # use dioxus::prelude::*;\n    /// # use std::time::Duration;\n    /// fn App() -> Element {\n    ///     let mut revision = use_signal(|| \"1d03b42\");\n    ///     let mut resource = use_resource(move || async move {\n    ///         // This will run every time the revision signal changes because we read the count inside the future\n    ///         reqwest::get(format!(\"https://github.com/DioxusLabs/awesome-dioxus/blob/{revision}/awesome.json\")).await\n    ///     });\n    ///\n    ///     rsx! {\n    ///         button {\n    ///             // We clear the value without modifying any running tasks with the `clear` method\n    ///             onclick: move |_| resource.clear(),\n    ///             \"Clear\"\n    ///         }\n    ///         \"{resource:?}\"\n    ///     }\n    /// }\n    /// ```\n    pub fn clear(&mut self) {\n        self.value.write().take();\n    }\n\n    /// Get a handle to the inner task backing this resource\n    /// Modify the task through this handle will cause inconsistent state\n    pub fn task(&self) -> Task {\n        self.task.cloned()\n    }\n\n    /// Is the resource's future currently running?\n    pub fn pending(&self) -> bool {\n        matches!(*self.state.peek(), UseResourceState::Pending)\n    }\n\n    /// Is the resource's future currently finished running?\n    ///\n    /// Reading this does not subscribe to the future's state\n    ///\n    /// ## Example\n    /// ```rust, no_run\n    /// # use dioxus::prelude::*;\n    /// # use std::time::Duration;\n    /// fn App() -> Element {\n    ///     let mut revision = use_signal(|| \"1d03b42\");\n    ///     let mut resource = use_resource(move || async move {\n    ///         // This will run every time the revision signal changes because we read the count inside the future\n    ///         reqwest::get(format!(\"https://github.com/DioxusLabs/awesome-dioxus/blob/{revision}/awesome.json\")).await\n    ///     });\n    ///\n    ///     // We can use the `finished` method to check if the future is finished\n    ///     if resource.finished() {\n    ///         rsx! {\n    ///             \"The resource is finished\"\n    ///         }\n    ///     } else {\n    ///         rsx! {\n    ///             \"The resource is still running\"\n    ///         }\n    ///     }\n    /// }\n    /// ```\n    pub fn finished(&self) -> bool {\n        matches!(\n            *self.state.peek(),\n            UseResourceState::Ready | UseResourceState::Stopped\n        )\n    }\n\n    /// Get the current state of the resource's future. This method returns a [`ReadSignal`] which can be read to get the current state of the resource or passed to other hooks and components.\n    ///\n    /// ## Example\n    /// ```rust, no_run\n    /// # use dioxus::prelude::*;\n    /// # use std::time::Duration;\n    /// fn App() -> Element {\n    ///     let mut revision = use_signal(|| \"1d03b42\");\n    ///     let mut resource = use_resource(move || async move {\n    ///         // This will run every time the revision signal changes because we read the count inside the future\n    ///         reqwest::get(format!(\"https://github.com/DioxusLabs/awesome-dioxus/blob/{revision}/awesome.json\")).await\n    ///     });\n    ///\n    ///     // We can read the current state of the future with the `state` method\n    ///     match resource.state().cloned() {\n    ///         UseResourceState::Pending => rsx! {\n    ///             \"The resource is still pending\"\n    ///         },\n    ///         UseResourceState::Paused => rsx! {\n    ///             \"The resource has been paused\"\n    ///         },\n    ///         UseResourceState::Stopped => rsx! {\n    ///             \"The resource has been stopped\"\n    ///         },\n    ///         UseResourceState::Ready => rsx! {\n    ///             \"The resource is ready!\"\n    ///         },\n    ///     }\n    /// }\n    /// ```\n    pub fn state(&self) -> ReadSignal<UseResourceState> {\n        self.state.into()\n    }\n\n    /// Get the current value of the resource's future.  This method returns a [`ReadSignal`] which can be read to get the current value of the resource or passed to other hooks and components.\n    ///\n    /// ## Example\n    ///\n    /// ```rust, no_run\n    /// # use dioxus::prelude::*;\n    /// # use std::time::Duration;\n    /// fn App() -> Element {\n    ///     let mut revision = use_signal(|| \"1d03b42\");\n    ///     let mut resource = use_resource(move || async move {\n    ///         // This will run every time the revision signal changes because we read the count inside the future\n    ///         reqwest::get(format!(\"https://github.com/DioxusLabs/awesome-dioxus/blob/{revision}/awesome.json\")).await\n    ///     });\n    ///\n    ///     // We can get a signal with the value of the resource with the `value` method\n    ///     let value = resource.value();\n    ///\n    ///     // Since our resource may not be ready yet, the value is an Option. Our request may also fail, so the get function returns a Result\n    ///     // The complete type we need to match is `Option<Result<String, reqwest::Error>>`\n    ///     // We can use `read_unchecked` to keep our matching code in one statement while avoiding a temporary variable error (this is still completely safe because dioxus checks the borrows at runtime)\n    ///     match &*value.read_unchecked() {\n    ///         Some(Ok(value)) => rsx! { \"{value:?}\" },\n    ///         Some(Err(err)) => rsx! { \"Error: {err}\" },\n    ///         None => rsx! { \"Loading...\" },\n    ///     }\n    /// }\n    /// ```\n    pub fn value(&self) -> ReadSignal<Option<T>> {\n        self.value.into()\n    }\n\n    /// Suspend the resource's future and only continue rendering when the future is ready\n    pub fn suspend(&self) -> std::result::Result<MappedSignal<T, Signal<Option<T>>>, RenderError> {\n        match self.state.cloned() {\n            UseResourceState::Stopped | UseResourceState::Paused | UseResourceState::Pending => {\n                let task = self.task();\n                if task.paused() {\n                    Ok(self.value.map(|v| v.as_ref().unwrap()))\n                } else {\n                    Err(RenderError::Suspended(SuspendedFuture::new(task)))\n                }\n            }\n            _ => Ok(self.value.map(|v| v.as_ref().unwrap())),\n        }\n    }\n}\n\nimpl<T, E> Resource<Result<T, E>> {\n    /// Convert the `Resource<Result<T, E>>` into an `Option<Result<MappedSignal<T>, MappedSignal<E>>>`\n    #[allow(clippy::type_complexity)]\n    pub fn result(\n        &self,\n    ) -> Option<\n        Result<\n            MappedSignal<T, Signal<Option<Result<T, E>>>>,\n            MappedSignal<E, Signal<Option<Result<T, E>>>>,\n        >,\n    > {\n        let value: MappedSignal<T, Signal<Option<Result<T, E>>>> = self.value.map(|v| match v {\n            Some(Ok(ref res)) => res,\n            _ => panic!(\"Resource is not ready\"),\n        });\n\n        let error: MappedSignal<E, Signal<Option<Result<T, E>>>> = self.value.map(|v| match v {\n            Some(Err(ref err)) => err,\n            _ => panic!(\"Resource is not ready\"),\n        });\n\n        match &*self.value.peek() {\n            Some(Ok(_)) => Some(Ok(value)),\n            Some(Err(_)) => Some(Err(error)),\n            None => None,\n        }\n    }\n}\n\nimpl<T> From<Resource<T>> for ReadSignal<Option<T>> {\n    fn from(val: Resource<T>) -> Self {\n        val.value.into()\n    }\n}\n\nimpl<T> Readable for Resource<T> {\n    type Target = Option<T>;\n    type Storage = UnsyncStorage;\n\n    #[track_caller]\n    fn try_read_unchecked(\n        &self,\n    ) -> Result<ReadableRef<'static, Self>, generational_box::BorrowError> {\n        self.value.try_read_unchecked()\n    }\n\n    #[track_caller]\n    fn try_peek_unchecked(\n        &self,\n    ) -> Result<ReadableRef<'static, Self>, generational_box::BorrowError> {\n        self.value.try_peek_unchecked()\n    }\n\n    fn subscribers(&self) -> Subscribers {\n        self.value.subscribers()\n    }\n}\n\nimpl<T> Writable for Resource<T> {\n    type WriteMetadata = <Signal<Option<T>> as Writable>::WriteMetadata;\n\n    fn try_write_unchecked(\n        &self,\n    ) -> Result<WritableRef<'static, Self>, generational_box::BorrowMutError>\n    where\n        Self::Target: 'static,\n    {\n        self.value.try_write_unchecked()\n    }\n}\n\nimpl<T> IntoAttributeValue for Resource<T>\nwhere\n    T: Clone + IntoAttributeValue,\n{\n    fn into_value(self) -> dioxus_core::AttributeValue {\n        self.with(|f| f.clone().into_value())\n    }\n}\n\nimpl<T> IntoDynNode for Resource<T>\nwhere\n    T: Clone + IntoDynNode,\n{\n    fn into_dyn_node(self) -> dioxus_core::DynamicNode {\n        self().into_dyn_node()\n    }\n}\n\n/// Allow calling a signal with signal() syntax\n///\n/// Currently only limited to copy types, though could probably specialize for string/arc/rc\nimpl<T: Clone> Deref for Resource<T> {\n    type Target = dyn Fn() -> Option<T>;\n\n    fn deref(&self) -> &Self::Target {\n        unsafe { ReadableExt::deref_impl(self) }\n    }\n}\n\nimpl<T> std::future::Future for Resource<T> {\n    type Output = ();\n\n    fn poll(\n        self: std::pin::Pin<&mut Self>,\n        cx: &mut std::task::Context<'_>,\n    ) -> std::task::Poll<Self::Output> {\n        match self.waker.clone().poll_unpin(cx) {\n            std::task::Poll::Ready(_) => std::task::Poll::Ready(()),\n            std::task::Poll::Pending => std::task::Poll::Pending,\n        }\n    }\n}\n"
  },
  {
    "path": "packages/hooks/src/use_root_context.rs",
    "content": "use dioxus_core::{provide_root_context, try_consume_context, use_hook};\n\n/// Try to get a value from the root of the virtual dom, if it doesn't exist, create a new one with the closure provided.\n///\n/// This is useful for global context inside of libraries. Instead of having the user provide context in the root of their app, you can use this hook to create a context at the root automatically.\n///\n/// # Example\n/// ```rust\n/// # #[derive(Clone)]\n/// # struct Logger;\n/// use dioxus::prelude::*;\n///\n/// fn use_logger() -> Logger {\n///     // We want one logger per app in the root. Instead of forcing the user to always provide a logger, we can insert a default logger if one doesn't exist.\n///     use_root_context(|| Logger)\n/// }\n/// ```\n#[doc = include_str!(\"../docs/rules_of_hooks.md\")]\n#[doc = include_str!(\"../docs/moving_state_around.md\")]\npub fn use_root_context<T: 'static + Clone>(new: impl FnOnce() -> T) -> T {\n    use_hook(|| {\n        try_consume_context::<T>()\n            // If no context is provided, create a new one at the root\n            .unwrap_or_else(|| provide_root_context(new()))\n    })\n}\n"
  },
  {
    "path": "packages/hooks/src/use_set_compare.rs",
    "content": "use dioxus_core::use_hook;\nuse dioxus_signals::{ReadSignal, SetCompare};\nuse std::hash::Hash;\n\n/// Creates a new SetCompare which efficiently tracks when a value changes to check if it is equal to a set of values.\n///\n/// Generally, you shouldn't need to use this hook. Instead you can use [`crate::use_memo()`]. If you have many values that you need to compare to a single value, this hook will change updates from O(n) to O(1) where n is the number of values you are comparing to.\n///\n/// ```rust\n/// use dioxus::prelude::*;\n///\n/// fn App() -> Element {\n///     let mut count = use_signal(|| 0);\n///     let compare = use_set_compare(move || count());\n///\n///     rsx! {\n///         for i in 0..10 {\n///             // Child will only re-render when i == count\n///             Child { compare, i }\n///         }\n///         button {\n///             // This will only rerender the child with the old and new value of i == count\n///             // Because we are using a set compare, this will be O(1) instead of the O(n) performance of a selector\n///             onclick: move |_| count += 1,\n///             \"Increment\"\n///         }\n///     }\n/// }\n///\n/// #[component]\n/// fn Child(i: usize, compare: SetCompare<usize>) -> Element {\n///     let active = use_set_compare_equal(i, compare);\n///     if active() {\n///         rsx! { \"Active\" }\n///     } else {\n///         rsx! { \"Inactive\" }\n///     }\n/// }\n/// ```\n#[doc = include_str!(\"../docs/rules_of_hooks.md\")]\n#[doc = include_str!(\"../docs/moving_state_around.md\")]\n#[must_use]\npub fn use_set_compare<R: Eq + Hash + 'static>(f: impl FnMut() -> R + 'static) -> SetCompare<R> {\n    use_hook(move || SetCompare::new(f))\n}\n\n/// A hook that returns true if the value is equal to the value in the set compare.\n#[doc = include_str!(\"../docs/rules_of_hooks.md\")]\n#[doc = include_str!(\"../docs/moving_state_around.md\")]\n#[must_use]\npub fn use_set_compare_equal<R: Eq + Hash + 'static>(\n    value: R,\n    mut compare: SetCompare<R>,\n) -> ReadSignal<bool> {\n    use_hook(move || compare.equal(value))\n}\n"
  },
  {
    "path": "packages/hooks/src/use_signal.rs",
    "content": "use dioxus_core::use_hook;\nuse dioxus_signals::{Signal, SignalData, Storage, SyncStorage, UnsyncStorage};\n\n/// Creates a new Signal. Signals are a Copy state management solution with automatic dependency tracking.\n///\n/// ```rust\n/// use dioxus::prelude::*;\n/// use dioxus_signals::*;\n///\n/// fn App() -> Element {\n///     let mut count = use_signal(|| 0);\n///\n///     // Because signals have automatic dependency tracking, if you never read them in a component, that component will not be re-rended when the signal is updated.\n///     // The app component will never be rerendered in this example.\n///     rsx! { Child { state: count } }\n/// }\n///\n/// #[component]\n/// fn Child(state: Signal<u32>) -> Element {\n///     use_future(move || async move {\n///         // Because the signal is a Copy type, we can use it in an async block without cloning it.\n///         *state.write() += 1;\n///     });\n///\n///     rsx! {\n///         button {\n///             onclick: move |_| *state.write() += 1,\n///             \"{state}\"\n///         }\n///     }\n/// }\n/// ```\n///\n#[doc = include_str!(\"../docs/rules_of_hooks.md\")]\n#[doc = include_str!(\"../docs/moving_state_around.md\")]\n#[doc(alias = \"use_state\")]\n#[track_caller]\n#[must_use]\npub fn use_signal<T: 'static>(f: impl FnOnce() -> T) -> Signal<T, UnsyncStorage> {\n    use_maybe_signal_sync(f)\n}\n\n/// Creates a new `Send + Sync`` Signal. Signals are a Copy state management solution with automatic dependency tracking.\n///\n/// ```rust\n/// use dioxus::prelude::*;\n/// use dioxus_signals::*;\n///\n/// fn App() -> Element {\n///     let mut count = use_signal_sync(|| 0);\n///\n///     // Because signals have automatic dependency tracking, if you never read them in a component, that component will not be re-rended when the signal is updated.\n///     // The app component will never be rerendered in this example.\n///     rsx! { Child { state: count } }\n/// }\n///\n/// #[component]\n/// fn Child(state: Signal<u32, SyncStorage>) -> Element {\n///     use_future(move || async move {\n///         // This signal is Send + Sync, so we can use it in an another thread\n///         tokio::spawn(async move {\n///             // Because the signal is a Copy type, we can use it in an async block without cloning it.\n///             *state.write() += 1;\n///         }).await;\n///     });\n///\n///     rsx! {\n///         button {\n///             onclick: move |_| *state.write() += 1,\n///             \"{state}\"\n///         }\n///     }\n/// }\n/// ```\n#[doc(alias = \"use_rw\")]\n#[must_use]\n#[track_caller]\npub fn use_signal_sync<T: Send + Sync + 'static>(f: impl FnOnce() -> T) -> Signal<T, SyncStorage> {\n    use_maybe_signal_sync(f)\n}\n\n#[must_use]\n#[track_caller]\nfn use_maybe_signal_sync<T: 'static, U: Storage<SignalData<T>>>(\n    f: impl FnOnce() -> T,\n) -> Signal<T, U> {\n    let caller = std::panic::Location::caller();\n\n    // todo: (jon)\n    // By default, we want to unsubscribe the current component from the signal on every render\n    // any calls to .read() in the body will re-subscribe the component to the signal\n    // use_before_render(move || signal.unsubscribe(current_scope_id().unwrap()));\n\n    use_hook(|| Signal::new_with_caller(f(), caller))\n}\n"
  },
  {
    "path": "packages/hooks/src/use_sorted.rs",
    "content": "use std::cmp::Ordering;\nuse std::ops::DerefMut;\n\nuse crate::use_memo;\nuse dioxus_signals::{ReadSignal, Signal};\n\npub fn use_sorted<V: 'static, T: PartialEq>(\n    collection: impl FnMut() -> Signal<V>,\n) -> ReadSignal<Vec<T>>\n// pub fn use_sorted<S, I, T>(iterable: impl FnMut() -> Signal<V>) -> ReadSignal<T>\n// where\n//     S: Into<MaybeSignal<I>>,\n//     T: Ord,\n//     I: DerefMut<Target = [T]> + Clone + PartialEq,\n{\n    use_memo(move || {\n        unimplemented!()\n        // let mut iterable = collection();\n        // iterable.sort();\n        // iterable\n    })\n    // let iterable = iterable.into();\n\n    // // use_memo(f)\n\n    // create_memo(move |_| {\n    //     let mut iterable = iterable.get();\n    //     iterable.sort();\n    //     iterable\n    // })\n    // .into()\n}\n\n// /// Version of [`use_sorted`] with a compare function.\n// pub fn use_sorted_by<S, I, T, F>(iterable: S, cmp_fn: F) -> Signal<I>\n// where\n//     S: Into<MaybeSignal<I>>,\n//     I: DerefMut<Target = [T]> + Clone + PartialEq,\n//     F: FnMut(&T, &T) -> Ordering + Clone + 'static,\n// {\n//     let iterable = iterable.into();\n\n//     create_memo(move |_| {\n//         let mut iterable = iterable.get();\n//         iterable.sort_by(cmp_fn.clone());\n//         iterable\n//     })\n//     .into()\n// }\n\n// /// Version of [`use_sorted`] by key.\n// pub fn use_sorted_by_key<S, I, T, K, F>(iterable: S, key_fn: F) -> Signal<I>\n// where\n//     S: Into<MaybeSignal<I>>,\n//     I: DerefMut<Target = [T]> + Clone + PartialEq,\n//     K: Ord,\n//     F: FnMut(&T) -> K + Clone + 'static,\n// {\n//     let iterable = iterable.into();\n\n//     create_memo(move |_| {\n//         let mut iterable = iterable.get();\n//         iterable.sort_by_key(key_fn.clone());\n//         iterable\n//     })\n//     .into()\n// }\n"
  },
  {
    "path": "packages/hooks/src/use_waker.rs",
    "content": "use dioxus_core::use_hook;\nuse dioxus_signals::{ReadableExt, Signal, WritableExt};\nuse futures_channel::oneshot::{Canceled, Receiver, Sender};\nuse futures_util::{future::Shared, FutureExt};\n\n/// A hook that provides a waker for other hooks to provide async/await capabilities.\n///\n/// This hook is a reactive wrapper over the `Shared<T>` future from the `futures` crate.\n/// It allows multiple awaiters to wait on the same value, similar to a broadcast channel from Tokio.\n///\n/// Calling `.await` on the waker will consume the waker, so you'll need to call `.wait()` on the\n/// source to get a new waker.\npub fn use_waker<T: Clone + 'static>() -> UseWaker<T> {\n    // We use a oneshot channel to send the value to the awaiter.\n    // The shared future allows multiple awaiters to wait on the same value.\n    let (task_tx, task_rx) = use_hook(|| {\n        let (tx, rx) = futures_channel::oneshot::channel::<T>();\n        let shared = rx.shared();\n        (Signal::new(tx), Signal::new(shared))\n    });\n\n    UseWaker { task_tx, task_rx }\n}\n\n#[derive(Debug)]\npub struct UseWaker<T: 'static> {\n    task_tx: Signal<Sender<T>>,\n    task_rx: Signal<Shared<Receiver<T>>>,\n}\n\nimpl<T: Clone + 'static> UseWaker<T> {\n    /// Wake the current task with the provided value.\n    /// All awaiters will receive a clone of the value.\n    pub fn wake(&mut self, value: T) {\n        // We ignore the error because it means the task has already been woken.\n        let (tx, rx) = futures_channel::oneshot::channel::<T>();\n        let shared = rx.shared();\n\n        // Swap out the old sender and receiver with the new ones.\n        let tx = self.task_tx.replace(tx);\n        let _rx = self.task_rx.replace(shared);\n\n        // And then send out the oneshot value, waking up all awaiters.\n        let _ = tx.send(value);\n    }\n\n    /// Returns a future that resolves when the task is woken.\n    pub async fn wait(&self) -> Result<T, Canceled> {\n        self.task_rx.cloned().await\n    }\n}\n\n// Can await the waker to be woken.\n// We use `.peek()` here to avoid reacting to changes in the underlying task_rx which could lead\n// to an effect/future loop.\nimpl<T: Clone + 'static> std::future::Future for UseWaker<T> {\n    type Output = Result<T, Canceled>;\n\n    fn poll(\n        self: std::pin::Pin<&mut Self>,\n        cx: &mut std::task::Context<'_>,\n    ) -> std::task::Poll<Self::Output> {\n        self.task_rx.peek().clone().poll_unpin(cx)\n    }\n}\n\nimpl<T> Copy for UseWaker<T> {}\nimpl<T> Clone for UseWaker<T> {\n    fn clone(&self) -> Self {\n        *self\n    }\n}\n"
  },
  {
    "path": "packages/hooks/tests/effect.rs",
    "content": "#![allow(unused, non_upper_case_globals, non_snake_case)]\nuse std::cell::RefCell;\nuse std::collections::HashMap;\nuse std::rc::Rc;\nuse std::time::Duration;\n\nuse dioxus::prelude::*;\nuse dioxus_core::ElementId;\nuse dioxus_signals::*;\n\n#[tokio::test]\nasync fn effects_rerun() {\n    #[derive(Default)]\n    struct RunCounter {\n        component: usize,\n        effect: usize,\n    }\n\n    let counter = Rc::new(RefCell::new(RunCounter::default()));\n    let mut dom = VirtualDom::new_with_props(\n        |counter: Rc<RefCell<RunCounter>>| {\n            counter.borrow_mut().component += 1;\n\n            let mut signal = use_signal(|| 0);\n            use_effect({\n                to_owned![counter];\n                move || {\n                    counter.borrow_mut().effect += 1;\n                    // This will subscribe the effect to the signal\n                    println!(\"Signal: {:?}\", signal);\n\n                    // Stop the wait for work manually\n                    dioxus_core::needs_update();\n                }\n            });\n            signal += 1;\n\n            rsx! {\n                div {}\n            }\n        },\n        counter.clone(),\n    );\n\n    dom.rebuild_in_place();\n    tokio::select! {\n        _ = dom.wait_for_work() => {}\n        _ = tokio::time::sleep(Duration::from_millis(500)) => panic!(\"timed out\")\n    };\n\n    let current_counter = counter.borrow();\n    assert_eq!(current_counter.component, 1);\n    assert_eq!(current_counter.effect, 1);\n}\n\n// https://github.com/DioxusLabs/dioxus/issues/2347\n// Effects should rerun when the signal changes if there are no changes to the component\n#[tokio::test]\nasync fn effects_rerun_without_rerender() {\n    #[derive(Default)]\n    struct RunCounter {\n        component: usize,\n        effect: usize,\n    }\n\n    let counter = Rc::new(RefCell::new(RunCounter::default()));\n    let mut dom = VirtualDom::new_with_props(\n        |counter: Rc<RefCell<RunCounter>>| {\n            counter.borrow_mut().component += 1;\n            println!(\"component {}\", counter.borrow().component);\n\n            let mut signal = use_signal(|| 0);\n            use_effect({\n                to_owned![counter];\n                move || {\n                    counter.borrow_mut().effect += 1;\n                    // This will subscribe the effect to the signal\n                    println!(\"Signal: {}\", signal);\n                }\n            });\n            use_future(move || async move {\n                for i in 0..10 {\n                    tokio::time::sleep(std::time::Duration::from_millis(10)).await;\n                    println!(\"future {}\", i);\n                    signal += 1;\n                }\n            });\n\n            rsx! {\n                div {}\n            }\n        },\n        counter.clone(),\n    );\n\n    dom.rebuild_in_place();\n    tokio::select! {\n        _ = dom.wait_for_work() => {}\n        _ = tokio::time::sleep(Duration::from_millis(500)) => {}\n    };\n\n    let current_counter = counter.borrow();\n    assert_eq!(current_counter.component, 1);\n    assert_eq!(current_counter.effect, 11);\n}\n"
  },
  {
    "path": "packages/hooks/tests/memo.rs",
    "content": "use dioxus_core::generation;\n\n#[tokio::test]\nasync fn memo_updates() {\n    use std::cell::RefCell;\n\n    use dioxus::prelude::*;\n\n    thread_local! {\n        static VEC_SIGNAL: RefCell<Option<Signal<Vec<usize>, SyncStorage>>> = const { RefCell::new(None) };\n    }\n\n    fn app() -> Element {\n        let mut vec = use_signal_sync(|| vec![0, 1, 2]);\n\n        // Signals should update if they are changed from another thread\n        use_hook(|| {\n            VEC_SIGNAL.with(|cell| {\n                *cell.borrow_mut() = Some(vec);\n            });\n            std::thread::spawn(move || {\n                std::thread::sleep(std::time::Duration::from_millis(100));\n                vec.push(5);\n            });\n        });\n\n        let len = vec.len();\n        let len_memo = use_memo(move || vec.len());\n\n        // Make sure memos that update in the middle of a component work\n        if generation() < 2 {\n            vec.push(len);\n        }\n\n        // The memo should always be up to date\n        assert_eq!(vec.len(), len_memo());\n\n        rsx! {\n            for i in 0..len {\n                Child { index: i, vec }\n            }\n        }\n    }\n\n    #[component]\n    fn Child(index: usize, vec: Signal<Vec<usize>, SyncStorage>) -> Element {\n        // This memo should not rerun after the element is removed\n        let item = use_memo(move || vec.read()[index]);\n\n        rsx! {\n            div { \"Item: {item}\" }\n        }\n    }\n\n    let race = async move {\n        let mut dom = VirtualDom::new(app);\n\n        dom.rebuild_in_place();\n        let mut signal = VEC_SIGNAL.with(|cell| (*cell.borrow()).unwrap());\n        // Wait for the signal to update\n        for _ in 0..2 {\n            dom.wait_for_work().await;\n            dom.render_immediate(&mut dioxus::dioxus_core::NoOpMutations);\n        }\n        assert_eq!(signal(), vec![0, 1, 2, 3, 4, 5]);\n        // Remove each element from the vec\n        for _ in 0..6 {\n            signal.pop();\n            dom.wait_for_work().await;\n            dom.render_immediate(&mut dioxus::dioxus_core::NoOpMutations);\n            println!(\"Signal: {signal:?}\");\n        }\n    };\n\n    tokio::select! {\n        _ = race => {},\n        _ = tokio::time::sleep(std::time::Duration::from_millis(1000)) => panic!(\"timed out\")\n    };\n}\n\n#[tokio::test]\nasync fn use_memo_only_triggers_one_update() {\n    use dioxus::prelude::*;\n    use std::cell::RefCell;\n\n    thread_local! {\n        static VEC_SIGNAL: RefCell<Vec<usize>> = const { RefCell::new(Vec::new()) };\n    }\n\n    fn app() -> Element {\n        let mut count = use_signal(|| 0);\n\n        let memorized = use_memo(move || dbg!(count() * 2));\n\n        use_memo(move || {\n            println!(\"reading doubled\");\n            let doubled = memorized();\n            VEC_SIGNAL.with_borrow_mut(|v| v.push(doubled))\n        });\n\n        // Writing to count many times in a row should not cause the memo to update other subscribers multiple times\n        use_hook(move || {\n            for _ in 0..10 {\n                count += 1;\n                // Reading the memo each time will trigger the memo to rerun immediately, but the VEC_SIGNAL should still only rerun once\n                println!(\"doubled {memorized}\");\n            }\n        });\n\n        rsx! {}\n    }\n\n    let mut dom = VirtualDom::new(app);\n    dom.rebuild_in_place();\n\n    tokio::select! {\n        _ = dom.wait_for_work() => {},\n        _ = tokio::time::sleep(std::time::Duration::from_millis(100)) => {}\n    };\n\n    dom.render_immediate(&mut dioxus::dioxus_core::NoOpMutations);\n\n    assert_eq!(VEC_SIGNAL.with(|v| v.borrow().clone()), vec![0, 20]);\n}\n"
  },
  {
    "path": "packages/html/Cargo.toml",
    "content": "[package]\nname = \"dioxus-html\"\nversion = { workspace = true }\nauthors = [\"Jonathan Kelley\"]\nedition = \"2021\"\ndescription = \"HTML Element pack for Dioxus - a concurrent renderer-agnostic Virtual DOM for interactive user experiences\"\nlicense = \"MIT OR Apache-2.0\"\nrepository = \"https://github.com/DioxusLabs/dioxus/\"\nhomepage = \"https://dioxuslabs.com\"\nkeywords = [\"dom\", \"ui\", \"gui\", \"react\"]\n\n[dependencies]\ndioxus-core = { workspace = true }\ndioxus-core-macro = { workspace = true }\ndioxus-core-types = { workspace = true }\ndioxus-rsx = { workspace = true, optional = true }\ndioxus-html-internal-macro = { workspace = true }\ndioxus-hooks = { workspace = true }\ngenerational-box = { workspace = true }\nserde = { workspace = true, features = [\"derive\"], optional = true }\nserde_repr = { workspace = true, optional = true }\njs-sys = { workspace = true, optional = true }\nkeyboard-types = { workspace = true, default-features = false }\nasync-trait = { workspace = true }\ntokio = { workspace = true, features = [\"fs\", \"io-util\"], optional = true }\nfutures-channel = { workspace = true }\nfutures-util = { workspace = true }\nserde_json = { workspace = true, optional = true }\ntracing = { workspace = true }\nrustversion = { workspace = true }\neuclid = \"0.22.11\"\nenumset = \"1.1.6\"\nbytes = { workspace = true }\n\n[build-dependencies]\nlazy-js-bundle = { workspace = true }\n\n[dev-dependencies]\nserde_json = \"1\"\ndioxus = { workspace = true }\ndioxus-web = { workspace = true }\ntokio = { workspace = true, features = [\"time\"] }\nmanganis = { workspace = true }\n\n[features]\ndefault = [\"serialize\"]\nserialize = [\n    \"dep:serde\",\n    \"dep:serde_json\",\n    \"dep:serde_repr\",\n    \"euclid/serde\",\n    \"keyboard-types/serde\",\n    \"dioxus-core/serialize\",\n    \"bytes/serde\"\n]\nhot-reload-context = [\"dep:dioxus-rsx\"]\nhtml-to-rsx = []\n\n[package.metadata.docs.rs]\ncargo-args = [\"-Zunstable-options\", \"-Zrustdoc-scrape-examples\"]\nfeature = [\"html-to-rsx\", \"hot-reload-context\", \"html-to-rsx\"]\n"
  },
  {
    "path": "packages/html/README.md",
    "content": "# `dioxus-html`: Html (and SVG) Namespace for Dioxus\n\n[![Crates.io][crates-badge]][crates-url]\n[![MIT licensed][mit-badge]][mit-url]\n[![Build Status][actions-badge]][actions-url]\n[![Discord chat][discord-badge]][discord-url]\n\n[crates-badge]: https://img.shields.io/crates/v/dioxus-html.svg\n[crates-url]: https://crates.io/crates/dioxus-html\n[mit-badge]: https://img.shields.io/badge/license-MIT-blue.svg\n[mit-url]: https://github.com/dioxuslabs/dioxus/blob/main/LICENSE-MIT\n[actions-badge]: https://github.com/dioxuslabs/dioxus/actions/workflows/main.yml/badge.svg\n[actions-url]: https://github.com/dioxuslabs/dioxus/actions?query=workflow%3ACI+branch%3Amaster\n[discord-badge]: https://img.shields.io/discord/899851952891002890.svg?logo=discord&style=flat-square\n[discord-url]: https://discord.gg/XgGxMSkvUM\n\n[Website](https://dioxuslabs.com) |\n[Guides](https://dioxuslabs.com/learn/0.7/) |\n[API Docs](https://docs.rs/dioxus-html/latest/dioxus_html) |\n[Chat](https://discord.gg/XgGxMSkvUM)\n\n## Overview\n\nThe Dioxus `rsx!` macro can accept any compile-time correct namespace on top of NodeFactory. This crate provides the HTML (and SVG) namespaces which get imported in the Dioxus prelude.\n\nHowever, this abstraction enables you to add any namespace of elements, provided they're in scope when rsx! is called. For an example, a UI that is designed for Augmented Reality might use different primitives than HTML:\n\n```rust, ignore\nuse ar_namespace::*;\n\nrsx! {\n    magic_div {\n        magic_header {}\n        magic_paragraph {\n            on_magic_click: move |event| {\n                //\n            }\n        }\n    }\n}\n```\n\nThis is currently a not-very-explored part of Dioxus. However, the namespacing system does make it possible to provide syntax highlighting, documentation, \"go to definition\" and compile-time correctness, so it's worth having it abstracted.\n\n## How it works:\n\nElements for dioxus must implement the (simple) DioxusElement trait to be used in the rsx! macro.\n\n```rust, ignore\nstruct div;\nimpl DioxusElement for div {\n    const TAG_NAME: &'static str = \"div\";\n    const NAME_SPACE: Option<&'static str> = None;\n}\n```\n\nAll elements should be defined as a zero-sized-struct (also known as unit struct). These structs are zero-cost and just provide the type-level trickery to Rust for compile-time correct templates.\n\nAttributes would then be implemented as constants on these unit structs.\n\nThe HTML namespace is defined mostly with macros. However, the expanded form would look something like this:\n\n```rust, ignore\nstruct base;\nimpl DioxusElement for base {\n    const TAG_NAME: &'static str = \"base\";\n    const NAME_SPACE: Option<&'static str> = None;\n}\nimpl base {\n    const href: (&'static str, Option<'static str>, bool) = (\"href\", None, false);\n    const target: (&'static str, Option<'static str>, bool) = (\"target\", None, false);\n}\n```\n\nBecause attributes are defined as methods on the unit struct, they guard the attribute creation behind a compile-time correct interface.\n\n## How to extend it:\n\nWhenever the rsx! macro is called, it relies on a module `dioxus_elements` to be in scope. When you enable the `html` feature in dioxus, this module gets imported in the prelude. However, you can extend this with your own set of custom elements by making your own `dioxus_elements` module and re-exporting the html namespace.\n\n```rust, ignore\nmod dioxus_elements {\n    use dioxus::prelude::dioxus_elements::*;\n    struct my_element;\n    impl DioxusElement for my_element {\n        const TAG_NAME: &'static str = \"base\";\n        const NAME_SPACE: Option<&'static str> = None;\n    }\n}\n```\n\n## Contributing\n\n- Report issues on our [issue tracker](https://github.com/dioxuslabs/dioxus/issues).\n- Join the discord and ask questions!\n\n## License\n\nThis project is licensed under the [MIT license].\n\n[mit license]: https://github.com/dioxuslabs/dioxus/blob/main/LICENSE-MIT\n\nUnless you explicitly state otherwise, any contribution intentionally submitted\nfor inclusion in Dioxus by you shall be licensed as MIT without any additional\nterms or conditions.\n"
  },
  {
    "path": "packages/html/docs/common_event_handler_errors.md",
    "content": "## Compiler errors you may run into while using event handlers\n\n<details>\n<summary>function requires argument type to outlive `'static`</summary>\n\nEvent handler in Dioxus need only access data that can last for the entire lifetime of the application. That generally means data that is moved into the closure. **If you get this error, you may have forgotten to add `move` to your closure.**\n\nBroken component:\n\n```rust, compile_fail\n# use dioxus::prelude::*;\n// We return an Element which can last as long as the component is on the screen\nfn App() -> Element {\n    // Signals are `Copy` which makes them very easy to move into `'static` closures like event handlers\n    let state = use_signal(|| \"hello world\".to_string());\n\n    rsx! {\n        button {\n            // ❌ Without `move`, rust will try to borrow the `state` signal which fails because the state signal is dropped at the end of the function\n            onclick: |_| {\n                println!(\"You clicked the button! The state is: {state}\");\n            },\n            \"Click me\"\n        }\n    }\n    // The state signal is dropped here, but the event handler still needs to access it\n}\n```\n\nFixed component:\n\n```rust, no_run\n# use dioxus::prelude::*;\nfn App() -> Element {\n    let state = use_signal(|| \"hello world\".to_string());\n\n    rsx! {\n        button {\n            // ✅ The `move` keyword tells rust it can move the `state` signal into the closure. Since the closure owns the signal state, it can read it even after the function returns\n            onclick: move |_| {\n                println!(\"You clicked the button! The state is: {state}\");\n            },\n            \"Click me\"\n        }\n    }\n}\n```\n\n</details>\n\n<details>\n<summary>use of moved value: `your_value` value used here after move</summary>\n\nData in rust has a single owner. If you run into this error, you have likely tried to move data that isn't `Copy` into two different closures. **You can fix this issue by making your data `Copy` or calling `clone` on it before you move it into the closure.**\n\nBroken component:\n\n```rust, compile_fail\n# use dioxus::prelude::*;\n// `MyComponent` accepts a string which cannot be copied implicitly\n#[component]\nfn MyComponent(string: String) -> Element {\n    rsx! {\n        button {\n            // ❌ We are moving the string into the onclick handler which means we can't access it elsewhere\n            onclick: move |_| {\n                println!(\"{string}\");\n            },\n            \"Print hello world\"\n        }\n        button {\n            // ❌ Since we already moved the string, we can't move it into the onclick handler again. This will cause a compiler error\n            onclick: move |_| {\n                println!(\"{string}\");\n            },\n            \"Print hello world again\"\n        }\n    }\n}\n```\n\nYou can fix this issue by either:\n\n- Making your data `Copy` with `ReadSignal`:\n\n```rust, no_run\n# use dioxus::prelude::*;\n// `MyComponent` accepts `ReadSignal<String>` which implements `Copy`\n#[component]\nfn MyComponent(string: ReadSignal<String>) -> Element {\n    rsx! {\n        button {\n            // ✅ Because the `string` signal is `Copy`, we can copy it into the closure while still having access to it elsewhere\n            onclick: move |_| println!(\"{}\", string),\n            \"Print hello world\"\n        }\n        button {\n            // ✅ Since `string` is `Copy`, we can move it into the onclick handler again\n            onclick: move |_| println!(\"{}\", string),\n            \"Print hello world again\"\n        }\n    }\n}\n```\n\n- Calling `clone` on your data before you move it into the closure:\n\n```rust, no_run\n# use dioxus::prelude::*;\n// `MyComponent` accepts a string which doesn't implement `Copy`\n#[component]\nfn MyComponent(string: String) -> Element {\n    rsx! {\n        button {\n            // ✅ The string only has one owner. We could move it into this closure, but since we want to use the string in other closures later, we will clone it instead\n            onclick: {\n                // Clone the string in a new block\n                let string = string.clone();\n                // Then move the cloned string into the closure\n                move |_| println!(\"{}\", string)\n            },\n            \"Print hello world\"\n        }\n        button {\n            // ✅ We don't use the string after this closure, so we can just move it into the closure directly\n            onclick: move |_| println!(\"{}\", string),\n            \"Print hello world again\"\n        }\n    }\n}\n```\n\n</details>\n"
  },
  {
    "path": "packages/html/docs/event_handlers.md",
    "content": "# Event Handlers\n\nEvent Handlers let you react to user input in your application. In Dioxus, event handlers accept a closure that is called when the event occurs:\n\n```rust, no_run\nuse dioxus::prelude::*;\n\nfn App() -> Element {\n    rsx! {\n        button {\n            // The `onclick` event accepts a closure with the signature `fn(Event)`\n            onclick: |event_data| println!(\"clicked! I got the event data: {event_data:?}\"),\n            \"Click me\"\n        }\n    }\n}\n```\n\n## Event Lifetimes\n\nEvents take a closure with the `'static` lifetime. This means that the closure can only access data that either exists for the entire lifetime of the application, or data that you move into the closure.\n\nState in dioxus is `copy` which makes it very easy to move into `'static` closures like event handlers:\n\n```rust, no_run\n# use dioxus::prelude::*;\nlet mut count = use_signal(|| 0);\n\nrsx! {\n    button {\n        // Since we added the `move` keyword, the closure will move the `count` signal into the closure\n        onclick: move |_| {\n            // This will panic because the `count` signal is not in scope\n            count.set(count() + 1);\n        },\n        \"Click me\"\n    }\n};\n```\n\nIf you need to access data that is not `Copy`, you may need to clone the data before you move it into the closure:\n\n```rust, no_run\n# use dioxus::prelude::*;\n// String is not `Copy`\nlet string = \"hello world\".to_string();\n\nrsx! {\n    button {\n        // The string only has one owner. We could move it into this closure, but since we want to use the string in other closures later, we will clone it instead\n        onclick: {\n            // Clone the string in a new block\n            let string = string.clone();\n            // Then move the cloned string into the closure\n            move |_| println!(\"{}\", string)\n        },\n        \"Print hello world\"\n    }\n    button {\n        // We don't use the string after this closure, so we can just move it into the closure directly\n        onclick: move |_| println!(\"{}\", string),\n        \"Print hello world again\"\n    }\n};\n```\n\n## Async Event Handlers\n\nIn addition to closures that return nothing, you can also use async closures to handle events. If you return an async block from an event handler, dioxus will automatically spawn it:\n\n```rust, no_run\nuse dioxus::prelude::*;\n\nfn App() -> Element {\n    rsx! {\n        button {\n            // The `onclick` event can also accept a closure that returns an async block\n            onclick: move |_| async move {\n                tokio::time::sleep(std::time::Duration::from_secs(1)).await;\n                println!(\"You clicked the button one second ago!\");\n            },\n            \"Click me\"\n        }\n    }\n}\n```\n"
  },
  {
    "path": "packages/html/src/attribute_groups.rs",
    "content": "#![allow(non_upper_case_globals)]\n#![allow(deprecated)]\n\nuse dioxus_core::HasAttributes;\nuse dioxus_core::IntoAttributeValue;\nuse dioxus_html_internal_macro::impl_extension_attributes;\n\nuse crate::AttributeDescription;\n\n#[cfg(feature = \"hot-reload-context\")]\nmacro_rules! mod_method_mapping {\n    (\n        $matching:ident;\n        $(#[$attr:meta])*\n        $name:ident;\n    ) => {\n        if $matching == stringify!($name) {\n            return Some((stringify!($name), None));\n        }\n    };\n    (\n        $matching:ident;\n        $(#[$attr:meta])*\n        $name:ident: $lit:literal;\n    ) => {\n        if $matching == stringify!($name) {\n            return Some(($lit, None));\n        }\n    };\n    (\n        $matching:ident;\n        $(#[$attr:meta])*\n        $name:ident: $lit:literal in $ns:literal;\n    ) => {\n        if $matching == stringify!($name) {\n            return Some(($lit, Some($ns)));\n        }\n    };\n    (\n        $matching:ident;\n        $(#[$attr:meta])*\n        $name:ident in $ns:literal;\n    ) => {\n        if $matching == stringify!($name) {\n            return Some((stringify!($name), Some($ns)));\n        }\n    };\n}\n\n#[cfg(feature = \"html-to-rsx\")]\nmacro_rules! html_to_rsx_attribute_mapping {\n    (\n        $matching:ident;\n        $(#[$attr:meta])*\n        $name:ident;\n    ) => {\n        if $matching == stringify!($name) {\n            return Some(stringify!($name));\n        }\n    };\n    (\n        $matching:ident;\n        $(#[$attr:meta])*\n        $name:ident: $lit:literal;\n    ) => {\n        if $matching == $lit {\n            return Some(stringify!($name));\n        }\n    };\n    (\n        $matching:ident;\n        $(#[$attr:meta])*\n        $name:ident: $lit:literal in $ns:literal;\n    ) => {\n        if $matching == $lit {\n            return Some(stringify!($name));\n        }\n    };\n    (\n        $matching:ident;\n        $(#[$attr:meta])*\n        $name:ident in $ns:literal;\n    ) => {\n        if $matching == stringify!($name) {\n            return Some(stringify!($name));\n        }\n    };\n}\n\nmacro_rules! mod_methods {\n    (\n        @base\n        $(#[$mod_attr:meta])*\n        $mod:ident;\n        $fn:ident;\n        $fn_html_to_rsx:ident;\n        $(\n            $(#[$attr:meta])*\n            $name:ident $(: $(no-$alias:ident)? $js_name:literal)? $(in $ns:literal)?;\n        )+\n    ) => {\n        $(#[$mod_attr])*\n        pub mod $mod {\n            use super::*;\n            $(\n                mod_methods! {\n                    @attr\n                    $(#[$attr])*\n                    $name $(: $(no-$alias)? $js_name)? $(in $ns)?;\n                }\n            )+\n        }\n\n        #[cfg(feature = \"hot-reload-context\")]\n        pub(crate) fn $fn(attr: &str) -> Option<(&'static str, Option<&'static str>)> {\n            $(\n                mod_method_mapping! {\n                    attr;\n                    $name $(: $js_name)? $(in $ns)?;\n                }\n            )*\n            None\n        }\n\n        #[cfg(feature = \"html-to-rsx\")]\n        #[doc = \"Converts an HTML attribute to an RSX attribute\"]\n        pub(crate) fn $fn_html_to_rsx(html: &str) -> Option<&'static str> {\n            $(\n                html_to_rsx_attribute_mapping! {\n                    html;\n                    $name $(: $js_name)? $(in $ns)?;\n                }\n            )*\n            None\n        }\n\n        impl_extension_attributes![$mod { $($name,)* }];\n    };\n\n    (\n        @attr\n        $(#[$attr:meta])*\n        $name:ident $(: no-alias $js_name:literal)? $(in $ns:literal)?;\n    ) => {\n        $(#[$attr])*\n        ///\n        /// ## Usage in rsx\n        ///\n        /// ```rust, ignore\n        /// # use dioxus::prelude::*;\n        #[doc = concat!(\"let \", stringify!($name), \" = \\\"value\\\";\")]\n        ///\n        /// rsx! {\n        ///     // Attributes need to be under the element they modify\n        ///     div {\n        ///         // Attributes are followed by a colon and then the value of the attribute\n        #[doc = concat!(\"        \", stringify!($name), \": \\\"value\\\"\")]\n        ///     }\n        ///     div {\n        ///         // Or you can use the shorthand syntax if you have a variable in scope that has the same name as the attribute\n        #[doc = concat!(\"        \", stringify!($name), \",\")]\n        ///     }\n        /// };\n        /// ```\n        pub const $name: AttributeDescription = mod_methods! { $name $(: $js_name)? $(in $ns)?; };\n    };\n\n    (\n        @attr\n        $(#[$attr:meta])*\n        $name:ident $(: $js_name:literal)? $(in $ns:literal)?;\n    ) => {\n        $(#[$attr])*\n        ///\n        /// ## Usage in rsx\n        ///\n        /// ```rust, ignore\n        /// # use dioxus::prelude::*;\n        #[doc = concat!(\"let \", stringify!($name), \" = \\\"value\\\";\")]\n        ///\n        /// rsx! {\n        ///     // Attributes need to be under the element they modify\n        ///     div {\n        ///         // Attributes are followed by a colon and then the value of the attribute\n        #[doc = concat!(\"        \", stringify!($name), \": \\\"value\\\"\")]\n        ///     }\n        ///     div {\n        ///         // Or you can use the shorthand syntax if you have a variable in scope that has the same name as the attribute\n        #[doc = concat!(\"        \", stringify!($name), \",\")]\n        ///     }\n        /// };\n        /// ```\n        $(\n            #[doc(alias = $js_name)]\n        )?\n        pub const $name: AttributeDescription = mod_methods! { $name $(: $js_name)? $(in $ns)?; };\n    };\n\n    // Rename the incoming ident and apply a custom namespace\n    ( $name:ident: $lit:literal in $ns:literal; ) => { ($lit, Some($ns), false) };\n\n    // Custom namespace\n    ( $name:ident in $ns:literal; ) => { (stringify!($name), Some($ns), false) };\n\n    // Rename the incoming ident\n    ( $name:ident: $lit:literal; ) => { ($lit, None, false ) };\n\n    // Don't rename the incoming ident\n    ( $name:ident; ) => { (stringify!($name), None, false) };\n}\n\nmod_methods! {\n    @base\n\n    global_attributes;\n    map_global_attributes;\n    map_html_global_attributes_to_rsx;\n\n    #[deprecated(note = \"This attribute does nothing. For most renderers, you should prefer calling [`dioxus_core::Event::prevent_default`] on the event instead. For liveview, you can use `\\\"onclick\\\": (evt) => evt.prevent_default()` to prevent the default action for this element.\")]\n    /// This attribute has been deprecated in favor of [`dioxus_core::Event::prevent_default`]\n    prevent_default: \"dioxus-prevent-default\";\n\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/accesskey>\n    accesskey;\n\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/autocapitalize>\n    autocapitalize;\n\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/autofocus>\n    autofocus;\n\n    /// The HTML class attribute is used to specify a class for an HTML element.\n    ///\n    /// ## Details\n    /// Multiple HTML elements can share the same class.\n    ///\n    /// The class global attribute is a space-separated list of the case-sensitive classes of the element.\n    /// Classes allow CSS and Javascript to select and access specific elements via the class selectors or\n    /// functions like the DOM method document.getElementsByClassName.\n    ///\n    /// ## Multiple Classes\n    ///\n    /// If you include multiple classes in a single element dioxus will automatically join them with a space.\n    ///\n    /// ```rust\n    /// # use dioxus::prelude::*;\n    /// rsx! {\n    ///     div {\n    ///         class: \"my-class\",\n    ///         class: \"my-other-class\"\n    ///     }\n    /// };\n    /// ```\n    ///\n    /// ## Optional Classes\n    ///\n    /// You can include optional attributes with an unterminated if statement as the value of the attribute. This is very useful for conditionally applying css classes:\n    ///\n    /// ```rust\n    /// # use dioxus::prelude::*;\n    /// rsx! {\n    ///     div {\n    ///         class: if true {\n    ///             \"my-class\"\n    ///         },\n    ///         class: if false {\n    ///             \"my-other-class\"\n    ///         }\n    ///     }\n    /// };\n    /// ```\n    ///\n    /// <https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/class>\n    class;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/contenteditable>\n    contenteditable;\n\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/dir>\n    dir;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/draggable>\n    draggable;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/enterkeyhint>\n    enterkeyhint;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/exportparts>\n    exportparts;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/hidden>\n    hidden;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/id>\n    id;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/inputmode>\n    inputmode;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/is>\n    is;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/itemid>\n    itemid;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/itemprop>\n    itemprop;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/itemref>\n    itemref;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/itemscope>\n    itemscope;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/itemtype>\n    itemtype;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/lang>\n    lang;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/nonce>\n    nonce;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/part>\n    part;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/popover>\n    popover;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/role>\n    role;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/slot>\n    slot;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/spellcheck>\n    spellcheck;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/style>\n    style;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/tabindex>\n    tabindex;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/title>\n    title;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/translate>\n    translate;\n\n\n    /// dangerous_inner_html is Dioxus's replacement for using innerHTML in the browser DOM. In general, setting\n    /// HTML from code is risky because it’s easy to inadvertently expose your users to a cross-site scripting (XSS)\n    /// attack. So, you can set HTML directly from Dioxus, but you have to type out dangerous_inner_html to remind\n    /// yourself that it’s dangerous\n    dangerous_inner_html;\n\n    // This macro creates an explicit method call for each of the style attributes.\n    //\n    // The left token specifies the name of the attribute in the rsx! macro, and the right string literal specifies the\n    // actual name of the attribute generated.\n    //\n    // This roughly follows the html spec\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/align-content>\n    align_content: \"align-content\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/align-items>\n    align_items: \"align-items\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/align-self>\n    align_self: \"align-self\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/alignment-adjust>\n    alignment_adjust: \"alignment-adjust\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/alignment-baseline>\n    alignment_baseline: \"alignment-baseline\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/all>\n    all in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/alt>\n    alt in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/animation>\n    animation in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/animation-delay>\n    animation_delay: \"animation-delay\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/animation-direction>\n    animation_direction: \"animation-direction\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/animation-duration>\n    animation_duration: \"animation-duration\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/animation-fill-mode>\n    animation_fill_mode: \"animation-fill-mode\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/animation-iteration-count>\n    animation_iteration_count: \"animation-iteration-count\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/animation-name>\n    animation_name: \"animation-name\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/animation-play-state>\n    animation_play_state: \"animation-play-state\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/animation-timing-function>\n    animation_timing_function: \"animation-timing-function\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/aspect-ratio>\n    aspect_ratio: \"aspect-ratio\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/azimuth>\n    azimuth in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/backdrop-filter>\n    backdrop_filter: \"backdrop-filter\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/backface-visibility>\n    backface_visibility: \"backface-visibility\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/background>\n    background in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/background-attachment>\n    background_attachment: \"background-attachment\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/background-clip>\n    background_clip: \"background-clip\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/background-color>\n    background_color: \"background-color\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/background-image>\n    background_image: \"background-image\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/background-origin>\n    background_origin: \"background-origin\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/background-position>\n    background_position: \"background-position\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/background-repeat>\n    background_repeat: \"background-repeat\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/background-size>\n    background_size: \"background-size\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/background-blend-mode>\n    background_blend_mode: \"background-blend-mode\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/baseline-shift>\n    baseline_shift: \"baseline-shift\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/bleed>\n    bleed in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/bookmark-label>\n    bookmark_label: \"bookmark-label\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/bookmark-level>\n    bookmark_level: \"bookmark-level\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/bookmark-state>\n    bookmark_state: \"bookmark-state\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/border>\n    border in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/border-color>\n    border_color: \"border-color\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/border-style>\n    border_style: \"border-style\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/border-width>\n    border_width: \"border-width\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/border-bottom>\n    border_bottom: \"border-bottom\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/border-bottom-color>\n    border_bottom_color: \"border-bottom-color\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/border-bottom-style>\n    border_bottom_style: \"border-bottom-style\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/border-bottom-width>\n    border_bottom_width: \"border-bottom-width\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/border-left>\n    border_left: \"border-left\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/border-left-color>\n    border_left_color: \"border-left-color\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/border-left-style>\n    border_left_style: \"border-left-style\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/border-left-width>\n    border_left_width: \"border-left-width\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/border-right>\n    border_right: \"border-right\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/border-right-color>\n    border_right_color: \"border-right-color\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/border-right-style>\n    border_right_style: \"border-right-style\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/border-right-width>\n    border_right_width: \"border-right-width\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/border-top>\n    border_top: \"border-top\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/border-top-color>\n    border_top_color: \"border-top-color\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/border-top-style>\n    border_top_style: \"border-top-style\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/border-top-width>\n    border_top_width: \"border-top-width\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/border-collapse>\n    border_collapse: \"border-collapse\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/border-image>\n    border_image: \"border-image\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/border-image-outset>\n    border_image_outset: \"border-image-outset\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/border-image-repeat>\n    border_image_repeat: \"border-image-repeat\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/border-image-slice>\n    border_image_slice: \"border-image-slice\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/border-image-source>\n    border_image_source: \"border-image-source\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/border-image-width>\n    border_image_width: \"border-image-width\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/border-radius>\n    border_radius: \"border-radius\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/border-bottom-left-radius>\n    border_bottom_left_radius: \"border-bottom-left-radius\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/border-bottom-right-radius>\n    border_bottom_right_radius: \"border-bottom-right-radius\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/border-top-left-radius>\n    border_top_left_radius: \"border-top-left-radius\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/border-top-right-radius>\n    border_top_right_radius: \"border-top-right-radius\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/border-spacing>\n    border_spacing: \"border-spacing\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/bottom>\n    bottom in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/box-decoration-break>\n    box_decoration_break: \"box-decoration-break\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/box-shadow>\n    box_shadow: \"box-shadow\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/box-sizing>\n    box_sizing: \"box-sizing\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/box-snap>\n    box_snap: \"box-snap\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/break-after>\n    break_after: \"break-after\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/break-before>\n    break_before: \"break-before\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/break-inside>\n    break_inside: \"break-inside\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/buffered-rendering>\n    buffered_rendering: \"buffered-rendering\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/caption-side>\n    caption_side: \"caption-side\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/clear>\n    clear in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/clear-side>\n    clear_side: \"clear-side\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/clip>\n    clip in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/clip-path>\n    clip_path: \"clip-path\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/clip-rule>\n    clip_rule: \"clip-rule\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/color>\n    color in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/color-adjust>\n    color_adjust: \"color-adjust\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/color-correction>\n    color_correction: \"color-correction\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/color-interpolation>\n    color_interpolation: \"color-interpolation\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/color-interpolation-filters>\n    color_interpolation_filters: \"color-interpolation-filters\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/color-profile>\n    color_profile: \"color-profile\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/color-rendering>\n    color_rendering: \"color-rendering\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/column-fill>\n    column_fill: \"column-fill\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/column-gap>\n    column_gap: \"column-gap\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/column-rule>\n    column_rule: \"column-rule\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/column-rule-color>\n    column_rule_color: \"column-rule-color\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/column-rule-style>\n    column_rule_style: \"column-rule-style\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/column-rule-width>\n    column_rule_width: \"column-rule-width\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/column-span>\n    column_span: \"column-span\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/columns>\n    columns in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/column-count>\n    column_count: \"column-count\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/column-width>\n    column_width: \"column-width\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/contain>\n    contain in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/content>\n    content in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/counter-increment>\n    counter_increment: \"counter-increment\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/counter-reset>\n    counter_reset: \"counter-reset\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/counter-set>\n    counter_set: \"counter-set\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/cue>\n    cue in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/cue-after>\n    cue_after: \"cue-after\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/cue-before>\n    cue_before: \"cue-before\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/cursor>\n    cursor in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/direction>\n    direction in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/display>\n    display in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/display-inside>\n    display_inside: \"display-inside\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/display-outside>\n    display_outside: \"display-outside\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/display-extras>\n    display_extras: \"display-extras\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/display-box>\n    display_box: \"display-box\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/dominant-baseline>\n    dominant_baseline: \"dominant-baseline\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/elevation>\n    elevation in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/empty-cells>\n    empty_cells: \"empty-cells\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/enable-background>\n    enable_background: \"enable-background\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/fill>\n    fill in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/fill-opacity>\n    fill_opacity: \"fill-opacity\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/fill-rule>\n    fill_rule: \"fill-rule\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/filter>\n    filter in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/float>\n    float in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/float-defer-column>\n    float_defer_column: \"float-defer-column\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/float-defer-page>\n    float_defer_page: \"float-defer-page\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/float-offset>\n    float_offset: \"float-offset\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/float-wrap>\n    float_wrap: \"float-wrap\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/flow-into>\n    flow_into: \"flow-into\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/flow-from>\n    flow_from: \"flow-from\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/flex>\n    flex in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/flex-basis>\n    flex_basis: \"flex-basis\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/flex-grow>\n    flex_grow: \"flex-grow\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/flex-shrink>\n    flex_shrink: \"flex-shrink\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/flex-flow>\n    flex_flow: \"flex-flow\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/flex-direction>\n    flex_direction: \"flex-direction\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/flex-wrap>\n    flex_wrap: \"flex-wrap\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/flood-color>\n    flood_color: \"flood-color\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/flood-opacity>\n    flood_opacity: \"flood-opacity\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/font>\n    font in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/font-family>\n    font_family: \"font-family\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/font-size>\n    font_size: \"font-size\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/font-stretch>\n    font_stretch: \"font-stretch\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/font-style>\n    font_style: \"font-style\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/font-weight>\n    font_weight: \"font-weight\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/font-feature-settings>\n    font_feature_settings: \"font-feature-settings\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/font-kerning>\n    font_kerning: \"font-kerning\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/font-language-override>\n    font_language_override: \"font-language-override\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/font-size-adjust>\n    font_size_adjust: \"font-size-adjust\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/font-synthesis>\n    font_synthesis: \"font-synthesis\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/font-variant>\n    font_variant: \"font-variant\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/font-variant-alternates>\n    font_variant_alternates: \"font-variant-alternates\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/font-variant-caps>\n    font_variant_caps: \"font-variant-caps\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/font-variant-east-asian>\n    font_variant_east_asian: \"font-variant-east-asian\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/font-variant-ligatures>\n    font_variant_ligatures: \"font-variant-ligatures\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/font-variant-numeric>\n    font_variant_numeric: \"font-variant-numeric\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/font-variant-position>\n    font_variant_position: \"font-variant-position\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/footnote-policy>\n    footnote_policy: \"footnote-policy\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/glyph-orientation-horizontal>\n    glyph_orientation_horizontal: \"glyph-orientation-horizontal\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/glyph-orientation-vertical>\n    glyph_orientation_vertical: \"glyph-orientation-vertical\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/grid>\n    grid in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/grid-auto-flow>\n    grid_auto_flow: \"grid-auto-flow\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/grid-auto-columns>\n    grid_auto_columns: \"grid-auto-columns\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/grid-auto-rows>\n    grid_auto_rows: \"grid-auto-rows\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/grid-template>\n    grid_template: \"grid-template\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/grid-template-areas>\n    grid_template_areas: \"grid-template-areas\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/grid-template-columns>\n    grid_template_columns: \"grid-template-columns\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/grid-template-rows>\n    grid_template_rows: \"grid-template-rows\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/grid-area>\n    grid_area: \"grid-area\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/grid-column>\n    grid_column: \"grid-column\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/grid-column-start>\n    grid_column_start: \"grid-column-start\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/grid-column-end>\n    grid_column_end: \"grid-column-end\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/grid-row>\n    grid_row: \"grid-row\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/grid-row-start>\n    grid_row_start: \"grid-row-start\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/grid-row-end>\n    grid_row_end: \"grid-row-end\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/hanging-punctuation>\n    hanging_punctuation: \"hanging-punctuation\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/height>\n    height in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/hyphenate-character>\n    hyphenate_character: \"hyphenate-character\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/hyphenate-limit-chars>\n    hyphenate_limit_chars: \"hyphenate-limit-chars\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/hyphenate-limit-last>\n    hyphenate_limit_last: \"hyphenate-limit-last\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/hyphenate-limit-lines>\n    hyphenate_limit_lines: \"hyphenate-limit-lines\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/hyphenate-limit-zone>\n    hyphenate_limit_zone: \"hyphenate-limit-zone\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/hyphens>\n    hyphens in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/icon>\n    icon in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/image-orientation>\n    image_orientation: \"image-orientation\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/image-resolution>\n    image_resolution: \"image-resolution\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/image-rendering>\n    image_rendering: \"image-rendering\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/ime>\n    ime in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/ime-align>\n    ime_align: \"ime-align\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/ime-mode>\n    ime_mode: \"ime-mode\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/ime-offset>\n    ime_offset: \"ime-offset\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/ime-width>\n    ime_width: \"ime-width\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/initial-letters>\n    initial_letters: \"initial-letters\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/inline-box-align>\n    inline_box_align: \"inline-box-align\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/isolation>\n    isolation in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/justify-content>\n    justify_content: \"justify-content\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/justify-items>\n    justify_items: \"justify-items\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/justify-self>\n    justify_self: \"justify-self\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/kerning>\n    kerning in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/left>\n    left in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/letter-spacing>\n    letter_spacing: \"letter-spacing\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/lighting-color>\n    lighting_color: \"lighting-color\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/line-box-contain>\n    line_box_contain: \"line-box-contain\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/line-break>\n    line_break: \"line-break\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/line-grid>\n    line_grid: \"line-grid\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/line-height>\n    line_height: \"line-height\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/line-slack>\n    line_slack: \"line-slack\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/line-snap>\n    line_snap: \"line-snap\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/list-style>\n    list_style: \"list-style\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/list-style-image>\n    list_style_image: \"list-style-image\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/list-style-position>\n    list_style_position: \"list-style-position\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/list-style-type>\n    list_style_type: \"list-style-type\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/margin>\n    margin in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/margin-bottom>\n    margin_bottom: \"margin-bottom\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/margin-left>\n    margin_left: \"margin-left\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/margin-right>\n    margin_right: \"margin-right\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/margin-top>\n    margin_top: \"margin-top\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/marker>\n    marker in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/marker-end>\n    marker_end: \"marker-end\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/marker-mid>\n    marker_mid: \"marker-mid\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/marker-pattern>\n    marker_pattern: \"marker-pattern\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/marker-segment>\n    marker_segment: \"marker-segment\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/marker-start>\n    marker_start: \"marker-start\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/marker-knockout-left>\n    marker_knockout_left: \"marker-knockout-left\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/marker-knockout-right>\n    marker_knockout_right: \"marker-knockout-right\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/marker-side>\n    marker_side: \"marker-side\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/marks>\n    marks in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/marquee-direction>\n    marquee_direction: \"marquee-direction\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/marquee-play-count>\n    marquee_play_count: \"marquee-play-count\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/marquee-speed>\n    marquee_speed: \"marquee-speed\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/marquee-style>\n    marquee_style: \"marquee-style\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/mask>\n    mask in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/mask-image>\n    mask_image: \"mask-image\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/mask-repeat>\n    mask_repeat: \"mask-repeat\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/mask-position>\n    mask_position: \"mask-position\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/mask-clip>\n    mask_clip: \"mask-clip\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/mask-origin>\n    mask_origin: \"mask-origin\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/mask-size>\n    mask_size: \"mask-size\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/mask-box>\n    mask_box: \"mask-box\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/mask-box-outset>\n    mask_box_outset: \"mask-box-outset\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/mask-box-repeat>\n    mask_box_repeat: \"mask-box-repeat\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/mask-box-slice>\n    mask_box_slice: \"mask-box-slice\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/mask-box-source>\n    mask_box_source: \"mask-box-source\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/mask-box-width>\n    mask_box_width: \"mask-box-width\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/mask-type>\n    mask_type: \"mask-type\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/max-height>\n    max_height: \"max-height\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/max-lines>\n    max_lines: \"max-lines\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/max-width>\n    max_width: \"max-width\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/min-height>\n    min_height: \"min-height\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/min-width>\n    min_width: \"min-width\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/mix-blend-mode>\n    mix_blend_mode: \"mix-blend-mode\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/nav-down>\n    nav_down: \"nav-down\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/nav-index>\n    nav_index: \"nav-index\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/nav-left>\n    nav_left: \"nav-left\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/nav-right>\n    nav_right: \"nav-right\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/nav-up>\n    nav_up: \"nav-up\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit>\n    object_fit: \"object-fit\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/object-position>\n    object_position: \"object-position\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/offset-after>\n    offset_after: \"offset-after\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/offset-before>\n    offset_before: \"offset-before\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/offset-end>\n    offset_end: \"offset-end\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/offset-start>\n    offset_start: \"offset-start\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/opacity>\n    opacity in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/order>\n    order in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/orphans>\n    orphans in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/outline>\n    outline in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/outline-color>\n    outline_color: \"outline-color\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/outline-style>\n    outline_style: \"outline-style\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/outline-width>\n    outline_width: \"outline-width\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/outline-offset>\n    outline_offset: \"outline-offset\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/overflow>\n    overflow in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/overflow-x>\n    overflow_x: \"overflow-x\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/overflow-y>\n    overflow_y: \"overflow-y\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/overflow-style>\n    overflow_style: \"overflow-style\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/overflow-wrap>\n    overflow_wrap: \"overflow-wrap\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/padding>\n    padding in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/padding-bottom>\n    padding_bottom: \"padding-bottom\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/padding-left>\n    padding_left: \"padding-left\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/padding-right>\n    padding_right: \"padding-right\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/padding-top>\n    padding_top: \"padding-top\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/page>\n    page in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/page-break-after>\n    page_break_after: \"page-break-after\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/page-break-before>\n    page_break_before: \"page-break-before\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/page-break-inside>\n    page_break_inside: \"page-break-inside\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/paint-order>\n    paint_order: \"paint-order\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/pause>\n    pause in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/pause-after>\n    pause_after: \"pause-after\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/pause-before>\n    pause_before: \"pause-before\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/perspective>\n    perspective in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/perspective-origin>\n    perspective_origin: \"perspective-origin\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/pitch>\n    pitch in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/pitch-range>\n    pitch_range: \"pitch-range\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/play-during>\n    play_during: \"play-during\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/pointer-events>\n    pointer_events: \"pointer-events\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/position>\n    position in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/quotes>\n    quotes in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/region-fragment>\n    region_fragment: \"region-fragment\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/resize>\n    resize in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/rest>\n    rest in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/rest-after>\n    rest_after: \"rest-after\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/rest-before>\n    rest_before: \"rest-before\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/richness>\n    richness in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/right>\n    right in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/ruby-align>\n    ruby_align: \"ruby-align\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/ruby-merge>\n    ruby_merge: \"ruby-merge\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/ruby-position>\n    ruby_position: \"ruby-position\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-behavior>\n    scroll_behavior: \"scroll-behavior\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-snap-coordinate>\n    scroll_snap_coordinate: \"scroll-snap-coordinate\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-snap-destination>\n    scroll_snap_destination: \"scroll-snap-destination\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-snap-points-x>\n    scroll_snap_points_x: \"scroll-snap-points-x\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-snap-points-y>\n    scroll_snap_points_y: \"scroll-snap-points-y\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-snap-type>\n    scroll_snap_type: \"scroll-snap-type\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/shape-image-threshold>\n    shape_image_threshold: \"shape-image-threshold\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/shape-inside>\n    shape_inside: \"shape-inside\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/shape-margin>\n    shape_margin: \"shape-margin\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/shape-outside>\n    shape_outside: \"shape-outside\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/shape-padding>\n    shape_padding: \"shape-padding\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/shape-rendering>\n    shape_rendering: \"shape-rendering\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/size>\n    size in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/speak>\n    speak in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/speak-as>\n    speak_as: \"speak-as\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/speak-header>\n    speak_header: \"speak-header\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/speak-numeral>\n    speak_numeral: \"speak-numeral\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/speak-punctuation>\n    speak_punctuation: \"speak-punctuation\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/speech-rate>\n    speech_rate: \"speech-rate\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/stop-color>\n    stop_color: \"stop-color\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/stop-opacity>\n    stop_opacity: \"stop-opacity\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/stress>\n    stress in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/string-set>\n    string_set: \"string-set\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/stroke>\n    stroke in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/stroke-dasharray>\n    stroke_dasharray: \"stroke-dasharray\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/stroke-dashoffset>\n    stroke_dashoffset: \"stroke-dashoffset\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/stroke-linecap>\n    stroke_linecap: \"stroke-linecap\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/stroke-linejoin>\n    stroke_linejoin: \"stroke-linejoin\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/stroke-miterlimit>\n    stroke_miterlimit: \"stroke-miterlimit\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/stroke-opacity>\n    stroke_opacity: \"stroke-opacity\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/stroke-width>\n    stroke_width: \"stroke-width\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/tab-size>\n    tab_size: \"tab-size\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/table-layout>\n    table_layout: \"table-layout\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/text-align>\n    text_align: \"text-align\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/text-align-all>\n    text_align_all: \"text-align-all\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/text-align-last>\n    text_align_last: \"text-align-last\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/text-anchor>\n    text_anchor: \"text-anchor\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/text-combine-upright>\n    text_combine_upright: \"text-combine-upright\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/text-decoration>\n    text_decoration: \"text-decoration\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/text-decoration-color>\n    text_decoration_color: \"text-decoration-color\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/text-decoration-line>\n    text_decoration_line: \"text-decoration-line\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/text-decoration-style>\n    text_decoration_style: \"text-decoration-style\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/text-decoration-skip>\n    text_decoration_skip: \"text-decoration-skip\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/text-emphasis>\n    text_emphasis: \"text-emphasis\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/text-emphasis-color>\n    text_emphasis_color: \"text-emphasis-color\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/text-emphasis-style>\n    text_emphasis_style: \"text-emphasis-style\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/text-emphasis-position>\n    text_emphasis_position: \"text-emphasis-position\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/text-emphasis-skip>\n    text_emphasis_skip: \"text-emphasis-skip\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/text-height>\n    text_height: \"text-height\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/text-indent>\n    text_indent: \"text-indent\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/text-justify>\n    text_justify: \"text-justify\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/text-orientation>\n    text_orientation: \"text-orientation\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/text-overflow>\n    text_overflow: \"text-overflow\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/text-rendering>\n    text_rendering: \"text-rendering\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/text-shadow>\n    text_shadow: \"text-shadow\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/text-size-adjust>\n    text_size_adjust: \"text-size-adjust\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/text-space-collapse>\n    text_space_collapse: \"text-space-collapse\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/text-spacing>\n    text_spacing: \"text-spacing\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/text-transform>\n    text_transform: \"text-transform\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/text-underline-position>\n    text_underline_position: \"text-underline-position\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/text-wrap>\n    text_wrap: \"text-wrap\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/top>\n    top in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/touch-action>\n    touch_action: \"touch-action\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/transform>\n    transform in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/transform-box>\n    transform_box: \"transform-box\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/transform-origin>\n    transform_origin: \"transform-origin\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/transform-style>\n    transform_style: \"transform-style\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/transition>\n    transition in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/transition-delay>\n    transition_delay: \"transition-delay\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/transition-duration>\n    transition_duration: \"transition-duration\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/transition-property>\n    transition_property: \"transition-property\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/unicode-bidi>\n    unicode_bidi: \"unicode-bidi\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/vector-effect>\n    vector_effect: \"vector-effect\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/vertical-align>\n    vertical_align: \"vertical-align\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/visibility>\n    visibility in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/voice-balance>\n    voice_balance: \"voice-balance\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/voice-duration>\n    voice_duration: \"voice-duration\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/voice-family>\n    voice_family: \"voice-family\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/voice-pitch>\n    voice_pitch: \"voice-pitch\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/voice-range>\n    voice_range: \"voice-range\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/voice-rate>\n    voice_rate: \"voice-rate\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/voice-stress>\n    voice_stress: \"voice-stress\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/voice-volume>\n    voice_volume: \"voice-volume\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/volume>\n    volume in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/white-space>\n    white_space: \"white-space\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/widows>\n    widows in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/width>\n    width in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/will-change>\n    will_change: \"will-change\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/word-break>\n    word_break: \"word-break\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/word-spacing>\n    word_spacing: \"word-spacing\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/word-wrap>\n    word_wrap: \"word-wrap\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/wrap-flow>\n    wrap_flow: \"wrap-flow\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/wrap-through>\n    wrap_through: \"wrap-through\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/writing-mode>\n    writing_mode: \"writing-mode\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/gap>\n    gap in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/list-style-type>\n    list_styler_type: \"list-style-type\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/row-gap>\n    row_gap: \"row-gap\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/transition-timing-function>\n    transition_timing_function: \"transition-timing-function\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/user-select>\n    user_select: \"user-select\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/-webkit-user-select>\n    webkit_user_select: \"-webkit-user-select\" in \"style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/z-index>\n    z_index: \"z-index\" in \"style\";\n\n    // area attribute\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-current>\n    aria_current: \"aria-current\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-details>\n    aria_details: \"aria-details\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-disabled>\n    aria_disabled: \"aria-disabled\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-hidden>\n    aria_hidden: \"aria-hidden\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-invalid>\n    aria_invalid: \"aria-invalid\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-keyshortcuts>\n    aria_keyshortcuts: \"aria-keyshortcuts\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-label>\n    aria_label: \"aria-label\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-roledescription>\n    aria_roledescription: \"aria-roledescription\";\n\n// Widget Attributes\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-autocomplete>\n    aria_autocomplete: \"aria-autocomplete\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-checked>\n    aria_checked: \"aria-checked\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-expanded>\n    aria_expanded: \"aria-expanded\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-haspopup>\n    aria_haspopup: \"aria-haspopup\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-level>\n    aria_level: \"aria-level\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-modal>\n    aria_modal: \"aria-modal\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-multiline>\n    aria_multiline: \"aria-multiline\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-multiselectable>\n    aria_multiselectable: \"aria-multiselectable\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-orientation>\n    aria_orientation: \"aria-orientation\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-placeholder>\n    aria_placeholder: \"aria-placeholder\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-pressed>\n    aria_pressed: \"aria-pressed\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-readonly>\n    aria_readonly: \"aria-readonly\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-required>\n    aria_required: \"aria-required\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-selected>\n    aria_selected: \"aria-selected\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-sort>\n    aria_sort: \"aria-sort\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-valuemax>\n    aria_valuemax: \"aria-valuemax\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-valuemin>\n    aria_valuemin: \"aria-valuemin\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-valuenow>\n    aria_valuenow: \"aria-valuenow\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-valuetext>\n    aria_valuetext: \"aria-valuetext\";\n\n// Live Region Attributes\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-atomic>\n    aria_atomic: \"aria-atomic\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-busy>\n    aria_busy: \"aria-busy\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-live>\n    aria_live: \"aria-live\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-relevant>\n    aria_relevant: \"aria-relevant\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-dropeffect>\n    aria_dropeffect: \"aria-dropeffect\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-grabbed>\n    aria_grabbed: \"aria-grabbed\";\n\n// Relationship Attributes\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-activedescendant>\n    aria_activedescendant: \"aria-activedescendant\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-colcount>\n    aria_colcount: \"aria-colcount\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-colindex>\n    aria_colindex: \"aria-colindex\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-colspan>\n    aria_colspan: \"aria-colspan\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-controls>\n    aria_controls: \"aria-controls\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-describedby>\n    aria_describedby: \"aria-describedby\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-errormessage>\n    aria_errormessage: \"aria-errormessage\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-flowto>\n    aria_flowto: \"aria-flowto\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-labelledby>\n    aria_labelledby: \"aria-labelledby\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-owns>\n    aria_owns: \"aria-owns\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-posinset>\n    aria_posinset: \"aria-posinset\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-rowcount>\n    aria_rowcount: \"aria-rowcount\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-rowindex>\n    aria_rowindex: \"aria-rowindex\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-rowspan>\n    aria_rowspan: \"aria-rowspan\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-setsize>\n    aria_setsize: \"aria-setsize\";\n}\n\nmod_methods! {\n    @base\n    svg_attributes;\n    map_svg_attributes;\n    map_html_svg_attributes_to_rsx;\n\n    /// Prevent the default action for this element. This attribute is only recommended in the LiveView renderer\n    /// which does not support the prevent default method on events.\n    ///\n    ///\n    /// For most renderers, you should prefer calling [`dioxus_core::Event::prevent_default`] on the event instead.\n    ///\n    ///\n    /// For more information, see the MDN docs:\n    /// <https://developer.mozilla.org/en-US/docs/Web/API/Event/preventDefault>\n    prevent_default: \"dioxus-prevent-default\";\n\n    /// dangerous_inner_html is Dioxus's replacement for using innerHTML in the browser DOM. In general, setting\n    /// HTML from code is risky because it’s easy to inadvertently expose your users to a cross-site scripting (XSS)\n    /// attack. So, you can set HTML directly from Dioxus, but you have to type out dangerous_inner_html to remind\n    /// yourself that it’s dangerous\n    dangerous_inner_html;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/accent-height>\n    accent_height: \"accent-height\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/accumulate>\n    accumulate;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/additive>\n    additive;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/alignment-baseline>\n    alignment_baseline: \"alignment-baseline\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/alphabetic>\n    alphabetic;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/amplitude>\n    amplitude;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/arabic-form>\n    arabic_form: \"arabic-form\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/ascent>\n    ascent;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/attributeName>\n    attribute_name: \"attributeName\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/attributeType>\n    attribute_type: \"attributeType\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/azimuth>\n    azimuth;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/baseFrequency>\n    base_frequency: \"baseFrequency\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/baseline-shift>\n    baseline_shift: \"baseline-shift\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/baseProfile>\n    base_profile: \"baseProfile\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/bbox>\n    bbox;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/begin>\n    begin;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/bias>\n    bias;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/by>\n    by;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/calcMode>\n    calc_mode: \"calcMode\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/cap-height>\n    cap_height: \"cap-height\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/class>\n    class;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/clip>\n    clip;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/clipPathUnits>\n    clip_path_units: \"clipPathUnits\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/clip-path>\n    clip_path: \"clip-path\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/clip-rule>\n    clip_rule: \"clip-rule\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/color>\n    color;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/color-interpolation>\n    color_interpolation: \"color-interpolation\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/color-interpolation-filters>\n    color_interpolation_filters: \"color-interpolation-filters\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/color-profile>\n    color_profile: \"color-profile\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/color-rendering>\n    color_rendering: \"color-rendering\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/contentScriptType>\n    content_script_type: \"contentScriptType\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/contentStyleType>\n    content_style_type: \"contentStyleType\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/crossorigin>\n    crossorigin;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/cursor>\n    cursor;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/cx>\n    cx;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/cy>\n    cy;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d>\n    d;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/decelerate>\n    decelerate;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/descent>\n    descent;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/diffuseConstant>\n    diffuse_constant: \"diffuseConstant\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/direction>\n    direction;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/display>\n    display;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/divisor>\n    divisor;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/dominant-baseline>\n    dominant_baseline: \"dominant-baseline\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/dur>\n    dur;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/dx>\n    dx;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/dy>\n    dy;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/edgeMode>\n    edge_mode: \"edgeMode\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/elevation>\n    elevation;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/enable-background>\n    enable_background: \"enable-background\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/end>\n    end;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/exponent>\n    exponent;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/fill>\n    fill;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/fill-opacity>\n    fill_opacity: \"fill-opacity\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/fill-rule>\n    fill_rule: \"fill-rule\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/filter>\n    filter;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/filterRes>\n    filterRes;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/filterUnits>\n    filterUnits;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/flood-color>\n    flood_color: \"flood-color\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/flood-opacity>\n    flood_opacity: \"flood-opacity\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/font-family>\n    font_family: \"font-family\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/font-size>\n    font_size: \"font-size\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/font-size-adjust>\n    font_size_adjust: \"font-size-adjust\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/font-stretch>\n    font_stretch: \"font-stretch\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/font-style>\n    font_style: \"font-style\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/font-variant>\n    font_variant: \"font-variant\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/font-weight>\n    font_weight: \"font-weight\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/format>\n    format;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/from>\n    from;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/fr>\n    fr;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/fx>\n    fx;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/fy>\n    fy;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/g1>\n    g1;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/g2>\n    g2;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/glyph-name>\n    glyph_name: \"glyph-name\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/glyph-orientation-horizontal>\n    glyph_orientation_horizontal: \"glyph-orientation-horizontal\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/glyph-orientation-vertical>\n    glyph_orientation_vertical: \"glyph-orientation-vertical\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/glyphRef>\n    glyph_ref: \"glyphRef\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/gradientTransform>\n    gradient_transform: \"gradientTransform\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/gradientUnits>\n    gradient_units: \"gradientUnits\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/hanging>\n    hanging;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/height>\n    height;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/href>\n    href;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/hreflang>\n    hreflang;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/horiz-adv-x>\n    horiz_adv_x: \"horiz-adv-x\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/horiz-origin-x>\n    horiz_origin_x: \"horiz-origin-x\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/id>\n    id;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/ideographic>\n    ideographic;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/image-rendering>\n    image_rendering: \"image-rendering\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/_in>\n    _in;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/in2>\n    in2;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/intercept>\n    intercept;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/k>\n    k;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/k1>\n    k1;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/k2>\n    k2;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/k3>\n    k3;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/k4>\n    k4;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/kernelMatrix>\n    kernel_matrix: \"kernelMatrix\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/kernelUnitLength>\n    kernel_unit_length: \"kernelUnitLength\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/kerning>\n    kerning;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/keyPoints>\n    key_points: \"keyPoints\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/keySplines>\n    key_splines: \"keySplines\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/keyTimes>\n    key_times: \"keyTimes\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/lang>\n    lang;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/lengthAdjust>\n    length_adjust: \"lengthAdjust\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/letter-spacing>\n    letter_spacing: \"letter-spacing\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/lighting-color>\n    lighting_color: \"lighting-color\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/limitingConeAngle>\n    limiting_cone_angle: \"limitingConeAngle\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/local>\n    local;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/marker-end>\n    marker_end: \"marker-end\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/marker-mid>\n    marker_mid: \"marker-mid\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/marker-start>\n    marker_start: \"marker-start\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/markerHeight>\n    marker_height: \"markerHeight\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/markerUnits>\n    marker_units: \"markerUnits\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/markerWidth>\n    marker_width: \"markerWidth\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/mask>\n    mask;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/maskContentUnits>\n    mask_content_units: \"maskContentUnits\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/maskUnits>\n    mask_units: \"maskUnits\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/mathematical>\n    mathematical;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/max>\n    max;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/media>\n    media;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/method>\n    method;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/min>\n    min;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/mode>\n    mode;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/name>\n    name;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/numOctaves>\n    num_octaves: \"numOctaves\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/offset>\n    offset;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/opacity>\n    opacity;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/operator>\n    operator;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/order>\n    order;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/orient>\n    orient;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/orientation>\n    orientation;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/origin>\n    origin;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/overflow>\n    overflow;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/overline-position>\n    overline_position: \"overline-position\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/overline-thickness>\n    overline_thickness: \"overline-thickness\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/panose-1>\n    panose_1: \"panose-1\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/paint-order>\n    paint_order: \"paint-order\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/path>\n    path;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/pathLength>\n    path_length: \"pathLength\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/patternContentUnits>\n    pattern_content_units: \"patternContentUnits\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/patternTransform>\n    pattern_transform: \"patternTransform\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/patternUnits>\n    pattern_units: \"patternUnits\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/ping>\n    ping;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/pointer-events>\n    pointer_events: \"pointer-events\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/points>\n    points;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/pointsAtX>\n    points_at_x: \"pointsAtX\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/pointsAtY>\n    points_at_y: \"pointsAtY\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/pointsAtZ>\n    points_at_z: \"pointsAtZ\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/preserveAlpha>\n    preserve_alpha: \"preserveAlpha\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/preserveAspectRatio>\n    preserve_aspect_ratio: \"preserveAspectRatio\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/primitiveUnits>\n    primitive_units: \"primitiveUnits\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/r>\n    r;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/radius>\n    radius;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/referrerPolicy>\n    referrer_policy: \"referrerPolicy\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/refX>\n    ref_x: \"refX\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/refY>\n    ref_y: \"refY\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/rel>\n    rel;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/rendering-intent>\n    rendering_intent: \"rendering-intent\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/repeatCount>\n    repeat_count: \"repeatCount\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/repeatDur>\n    repeat_dur: \"repeatDur\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/requiredExtensions>\n    required_extensions: \"requiredExtensions\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/requiredFeatures>\n    required_features: \"requiredFeatures\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/restart>\n    restart;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/result>\n    result;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/role>\n    role;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/rotate>\n    rotate;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/rx>\n    rx;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/ry>\n    ry;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/scale>\n    scale;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/seed>\n    seed;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/shape-rendering>\n    shape_rendering: \"shape-rendering\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/slope>\n    slope;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/spacing>\n    spacing;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/specularConstant>\n    specular_constant: \"specularConstant\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/specularExponent>\n    specular_exponent: \"specularExponent\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/speed>\n    speed;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/spreadMethod>\n    spread_method: \"spreadMethod\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/startOffset>\n    start_offset: \"startOffset\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stdDeviation>\n    std_deviation: \"stdDeviation\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stemh>\n    stemh;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stemv>\n    stemv;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stitchTiles>\n    stitch_tiles: \"stitchTiles\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stop-color>\n    stop_color: \"stop-color\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stop-opacity>\n    stop_opacity: \"stop-opacity\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/strikethrough-position>\n    strikethrough_position: \"strikethrough-position\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/strikethrough-thickness>\n    strikethrough_thickness: \"strikethrough-thickness\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/string>\n    string;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke>\n    stroke;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke-dasharray>\n    stroke_dasharray: \"stroke-dasharray\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke-dashoffset>\n    stroke_dashoffset: \"stroke-dashoffset\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke-linecap>\n    stroke_linecap: \"stroke-linecap\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke-linejoin>\n    stroke_linejoin: \"stroke-linejoin\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke-miterlimit>\n    stroke_miterlimit: \"stroke-miterlimit\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke-opacity>\n    stroke_opacity: \"stroke-opacity\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke-width>\n    stroke_width: \"stroke-width\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/style>\n    style;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/surfaceScale>\n    surface_scale: \"surfaceScale\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/systemLanguage>\n    system_language: \"systemLanguage\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/tabindex>\n    tabindex;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/tableValues>\n    table_values: \"tableValues\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/target>\n    target;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/targetX>\n    target_x: \"targetX\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/targetY>\n    target_y: \"targetY\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/text-anchor>\n    text_anchor: \"text-anchor\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/text-decoration>\n    text_decoration: \"text-decoration\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/text-rendering>\n    text_rendering: \"text-rendering\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/textLength>\n    text_length: \"textLength\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/to>\n    to;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/transform>\n    transform;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/transform-origin>\n    transform_origin: \"transform-origin\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/type>\n    r#type: no-alias \"type\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/u1>\n    u1;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/u2>\n    u2;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/underline-position>\n    underline_position: \"underline-position\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/underline-thickness>\n    underline_thickness: \"underline-thickness\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/unicode>\n    unicode;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/unicode-bidi>\n    unicode_bidi: \"unicode-bidi\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/unicode-range>\n    unicode_range: \"unicode-range\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/units-per-em>\n    units_per_em: \"units-per-em\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/v-alphabetic>\n    v_alphabetic: \"v-alphabetic\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/v-hanging>\n    v_hanging: \"v-hanging\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/v-ideographic>\n    v_ideographic: \"v-ideographic\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/v-mathematical>\n    v_mathematical: \"v-mathematical\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/values>\n    values;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/vector-effect>\n    vector_effect: \"vector-effect\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/version>\n    version;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/vert-adv-y>\n    vert_adv_y: \"vert-adv-y\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/vert-origin-x>\n    vert_origin_x: \"vert-origin-x\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/vert-origin-y>\n    vert_origin_y: \"vert-origin-y\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/viewBox>\n    view_box: \"viewBox\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/viewTarget>\n    view_target: \"viewTarget\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/visibility>\n    visibility;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/width>\n    width;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/widths>\n    widths;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/word-spacing>\n    word_spacing: \"word-spacing\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/writing-mode>\n    writing_mode: \"writing-mode\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/x>\n    x;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/x-height>\n    x_height: \"x-height\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/x1>\n    x1;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/x2>\n    x2;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/xmlns>\n    xmlns;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/xChannelSelector>\n    x_channel_selector: \"xChannelSelector\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/y>\n    y;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/y1>\n    y1;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/y2>\n    y2;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/yChannelSelector>\n    y_channel_selector: \"yChannelSelector\";\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/z>\n    z;\n\n    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/zoomAndPan>\n    zoom_and_pan: \"zoomAndPan\";\n\n}\n"
  },
  {
    "path": "packages/html/src/data_transfer.rs",
    "content": "pub struct DataTransfer {\n    inner: Box<dyn NativeDataTransfer>,\n}\n\nimpl DataTransfer {\n    pub fn new(inner: impl NativeDataTransfer + 'static) -> Self {\n        Self {\n            inner: Box::new(inner),\n        }\n    }\n\n    #[cfg(feature = \"serialize\")]\n    pub fn store(&self, item: impl Serialize) -> Result<(), String> {\n        let serialized = serde_json::to_string(&item).map_err(|e| e.to_string())?;\n        self.set_data(\"application/json\", &serialized)\n    }\n\n    #[cfg(feature = \"serialize\")]\n    pub fn retrieve<T: for<'de> serde::Deserialize<'de>>(&self) -> Result<Option<T>, String> {\n        if let Some(data) = self.get_data(\"application/json\") {\n            let deserialized = serde_json::from_str(&data).map_err(|e| e.to_string())?;\n            Ok(Some(deserialized))\n        } else {\n            Ok(None)\n        }\n    }\n\n    pub fn get_data(&self, format: &str) -> Option<String> {\n        self.inner.get_data(format)\n    }\n\n    pub fn get_as_text(&self) -> Option<String> {\n        self.get_data(\"text/plain\")\n    }\n\n    pub fn set_data(&self, format: &str, data: &str) -> Result<(), String> {\n        self.inner.set_data(format, data)\n    }\n\n    pub fn clear_data(&self, format: Option<&str>) -> Result<(), String> {\n        self.inner.clear_data(format)\n    }\n\n    pub fn effect_allowed(&self) -> String {\n        self.inner.effect_allowed()\n    }\n\n    pub fn set_effect_allowed(&self, effect: &str) {\n        self.inner.set_effect_allowed(effect)\n    }\n\n    pub fn drop_effect(&self) -> String {\n        self.inner.drop_effect()\n    }\n\n    pub fn set_drop_effect(&self, effect: &str) {\n        self.inner.set_drop_effect(effect)\n    }\n\n    pub fn files(&self) -> Vec<crate::file_data::FileData> {\n        self.inner.files()\n    }\n}\n\npub trait NativeDataTransfer: Send + Sync {\n    fn get_data(&self, format: &str) -> Option<String>;\n    fn set_data(&self, format: &str, data: &str) -> Result<(), String>;\n    fn clear_data(&self, format: Option<&str>) -> Result<(), String>;\n    fn effect_allowed(&self) -> String;\n    fn set_effect_allowed(&self, effect: &str);\n    fn drop_effect(&self) -> String;\n    fn set_drop_effect(&self, effect: &str);\n    fn files(&self) -> Vec<crate::file_data::FileData>;\n}\n\npub trait HasDataTransferData {\n    fn data_transfer(&self) -> DataTransfer;\n}\n\n#[cfg(feature = \"serialize\")]\npub use ser::*;\n#[cfg(feature = \"serialize\")]\nuse serde::Serialize;\n\n#[cfg(feature = \"serialize\")]\nmod ser {\n    use crate::DragData;\n\n    use super::*;\n    use serde::{Deserialize, Serialize};\n\n    /// A serialized version of DataTransfer\n    #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]\n    pub struct SerializedDataTransfer {\n        pub items: Vec<SerializedDataTransferItem>,\n        pub files: Vec<crate::file_data::SerializedFileData>,\n        pub effect_allowed: String,\n        pub drop_effect: String,\n    }\n\n    #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]\n    pub struct SerializedDataTransferItem {\n        pub kind: String,\n        pub type_: String,\n        pub data: String,\n    }\n\n    impl NativeDataTransfer for SerializedDataTransfer {\n        fn get_data(&self, format: &str) -> Option<String> {\n            self.items\n                .iter()\n                .find(|item| item.type_ == format)\n                .map(|item| item.data.clone())\n        }\n\n        fn set_data(&self, _format: &str, _data: &str) -> Result<(), String> {\n            // todo!()\n            // Err(\"Cannot set data on serialized DataTransfer\".into())\n            Ok(())\n        }\n\n        fn clear_data(&self, _format: Option<&str>) -> Result<(), String> {\n            // todo!()\n            // Err(\"Cannot clear data on serialized DataTransfer\".into())\n            Ok(())\n        }\n\n        fn effect_allowed(&self) -> String {\n            self.effect_allowed.clone()\n        }\n\n        fn set_effect_allowed(&self, _effect: &str) {\n            // No-op\n        }\n\n        fn drop_effect(&self) -> String {\n            self.drop_effect.clone()\n        }\n\n        fn set_drop_effect(&self, _effect: &str) {\n            // No-op\n        }\n\n        fn files(&self) -> Vec<crate::file_data::FileData> {\n            self.files\n                .iter()\n                .map(|f| crate::file_data::FileData::new(f.clone()))\n                .collect()\n        }\n    }\n\n    impl From<&DragData> for SerializedDataTransfer {\n        fn from(_drag: &DragData) -> Self {\n            // todo!()\n            Self {\n                items: vec![],\n                files: vec![],\n                effect_allowed: \"all\".into(),\n                drop_effect: \"none\".into(),\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "packages/html/src/elements.rs",
    "content": "#![allow(non_upper_case_globals)]\n\nuse dioxus_core::HasAttributes;\nuse dioxus_core::IntoAttributeValue;\n#[cfg(feature = \"hot-reload-context\")]\nuse dioxus_core_types::HotReloadingContext;\nuse dioxus_html_internal_macro::impl_extension_attributes;\n\n#[cfg(feature = \"hot-reload-context\")]\nuse crate::{map_global_attributes, map_svg_attributes};\n\npub type AttributeDescription = (&'static str, Option<&'static str>, bool);\n\nmacro_rules! impl_attribute {\n    (\n        $element:ident {\n            $(#[$attr_method:meta])*\n            $fil:ident: $vil:ident (DEFAULT),\n        }\n    ) => {\n        $(#[$attr_method])*\n        ///\n        /// ## Usage in rsx\n        ///\n        /// ```rust, no_run\n        /// # use dioxus::prelude::*;\n        #[doc = concat!(\"let \", stringify!($fil), \" = \\\"value\\\";\")]\n        ///\n        /// rsx! {\n        ///     // Attributes need to be under the element they modify\n        #[doc = concat!(\"    \", stringify!($element), \" {\")]\n        ///         // Attributes are followed by a colon and then the value of the attribute\n        #[doc = concat!(\"        \", stringify!($fil), \": \\\"value\\\"\")]\n        ///     }\n        #[doc = concat!(\"    \", stringify!($element), \" {\")]\n        ///         // Or you can use the shorthand syntax if you have a variable in scope that has the same name as the attribute\n        #[doc = concat!(\"        \", stringify!($fil), \",\")]\n        ///     }\n        /// };\n        /// ```\n        pub const $fil: AttributeDescription = (stringify!($fil), None, false);\n    };\n\n    (\n        $element:ident {\n            $(#[$attr_method:meta])*\n            $fil:ident: $vil:ident ($name:literal),\n        }\n    ) => {\n        $(#[$attr_method])*\n        ///\n        /// ## Usage in rsx\n        ///\n        /// ```rust, no_run\n        /// # use dioxus::prelude::*;\n        #[doc = concat!(\"let \", stringify!($fil), \" = \\\"value\\\";\")]\n        ///\n        /// rsx! {\n        ///     // Attributes need to be under the element they modify\n        #[doc = concat!(\"    \", stringify!($element), \" {\")]\n        ///         // Attributes are followed by a colon and then the value of the attribute\n        #[doc = concat!(\"        \", stringify!($fil), \": \\\"value\\\"\")]\n        ///     }\n        #[doc = concat!(\"    \", stringify!($element), \" {\")]\n        ///         // Or you can use the shorthand syntax if you have a variable in scope that has the same name as the attribute\n        #[doc = concat!(\"        \", stringify!($fil), \",\")]\n        ///     }\n        /// };\n        /// ```\n        pub const $fil: AttributeDescription = ($name, None, false);\n    };\n\n    (\n        $element:ident {\n            $(#[$attr_method:meta])*\n            $fil:ident: $vil:ident (volatile),\n        }\n    ) => {\n        $(#[$attr_method])*\n        ///\n        /// ## Usage in rsx\n        ///\n        /// ```rust, no_run\n        /// # use dioxus::prelude::*;\n        #[doc = concat!(\"let \", stringify!($fil), \" = \\\"value\\\";\")]\n        ///\n        /// rsx! {\n        ///     // Attributes need to be under the element they modify\n        #[doc = concat!(\"    \", stringify!($element), \" {\")]\n        ///         // Attributes are followed by a colon and then the value of the attribute\n        #[doc = concat!(\"        \", stringify!($fil), \": \\\"value\\\"\")]\n        ///     }\n        #[doc = concat!(\"    \", stringify!($element), \" {\")]\n        ///         // Or you can use the shorthand syntax if you have a variable in scope that has the same name as the attribute\n        #[doc = concat!(\"        \", stringify!($fil), \",\")]\n        ///     }\n        /// };\n        /// ```\n        pub const $fil: AttributeDescription = (stringify!($fil), None, true);\n    };\n\n    (\n        $element:ident {\n            $(#[$attr_method:meta])*\n            $fil:ident: $vil:ident (in $ns:literal),\n        }\n    ) => {\n        $(#[$attr_method])*\n        ///\n        /// ## Usage in rsx\n        ///\n        /// ```rust, no_run\n        /// # use dioxus::prelude::*;\n        #[doc = concat!(\"let \", stringify!($fil), \" = \\\"value\\\";\")]\n        ///\n        /// rsx! {\n        ///     // Attributes need to be under the element they modify\n        #[doc = concat!(\"    \", stringify!($element), \" {\")]\n        ///         // Attributes are followed by a colon and then the value of the attribute\n        #[doc = concat!(\"        \", stringify!($fil), \": \\\"value\\\"\")]\n        ///     }\n        #[doc = concat!(\"    \", stringify!($element), \" {\")]\n        ///         // Or you can use the shorthand syntax if you have a variable in scope that has the same name as the attribute\n        #[doc = concat!(\"        \", stringify!($fil), \",\")]\n        ///     }\n        /// };\n        /// ```\n        pub const $fil: AttributeDescription = (stringify!($fil), Some($ns), false)\n    };\n\n    (\n        $element:ident {\n            $(#[$attr_method:meta])*\n            $fil:ident: $vil:ident (in $ns:literal : volatile),\n        }\n    ) => {\n        $(#[$attr_method])*\n        ///\n        /// ## Usage in rsx\n        ///\n        /// ```rust, no_run\n        /// # use dioxus::prelude::*;\n        #[doc = concat!(\"let \", stringify!($fil), \" = \\\"value\\\";\")]\n        ///\n        /// rsx! {\n        ///     // Attributes need to be under the element they modify\n        #[doc = concat!(\"    \", stringify!($element), \" {\")]\n        ///         // Attributes are followed by a colon and then the value of the attribute\n        #[doc = concat!(\"        \", stringify!($fil), \": \\\"value\\\"\")]\n        ///     }\n        #[doc = concat!(\"    \", stringify!($element), \" {\")]\n        ///         // Or you can use the shorthand syntax if you have a variable in scope that has the same name as the attribute\n        #[doc = concat!(\"        \", stringify!($fil), \",\")]\n        ///     }\n        /// };\n        /// ```\n        pub const $fil: AttributeDescription = (stringify!($fil), Some($ns), true)\n    };\n}\n\n#[cfg(feature = \"hot-reload-context\")]\nmacro_rules! impl_attribute_match {\n    (\n        $attr:ident $fil:ident: $vil:ident (DEFAULT),\n    ) => {\n        if $attr == stringify!($fil) {\n            return Some((stringify!($fil), None));\n        }\n    };\n\n    (\n        $attr:ident $fil:ident: $vil:ident (volatile),\n    ) => {\n        if $attr == stringify!($fil) {\n            return Some((stringify!($fil), None));\n        }\n    };\n\n    (\n        $attr:ident $fil:ident: $vil:ident ($name:literal),\n    ) => {\n        if $attr == stringify!($fil) {\n            return Some(($name, None));\n        }\n    };\n\n    (\n        $attr:ident $fil:ident: $vil:ident (in $ns:literal),\n    ) => {\n        if $attr == stringify!($fil) {\n            return Some((stringify!($fil), Some($ns)));\n        }\n    };\n}\n\n#[cfg(feature = \"html-to-rsx\")]\nmacro_rules! impl_html_to_rsx_attribute_match {\n    (\n        $attr:ident $fil:ident $name:literal\n    ) => {\n        if $attr == $name {\n            return Some(stringify!($fil));\n        }\n    };\n\n    (\n        $attr:ident $fil:ident $_:tt\n    ) => {\n        if $attr == stringify!($fil) {\n            return Some(stringify!($fil));\n        }\n    };\n}\n\nmacro_rules! impl_element {\n    (\n        $(#[$attr:meta])*\n        $name:ident None {\n            $(\n                $(#[$attr_method:meta])*\n                $fil:ident: $vil:ident $extra:tt,\n            )*\n        }\n    ) => {\n        #[allow(non_camel_case_types)]\n        $(#[$attr])*\n        ///\n        /// ## Usage in rsx\n        ///\n        /// ```rust, no_run\n        /// # use dioxus::prelude::*;\n        /// # let attributes = vec![];\n        /// # fn ChildComponent() -> Element { unimplemented!() }\n        /// # let raw_expression: Element = rsx! {};\n        /// rsx! {\n        ///     // Elements are followed by braces that surround any attributes and children for that element\n        #[doc = concat!(\"    \", stringify!($name), \" {\")]\n        ///         // Add any attributes first\n        ///         class: \"my-class\",\n        ///         \"custom-attribute-name\": \"value\",\n        ///         // Then add any attributes you are spreading into this element\n        ///         ..attributes,\n        ///         // Then add any children elements, components, text nodes, or raw expressions\n        ///         div {}\n        ///         ChildComponent {}\n        ///         \"child text\"\n        ///         {raw_expression}\n        ///     }\n        /// };\n        /// ```\n        pub mod $name {\n            #[allow(unused)]\n            use super::*;\n            pub use crate::attribute_groups::global_attributes::*;\n\n            pub const TAG_NAME: &'static str = stringify!($name);\n            pub const NAME_SPACE: Option<&'static str> = None;\n\n            $(\n                impl_attribute!(\n                    $name {\n                        $(#[$attr_method])*\n                        $fil: $vil ($extra),\n                    }\n                );\n            )*\n        }\n    };\n\n    (\n        $(#[$attr:meta])*\n        $name:ident $namespace:literal {\n            $(\n                $(#[$attr_method:meta])*\n                $fil:ident: $vil:ident $extra:tt,\n            )*\n        }\n    ) => {\n        $(#[$attr])*\n        ///\n        /// ## Usage in rsx\n        ///\n        /// ```rust, no_run\n        /// # use dioxus::prelude::*;\n        /// # let attributes = vec![];\n        /// # fn ChildComponent() -> Element { unimplemented!() }\n        /// # let raw_expression: Element = rsx! {};\n        /// rsx! {\n        ///     // Elements are followed by braces that surround any attributes and children for that element\n        #[doc = concat!(\"    \", stringify!($name), \" {\")]\n        ///         // Add any attributes first\n        ///         color: \"red\",\n        ///         \"custom-attribute-name\": \"value\",\n        ///         // Then add any attributes you are spreading into this element\n        ///         ..attributes,\n        ///         // Then add any children elements, components, text nodes, or raw expressions\n        ///         circle { cx: \"10\", cy: \"10\", r: \"2\", fill: \"red\" }\n        ///         ChildComponent {}\n        ///         \"child text\"\n        ///         {raw_expression}\n        ///     }\n        /// };\n        /// ```\n        pub mod $name {\n            #[allow(unused)]\n            use super::*;\n            pub use crate::attribute_groups::svg_attributes::*;\n\n            pub const TAG_NAME: &'static str = stringify!($name);\n            pub const NAME_SPACE: Option<&'static str> = Some($namespace);\n\n            $(\n                impl_attribute!(\n                    $name {\n                        $(#[$attr_method])*\n                        $fil: $vil ($extra),\n                    }\n                );\n            )*\n        }\n    };\n\n    (\n        $(#[$attr:meta])*\n        $element:ident [$name:literal, $namespace:tt] {\n            $(\n                $(#[$attr_method:meta])*\n                $fil:ident: $vil:ident $extra:tt,\n            )*\n        }\n    ) => {\n        #[allow(non_camel_case_types)]\n        $(#[$attr])*\n        ///\n        /// ## Usage in rsx\n        ///\n        /// ```rust, no_run\n        /// # use dioxus::prelude::*;\n        /// # let attributes = vec![];\n        /// # fn ChildComponent() -> Element { unimplemented!() }\n        /// # let raw_expression: Element = rsx! {};\n        /// rsx! {\n        ///     // Elements are followed by braces that surround any attributes and children for that element\n        #[doc = concat!(\"    \", stringify!($element), \" {\")]\n        ///         // Add any attributes first\n        ///         color: \"red\",\n        ///         \"custom-attribute-name\": \"value\",\n        ///         // Then add any attributes you are spreading into this element\n        ///         ..attributes,\n        ///         // Then add any children elements, components, text nodes, or raw expressions\n        ///         circle { cx: \"10\", cy: \"10\", r: \"2\", fill: \"red\" }\n        ///         ChildComponent {}\n        ///         \"child text\"\n        ///         {raw_expression}\n        ///     }\n        /// };\n        /// ```\n        pub mod $element {\n            #[allow(unused)]\n            use super::*;\n            pub use crate::attribute_groups::svg_attributes::*;\n\n            pub const TAG_NAME: &'static str = $name;\n            pub const NAME_SPACE: Option<&'static str> = Some($namespace);\n\n            $(\n                impl_attribute!(\n                    $element {\n                        $(#[$attr_method])*\n                        $fil: $vil ($extra),\n                    }\n                );\n            )*\n        }\n    }\n}\n\n#[cfg(feature = \"hot-reload-context\")]\nmacro_rules! impl_element_match {\n    (\n        $el:ident $name:ident None {\n            $(\n                $fil:ident: $vil:ident $extra:tt,\n            )*\n        }\n    ) => {\n        if $el == stringify!($name) {\n            return Some((stringify!($name), None));\n        }\n    };\n\n    (\n        $el:ident $name:ident $namespace:literal {\n            $(\n                $fil:ident: $vil:ident $extra:tt,\n            )*\n        }\n    ) => {\n        if $el == stringify!($name) {\n            return Some((stringify!($name), Some($namespace)));\n        }\n    };\n\n    (\n        $el:ident $name:ident [$_:literal, $namespace:tt] {\n            $(\n                $fil:ident: $vil:ident $extra:tt,\n            )*\n        }\n    ) => {\n        if $el == stringify!($name) {\n            return Some((stringify!($name), Some($namespace)));\n        }\n    };\n}\n\n#[cfg(feature = \"hot-reload-context\")]\nmacro_rules! impl_element_match_attributes {\n    (\n        $el:ident $attr:ident $name:ident None {\n            $(\n                $fil:ident: $vil:ident $extra:tt,\n            )*\n        }\n    ) => {\n        if $el == stringify!($name) {\n            $(\n                impl_attribute_match!(\n                    $attr $fil: $vil ($extra),\n                );\n            )*\n\n            return impl_map_global_attributes!($el $attr $name None);\n        }\n    };\n\n    (\n        $el:ident $attr:ident $name:ident $namespace:tt {\n            $(\n                $fil:ident: $vil:ident $extra:tt,\n            )*\n        }\n    ) => {\n        if $el == stringify!($name) {\n            $(\n                impl_attribute_match!(\n                    $attr $fil: $vil ($extra),\n                );\n            )*\n\n            return impl_map_global_attributes!($el $attr $name $namespace);\n        }\n    }\n}\n\n#[cfg(feature = \"hot-reload-context\")]\nmacro_rules! impl_map_global_attributes {\n    (\n        $el:ident $attr:ident $element:ident None\n    ) => {\n        map_global_attributes($attr)\n    };\n\n    (\n        $el:ident $attr:ident $element:ident $namespace:literal\n    ) => {\n        if $namespace == \"http://www.w3.org/2000/svg\" {\n            map_svg_attributes($attr)\n        } else {\n            map_global_attributes($attr)\n        }\n    };\n\n    (\n        $el:ident $attr:ident $element:ident [$name:literal, $namespace:tt]\n    ) => {\n        if $namespace == \"http://www.w3.org/2000/svg\" {\n            map_svg_attributes($attr)\n        } else {\n            map_global_attributes($attr)\n        }\n    };\n}\n\nmacro_rules! builder_constructors {\n    (\n        $(\n            $(#[$attr:meta])*\n            $name:ident $namespace:tt {\n                $(\n                    $(#[$attr_method:meta])*\n                    $fil:ident: $vil:ident $extra:tt,\n                )*\n            };\n         )*\n        ) => {\n        #[cfg(feature = \"hot-reload-context\")]\n        pub struct HtmlCtx;\n\n        #[cfg(feature = \"hot-reload-context\")]\n        impl HotReloadingContext for HtmlCtx {\n            fn map_attribute(element: &str, attribute: &str) -> Option<(&'static str, Option<&'static str>)> {\n                $(\n                    impl_element_match_attributes!(\n                        element attribute $name $namespace {\n                            $(\n                                $fil: $vil $extra,\n                            )*\n                        }\n                    );\n                )*\n                None\n            }\n\n            fn map_element(element: &str) -> Option<(&'static str, Option<&'static str>)> {\n                $(\n                    impl_element_match!(\n                        element $name $namespace {\n                            $(\n                                $fil: $vil $extra,\n                            )*\n                        }\n                    );\n                )*\n                None\n            }\n        }\n\n        #[cfg(feature = \"html-to-rsx\")]\n        pub fn map_html_attribute_to_rsx(html: &str) -> Option<&'static str> {\n            $(\n                $(\n                    impl_html_to_rsx_attribute_match!(\n                        html $fil $extra\n                    );\n                )*\n            )*\n\n            if let Some(name) = crate::map_html_global_attributes_to_rsx(html) {\n                return Some(name);\n            }\n\n            if let Some(name) = crate::map_html_svg_attributes_to_rsx(html) {\n                return Some(name);\n            }\n\n            None\n        }\n\n        #[cfg(feature = \"html-to-rsx\")]\n        pub fn map_html_element_to_rsx(html: &str) -> Option<&'static str> {\n            $(\n                if html == stringify!($name) {\n                    return Some(stringify!($name));\n                }\n            )*\n\n            None\n        }\n\n        $(\n            impl_element!(\n                $(#[$attr])*\n                $name $namespace {\n                    $(\n                        $(#[$attr_method])*\n                        $fil: $vil $extra,\n                    )*\n                }\n            );\n        )*\n\n        /// This module contains helpers for rust analyzer autocompletion\n        #[doc(hidden)]\n        pub mod completions {\n            /// This helper tells rust analyzer that it should autocomplete the element name with braces.\n            #[allow(non_camel_case_types)]\n            pub enum CompleteWithBraces {\n                $(\n                    $(#[$attr])*\n                    ///\n                    /// ## Usage in rsx\n                    ///\n                    /// ```rust, no_run\n                    /// # use dioxus::prelude::*;\n                    /// # let attributes = vec![];\n                    /// # fn ChildComponent() -> Element { unimplemented!() }\n                    /// # let raw_expression: Element = rsx! {};\n                    /// rsx! {\n                    ///     // Elements are followed by braces that surround any attributes and children for that element\n                    #[doc = concat!(\"    \", stringify!($name), \" {\")]\n                    ///         // Add any attributes first\n                    ///         class: \"my-class\",\n                    ///         \"custom-attribute-name\": \"value\",\n                    ///         // Then add any attributes you are spreading into this element\n                    ///         ..attributes,\n                    ///         // Then add any children elements, components, text nodes, or raw expressions\n                    ///         div {}\n                    ///         ChildComponent {}\n                    ///         \"child text\"\n                    ///         {raw_expression}\n                    ///     }\n                    /// };\n                    /// ```\n                    $name {}\n                ),*\n            }\n        }\n\n        pub(crate) mod extensions {\n            use super::*;\n            $(\n                impl_extension_attributes![$name { $($fil,)* }];\n            )*\n        }\n    };\n}\n\n// Organized in the same order as\n// https://developer.mozilla.org/en-US/docs/Web/HTML/Element\n//\n// Does not include obsolete elements.\n//\n// This namespace represents a collection of modern HTML-5 compatible elements.\n//\n// This list does not include obsolete, deprecated, experimental, or poorly supported elements.\nbuilder_constructors! {\n    // Document metadata\n\n    /// Build a\n    /// [`<base>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base)\n    /// element.\n    ///\n    base None {\n        href: Uri DEFAULT,\n        target: Target DEFAULT,\n    };\n\n    /// Build a\n    /// [`<head>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/head)\n    /// element.\n    head None {};\n\n    /// Build a\n    /// [`<link>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link)\n    /// element.\n    link None {\n        // as: Mime,\n        crossorigin: CrossOrigin DEFAULT,\n        href: Uri DEFAULT,\n        hreflang: LanguageTag DEFAULT,\n        media: String DEFAULT, // FIXME media query\n        rel: LinkType DEFAULT,\n        sizes: String DEFAULT, // FIXME\n        title: String DEFAULT, // FIXME\n        r#type: Mime \"type\",\n        integrity: String DEFAULT,\n        disabled: Bool DEFAULT,\n        referrerpolicy: ReferrerPolicy DEFAULT,\n        fetchpriority: FetchPriority DEFAULT,\n        blocking: Blocking DEFAULT,\n        r#as: As \"as\",\n    };\n\n    /// Build a\n    /// [`<meta>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta)\n    /// element.\n    meta None {\n        charset: String DEFAULT, // FIXME IANA standard names\n        content: String DEFAULT,\n        http_equiv: String \"http-equiv\",\n        name: Metadata DEFAULT,\n        property: Metadata DEFAULT,\n    };\n\n    /// Build a\n    /// [`<style>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/style)\n    /// element.\n    style None {\n        r#type: Mime \"type\",\n        media: String DEFAULT, // FIXME media query\n        nonce: Nonce DEFAULT,\n        title: String DEFAULT, // FIXME\n    };\n\n    /// Build a\n    /// [`<title>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/title)\n    /// element.\n    title None { };\n\n    // Sectioning root\n\n    /// Build a\n    /// [`<body>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/body)\n    /// element.\n    body None {};\n\n    // ------------------\n    // Content sectioning\n    // ------------------\n\n    /// Build a\n    /// [`<address>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/address)\n    /// element.\n    address None {};\n\n    /// Build a\n    /// [`<article>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/article)\n    /// element.\n    article None {};\n\n    /// Build a\n    /// [`<aside>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/aside)\n    /// element.\n    aside None {};\n\n    /// Build a\n    /// [`<footer>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/footer)\n    /// element.\n    footer None {};\n\n    /// Build a\n    /// [`<header>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/header)\n    /// element.\n    header None {};\n\n    /// Build a\n    /// [`<hgroup>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/hgroup)\n    /// element.\n    hgroup None {};\n\n    /// Build a\n    /// [`<h1>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h1)\n    /// element.\n    ///\n    /// # About\n    /// - The HTML `<h1>` element is found within the `<body>` tag.\n    /// - Headings can range from `<h1>` to `<h6>`.\n    /// - The most important heading is `<h1>` and the least important heading is `<h6>`.\n    /// - The `<h1>` heading is the first heading in the document.\n    /// - The `<h1>` heading is usually a large bolded font.\n    h1 None {};\n\n    /// Build a\n    /// [`<h2>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h2)\n    /// element.\n    ///\n    /// # About\n    /// - The HTML `<h2>` element is found within the `<body>` tag.\n    /// - Headings can range from `<h1>` to `<h6>`.\n    /// - The most important heading is `<h1>` and the least important heading is `<h6>`.\n    /// - The `<h2>` heading is the second heading in the document.\n    /// - The `<h2>` heading is usually a large bolded font.\n    h2 None {};\n\n    /// Build a\n    /// [`<h3>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h3)\n    /// element.\n    ///\n    /// # About\n    /// - The HTML `<h1>` element is found within the `<body>` tag.\n    /// - Headings can range from `<h1>` to `<h6>`.\n    /// - The most important heading is `<h1>` and the least important heading is `<h6>`.\n    /// - The `<h1>` heading is the first heading in the document.\n    /// - The `<h1>` heading is usually a large bolded font.\n    h3 None {};\n\n    /// Build a\n    /// [`<h4>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h4)\n    /// element.\n    h4 None {};\n\n    /// Build a\n    /// [`<h5>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h5)\n    /// element.\n    h5 None {};\n\n    /// Build a\n    /// [`<h6>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h6)\n    /// element.\n    h6 None {};\n\n    /// Build a\n    /// [`<main>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/main)\n    /// element.\n    main None {};\n\n    /// Build a\n    /// [`<nav>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/nav)\n    /// element.\n    nav None {};\n\n    /// Build a\n    /// [`<section>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/section)\n    /// element.\n    section None {};\n\n    // Text content\n\n    /// Build a\n    /// [`<blockquote>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/blockquote)\n    /// element.\n    blockquote None {\n        cite: Uri DEFAULT,\n    };\n    /// Build a\n    /// [`<dd>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dd)\n    /// element.\n    dd None {};\n\n    /// Build a\n    /// [`<div>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/div)\n    /// element.\n    ///\n    /// Part of the HTML namespace. Only works in HTML-compatible renderers\n    ///\n    /// ## Definition and Usage\n    /// - The `<div>` tag defines a division or a section in an HTML document.\n    /// - The `<div>` tag is used as a container for HTML elements - which is then styled with CSS or manipulated with  JavaScript.\n    /// - The `<div>` tag is easily styled by using the class or id attribute.\n    /// - Any sort of content can be put inside the `<div>` tag!\n    ///\n    /// Note: By default, browsers always place a line break before and after the `<div>` element.\n    ///\n    /// ## References:\n    /// - <https://developer.mozilla.org/en-US/docs/Web/HTML/Element/div>\n    /// - <https://www.w3schools.com/tags/tag_div.asp>\n    div None {};\n\n    /// Build a\n    /// [`<dl>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dl)\n    /// element.\n    dl None {};\n\n    /// Build a\n    /// [`<dt>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dt)\n    /// element.\n    dt None {};\n\n    /// Build a\n    /// [`<figcaption>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/figcaption)\n    /// element.\n    figcaption None {};\n\n    /// Build a\n    /// [`<figure>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/figure)\n    /// element.\n    figure None {};\n\n    /// Build a\n    /// [`<hr>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/hr)\n    /// element.\n    hr None {};\n\n    /// Build a\n    /// [`<li>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/li)\n    /// element.\n    li None {\n        value: isize DEFAULT,\n    };\n\n    /// Build a\n    /// [`<ol>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ol)\n    /// element.\n    ol None {\n        reversed: Bool DEFAULT,\n        start: isize DEFAULT,\n        r#type: OrderedListType \"type\",\n    };\n\n    /// Build a\n    /// [`<p>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/p)\n    /// element.\n    p None {};\n\n    /// Build a\n    /// [`<pre>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/pre)\n    /// element.\n    pre None {};\n\n    /// Build a\n    /// [`<ul>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ul)\n    /// element.\n    ul None {};\n\n\n    // Inline text semantics\n\n    /// Build a\n    /// [`<a>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a)\n    /// element.\n    a None {\n        download: String DEFAULT,\n        href: Uri DEFAULT,\n        hreflang: LanguageTag DEFAULT,\n        target: Target DEFAULT,\n        r#type: Mime \"type\",\n        // ping: SpacedList<Uri>,\n        // rel: SpacedList<LinkType>,\n        ping: SpacedList DEFAULT,\n        rel: SpacedList DEFAULT,\n    };\n\n    /// Build a\n    /// [`<abbr>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/abbr)\n    /// element.\n    abbr None {};\n\n    /// Build a\n    /// [`<b>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/b)\n    /// element.\n    b None {};\n\n    /// Build a\n    /// [`<bdi>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/bdi)\n    /// element.\n    bdi None {};\n\n    /// Build a\n    /// [`<bdo>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/bdo)\n    /// element.\n    bdo None {};\n\n    /// Build a\n    /// [`<br>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/br)\n    /// element.\n    br None {};\n\n    /// Build a\n    /// [`<cite>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/cite)\n    /// element.\n    cite None {};\n\n    /// Build a\n    /// [`<code>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/code)\n    /// element.\n    code None {\n        language: String DEFAULT,\n    };\n\n    /// Build a\n    /// [`<data>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/data)\n    /// element.\n    data None {\n        value: String DEFAULT,\n    };\n\n    /// Build a\n    /// [`<dfn>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dfn)\n    /// element.\n    dfn None {};\n\n    /// Build a\n    /// [`<em>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/em)\n    /// element.\n    em None {};\n\n    /// Build a\n    /// [`<i>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/i)\n    /// element.\n    i None {};\n\n    /// Build a\n    /// [`<kbd>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/kbd)\n    /// element.\n    kbd None {};\n\n    /// Build a\n    /// [`<mark>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/mark)\n    /// element.\n    mark None {};\n\n    /// Build a\n    /// [`<menu>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/menu)\n    /// element.\n    menu None {};\n\n    /// Build a\n    /// [`<q>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/q)\n    /// element.\n    q None {\n        cite: Uri DEFAULT,\n    };\n\n\n    /// Build a\n    /// [`<rp>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/rp)\n    /// element.\n    rp None {};\n\n\n    /// Build a\n    /// [`<rt>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/rt)\n    /// element.\n    rt None {};\n\n\n    /// Build a\n    /// [`<ruby>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ruby)\n    /// element.\n    ruby None {};\n\n    /// Build a\n    /// [`<s>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/s)\n    /// element.\n    s None {};\n\n    /// Build a\n    /// [`<samp>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/samp)\n    /// element.\n    samp None {};\n\n    /// Build a\n    /// [`<small>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/small)\n    /// element.\n    small None {};\n\n    /// Build a\n    /// [`<span>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/span)\n    /// element.\n    span None {};\n\n    /// Build a\n    /// [`<strong>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/strong)\n    /// element.\n    strong None {};\n\n    /// Build a\n    /// [`<sub>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/sub)\n    /// element.\n    sub None {};\n\n    /// Build a\n    /// [`<sup>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/sup)\n    /// element.\n    sup None {};\n\n    /// Build a\n    /// [`<time>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/time)\n    /// element.\n    time None {\n        datetime: Datetime DEFAULT,\n    };\n\n    /// Build a\n    /// [`<u>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/u)\n    /// element.\n    u None {};\n\n    /// Build a\n    /// [`<var>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/var)\n    /// element.\n    var None {};\n\n    /// Build a\n    /// [`<wbr>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/wbr)\n    /// element.\n    wbr None {};\n\n\n    // Image and multimedia\n\n    /// Build a\n    /// [`<area>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/area)\n    /// element.\n    area None {\n        alt: String DEFAULT,\n        coords: String DEFAULT, // TODO could perhaps be validated\n        download: Bool DEFAULT,\n        href: Uri DEFAULT,\n        hreflang: LanguageTag DEFAULT,\n        shape: AreaShape DEFAULT,\n        target: Target DEFAULT,\n        // ping: SpacedList<Uri>,\n        // rel: SpacedSet<LinkType>,\n    };\n\n    /// Build a\n    /// [`<audio>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/audio)\n    /// element.\n    audio None {\n        autoplay: Bool DEFAULT,\n        controls: Bool DEFAULT,\n        crossorigin: CrossOrigin DEFAULT,\n        muted: Bool DEFAULT,\n        preload: Preload DEFAULT,\n        src: Uri DEFAULT,\n        r#loop: Bool \"loop\",\n    };\n\n    /// Build a\n    /// [`<img>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img)\n    /// element.\n    img None {\n        alt: String DEFAULT,\n        crossorigin: CrossOrigin DEFAULT,\n        decoding: ImageDecoding DEFAULT,\n        height: usize DEFAULT,\n        ismap: Bool DEFAULT,\n        loading: String DEFAULT,\n        src: Uri DEFAULT,\n        srcset: String DEFAULT, // FIXME this is much more complicated\n        usemap: String DEFAULT, // FIXME should be a fragment starting with '#'\n        width: usize DEFAULT,\n        referrerpolicy: String DEFAULT,\n        sizes: String DEFAULT, // FIXME\n        elementtiming: String DEFAULT,\n        fetchpriority: String DEFAULT,\n        attributionsrc: String DEFAULT,\n    };\n\n    /// Build a\n    /// [`<map>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/map)\n    /// element.\n    map None {\n        name: Id DEFAULT,\n    };\n\n    /// Build a\n    /// [`<track>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/track)\n    /// element.\n    track None {\n        default: Bool DEFAULT,\n        kind: VideoKind DEFAULT,\n        label: String DEFAULT,\n        src: Uri DEFAULT,\n        srclang: LanguageTag DEFAULT,\n    };\n\n    /// Build a\n    /// [`<video>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video)\n    /// element.\n    video None {\n        autoplay: Bool DEFAULT,\n        controls: Bool DEFAULT,\n        crossorigin: CrossOrigin DEFAULT,\n        height: usize DEFAULT,\n        r#loop: Bool \"loop\",\n        muted: Bool DEFAULT,\n        preload: Preload DEFAULT,\n        playsinline: Bool DEFAULT,\n        poster: Uri DEFAULT,\n        src: Uri DEFAULT,\n        width: usize DEFAULT,\n    };\n\n\n    // Embedded content\n\n    /// Build a\n    /// [`<embed>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/embed)\n    /// element.\n    embed None {\n        height: usize DEFAULT,\n        src: Uri DEFAULT,\n        r#type: Mime \"type\",\n        width: usize DEFAULT,\n    };\n\n    /// Build a\n    /// [`<iframe>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe)\n    /// element.\n    iframe None {\n        allow: FeaturePolicy DEFAULT,\n        allowfullscreen: Bool DEFAULT,\n        allowpaymentrequest: Bool DEFAULT,\n        height: usize DEFAULT,\n        name: Id DEFAULT,\n        referrerpolicy: ReferrerPolicy DEFAULT,\n        src: Uri DEFAULT,\n        srcdoc: Uri DEFAULT,\n        width: usize DEFAULT,\n\n        margin_width: String \"marginWidth\",\n        align: String DEFAULT,\n        longdesc: String DEFAULT,\n\n        scrolling: String DEFAULT,\n        margin_height: String \"marginHeight\",\n        frame_border: String \"frameBorder\",\n        // sandbox: SpacedSet<Sandbox>,\n    };\n\n    /// Build a\n    /// [`<object>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/object)\n    /// element.\n    object None {\n        data: Uri DEFAULT,\n        form: Id DEFAULT,\n        height: usize DEFAULT,\n        name: Id DEFAULT,\n        r#type: Mime \"type\",\n        typemustmatch: Bool DEFAULT,\n        usemap: String DEFAULT, // TODO should be a fragment starting with '#'\n        width: usize DEFAULT,\n    };\n\n    /// Build a\n    /// [`<param>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/param)\n    /// element.\n    param None {\n        name: String DEFAULT,\n        value: String DEFAULT,\n    };\n\n    /// Build a\n    /// [`<picture>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/picture)\n    /// element.\n    picture None {};\n\n    /// Build a\n    /// [`<source>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/source)\n    /// element.\n    source None {\n        src: Uri DEFAULT,\n        r#type: Mime \"type\",\n    };\n\n\n    // Scripting\n\n    /// Build a\n    /// [`<canvas>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/canvas)\n    /// element.\n    canvas None {\n        height: usize DEFAULT,\n        width: usize DEFAULT,\n    };\n\n    /// Build a\n    /// [`<noscript>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/noscript)\n    /// element.\n    noscript None {};\n\n    /// Build a\n    /// [`<script>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script)\n    /// element.\n    ///\n    /// The script HTML element is used to embed executable code or data; this is typically used to embed or refer to\n    /// JavaScript code. The script element can also be used with other languages, such as WebGL's GLSL shader\n    /// programming language and JSON.\n    script None {\n        /// Normal script elements pass minimal information to the window.onerror for scripts which do not pass the\n        /// standard CORS checks. To allow error logging for sites which use a separate domain for static media, use\n        /// this attribute. See CORS settings attributes for a more descriptive explanation of its valid arguments.\n        crossorigin: CrossOrigin DEFAULT,\n\n        /// This Boolean attribute is set to indicate to a browser that the script is meant to be executed after the\n        /// document has been parsed, but before firing DOMContentLoaded.\n        ///\n        /// Scripts with the defer attribute will prevent the DOMContentLoaded event from firing until the script has\n        /// loaded and finished evaluating.\n        ///\n        /// ----\n        /// ### Warning:\n        ///\n        /// This attribute must not be used if the src attribute is absent (i.e. for inline scripts), in this\n        /// case it would have no effect.\n        ///\n        /// ----\n        ///\n        /// The defer attribute has no effect on module scripts — they defer by default.\n        /// Scripts with the defer attribute will execute in the order in which they appear in the document.\n        ///\n        /// This attribute allows the elimination of parser-blocking JavaScript where the browser would have to load and\n        /// evaluate scripts before continuing to parse. async has a similar effect in this case.\n        defer: Bool DEFAULT,\n        integrity: Integrity DEFAULT,\n        nomodule: Bool DEFAULT,\n        nonce: Nonce DEFAULT,\n        src: Uri DEFAULT,\n        text: String DEFAULT,\n        fetchpriority: String DEFAULT,\n        referrerpolicy: String DEFAULT,\n\n        r#async: Bool \"async\",\n        r#type: String \"type\", // TODO could be an enum\n        r#script: String \"script\",\n    };\n\n\n    // Demarcating edits\n\n    /// Build a\n    /// [`<del>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/del)\n    /// element.\n    del None {\n        cite: Uri DEFAULT,\n        datetime: Datetime DEFAULT,\n    };\n\n    /// Build a\n    /// [`<ins>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ins)\n    /// element.\n    ins None {\n        cite: Uri DEFAULT,\n        datetime: Datetime DEFAULT,\n    };\n\n\n    // Table content\n\n    /// Build a\n    /// [`<caption>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/caption)\n    /// element.\n    caption None {};\n\n    /// Build a\n    /// [`<col>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/col)\n    /// element.\n    col None {\n        span: usize DEFAULT,\n    };\n\n    /// Build a\n    /// [`<colgroup>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/colgroup)\n    /// element.\n    colgroup None {\n        span: usize DEFAULT,\n    };\n\n    /// Build a\n    /// [`<table>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/table)\n    /// element.\n    table None {};\n\n    /// Build a\n    /// [`<tbody>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/tbody)\n    /// element.\n    tbody None {};\n\n    /// Build a\n    /// [`<td>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/td)\n    /// element.\n    td None {\n        colspan: usize DEFAULT,\n        rowspan: usize DEFAULT,\n        // headers: SpacedSet<Id>,\n    };\n\n    /// Build a\n    /// [`<tfoot>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/tfoot)\n    /// element.\n    tfoot None {};\n\n    /// Build a\n    /// [`<th>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/th)\n    /// element.\n    th None {\n        abbr: String DEFAULT,\n        colspan: usize DEFAULT,\n        rowspan: usize DEFAULT,\n        scope: TableHeaderScope DEFAULT,\n        // headers: SpacedSet<Id>,\n    };\n\n    /// Build a\n    /// [`<thead>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/thead)\n    /// element.\n    thead None {};\n\n    /// Build a\n    /// [`<tr>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/tr)\n    /// element.\n    tr None {};\n\n\n    // Forms\n\n    /// Build a\n    /// [`<button>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button)\n    /// element.\n    button None {\n        autofocus: Bool DEFAULT,\n        disabled: Bool DEFAULT,\n        form: Id DEFAULT,\n        formaction: Uri DEFAULT,\n        formenctype: FormEncodingType DEFAULT,\n        formmethod: FormMethod DEFAULT,\n        formnovalidate: Bool DEFAULT,\n        formtarget: Target DEFAULT,\n        name: Id DEFAULT,\n        popovertarget: String DEFAULT,\n        popovertargetaction: String DEFAULT,\n        value: String DEFAULT,\n        r#type: String \"type\",\n    };\n\n    /// Build a\n    /// [`<datalist>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/datalist)\n    /// element.\n    datalist None {};\n\n    /// Build a\n    /// [`<fieldset>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/fieldset)\n    /// element.\n    fieldset None {\n        disabled: Bool DEFAULT,\n        form: Id DEFAULT,\n        name: Id DEFAULT,\n    };\n\n    /// Build a\n    /// [`<form>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form)\n    /// element.\n    form None {\n        // accept-charset: SpacedList<CharacterEncoding>,\n        action: Uri DEFAULT,\n        autocomplete: OnOff DEFAULT,\n        enctype: FormEncodingType DEFAULT,\n        method: FormMethod DEFAULT,\n        name: Id DEFAULT,\n        novalidate: Bool DEFAULT,\n        target: Target DEFAULT,\n    };\n\n    /// Build a\n    /// [`<input>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input)\n    /// element.\n    input None {\n        accept: String DEFAULT,\n        alt: String DEFAULT,\n        autocomplete: String DEFAULT,\n        /// cf. <https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes/autocorrect>\n        autocorrect: OnOff DEFAULT,\n        autofocus: Bool DEFAULT,\n        capture: String DEFAULT,\n        checked: Bool DEFAULT,\n        directory: Bool \"webkitdirectory\",\n        disabled: Bool DEFAULT,\n        form: Id DEFAULT,\n        formaction: Uri DEFAULT,\n        formenctype: FormEncodingType DEFAULT,\n        formmethod: FormDialogMethod DEFAULT,\n        formnovalidate: Bool DEFAULT,\n        formtarget: Target DEFAULT,\n        height: isize DEFAULT,\n        initial_checked: Bool DEFAULT,\n        list: Id DEFAULT,\n        max: String DEFAULT,\n        maxlength: usize DEFAULT,\n        min: String DEFAULT,\n        minlength: usize DEFAULT,\n        multiple: Bool DEFAULT,\n        name: Id DEFAULT,\n        pattern: String DEFAULT,\n        popovertarget: String DEFAULT,\n        popovertargetaction: String DEFAULT,\n        placeholder: String DEFAULT,\n        readonly: Bool DEFAULT,\n        required: Bool DEFAULT,\n        size: usize DEFAULT,\n        spellcheck: Bool DEFAULT,\n        src: Uri DEFAULT,\n        step: String DEFAULT,\n        tabindex: usize DEFAULT,\n        width: isize DEFAULT,\n\n        /// The type of input\n        ///\n        /// Here are the different input types you can use in HTML:\n        ///\n        /// - `button`\n        /// - `checkbox`\n        /// - `color`\n        /// - `date`\n        /// - `datetime-local`\n        /// - `email`\n        /// - `file`\n        /// - `hidden`\n        /// - `image`\n        /// - `month`\n        /// - `number`\n        /// - `password`\n        /// - `radio`\n        /// - `range`\n        /// - `reset`\n        /// - `search`\n        /// - `submit`\n        /// - `tel`\n        /// - `text`\n        /// - `time`\n        /// - `url`\n        /// - `week`\n\n        r#type: InputType \"type\",\n        // value: String,\n        value: String volatile,\n        initial_value: String DEFAULT,\n    };\n\n    /// Build a\n    /// [`<label>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/label)\n    /// element.\n    label None {\n        form: Id DEFAULT,\n        r#for: Id \"for\",\n    };\n\n    /// Build a\n    /// [`<legend>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/legend)\n    /// element.\n    legend None {};\n\n    /// Build a\n    /// [`<meter>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meter)\n    /// element.\n    meter None {\n        value: isize DEFAULT,\n        min: isize DEFAULT,\n        max: isize DEFAULT,\n        low: isize DEFAULT,\n        high: isize DEFAULT,\n        optimum: isize DEFAULT,\n        form: Id DEFAULT,\n    };\n\n    /// Build a\n    /// [`<optgroup>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/optgroup)\n    /// element.\n    optgroup None {\n        disabled: Bool DEFAULT,\n        label: String DEFAULT,\n    };\n\n    /// Build a\n    /// [`<option>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/option)\n    /// element.\n    option None {\n        disabled: Bool DEFAULT,\n        label: String DEFAULT,\n\n\n        value: String DEFAULT,\n\n        selected: Bool volatile,\n        initial_selected: Bool DEFAULT,\n    };\n\n    /// Build a\n    /// [`<output>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/output)\n    /// element.\n    output None {\n        form: Id DEFAULT,\n        name: Id DEFAULT,\n        // r#for: SpacedSet<Id>,\n    };\n\n    /// Build a\n    /// [`<progress>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/progress)\n    /// element.\n    progress None {\n        max: f64 DEFAULT,\n        value: f64 DEFAULT,\n    };\n\n    /// Build a\n    /// [`<select>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/select)\n    /// element.\n    select None {\n        // defined below\n        // value: String,\n        autocomplete: String DEFAULT,\n        autofocus: Bool DEFAULT,\n        disabled: Bool DEFAULT,\n        form: Id DEFAULT,\n        multiple: Bool DEFAULT,\n        name: Id DEFAULT,\n        required: Bool DEFAULT,\n        size: usize DEFAULT,\n        value: String volatile,\n    };\n\n    /// Build a\n    /// [`<textarea>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/textarea)\n    /// element.\n    textarea None {\n        autocomplete: OnOff DEFAULT,\n        /// cf. <https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes/autocorrect>\n        autocorrect: OnOff DEFAULT,\n        autofocus: Bool DEFAULT,\n        cols: usize DEFAULT,\n        disabled: Bool DEFAULT,\n        form: Id DEFAULT,\n        maxlength: usize DEFAULT,\n        minlength: usize DEFAULT,\n        name: Id DEFAULT,\n        placeholder: String DEFAULT,\n        readonly: Bool DEFAULT,\n        required: Bool DEFAULT,\n        rows: usize DEFAULT,\n        spellcheck: BoolOrDefault DEFAULT,\n        wrap: Wrap DEFAULT,\n        value: String volatile,\n\n        initial_value: String DEFAULT,\n    };\n\n\n    // Interactive elements\n\n    /// Build a\n    /// [`<details>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/details)\n    /// element.\n    details None {\n        open: Bool DEFAULT,\n    };\n\n    /// Build dialog\n    /// [`<dialog>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog)\n    /// element.\n    dialog None {\n        open: Bool DEFAULT,\n    };\n\n    /// Build a\n    /// [`<summary>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/summary)\n    /// element.\n    summary None {};\n\n    // Web components\n\n    /// Build a\n    /// [`<slot>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/slot)\n    /// element.\n    slot None {\n        name: String DEFAULT,\n    };\n\n    /// Build a\n    /// [`<template>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template)\n    /// element.\n    template None {};\n\n    // SVG components\n    /// Build a\n    /// [`<svg>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/svg)\n    /// element.\n    svg \"http://www.w3.org/2000/svg\" { };\n\n\n    // /// Build a\n    // /// [`<a>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/a)\n    // /// element.\n    // a \"http://www.w3.org/2000/svg\" {};\n\n    /// Build a\n    /// [`<animate>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/animate)\n    /// element.\n    animate \"http://www.w3.org/2000/svg\" {};\n\n    /// Build a\n    /// [`<animateMotion>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/animateMotion)\n    /// element.\n    animateMotion \"http://www.w3.org/2000/svg\" {};\n\n    /// Build a\n    /// [`<animateTransform>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/animateTransform)\n    /// element.\n    animateTransform \"http://www.w3.org/2000/svg\" {};\n\n    /// Build a\n    /// [`<circle>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/circle)\n    /// element.\n    circle \"http://www.w3.org/2000/svg\" {};\n\n    /// Build a\n    /// [`<clipPath>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/clipPath)\n    /// element.\n    clipPath \"http://www.w3.org/2000/svg\" {};\n\n    /// Build a\n    /// [`<defs>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/defs)\n    /// element.\n    defs \"http://www.w3.org/2000/svg\" {};\n\n    /// Build a\n    /// [`<desc>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/desc)\n    /// element.\n    desc \"http://www.w3.org/2000/svg\" {};\n\n    /// Build a\n    /// [`<discard>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/discard)\n    /// element.\n    discard \"http://www.w3.org/2000/svg\" {};\n\n    /// Build a\n    /// [`<ellipse>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/ellipse)\n    /// element.\n    ellipse \"http://www.w3.org/2000/svg\" {};\n\n    /// Build a\n    /// [`<feBlend>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feBlend)\n    /// element.\n    feBlend \"http://www.w3.org/2000/svg\" {};\n\n    /// Build a\n    /// [`<feColorMatrix>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feColorMatrix)\n    /// element.\n    feColorMatrix \"http://www.w3.org/2000/svg\" {};\n\n    /// Build a\n    /// [`<feComponentTransfer>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feComponentTransfer)\n    /// element.\n    feComponentTransfer \"http://www.w3.org/2000/svg\" {};\n\n    /// Build a\n    /// [`<feComposite>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feComposite)\n    /// element.\n    feComposite \"http://www.w3.org/2000/svg\" {};\n\n    /// Build a\n    /// [`<feConvolveMatrix>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feConvolveMatrix)\n    /// element.\n    feConvolveMatrix \"http://www.w3.org/2000/svg\" {};\n\n    /// Build a\n    /// [`<feDiffuseLighting>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feDiffuseLighting)\n    /// element.\n    feDiffuseLighting \"http://www.w3.org/2000/svg\" {};\n\n    /// Build a\n    /// [`<feDisplacementMap>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feDisplacementMap)\n    /// element.\n    feDisplacementMap \"http://www.w3.org/2000/svg\" {};\n\n    /// Build a\n    /// [`<feDistantLight>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feDistantLight)\n    /// element.\n    feDistantLight \"http://www.w3.org/2000/svg\" {};\n\n    /// Build a\n    /// [`<feDropShadow>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feDropShadow)\n    /// element.\n    feDropShadow \"http://www.w3.org/2000/svg\" {};\n\n    /// Build a\n    /// [`<feFlood>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feFlood)\n    /// element.\n    feFlood \"http://www.w3.org/2000/svg\" {};\n\n    /// Build a\n    /// [`<feFuncA>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feFuncA)\n    /// element.\n    feFuncA \"http://www.w3.org/2000/svg\" {};\n\n    /// Build a\n    /// [`<feFuncB>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feFuncB)\n    /// element.\n    feFuncB \"http://www.w3.org/2000/svg\" {};\n\n    /// Build a\n    /// [`<feFuncG>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feFuncG)\n    /// element.\n    feFuncG \"http://www.w3.org/2000/svg\" {};\n\n    /// Build a\n    /// [`<feFuncR>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feFuncR)\n    /// element.\n    feFuncR \"http://www.w3.org/2000/svg\" {};\n\n    /// Build a\n    /// [`<feGaussianBlur>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feGaussianBlur)\n    /// element.\n    feGaussianBlur \"http://www.w3.org/2000/svg\" {};\n\n    /// Build a\n    /// [`<feImage>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feImage)\n    /// element.\n    feImage \"http://www.w3.org/2000/svg\" {};\n\n    /// Build a\n    /// [`<feMerge>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feMerge)\n    /// element.\n    feMerge \"http://www.w3.org/2000/svg\" {};\n\n    /// Build a\n    /// [`<feMergeNode>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feMergeNode)\n    /// element.\n    feMergeNode \"http://www.w3.org/2000/svg\" {};\n\n    /// Build a\n    /// [`<feMorphology>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feMorphology)\n    /// element.\n    feMorphology \"http://www.w3.org/2000/svg\" {};\n\n    /// Build a\n    /// [`<feOffset>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feOffset)\n    /// element.\n    feOffset \"http://www.w3.org/2000/svg\" {};\n\n    /// Build a\n    /// [`<fePointLight>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/fePointLight)\n    /// element.\n    fePointLight \"http://www.w3.org/2000/svg\" {};\n\n    /// Build a\n    /// [`<feSpecularLighting>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feSpecularLighting)\n    /// element.\n    feSpecularLighting \"http://www.w3.org/2000/svg\" {};\n\n    /// Build a\n    /// [`<feSpotLight>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feSpotLight)\n    /// element.\n    feSpotLight \"http://www.w3.org/2000/svg\" {};\n\n    /// Build a\n    /// [`<feTile>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feTile)\n    /// element.\n    feTile \"http://www.w3.org/2000/svg\" {};\n\n    /// Build a\n    /// [`<feTurbulence>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feTurbulence)\n    /// element.\n    feTurbulence \"http://www.w3.org/2000/svg\" {};\n\n    /// Build a\n    /// [`<filter>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/filter)\n    /// element.\n    filter \"http://www.w3.org/2000/svg\" {};\n\n    /// Build a\n    /// [`<foreignObject>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/foreignObject)\n    /// element.\n    foreignObject \"http://www.w3.org/2000/svg\" {};\n\n    /// Build a\n    /// [`<g>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/g)\n    /// element.\n    g \"http://www.w3.org/2000/svg\" {};\n\n    /// Build a\n    /// [`<hatch>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/hatch)\n    /// element.\n    hatch \"http://www.w3.org/2000/svg\" {};\n\n    /// Build a\n    /// [`<hatchpath>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/hatchpath)\n    /// element.\n    hatchpath \"http://www.w3.org/2000/svg\" {};\n\n    /// Build a\n    /// [`<image>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/image)\n    /// element.\n    image \"http://www.w3.org/2000/svg\" {};\n\n    /// Build a\n    /// [`<line>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/line)\n    /// element.\n    line \"http://www.w3.org/2000/svg\" {};\n\n    /// Build a\n    /// [`<linearGradient>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/linearGradient)\n    /// element.\n    linearGradient \"http://www.w3.org/2000/svg\" {};\n\n    /// Build a\n    /// [`<marker>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/marker)\n    /// element.\n    marker \"http://www.w3.org/2000/svg\" {};\n\n    /// Build a\n    /// [`<mask>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/mask)\n    /// element.\n    mask \"http://www.w3.org/2000/svg\" {};\n\n    /// Build a\n    /// [`<metadata>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/metadata)\n    /// element.\n    metadata \"http://www.w3.org/2000/svg\" {};\n\n    /// Build a\n    /// [`<mpath>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/mpath)\n    /// element.\n    mpath \"http://www.w3.org/2000/svg\" {};\n\n    /// Build a\n    /// [`<path>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/path)\n    /// element.\n    path \"http://www.w3.org/2000/svg\" {};\n\n    /// Build a\n    /// [`<pattern>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/pattern)\n    /// element.\n    pattern \"http://www.w3.org/2000/svg\" {};\n\n    /// Build a\n    /// [`<polygon>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/polygon)\n    /// element.\n    polygon \"http://www.w3.org/2000/svg\" {};\n\n    /// Build a\n    /// [`<polyline>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/polyline)\n    /// element.\n    polyline \"http://www.w3.org/2000/svg\" {};\n\n    /// Build a\n    /// [`<radialGradient>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/radialGradient)\n    /// element.\n    radialGradient \"http://www.w3.org/2000/svg\" {};\n\n    /// Build a\n    /// [`<rect>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/rect)\n    /// element.\n    rect \"http://www.w3.org/2000/svg\" {};\n\n    // /// Build a\n    // /// [`<script>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/script)\n    // /// element.\n    // script \"http://www.w3.org/2000/svg\" {};\n\n    /// Build a\n    /// [`<set>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/set)\n    /// element.\n    set \"http://www.w3.org/2000/svg\" {};\n\n    /// Build a\n    /// [`<stop>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/stop)\n    /// element.\n    stop \"http://www.w3.org/2000/svg\" {};\n\n    // /// Build a\n    // /// [`<style>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/style)\n    // /// element.\n    // style \"http://www.w3.org/2000/svg\" {};\n\n    // /// Build a\n    // /// [`<svg>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/svg)\n    // /// element.\n    // svg \"http://www.w3.org/2000/svg\" {};\n\n    /// Build a\n    /// [`<switch>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/switch)\n    /// element.\n    switch \"http://www.w3.org/2000/svg\" {};\n\n    /// Build a\n    /// [`<symbol>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/symbol)\n    /// element.\n    symbol \"http://www.w3.org/2000/svg\" {};\n\n    /// Build a\n    /// [`<text>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/text)\n    /// element.\n    text \"http://www.w3.org/2000/svg\" {};\n\n    /// Build a\n    /// [`<textPath>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/textPath)\n    /// element.\n    textPath \"http://www.w3.org/2000/svg\" {};\n\n    // /// Build a\n    // /// [`<title>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/title)\n    // /// element.\n    // title \"http://www.w3.org/2000/svg\" {};\n\n    /// Build a\n    /// [`<tspan>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/tspan)\n    /// element.\n    tspan \"http://www.w3.org/2000/svg\" {};\n\n    /// Build a\n    /// [`<view>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/view)\n    /// element.\n    view \"http://www.w3.org/2000/svg\" {};\n\n    // /// Build a\n    // /// [`<use>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/use)\n    // /// element.\n    r#use [\"use\", \"http://www.w3.org/2000/svg\"] {\n        href: String DEFAULT,\n    };\n\n    // MathML elements\n\n    /// Build a\n    /// [`<annotation>`](https://w3c.github.io/mathml-core/#dfn-annotation)\n    /// element.\n    annotation \"http://www.w3.org/1998/Math/MathML\" {\n            encoding: String DEFAULT,\n    };\n\n    /// Build a\n    /// [`<annotation-xml>`](https://w3c.github.io/mathml-core/#dfn-annotation-xml)\n    /// element.\n    annotationXml [\"annotation-xml\", \"http://www.w3.org/1998/Math/MathML\"] {\n            encoding: String DEFAULT,\n    };\n\n    /// Build a\n    /// [`<merror>`](https://developer.mozilla.org/en-US/docs/Web/MathML/Element/merror)\n    /// element.\n    merror \"http://www.w3.org/1998/Math/MathML\" {};\n\n    /// Build a\n    /// [`<math>`](https://developer.mozilla.org/en-US/docs/Web/MathML/Element/math)\n    /// element.\n    math \"http://www.w3.org/1998/Math/MathML\" {\n        display: String DEFAULT,\n    };\n\n    /// Build a\n    /// [`<mfrac>`](https://developer.mozilla.org/en-US/docs/Web/MathML/Element/mfrac)\n    /// element.\n    mfrac \"http://www.w3.org/1998/Math/MathML\" {\n        linethickness: usize DEFAULT,\n    };\n\n    /// Build a\n    /// [`<mi>`](https://developer.mozilla.org/en-US/docs/Web/MathML/Element/mi)\n    /// element.\n    mi \"http://www.w3.org/1998/Math/MathML\" {\n        mathvariant: String DEFAULT,\n    };\n\n    /// Build a\n    /// [`<mmultiscripts>`](https://developer.mozilla.org/en-US/docs/Web/MathML/Element/mmultiscripts)\n    /// element.\n    mmultiscripts \"http://www.w3.org/1998/math/mathml\" {};\n\n    /// Build a\n    /// [`<mn>`](https://developer.mozilla.org/en-US/docs/Web/MathML/Element/mn)\n    /// element.\n    mn \"http://www.w3.org/1998/Math/MathML\" {};\n\n    /// Build a\n    /// [`<mo>`](https://developer.mozilla.org/en-US/docs/Web/MathML/Element/mo)\n    /// element.\n    mo \"http://www.w3.org/1998/Math/MathML\" {\n        fence: Bool DEFAULT,\n        largeop: Bool DEFAULT,\n        lspace: usize DEFAULT,\n        maxsize: usize DEFAULT,\n        minsize: usize DEFAULT,\n        movablelimits: Bool DEFAULT,\n        rspace: usize DEFAULT,\n        separator: Bool DEFAULT,\n        stretchy: Bool DEFAULT,\n        symmetric: Bool DEFAULT,\n    };\n\n    /// Build a\n    /// [`<mover>`](https://developer.mozilla.org/en-US/docs/Web/MathML/Element/mover)\n    /// element.\n    mover \"http://www.w3.org/1998/Math/MathML\" {\n        accent: Bool DEFAULT,\n    };\n\n    /// Build a\n    /// [`<mpadded>`](https://developer.mozilla.org/en-US/docs/Web/MathML/Element/mpadded)\n    /// element.\n    mpadded \"http://www.w3.org/1998/Math/MathML\" {\n        depth: usize DEFAULT,\n        height: usize DEFAULT,\n        lspace: usize DEFAULT,\n        voffset: usize DEFAULT,\n        width: usize DEFAULT,\n    };\n\n    /// Build a\n    /// [`<mphantom>`](https://developer.mozilla.org/en-US/docs/Web/MathML/Element/mphantom)\n    /// element.\n    mphantom \"http://www.w3.org/1998/Math/MathML\" {};\n\n    /// Build a\n    /// [`<mprescripts>`](https://developer.mozilla.org/en-US/docs/Web/MathML/Element/mprescripts)\n    /// element.\n    mprescripts \"http://www.w3.org/1998/Math/MathML\" {};\n\n    /// Build a\n    /// [`<mroot>`](https://developer.mozilla.org/en-US/docs/Web/MathML/Element/mroot)\n    /// element.\n    mroot \"http://www.w3.org/1998/Math/MathML\" {};\n\n    /// Build a\n    /// [`<mrow>`](https://developer.mozilla.org/en-US/docs/Web/MathML/Element/mrow)\n    /// element.\n    mrow \"http://www.w3.org/1998/Math/MathML\" {\n\n    };\n\n    /// Build a\n    /// [`<ms>`](https://developer.mozilla.org/en-US/docs/Web/MathML/Element/ms)\n    /// element.\n    ms \"http://www.w3.org/1998/Math/MathML\" {\n        lquote: String DEFAULT,\n        rquote: String DEFAULT,\n    };\n\n    /// Build a\n    /// [`<mspace>`](https://developer.mozilla.org/en-US/docs/Web/MathML/Element/mspace)\n    /// element.\n    mspace \"http://www.w3.org/1998/Math/MathML\" {\n        depth: usize DEFAULT,\n        height: usize DEFAULT,\n        width: usize DEFAULT,\n    };\n\n    /// Build a\n    /// [`<msqrt>`](https://developer.mozilla.org/en-US/docs/Web/MathML/Element/msqrt)\n    /// element.\n    msqrt \"http://www.w3.org/1998/Math/MathML\" {};\n\n    /// Build a\n    /// [`<mstyle>`](https://developer.mozilla.org/en-US/docs/Web/MathML/Element/mstyle)\n    /// element.\n    mstyle \"http://www.w3.org/1998/Math/MathML\" {};\n\n    /// Build a\n    /// [`<msub>`](https://developer.mozilla.org/en-US/docs/Web/MathML/Element/msub)\n    /// element.\n    msub \"http://www.w3.org/1998/Math/MathML\" {};\n\n    /// Build a\n    /// [`<msubsup>`](https://developer.mozilla.org/en-US/docs/Web/MathML/Element/msubsup)\n    /// element.\n    msubsup \"http://www.w3.org/1998/Math/MathML\" {};\n\n    /// Build a\n    /// [`<msup>`](https://developer.mozilla.org/en-US/docs/Web/MathML/Element/msup)\n    /// element.\n    msup \"http://www.w3.org/1998/Math/MathML\" {};\n\n    /// Build a\n    /// [`<mtable>`](https://developer.mozilla.org/en-US/docs/Web/MathML/Element/mtable)\n    /// element.\n    mtable \"http://www.w3.org/1998/Math/MathML\" {};\n\n    /// Build a\n    /// [`<mtd>`](https://developer.mozilla.org/en-US/docs/Web/MathML/Element/mtd)\n    /// element.\n    mtd \"http://www.w3.org/1998/Math/MathML\" {\n        columnspan: usize DEFAULT,\n        rowspan: usize DEFAULT,\n    };\n\n    /// Build a\n    /// [`<mtext>`](https://developer.mozilla.org/en-US/docs/Web/MathML/Element/mtext)\n    /// element.\n    mtext \"http://www.w3.org/1998/Math/MathML\" {};\n\n    /// Build a\n    /// [`<mtr>`](https://developer.mozilla.org/en-US/docs/Web/MathML/Element/mtr)\n    /// element.\n    mtr \"http://www.w3.org/1998/Math/MathML\" {};\n\n    /// Build a\n    /// [`<munder>`](https://developer.mozilla.org/en-US/docs/Web/MathML/Element/munder)\n    /// element.\n    munder \"http://www.w3.org/1998/Math/MathML\" {\n        accentunder: Bool DEFAULT,\n    };\n\n    /// Build a\n    /// [`<munderover>`](https://developer.mozilla.org/en-US/docs/Web/MathML/Element/munderover)\n    /// element.\n    munderover \"http://www.w3.org/1998/Math/MathML\" {\n        accent: Bool DEFAULT,\n        accentunder: Bool DEFAULT,\n    };\n\n    /// Build a\n    /// [`<semantics>`](https://developer.mozilla.org/en-US/docs/Web/MathML/Element/semantics)\n    /// element.\n    semantics \"http://www.w3.org/1998/Math/MathML\" {\n        encoding: String DEFAULT,\n    };\n}\n"
  },
  {
    "path": "packages/html/src/events/animation.rs",
    "content": "use dioxus_core::Event;\n\npub type AnimationEvent = Event<AnimationData>;\n\npub struct AnimationData {\n    inner: Box<dyn HasAnimationData>,\n}\n\nimpl AnimationData {\n    /// Create a new AnimationData\n    pub fn new(inner: impl HasAnimationData + 'static) -> Self {\n        Self {\n            inner: Box::new(inner),\n        }\n    }\n\n    /// The name of the animation\n    pub fn animation_name(&self) -> String {\n        self.inner.animation_name()\n    }\n\n    /// The name of the pseudo-element the animation runs on\n    pub fn pseudo_element(&self) -> String {\n        self.inner.pseudo_element()\n    }\n\n    /// The amount of time the animation has been running\n    pub fn elapsed_time(&self) -> f32 {\n        self.inner.elapsed_time()\n    }\n\n    /// Downcast this event to a concrete event type\n    #[inline(always)]\n    pub fn downcast<T: 'static>(&self) -> Option<&T> {\n        self.inner.as_ref().as_any().downcast_ref::<T>()\n    }\n}\n\nimpl<E: HasAnimationData> From<E> for AnimationData {\n    fn from(e: E) -> Self {\n        Self { inner: Box::new(e) }\n    }\n}\n\nimpl std::fmt::Debug for AnimationData {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_struct(\"AnimationData\")\n            .field(\"animation_name\", &self.animation_name())\n            .field(\"pseudo_element\", &self.pseudo_element())\n            .field(\"elapsed_time\", &self.elapsed_time())\n            .finish()\n    }\n}\n\nimpl PartialEq for AnimationData {\n    fn eq(&self, other: &Self) -> bool {\n        self.animation_name() == other.animation_name()\n            && self.pseudo_element() == other.pseudo_element()\n            && self.elapsed_time() == other.elapsed_time()\n    }\n}\n\n#[cfg(feature = \"serialize\")]\n/// A serialized version of AnimationData\n#[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq, Clone)]\npub struct SerializedAnimationData {\n    animation_name: String,\n    pseudo_element: String,\n    elapsed_time: f32,\n}\n\n#[cfg(feature = \"serialize\")]\nimpl From<&AnimationData> for SerializedAnimationData {\n    fn from(data: &AnimationData) -> Self {\n        Self {\n            animation_name: data.animation_name(),\n            pseudo_element: data.pseudo_element(),\n            elapsed_time: data.elapsed_time(),\n        }\n    }\n}\n\n#[cfg(feature = \"serialize\")]\nimpl HasAnimationData for SerializedAnimationData {\n    fn animation_name(&self) -> String {\n        self.animation_name.clone()\n    }\n\n    fn pseudo_element(&self) -> String {\n        self.pseudo_element.clone()\n    }\n\n    fn elapsed_time(&self) -> f32 {\n        self.elapsed_time\n    }\n\n    fn as_any(&self) -> &dyn std::any::Any {\n        self\n    }\n}\n\n#[cfg(feature = \"serialize\")]\nimpl serde::Serialize for AnimationData {\n    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {\n        SerializedAnimationData::from(self).serialize(serializer)\n    }\n}\n\n#[cfg(feature = \"serialize\")]\nimpl<'de> serde::Deserialize<'de> for AnimationData {\n    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {\n        let data = SerializedAnimationData::deserialize(deserializer)?;\n        Ok(Self {\n            inner: Box::new(data),\n        })\n    }\n}\n\n/// A trait for any object that has the data for an animation event\npub trait HasAnimationData: std::any::Any {\n    /// The name of the animation\n    fn animation_name(&self) -> String;\n\n    /// The name of the pseudo-element the animation runs on\n    fn pseudo_element(&self) -> String;\n\n    /// The amount of time the animation has been running\n    fn elapsed_time(&self) -> f32;\n\n    /// return self as Any\n    fn as_any(&self) -> &dyn std::any::Any;\n}\n\nimpl_event! [\n    AnimationData;\n\n    /// onanimationstart\n    onanimationstart\n\n    /// onanimationend\n    onanimationend\n\n    /// onanimationiteration\n    onanimationiteration\n];\n"
  },
  {
    "path": "packages/html/src/events/cancel.rs",
    "content": "use dioxus_core::Event;\n\npub type CancelEvent = Event<CancelData>;\n\npub struct CancelData {\n    inner: Box<dyn HasCancelData>,\n}\n\nimpl<E: HasCancelData> From<E> for CancelData {\n    fn from(e: E) -> Self {\n        Self { inner: Box::new(e) }\n    }\n}\n\nimpl std::fmt::Debug for CancelData {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_struct(\"CancelData\").finish()\n    }\n}\n\nimpl PartialEq for CancelData {\n    fn eq(&self, _other: &Self) -> bool {\n        true\n    }\n}\n\nimpl CancelData {\n    /// Create a new CancelData\n    pub fn new(inner: impl HasCancelData + 'static) -> Self {\n        Self {\n            inner: Box::new(inner),\n        }\n    }\n\n    /// Downcast this event to a concrete event type\n    #[inline(always)]\n    pub fn downcast<T: 'static>(&self) -> Option<&T> {\n        self.inner.as_any().downcast_ref::<T>()\n    }\n}\n\n#[cfg(feature = \"serialize\")]\n/// A serialized version of CancelData\n#[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq, Clone)]\npub struct SerializedCancelData {}\n\n#[cfg(feature = \"serialize\")]\nimpl From<&CancelData> for SerializedCancelData {\n    fn from(_: &CancelData) -> Self {\n        Self {}\n    }\n}\n\n#[cfg(feature = \"serialize\")]\nimpl HasCancelData for SerializedCancelData {\n    fn as_any(&self) -> &dyn std::any::Any {\n        self\n    }\n}\n\n#[cfg(feature = \"serialize\")]\nimpl serde::Serialize for CancelData {\n    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {\n        SerializedCancelData::from(self).serialize(serializer)\n    }\n}\n\n#[cfg(feature = \"serialize\")]\nimpl<'de> serde::Deserialize<'de> for CancelData {\n    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {\n        let data = SerializedCancelData::deserialize(deserializer)?;\n        Ok(Self {\n            inner: Box::new(data),\n        })\n    }\n}\n\npub trait HasCancelData: std::any::Any {\n    /// return self as Any\n    fn as_any(&self) -> &dyn std::any::Any;\n}\n\nimpl_event! {\n    CancelData;\n\n    /// oncancel\n    oncancel\n}\n"
  },
  {
    "path": "packages/html/src/events/clipboard.rs",
    "content": "use dioxus_core::Event;\n\npub type ClipboardEvent = Event<ClipboardData>;\n\npub struct ClipboardData {\n    inner: Box<dyn HasClipboardData>,\n}\n\nimpl<E: HasClipboardData> From<E> for ClipboardData {\n    fn from(e: E) -> Self {\n        Self { inner: Box::new(e) }\n    }\n}\n\nimpl std::fmt::Debug for ClipboardData {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_struct(\"ClipboardData\").finish()\n    }\n}\n\nimpl PartialEq for ClipboardData {\n    fn eq(&self, _other: &Self) -> bool {\n        true\n    }\n}\n\nimpl ClipboardData {\n    /// Create a new ClipboardData\n    pub fn new(inner: impl HasClipboardData) -> Self {\n        Self {\n            inner: Box::new(inner),\n        }\n    }\n\n    /// Downcast this event to a concrete event type\n    #[inline(always)]\n    pub fn downcast<T: 'static>(&self) -> Option<&T> {\n        self.inner.as_ref().as_any().downcast_ref::<T>()\n    }\n}\n\n#[cfg(feature = \"serialize\")]\n/// A serialized version of ClipboardData\n#[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq, Clone)]\npub struct SerializedClipboardData {}\n\n#[cfg(feature = \"serialize\")]\nimpl From<&ClipboardData> for SerializedClipboardData {\n    fn from(_: &ClipboardData) -> Self {\n        Self {}\n    }\n}\n\n#[cfg(feature = \"serialize\")]\nimpl HasClipboardData for SerializedClipboardData {\n    fn as_any(&self) -> &dyn std::any::Any {\n        self\n    }\n}\n\n#[cfg(feature = \"serialize\")]\nimpl serde::Serialize for ClipboardData {\n    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {\n        SerializedClipboardData::from(self).serialize(serializer)\n    }\n}\n\n#[cfg(feature = \"serialize\")]\nimpl<'de> serde::Deserialize<'de> for ClipboardData {\n    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {\n        let data = SerializedClipboardData::deserialize(deserializer)?;\n        Ok(Self {\n            inner: Box::new(data),\n        })\n    }\n}\n\npub trait HasClipboardData: std::any::Any {\n    /// return self as Any\n    fn as_any(&self) -> &dyn std::any::Any;\n}\n\nimpl_event![\n    ClipboardData;\n\n    /// oncopy\n    oncopy\n\n    /// oncut\n    oncut\n\n    /// onpaste\n    onpaste\n];\n"
  },
  {
    "path": "packages/html/src/events/composition.rs",
    "content": "use dioxus_core::Event;\n\npub type CompositionEvent = Event<CompositionData>;\n\npub struct CompositionData {\n    inner: Box<dyn HasCompositionData>,\n}\n\nimpl std::fmt::Debug for CompositionData {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_struct(\"CompositionData\")\n            .field(\"data\", &self.data())\n            .finish()\n    }\n}\n\nimpl<E: HasCompositionData> From<E> for CompositionData {\n    fn from(e: E) -> Self {\n        Self { inner: Box::new(e) }\n    }\n}\n\nimpl PartialEq for CompositionData {\n    fn eq(&self, other: &Self) -> bool {\n        self.data() == other.data()\n    }\n}\n\nimpl CompositionData {\n    /// Create a new CompositionData\n    pub fn new(inner: impl HasCompositionData + 'static) -> Self {\n        Self {\n            inner: Box::new(inner),\n        }\n    }\n\n    /// The characters generated by the input method that raised the event\n    pub fn data(&self) -> String {\n        self.inner.data()\n    }\n\n    pub fn downcast<T: 'static>(&self) -> Option<&T> {\n        self.inner.as_any().downcast_ref()\n    }\n}\n\n#[cfg(feature = \"serialize\")]\n/// A serialized version of CompositionData\n#[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq, Clone)]\npub struct SerializedCompositionData {\n    data: String,\n}\n\n#[cfg(feature = \"serialize\")]\nimpl From<&CompositionData> for SerializedCompositionData {\n    fn from(data: &CompositionData) -> Self {\n        Self { data: data.data() }\n    }\n}\n\n#[cfg(feature = \"serialize\")]\nimpl HasCompositionData for SerializedCompositionData {\n    fn data(&self) -> String {\n        self.data.clone()\n    }\n\n    fn as_any(&self) -> &dyn std::any::Any {\n        self\n    }\n}\n\n#[cfg(feature = \"serialize\")]\nimpl serde::Serialize for CompositionData {\n    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {\n        SerializedCompositionData::from(self).serialize(serializer)\n    }\n}\n\n#[cfg(feature = \"serialize\")]\nimpl<'de> serde::Deserialize<'de> for CompositionData {\n    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {\n        let data = SerializedCompositionData::deserialize(deserializer)?;\n        Ok(Self {\n            inner: Box::new(data),\n        })\n    }\n}\n\n/// A trait for any object that has the data for a composition event\npub trait HasCompositionData: std::any::Any {\n    /// The characters generated by the input method that raised the event\n    fn data(&self) -> String;\n\n    /// return self as Any\n    fn as_any(&self) -> &dyn std::any::Any;\n}\n\nimpl_event! [\n    CompositionData;\n\n    /// oncompositionstart\n    oncompositionstart\n\n    /// oncompositionend\n    oncompositionend\n\n    /// oncompositionupdate\n    oncompositionupdate\n];\n"
  },
  {
    "path": "packages/html/src/events/drag.rs",
    "content": "use crate::input_data::{MouseButton, MouseButtonSet};\nuse crate::*;\nuse crate::{\n    data_transfer::DataTransfer,\n    geometry::{ClientPoint, Coordinates, ElementPoint, PagePoint, ScreenPoint},\n};\n\nuse dioxus_core::Event;\nuse keyboard_types::Modifiers;\n\nuse crate::HasMouseData;\n\npub type DragEvent = Event<DragData>;\n\n/// The DragEvent interface is a DOM event that represents a drag and drop interaction. The user initiates a drag by\n/// placing a pointer device (such as a mouse) on the touch surface and then dragging the pointer to a new location\n/// (such as another DOM element). Applications are free to interpret a drag and drop interaction in an\n/// application-specific way.\npub struct DragData {\n    inner: Box<dyn HasDragData>,\n}\n\nimpl<E: HasDragData + 'static> From<E> for DragData {\n    fn from(e: E) -> Self {\n        Self { inner: Box::new(e) }\n    }\n}\n\nimpl std::fmt::Debug for DragData {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_struct(\"DragData\")\n            .field(\"coordinates\", &self.coordinates())\n            .field(\"modifiers\", &self.modifiers())\n            .field(\"held_buttons\", &self.held_buttons())\n            .field(\"trigger_button\", &self.trigger_button())\n            .finish()\n    }\n}\n\nimpl PartialEq for DragData {\n    fn eq(&self, other: &Self) -> bool {\n        self.coordinates() == other.coordinates()\n            && self.modifiers() == other.modifiers()\n            && self.held_buttons() == other.held_buttons()\n            && self.trigger_button() == other.trigger_button()\n    }\n}\n\nimpl DragData {\n    /// Create a new DragData\n    pub fn new(inner: impl HasDragData + 'static) -> Self {\n        Self {\n            inner: Box::new(inner),\n        }\n    }\n\n    /// The DataTransfer object is used to hold the data that is being dragged during a drag and drop operation.\n    pub fn data_transfer(&self) -> DataTransfer {\n        self.inner.data_transfer()\n    }\n\n    /// Downcast this event data to a specific type\n    #[inline(always)]\n    pub fn downcast<T: 'static>(&self) -> Option<&T> {\n        HasDragData::as_any(&*self.inner).downcast_ref::<T>()\n    }\n}\n\nimpl crate::HasFileData for DragData {\n    fn files(&self) -> Vec<FileData> {\n        self.inner.files()\n    }\n}\n\nimpl InteractionLocation for DragData {\n    fn client_coordinates(&self) -> ClientPoint {\n        self.inner.client_coordinates()\n    }\n\n    fn page_coordinates(&self) -> PagePoint {\n        self.inner.page_coordinates()\n    }\n\n    fn screen_coordinates(&self) -> ScreenPoint {\n        self.inner.screen_coordinates()\n    }\n}\n\nimpl InteractionElementOffset for DragData {\n    fn element_coordinates(&self) -> ElementPoint {\n        self.inner.element_coordinates()\n    }\n\n    fn coordinates(&self) -> Coordinates {\n        self.inner.coordinates()\n    }\n}\n\nimpl ModifiersInteraction for DragData {\n    fn modifiers(&self) -> Modifiers {\n        self.inner.modifiers()\n    }\n}\n\nimpl PointerInteraction for DragData {\n    fn held_buttons(&self) -> MouseButtonSet {\n        self.inner.held_buttons()\n    }\n\n    // todo the following is kind of bad; should we just return None when the trigger_button is unreliable (and frankly irrelevant)? i guess we would need the event_type here\n    fn trigger_button(&self) -> Option<MouseButton> {\n        self.inner.trigger_button()\n    }\n}\n\n#[cfg(feature = \"serialize\")]\npub use ser::*;\n\n#[cfg(feature = \"serialize\")]\nmod ser {\n    use super::*;\n\n    /// A serialized version of DragData\n    #[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq, Clone)]\n    pub struct SerializedDragData {\n        pub mouse: crate::point_interaction::SerializedPointInteraction,\n\n        pub data_transfer: crate::data_transfer::SerializedDataTransfer,\n    }\n\n    impl SerializedDragData {\n        fn new(drag: &DragData) -> Self {\n            Self {\n                mouse: crate::point_interaction::SerializedPointInteraction::from(drag),\n                data_transfer: crate::data_transfer::SerializedDataTransfer::from(drag),\n            }\n        }\n    }\n\n    impl HasDataTransferData for SerializedDragData {\n        fn data_transfer(&self) -> crate::data_transfer::DataTransfer {\n            crate::data_transfer::DataTransfer::new(self.data_transfer.clone())\n        }\n    }\n\n    impl HasDragData for SerializedDragData {\n        fn as_any(&self) -> &dyn std::any::Any {\n            self\n        }\n    }\n\n    impl crate::file_data::HasFileData for SerializedDragData {\n        fn files(&self) -> Vec<FileData> {\n            self.data_transfer\n                .files\n                .iter()\n                .map(|f| FileData::new(f.clone()))\n                .collect()\n        }\n    }\n\n    impl HasMouseData for SerializedDragData {\n        fn as_any(&self) -> &dyn std::any::Any {\n            self\n        }\n    }\n\n    impl InteractionLocation for SerializedDragData {\n        fn client_coordinates(&self) -> ClientPoint {\n            self.mouse.client_coordinates()\n        }\n\n        fn page_coordinates(&self) -> PagePoint {\n            self.mouse.page_coordinates()\n        }\n\n        fn screen_coordinates(&self) -> ScreenPoint {\n            self.mouse.screen_coordinates()\n        }\n    }\n\n    impl InteractionElementOffset for SerializedDragData {\n        fn element_coordinates(&self) -> ElementPoint {\n            self.mouse.element_coordinates()\n        }\n\n        fn coordinates(&self) -> Coordinates {\n            self.mouse.coordinates()\n        }\n    }\n\n    impl ModifiersInteraction for SerializedDragData {\n        fn modifiers(&self) -> Modifiers {\n            self.mouse.modifiers()\n        }\n    }\n\n    impl PointerInteraction for SerializedDragData {\n        fn held_buttons(&self) -> MouseButtonSet {\n            self.mouse.held_buttons()\n        }\n\n        fn trigger_button(&self) -> Option<MouseButton> {\n            self.mouse.trigger_button()\n        }\n    }\n\n    impl serde::Serialize for DragData {\n        fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {\n            SerializedDragData::new(self).serialize(serializer)\n        }\n    }\n\n    impl<'de> serde::Deserialize<'de> for DragData {\n        fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {\n            let data = SerializedDragData::deserialize(deserializer)?;\n            Ok(Self {\n                inner: Box::new(data),\n            })\n        }\n    }\n}\n\n/// A trait for any object that has the data for a drag event\npub trait HasDragData: HasMouseData + crate::HasFileData + crate::HasDataTransferData {\n    /// return self as Any\n    fn as_any(&self) -> &dyn std::any::Any;\n}\n\nimpl_event! {\n    DragData;\n\n    /// ondrag\n    ondrag\n\n    /// ondragend\n    ondragend\n\n    /// ondragenter\n    ondragenter\n\n    /// ondragexit\n    ondragexit\n\n    /// ondragleave\n    ondragleave\n\n    /// ondragover\n    ondragover\n\n    /// ondragstart\n    ondragstart\n\n    /// ondrop\n    ondrop\n}\n"
  },
  {
    "path": "packages/html/src/events/focus.rs",
    "content": "use dioxus_core::Event;\n\npub type FocusEvent = Event<FocusData>;\n\npub struct FocusData {\n    inner: Box<dyn HasFocusData>,\n}\n\nimpl<E: HasFocusData> From<E> for FocusData {\n    fn from(e: E) -> Self {\n        Self { inner: Box::new(e) }\n    }\n}\n\nimpl std::fmt::Debug for FocusData {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_struct(\"FocusData\").finish()\n    }\n}\n\nimpl PartialEq for FocusData {\n    fn eq(&self, _other: &Self) -> bool {\n        true\n    }\n}\n\nimpl FocusData {\n    /// Create a new FocusData\n    pub fn new(inner: impl HasFocusData + 'static) -> Self {\n        Self {\n            inner: Box::new(inner),\n        }\n    }\n\n    /// Downcast this event data to a specific type\n    #[inline(always)]\n    pub fn downcast<T: 'static>(&self) -> Option<&T> {\n        self.inner.as_any().downcast_ref::<T>()\n    }\n}\n\n#[cfg(feature = \"serialize\")]\n/// A serialized version of FocusData\n#[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq, Clone, Default)]\npub struct SerializedFocusData {}\n\n#[cfg(feature = \"serialize\")]\nimpl From<&FocusData> for SerializedFocusData {\n    fn from(_: &FocusData) -> Self {\n        Self {}\n    }\n}\n\n#[cfg(feature = \"serialize\")]\nimpl HasFocusData for SerializedFocusData {\n    fn as_any(&self) -> &dyn std::any::Any {\n        self\n    }\n}\n\n#[cfg(feature = \"serialize\")]\nimpl serde::Serialize for FocusData {\n    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {\n        SerializedFocusData::from(self).serialize(serializer)\n    }\n}\n\n#[cfg(feature = \"serialize\")]\nimpl<'de> serde::Deserialize<'de> for FocusData {\n    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {\n        let data = SerializedFocusData::deserialize(deserializer)?;\n        Ok(Self {\n            inner: Box::new(data),\n        })\n    }\n}\n\npub trait HasFocusData: std::any::Any {\n    /// return self as Any\n    fn as_any(&self) -> &dyn std::any::Any;\n}\n\nimpl_event! [\n    FocusData;\n\n    /// onfocus\n    onfocus\n\n    // onfocusout\n    onfocusout\n\n    // onfocusin\n    onfocusin\n\n    /// onblur\n    onblur\n];\n"
  },
  {
    "path": "packages/html/src/events/form.rs",
    "content": "use crate::file_data::HasFileData;\nuse crate::FileData;\nuse std::fmt::Debug;\n\nuse dioxus_core::Event;\n\npub type FormEvent = Event<FormData>;\n\n/* DOMEvent:  Send + SyncTarget relatedTarget */\npub struct FormData {\n    inner: Box<dyn HasFormData>,\n}\n\nimpl FormData {\n    /// Create a new form event\n    pub fn new(event: impl HasFormData + 'static) -> Self {\n        Self {\n            inner: Box::new(event),\n        }\n    }\n\n    /// Get the value of the form event\n    pub fn value(&self) -> String {\n        self.inner.value()\n    }\n\n    /// Get the value of the form event as a parsed type\n    pub fn parsed<T>(&self) -> Result<T, T::Err>\n    where\n        T: std::str::FromStr,\n    {\n        self.value().parse()\n    }\n\n    /// Try to parse the value as a boolean\n    ///\n    /// Returns false if the value is not a boolean, or if it is false!\n    /// Does not verify anything about the event itself, use with caution\n    pub fn checked(&self) -> bool {\n        self.value().parse().unwrap_or(false)\n    }\n\n    /// Collect all the named form values from the containing form.\n    ///\n    /// Every input must be named!\n    pub fn values(&self) -> Vec<(String, FormValue)> {\n        self.inner.values()\n    }\n\n    /// Get the first value with the given name\n    pub fn get_first(&self, name: &str) -> Option<FormValue> {\n        self.values()\n            .into_iter()\n            .find_map(|(k, v)| if k == name { Some(v) } else { None })\n    }\n\n    /// Get all values with the given name\n    pub fn get(&self, name: &str) -> Vec<FormValue> {\n        self.values()\n            .into_iter()\n            .filter_map(|(k, v)| if k == name { Some(v) } else { None })\n            .collect()\n    }\n\n    /// Get the files of the form event\n    pub fn files(&self) -> Vec<FileData> {\n        self.inner.files()\n    }\n\n    /// Downcast this event to a concrete event type\n    #[inline(always)]\n    pub fn downcast<T: 'static>(&self) -> Option<&T> {\n        self.inner.as_any().downcast_ref::<T>()\n    }\n\n    /// Did this form pass its own validation?\n    pub fn valid(&self) -> bool {\n        !self.inner.value().is_empty()\n    }\n}\n\nimpl FormData {\n    /// Parse the values into a struct with one field per value\n    #[cfg(feature = \"serialize\")]\n    pub fn parsed_values<T>(&self) -> Result<T, serde_json::Error>\n    where\n        T: serde::de::DeserializeOwned,\n    {\n        use crate::SerializedFileData;\n\n        let values = &self.values();\n\n        let mut map = serde_json::Map::new();\n        for (key, value) in values {\n            let entry = map\n                .entry(key.clone())\n                .or_insert_with(|| serde_json::Value::Array(Vec::new()));\n\n            match value {\n                FormValue::Text(text) => {\n                    entry\n                        .as_array_mut()\n                        .expect(\"entry should be an array\")\n                        .push(serde_json::Value::String(text.clone()));\n                }\n                // we create the serialized variant with no bytes\n                // SerializedFileData, if given a real path, will read the bytes from disk (synchronously)\n                FormValue::File(Some(file_data)) => {\n                    let serialized = SerializedFileData {\n                        path: file_data.path().to_owned(),\n                        size: file_data.size(),\n                        last_modified: file_data.last_modified(),\n                        content_type: file_data.content_type(),\n                        contents: None,\n                    };\n                    entry\n                        .as_array_mut()\n                        .expect(\"entry should be an array\")\n                        .push(serde_json::to_value(&serialized).unwrap_or(serde_json::Value::Null));\n                }\n                FormValue::File(None) => {\n                    entry\n                        .as_array_mut()\n                        .expect(\"entry should be an array\")\n                        .push(\n                            serde_json::to_value(SerializedFileData::empty())\n                                .unwrap_or(serde_json::Value::Null),\n                        );\n                }\n            }\n        }\n\n        // Go through the map and convert single-element arrays to just the element\n        let map = map\n            .into_iter()\n            .map(|(k, v)| match v {\n                serde_json::Value::Array(arr) if arr.len() == 1 => {\n                    (k, arr.into_iter().next().unwrap())\n                }\n                _ => (k, v),\n            })\n            .collect::<serde_json::Map<String, serde_json::Value>>();\n\n        serde_json::from_value(serde_json::Value::Object(map))\n    }\n}\n\nimpl HasFileData for FormData {\n    fn files(&self) -> Vec<FileData> {\n        self.inner.files()\n    }\n}\n\nimpl<E: HasFormData> From<E> for FormData {\n    fn from(e: E) -> Self {\n        Self { inner: Box::new(e) }\n    }\n}\n\nimpl PartialEq for FormData {\n    fn eq(&self, other: &Self) -> bool {\n        self.value() == other.value() && self.values() == other.values()\n    }\n}\n\nimpl Debug for FormData {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_struct(\"FormEvent\")\n            .field(\"value\", &self.value())\n            .field(\"values\", &self.values())\n            .field(\"valid\", &self.valid())\n            .finish()\n    }\n}\n\n/// A value in a form, either text or a file\n#[derive(Debug, Clone, PartialEq)]\npub enum FormValue {\n    Text(String),\n    File(Option<FileData>),\n}\n\nimpl PartialEq<str> for FormValue {\n    fn eq(&self, other: &str) -> bool {\n        match self {\n            FormValue::Text(s) => s == other,\n            FormValue::File(_f) => false,\n        }\n    }\n}\n\nimpl PartialEq<&str> for FormValue {\n    fn eq(&self, other: &&str) -> bool {\n        match self {\n            FormValue::Text(s) => s == other,\n            FormValue::File(_f) => false,\n        }\n    }\n}\n\n/// An object that has all the data for a form event\npub trait HasFormData: HasFileData + std::any::Any {\n    fn value(&self) -> String;\n\n    fn valid(&self) -> bool;\n\n    fn values(&self) -> Vec<(String, FormValue)>;\n\n    /// return self as Any\n    fn as_any(&self) -> &dyn std::any::Any;\n}\n\n#[cfg(feature = \"serialize\")]\npub use serialize::*;\n\n#[cfg(feature = \"serialize\")]\nmod serialize {\n    use crate::SerializedFileData;\n\n    use super::*;\n\n    /// A serialized form data object\n    #[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq, Clone)]\n    pub struct SerializedFormData {\n        #[serde(default)]\n        pub value: String,\n\n        #[serde(default)]\n        pub values: Vec<SerializedFormObject>,\n\n        #[serde(default)]\n        pub valid: bool,\n    }\n\n    #[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq, Clone)]\n    pub struct SerializedFormObject {\n        pub key: String,\n        pub text: Option<String>,\n        pub file: Option<SerializedFileData>,\n    }\n\n    #[cfg(feature = \"serialize\")]\n    impl SerializedFormData {\n        /// Create a new serialized form data object\n        pub fn new(value: String, values: Vec<SerializedFormObject>) -> Self {\n            Self {\n                value,\n                values,\n                valid: true,\n            }\n        }\n\n        /// Create a serialized form data object from a form data object\n        fn from_form_lossy(data: &FormData) -> Self {\n            if let Some(data) = data.downcast::<SerializedFormData>() {\n                return data.clone();\n            }\n\n            let values = data\n                .values()\n                .iter()\n                .map(|(key, value)| match value {\n                    FormValue::Text(s) => SerializedFormObject {\n                        key: key.clone(),\n                        text: Some(s.to_string()),\n                        file: None,\n                    },\n                    FormValue::File(f) => SerializedFormObject {\n                        key: key.clone(),\n                        text: None,\n                        file: if let Some(f) = f {\n                            Some(SerializedFileData {\n                                path: f.path(),\n                                size: f.size(),\n                                last_modified: f.last_modified(),\n                                content_type: f.content_type(),\n                                contents: None,\n                            })\n                        } else {\n                            Some(SerializedFileData::empty())\n                        },\n                    },\n                })\n                .collect();\n\n            Self {\n                values,\n                value: data.value(),\n                valid: data.valid(),\n            }\n        }\n    }\n\n    impl HasFormData for SerializedFormData {\n        fn value(&self) -> String {\n            self.value.clone()\n        }\n\n        fn values(&self) -> Vec<(String, FormValue)> {\n            self.values\n                .iter()\n                .map(|v| {\n                    let value = if let Some(text) = &v.text {\n                        FormValue::Text(text.clone())\n                    } else if let Some(_file) = &v.file {\n                        // todo: we lose the file contents here\n                        FormValue::File(None)\n                    } else {\n                        FormValue::File(None)\n                    };\n                    (v.key.clone(), value)\n                })\n                .collect()\n        }\n\n        fn valid(&self) -> bool {\n            self.valid\n        }\n\n        fn as_any(&self) -> &dyn std::any::Any {\n            self\n        }\n    }\n\n    impl HasFileData for SerializedFormData {\n        fn files(&self) -> Vec<FileData> {\n            self.values\n                .iter()\n                .filter_map(|v| v.file.as_ref().map(|f| FileData::new(f.clone())))\n                .collect()\n        }\n    }\n\n    impl serde::Serialize for FormData {\n        fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {\n            SerializedFormData::from_form_lossy(self).serialize(serializer)\n        }\n    }\n\n    impl<'de> serde::Deserialize<'de> for FormData {\n        fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {\n            let data = SerializedFormData::deserialize(deserializer)?;\n            Ok(Self {\n                inner: Box::new(data),\n            })\n        }\n    }\n}\n\nimpl_event! {\n    FormData;\n\n    /// onchange\n    onchange\n\n    /// The `oninput` event is fired when the value of a `<input>`, `<select>`, or `<textarea>` element is changed.\n    ///\n    /// There are two main approaches to updating your input element:\n    /// 1) Controlled inputs directly update the value of the input element as the user interacts with the element\n    ///\n    /// ```rust\n    /// use dioxus::prelude::*;\n    ///\n    /// fn App() -> Element {\n    ///     let mut value = use_signal(|| \"hello world\".to_string());\n    ///\n    ///     rsx! {\n    ///         input {\n    ///             // We directly set the value of the input element to our value signal\n    ///             value: \"{value}\",\n    ///             // The `oninput` event handler will run every time the user changes the value of the input element\n    ///             // We can set the `value` signal to the new value of the input element\n    ///             oninput: move |event| value.set(event.value())\n    ///         }\n    ///         // Since this is a controlled input, we can also update the value of the input element directly\n    ///         button {\n    ///             onclick: move |_| value.write().clear(),\n    ///             \"Clear\"\n    ///         }\n    ///     }\n    /// }\n    /// ```\n    ///\n    /// 2) Uncontrolled inputs just read the value of the input element as it changes\n    ///\n    /// ```rust\n    /// use dioxus::prelude::*;\n    ///\n    /// fn App() -> Element {\n    ///     rsx! {\n    ///         input {\n    ///             // In uncontrolled inputs, we don't set the value of the input element directly\n    ///             // But you can still read the value of the input element\n    ///             oninput: move |event| println!(\"{}\", event.value()),\n    ///         }\n    ///         // Since we don't directly control the value of the input element, we can't easily modify it\n    ///     }\n    /// }\n    /// ```\n    oninput\n\n    /// oninvalid\n    oninvalid\n\n    /// onreset\n    onreset\n\n    /// onsubmit\n    onsubmit\n}\n"
  },
  {
    "path": "packages/html/src/events/image.rs",
    "content": "use dioxus_core::Event;\n\npub type ImageEvent = Event<ImageData>;\npub struct ImageData {\n    inner: Box<dyn HasImageData>,\n}\n\nimpl<E: HasImageData> From<E> for ImageData {\n    fn from(e: E) -> Self {\n        Self { inner: Box::new(e) }\n    }\n}\n\nimpl std::fmt::Debug for ImageData {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_struct(\"ImageData\")\n            .field(\"load_error\", &self.load_error())\n            .finish()\n    }\n}\n\nimpl PartialEq for ImageData {\n    fn eq(&self, other: &Self) -> bool {\n        self.load_error() == other.load_error()\n    }\n}\n\nimpl ImageData {\n    /// Create a new ImageData\n    pub fn new(e: impl HasImageData) -> Self {\n        Self { inner: Box::new(e) }\n    }\n\n    /// If the renderer encountered an error while loading the image\n    pub fn load_error(&self) -> bool {\n        self.inner.load_error()\n    }\n\n    pub fn downcast<T: 'static>(&self) -> Option<&T> {\n        self.inner.as_any().downcast_ref::<T>()\n    }\n}\n\n#[cfg(feature = \"serialize\")]\n/// A serialized version of ImageData\n#[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq, Clone)]\npub struct SerializedImageData {\n    #[cfg_attr(feature = \"serialize\", serde(default))]\n    load_error: bool,\n}\n\n#[cfg(feature = \"serialize\")]\nimpl From<&ImageData> for SerializedImageData {\n    fn from(data: &ImageData) -> Self {\n        Self {\n            load_error: data.load_error(),\n        }\n    }\n}\n\n#[cfg(feature = \"serialize\")]\nimpl HasImageData for SerializedImageData {\n    fn as_any(&self) -> &dyn std::any::Any {\n        self\n    }\n\n    fn load_error(&self) -> bool {\n        self.load_error\n    }\n}\n\n#[cfg(feature = \"serialize\")]\nimpl serde::Serialize for ImageData {\n    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {\n        SerializedImageData::from(self).serialize(serializer)\n    }\n}\n\n#[cfg(feature = \"serialize\")]\nimpl<'de> serde::Deserialize<'de> for ImageData {\n    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {\n        let data = SerializedImageData::deserialize(deserializer)?;\n        Ok(Self {\n            inner: Box::new(data),\n        })\n    }\n}\n\n/// A trait for any object that has the data for an image event\npub trait HasImageData: std::any::Any {\n    /// If the renderer encountered an error while loading the image\n    fn load_error(&self) -> bool;\n\n    /// return self as Any\n    fn as_any(&self) -> &dyn std::any::Any;\n}\n\nimpl_event! [\n    ImageData;\n\n    /// onerror\n    onerror\n\n    /// onload\n    onload\n];\n"
  },
  {
    "path": "packages/html/src/events/keyboard.rs",
    "content": "use dioxus_core::Event;\nuse keyboard_types::{Code, Key, Location, Modifiers};\nuse std::fmt::Debug;\n\nuse crate::ModifiersInteraction;\n\n#[cfg(feature = \"serialize\")]\nfn resilient_deserialize_code<'de, D>(deserializer: D) -> Result<Code, D::Error>\nwhere\n    D: serde::Deserializer<'de>,\n{\n    use serde::Deserialize;\n    // If we fail to deserialize the code for any reason, just return Unidentified instead of failing.\n    Ok(Code::deserialize(deserializer).unwrap_or(Code::Unidentified))\n}\n\npub type KeyboardEvent = Event<KeyboardData>;\npub struct KeyboardData {\n    inner: Box<dyn HasKeyboardData>,\n}\n\nimpl<E: HasKeyboardData> From<E> for KeyboardData {\n    fn from(e: E) -> Self {\n        Self { inner: Box::new(e) }\n    }\n}\n\nimpl std::fmt::Debug for KeyboardData {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_struct(\"KeyboardData\")\n            .field(\"key\", &self.key())\n            .field(\"code\", &self.code())\n            .field(\"modifiers\", &self.modifiers())\n            .field(\"location\", &self.location())\n            .field(\"is_auto_repeating\", &self.is_auto_repeating())\n            .field(\"is_composing\", &self.is_composing())\n            .finish()\n    }\n}\n\nimpl PartialEq for KeyboardData {\n    fn eq(&self, other: &Self) -> bool {\n        self.key() == other.key()\n            && self.code() == other.code()\n            && self.modifiers() == other.modifiers()\n            && self.location() == other.location()\n            && self.is_auto_repeating() == other.is_auto_repeating()\n            && self.is_composing() == other.is_composing()\n    }\n}\n\nimpl KeyboardData {\n    /// Create a new KeyboardData\n    pub fn new(inner: impl HasKeyboardData + 'static) -> Self {\n        Self {\n            inner: Box::new(inner),\n        }\n    }\n\n    /// The value of the key pressed by the user, taking into consideration the state of modifier keys such as Shift as well as the keyboard locale and layout.\n    pub fn key(&self) -> Key {\n        self.inner.key()\n    }\n\n    /// A physical key on the keyboard (as opposed to the character generated by pressing the key). In other words, this property returns a value that isn't altered by keyboard layout or the state of the modifier keys.\n    pub fn code(&self) -> Code {\n        self.inner.code()\n    }\n\n    /// The location of the key on the keyboard or other input device.\n    pub fn location(&self) -> Location {\n        self.inner.location()\n    }\n\n    /// `true` iff the key is being held down such that it is automatically repeating.\n    pub fn is_auto_repeating(&self) -> bool {\n        self.inner.is_auto_repeating()\n    }\n\n    /// Indicates whether the key is fired within a composition session.\n    pub fn is_composing(&self) -> bool {\n        self.inner.is_composing()\n    }\n\n    /// Downcast this KeyboardData to a concrete type.\n    #[inline(always)]\n    pub fn downcast<T: 'static>(&self) -> Option<&T> {\n        self.inner.as_any().downcast_ref::<T>()\n    }\n}\n\nimpl ModifiersInteraction for KeyboardData {\n    fn modifiers(&self) -> Modifiers {\n        self.inner.modifiers()\n    }\n}\n\n#[cfg(feature = \"serialize\")]\n/// A serialized version of KeyboardData\n#[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq, Clone)]\npub struct SerializedKeyboardData {\n    char_code: u32,\n    is_composing: bool,\n    key: String,\n    key_code: KeyCode,\n    #[serde(deserialize_with = \"resilient_deserialize_code\")]\n    code: Code,\n    alt_key: bool,\n    ctrl_key: bool,\n    meta_key: bool,\n    shift_key: bool,\n    location: usize,\n    repeat: bool,\n    which: usize,\n}\n\n#[cfg(feature = \"serialize\")]\nimpl SerializedKeyboardData {\n    /// Create a new SerializedKeyboardData\n    pub fn new(\n        key: Key,\n        code: Code,\n        location: Location,\n        is_auto_repeating: bool,\n        modifiers: Modifiers,\n        is_composing: bool,\n    ) -> Self {\n        Self {\n            char_code: key.legacy_charcode(),\n            is_composing,\n            key: key.to_string(),\n            key_code: KeyCode::from_raw_code(\n                std::convert::TryInto::try_into(key.legacy_keycode())\n                    .expect(\"could not convert keycode to u8\"),\n            ),\n            code,\n            alt_key: modifiers.contains(Modifiers::ALT),\n            ctrl_key: modifiers.contains(Modifiers::CONTROL),\n            meta_key: modifiers.contains(Modifiers::META),\n            shift_key: modifiers.contains(Modifiers::SHIFT),\n            location: crate::input_data::encode_key_location(location),\n            repeat: is_auto_repeating,\n            which: std::convert::TryInto::try_into(key.legacy_charcode())\n                .expect(\"could not convert charcode to usize\"),\n        }\n    }\n}\n\n#[cfg(feature = \"serialize\")]\nimpl From<&KeyboardData> for SerializedKeyboardData {\n    fn from(data: &KeyboardData) -> Self {\n        Self::new(\n            data.key(),\n            data.code(),\n            data.location(),\n            data.is_auto_repeating(),\n            data.modifiers(),\n            data.is_composing(),\n        )\n    }\n}\n\n#[cfg(feature = \"serialize\")]\nimpl HasKeyboardData for SerializedKeyboardData {\n    fn key(&self) -> Key {\n        std::str::FromStr::from_str(&self.key).unwrap_or(Key::Unidentified)\n    }\n\n    fn code(&self) -> Code {\n        self.code\n    }\n\n    fn location(&self) -> Location {\n        crate::input_data::decode_key_location(self.location)\n    }\n\n    fn is_auto_repeating(&self) -> bool {\n        self.repeat\n    }\n\n    fn is_composing(&self) -> bool {\n        self.is_composing\n    }\n\n    fn as_any(&self) -> &dyn std::any::Any {\n        self\n    }\n}\n\n#[cfg(feature = \"serialize\")]\nimpl ModifiersInteraction for SerializedKeyboardData {\n    fn modifiers(&self) -> Modifiers {\n        let mut modifiers = Modifiers::empty();\n\n        if self.alt_key {\n            modifiers.insert(Modifiers::ALT);\n        }\n        if self.ctrl_key {\n            modifiers.insert(Modifiers::CONTROL);\n        }\n        if self.meta_key {\n            modifiers.insert(Modifiers::META);\n        }\n        if self.shift_key {\n            modifiers.insert(Modifiers::SHIFT);\n        }\n\n        modifiers\n    }\n}\n\n#[cfg(feature = \"serialize\")]\nimpl serde::Serialize for KeyboardData {\n    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {\n        SerializedKeyboardData::from(self).serialize(serializer)\n    }\n}\n\n#[cfg(feature = \"serialize\")]\nimpl<'de> serde::Deserialize<'de> for KeyboardData {\n    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {\n        let data = SerializedKeyboardData::deserialize(deserializer)?;\n        Ok(Self {\n            inner: Box::new(data),\n        })\n    }\n}\n\nimpl_event! {\n    KeyboardData;\n\n    /// onkeydown\n    onkeydown\n\n    /// onkeypress\n    onkeypress\n\n    /// onkeyup\n    onkeyup\n}\n\npub trait HasKeyboardData: ModifiersInteraction + std::any::Any {\n    /// The value of the key pressed by the user, taking into consideration the state of modifier keys such as Shift as well as the keyboard locale and layout.\n    fn key(&self) -> Key;\n\n    /// A physical key on the keyboard (as opposed to the character generated by pressing the key). In other words, this property returns a value that isn't altered by keyboard layout or the state of the modifier keys.\n    fn code(&self) -> Code;\n\n    /// The location of the key on the keyboard or other input device.\n    fn location(&self) -> Location;\n\n    /// `true` iff the key is being held down such that it is automatically repeating.\n    fn is_auto_repeating(&self) -> bool;\n\n    /// Indicates whether the key is fired within a composition session.\n    fn is_composing(&self) -> bool;\n\n    /// return self as Any\n    fn as_any(&self) -> &dyn std::any::Any;\n}\n\n#[cfg(feature = \"serialize\")]\nimpl<'de> serde::Deserialize<'de> for KeyCode {\n    fn deserialize<D>(deserializer: D) -> Result<KeyCode, D::Error>\n    where\n        D: serde::Deserializer<'de>,\n    {\n        // We could be deserializing a unicode character, so we need to use u64 even if the output only takes u8\n        let value = u64::deserialize(deserializer)?;\n\n        if let Ok(smaller_uint) = value.try_into() {\n            Ok(KeyCode::from_raw_code(smaller_uint))\n        } else {\n            Ok(KeyCode::Unknown)\n        }\n    }\n}\n\n#[cfg_attr(feature = \"serialize\", derive(serde_repr::Serialize_repr))]\n#[derive(Clone, Copy, Debug, Eq, PartialEq)]\n#[repr(u8)]\npub enum KeyCode {\n    // That key has no keycode, = 0\n    // break, = 3\n    // backspace / delete, = 8\n    // tab, = 9\n    // clear, = 12\n    // enter, = 13\n    // shift, = 16\n    // ctrl, = 17\n    // alt, = 18\n    // pause/break, = 19\n    // caps lock, = 20\n    // hangul, = 21\n    // hanja, = 25\n    // escape, = 27\n    // conversion, = 28\n    // non-conversion, = 29\n    // spacebar, = 32\n    // page up, = 33\n    // page down, = 34\n    // end, = 35\n    // home, = 36\n    // left arrow, = 37\n    // up arrow, = 38\n    // right arrow, = 39\n    // down arrow, = 40\n    // select, = 41\n    // print, = 42\n    // execute, = 43\n    // Print Screen, = 44\n    // insert, = 45\n    // delete, = 46\n    // help, = 47\n    // 0, = 48\n    // 1, = 49\n    // 2, = 50\n    // 3, = 51\n    // 4, = 52\n    // 5, = 53\n    // 6, = 54\n    // 7, = 55\n    // 8, = 56\n    // 9, = 57\n    // :, = 58\n    // semicolon (firefox), equals, = 59\n    // <, = 60\n    // equals (firefox), = 61\n    // ß, = 63\n    // @ (firefox), = 64\n    // a, = 65\n    // b, = 66\n    // c, = 67\n    // d, = 68\n    // e, = 69\n    // f, = 70\n    // g, = 71\n    // h, = 72\n    // i, = 73\n    // j, = 74\n    // k, = 75\n    // l, = 76\n    // m, = 77\n    // n, = 78\n    // o, = 79\n    // p, = 80\n    // q, = 81\n    // r, = 82\n    // s, = 83\n    // t, = 84\n    // u, = 85\n    // v, = 86\n    // w, = 87\n    // x, = 88\n    // y, = 89\n    // z, = 90\n    // Windows Key / Left ⌘ / Chromebook Search key, = 91\n    // right window key, = 92\n    // Windows Menu / Right ⌘, = 93\n    // sleep, = 95\n    // numpad 0, = 96\n    // numpad 1, = 97\n    // numpad 2, = 98\n    // numpad 3, = 99\n    // numpad 4, = 100\n    // numpad 5, = 101\n    // numpad 6, = 102\n    // numpad 7, = 103\n    // numpad 8, = 104\n    // numpad 9, = 105\n    // multiply, = 106\n    // add, = 107\n    // numpad period (firefox), = 108\n    // subtract, = 109\n    // decimal point, = 110\n    // divide, = 111\n    // f1, = 112\n    // f2, = 113\n    // f3, = 114\n    // f4, = 115\n    // f5, = 116\n    // f6, = 117\n    // f7, = 118\n    // f8, = 119\n    // f9, = 120\n    // f10, = 121\n    // f11, = 122\n    // f12, = 123\n    // f13, = 124\n    // f14, = 125\n    // f15, = 126\n    // f16, = 127\n    // f17, = 128\n    // f18, = 129\n    // f19, = 130\n    // f20, = 131\n    // f21, = 132\n    // f22, = 133\n    // f23, = 134\n    // f24, = 135\n    // f25, = 136\n    // f26, = 137\n    // f27, = 138\n    // f28, = 139\n    // f29, = 140\n    // f30, = 141\n    // f31, = 142\n    // f32, = 143\n    // num lock, = 144\n    // scroll lock, = 145\n    // airplane mode, = 151\n    // ^, = 160\n    // !, = 161\n    // ؛ (arabic semicolon), = 162\n    // #, = 163\n    // $, = 164\n    // ù, = 165\n    // page backward, = 166\n    // page forward, = 167\n    // refresh, = 168\n    // closing paren (AZERTY), = 169\n    // *, = 170\n    // ~ + * key, = 171\n    // home key, = 172\n    // minus (firefox), mute/unmute, = 173\n    // decrease volume level, = 174\n    // increase volume level, = 175\n    // next, = 176\n    // previous, = 177\n    // stop, = 178\n    // play/pause, = 179\n    // e-mail, = 180\n    // mute/unmute (firefox), = 181\n    // decrease volume level (firefox), = 182\n    // increase volume level (firefox), = 183\n    // semi-colon / ñ, = 186\n    // equal sign, = 187\n    // comma, = 188\n    // dash, = 189\n    // period, = 190\n    // forward slash / ç, = 191\n    // grave accent / ñ / æ / ö, = 192\n    // ?, / or °, = 193\n    // numpad period (chrome), = 194\n    // open bracket, = 219\n    // back slash, = 220\n    // close bracket / å, = 221\n    // single quote / ø / ä, = 222\n    // `, = 223\n    // left or right ⌘ key (firefox), = 224\n    // altgr, = 225\n    // < /git >, left back slash, = 226\n    // GNOME Compose Key, = 230\n    // ç, = 231\n    // XF86Forward, = 233\n    // XF86Back, = 234\n    // non-conversion, = 235\n    // alphanumeric, = 240\n    // hiragana/katakana, = 242\n    // half-width/full-width, = 243\n    // kanji, = 244\n    // unlock trackpad (Chrome/Edge), = 251\n    // toggle touchpad, = 255\n    NA = 0,\n    Break = 3,\n    Backspace = 8,\n    Tab = 9,\n    Clear = 12,\n    Enter = 13,\n    Shift = 16,\n    Ctrl = 17,\n    Alt = 18,\n    Pause = 19,\n    CapsLock = 20,\n    // hangul, = 21\n    // hanja, = 25\n    Escape = 27,\n    // conversion, = 28\n    // non-conversion, = 29\n    Space = 32,\n    PageUp = 33,\n    PageDown = 34,\n    End = 35,\n    Home = 36,\n    LeftArrow = 37,\n    UpArrow = 38,\n    RightArrow = 39,\n    DownArrow = 40,\n    // select, = 41\n    // print, = 42\n    // execute, = 43\n    // Print Screen, = 44\n    Insert = 45,\n    Delete = 46,\n    // help, = 47\n    Num0 = 48,\n    Num1 = 49,\n    Num2 = 50,\n    Num3 = 51,\n    Num4 = 52,\n    Num5 = 53,\n    Num6 = 54,\n    Num7 = 55,\n    Num8 = 56,\n    Num9 = 57,\n    // :, = 58\n    // semicolon (firefox), equals, = 59\n    // <, = 60\n    // equals (firefox), = 61\n    // ß, = 63\n    // @ (firefox), = 64\n    A = 65,\n    B = 66,\n    C = 67,\n    D = 68,\n    E = 69,\n    F = 70,\n    G = 71,\n    H = 72,\n    I = 73,\n    J = 74,\n    K = 75,\n    L = 76,\n    M = 77,\n    N = 78,\n    O = 79,\n    P = 80,\n    Q = 81,\n    R = 82,\n    S = 83,\n    T = 84,\n    U = 85,\n    V = 86,\n    W = 87,\n    X = 88,\n    Y = 89,\n    Z = 90,\n    LeftWindow = 91,\n    RightWindow = 92,\n    SelectKey = 93,\n    Numpad0 = 96,\n    Numpad1 = 97,\n    Numpad2 = 98,\n    Numpad3 = 99,\n    Numpad4 = 100,\n    Numpad5 = 101,\n    Numpad6 = 102,\n    Numpad7 = 103,\n    Numpad8 = 104,\n    Numpad9 = 105,\n    Multiply = 106,\n    Add = 107,\n    Subtract = 109,\n    DecimalPoint = 110,\n    Divide = 111,\n    F1 = 112,\n    F2 = 113,\n    F3 = 114,\n    F4 = 115,\n    F5 = 116,\n    F6 = 117,\n    F7 = 118,\n    F8 = 119,\n    F9 = 120,\n    F10 = 121,\n    F11 = 122,\n    F12 = 123,\n    // f13, = 124\n    // f14, = 125\n    // f15, = 126\n    // f16, = 127\n    // f17, = 128\n    // f18, = 129\n    // f19, = 130\n    // f20, = 131\n    // f21, = 132\n    // f22, = 133\n    // f23, = 134\n    // f24, = 135\n    // f25, = 136\n    // f26, = 137\n    // f27, = 138\n    // f28, = 139\n    // f29, = 140\n    // f30, = 141\n    // f31, = 142\n    // f32, = 143\n    NumLock = 144,\n    ScrollLock = 145,\n    // airplane mode, = 151\n    // ^, = 160\n    // !, = 161\n    // ؛ (arabic semicolon), = 162\n    // #, = 163\n    // $, = 164\n    // ù, = 165\n    // page backward, = 166\n    // page forward, = 167\n    // refresh, = 168\n    // closing paren (AZERTY), = 169\n    // *, = 170\n    // ~ + * key, = 171\n    // home key, = 172\n    // minus (firefox), mute/unmute, = 173\n    // decrease volume level, = 174\n    // increase volume level, = 175\n    // next, = 176\n    // previous, = 177\n    // stop, = 178\n    // play/pause, = 179\n    // e-mail, = 180\n    // mute/unmute (firefox), = 181\n    // decrease volume level (firefox), = 182\n    // increase volume level (firefox), = 183\n    Semicolon = 186,\n    EqualSign = 187,\n    Comma = 188,\n    Dash = 189,\n    Period = 190,\n    ForwardSlash = 191,\n    GraveAccent = 192,\n    // ?, / or °, = 193\n    // numpad period (chrome), = 194\n    OpenBracket = 219,\n    BackSlash = 220,\n    CloseBracket = 221,\n    SingleQuote = 222,\n    // `, = 223\n    // left or right ⌘ key (firefox), = 224\n    // altgr, = 225\n    // < /git >, left back slash, = 226\n    // GNOME Compose Key, = 230\n    // ç, = 231\n    // XF86Forward, = 233\n    // XF86Back, = 234\n    // non-conversion, = 235\n    // alphanumeric, = 240\n    // hiragana/katakana, = 242\n    // half-width/full-width, = 243\n    // kanji, = 244\n    // unlock trackpad (Chrome/Edge), = 251\n    // toggle touchpad, = 255\n    Unknown,\n}\n\nimpl KeyCode {\n    pub fn from_raw_code(i: u8) -> Self {\n        use KeyCode::*;\n        match i {\n            8 => Backspace,\n            9 => Tab,\n            13 => Enter,\n            16 => Shift,\n            17 => Ctrl,\n            18 => Alt,\n            19 => Pause,\n            20 => CapsLock,\n            27 => Escape,\n            33 => PageUp,\n            34 => PageDown,\n            35 => End,\n            36 => Home,\n            37 => LeftArrow,\n            38 => UpArrow,\n            39 => RightArrow,\n            40 => DownArrow,\n            45 => Insert,\n            46 => Delete,\n            48 => Num0,\n            49 => Num1,\n            50 => Num2,\n            51 => Num3,\n            52 => Num4,\n            53 => Num5,\n            54 => Num6,\n            55 => Num7,\n            56 => Num8,\n            57 => Num9,\n            65 => A,\n            66 => B,\n            67 => C,\n            68 => D,\n            69 => E,\n            70 => F,\n            71 => G,\n            72 => H,\n            73 => I,\n            74 => J,\n            75 => K,\n            76 => L,\n            77 => M,\n            78 => N,\n            79 => O,\n            80 => P,\n            81 => Q,\n            82 => R,\n            83 => S,\n            84 => T,\n            85 => U,\n            86 => V,\n            87 => W,\n            88 => X,\n            89 => Y,\n            90 => Z,\n            91 => LeftWindow,\n            92 => RightWindow,\n            93 => SelectKey,\n            96 => Numpad0,\n            97 => Numpad1,\n            98 => Numpad2,\n            99 => Numpad3,\n            100 => Numpad4,\n            101 => Numpad5,\n            102 => Numpad6,\n            103 => Numpad7,\n            104 => Numpad8,\n            105 => Numpad9,\n            106 => Multiply,\n            107 => Add,\n            109 => Subtract,\n            110 => DecimalPoint,\n            111 => Divide,\n            112 => F1,\n            113 => F2,\n            114 => F3,\n            115 => F4,\n            116 => F5,\n            117 => F6,\n            118 => F7,\n            119 => F8,\n            120 => F9,\n            121 => F10,\n            122 => F11,\n            123 => F12,\n            144 => NumLock,\n            145 => ScrollLock,\n            186 => Semicolon,\n            187 => EqualSign,\n            188 => Comma,\n            189 => Dash,\n            190 => Period,\n            191 => ForwardSlash,\n            192 => GraveAccent,\n            219 => OpenBracket,\n            220 => BackSlash,\n            221 => CloseBracket,\n            222 => SingleQuote,\n            _ => Unknown,\n        }\n    }\n\n    // get the raw code\n    pub fn raw_code(&self) -> u32 {\n        *self as u32\n    }\n}\n"
  },
  {
    "path": "packages/html/src/events/media.rs",
    "content": "use dioxus_core::Event;\n\npub type MediaEvent = Event<MediaData>;\npub struct MediaData {\n    inner: Box<dyn HasMediaData>,\n}\n\nimpl<E: HasMediaData> From<E> for MediaData {\n    fn from(e: E) -> Self {\n        Self { inner: Box::new(e) }\n    }\n}\n\nimpl std::fmt::Debug for MediaData {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_struct(\"MediaData\").finish()\n    }\n}\n\nimpl PartialEq for MediaData {\n    fn eq(&self, _: &Self) -> bool {\n        true\n    }\n}\n\nimpl MediaData {\n    /// Create a new MediaData\n    pub fn new(inner: impl HasMediaData + 'static) -> Self {\n        Self {\n            inner: Box::new(inner),\n        }\n    }\n\n    /// Downcast this event to a concrete event type\n    #[inline(always)]\n    pub fn downcast<T: 'static>(&self) -> Option<&T> {\n        self.inner.as_any().downcast_ref::<T>()\n    }\n}\n\n#[cfg(feature = \"serialize\")]\n/// A serialized version of MediaData\n#[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq, Clone)]\npub struct SerializedMediaData {}\n\n#[cfg(feature = \"serialize\")]\nimpl From<&MediaData> for SerializedMediaData {\n    fn from(_: &MediaData) -> Self {\n        Self {}\n    }\n}\n\n#[cfg(feature = \"serialize\")]\nimpl HasMediaData for SerializedMediaData {\n    fn as_any(&self) -> &dyn std::any::Any {\n        self\n    }\n}\n\n#[cfg(feature = \"serialize\")]\nimpl serde::Serialize for MediaData {\n    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {\n        SerializedMediaData::from(self).serialize(serializer)\n    }\n}\n\n#[cfg(feature = \"serialize\")]\nimpl<'de> serde::Deserialize<'de> for MediaData {\n    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {\n        let data = SerializedMediaData::deserialize(deserializer)?;\n        Ok(Self {\n            inner: Box::new(data),\n        })\n    }\n}\n\npub trait HasMediaData: std::any::Any {\n    /// return self as Any\n    fn as_any(&self) -> &dyn std::any::Any;\n}\n\nimpl_event! [\n    MediaData;\n\n    ///abort\n    onabort\n\n    ///canplay\n    oncanplay\n\n    ///canplaythrough\n    oncanplaythrough\n\n    ///durationchange\n    ondurationchange\n\n    ///emptied\n    onemptied\n\n    ///encrypted\n    onencrypted\n\n    ///ended\n    onended\n\n    // todo: this conflicts with Media events\n    // neither have data, so it's okay\n    // ///error\n    // onerror\n\n    ///loadeddata\n    onloadeddata\n\n    ///loadedmetadata\n    onloadedmetadata\n\n    ///loadstart\n    onloadstart\n\n    ///pause\n    onpause\n\n    ///play\n    onplay\n\n    ///playing\n    onplaying\n\n    ///progress\n    onprogress\n\n    ///ratechange\n    onratechange\n\n    ///seeked\n    onseeked\n\n    ///seeking\n    onseeking\n\n    ///stalled\n    onstalled\n\n    ///suspend\n    onsuspend\n\n    ///timeupdate\n    ontimeupdate\n\n    ///volumechange\n    onvolumechange\n\n    ///waiting\n    onwaiting\n];\n"
  },
  {
    "path": "packages/html/src/events/mod.rs",
    "content": "#![doc = include_str!(\"../../docs/event_handlers.md\")]\n\nuse std::any::Any;\nuse std::sync::RwLock;\n\nmacro_rules! impl_event {\n    (\n        $data:ty;\n        $(\n            $( #[$attr:meta] )*\n            $name:ident $(: $js_name:literal)?\n        )*\n    ) => {\n        $(\n            $( #[$attr] )*\n            /// <details open>\n            /// <summary>General Event Handler Information</summary>\n            ///\n            #[doc = include_str!(\"../../docs/event_handlers.md\")]\n            ///\n            /// </details>\n            ///\n            #[doc = include_str!(\"../../docs/common_event_handler_errors.md\")]\n            $(\n                #[doc(alias = $js_name)]\n            )?\n            #[inline]\n            pub fn $name<__Marker>(mut _f: impl ::dioxus_core::SuperInto<::dioxus_core::ListenerCallback<$data>, __Marker>) -> ::dioxus_core::Attribute {\n                let event_handler = _f.super_into();\n                ::dioxus_core::Attribute::new(\n                    impl_event!(@name $name $($js_name)?),\n                    ::dioxus_core::AttributeValue::listener(move |e: ::dioxus_core::Event<crate::PlatformEventData>| {\n                        let event: ::dioxus_core::Event<$data> = e.map(|data| {\n                            data.into()\n                        });\n                        event_handler.call(event.into_any());\n                    }),\n                    None,\n                    false,\n                ).into()\n            }\n\n            #[doc(hidden)]\n            $( #[$attr] )*\n            pub mod $name {\n                use super::*;\n\n                // When expanding the macro, we use this version of the function if we see an inline closure to give better type inference\n                $( #[$attr] )*\n                pub fn call_with_explicit_closure<\n                    __Marker,\n                    Return: ::dioxus_core::SpawnIfAsync<__Marker> + 'static,\n                >(\n                    event_handler: impl FnMut(::dioxus_core::Event<$data>) -> Return + 'static,\n                ) -> ::dioxus_core::Attribute {\n                    #[allow(deprecated)]\n                    super::$name(event_handler)\n                }\n            }\n        )*\n    };\n\n    (@name $name:ident $js_name:literal) => {\n        $js_name\n    };\n    (@name $name:ident) => {\n        stringify!($name)\n    };\n}\n\nstatic EVENT_CONVERTER: RwLock<Option<Box<dyn HtmlEventConverter>>> = RwLock::new(None);\n\n#[inline]\npub fn set_event_converter(converter: Box<dyn HtmlEventConverter>) {\n    *EVENT_CONVERTER.write().unwrap() = Some(converter);\n}\n\n#[inline]\npub(crate) fn with_event_converter<F, R>(f: F) -> R\nwhere\n    F: FnOnce(&dyn HtmlEventConverter) -> R,\n{\n    let converter = EVENT_CONVERTER.read().unwrap();\n    f(converter.as_ref().unwrap().as_ref())\n}\n\n/// A platform specific event.\npub struct PlatformEventData {\n    event: Box<dyn Any>,\n}\n\nimpl PlatformEventData {\n    pub fn new(event: Box<dyn Any>) -> Self {\n        Self { event }\n    }\n\n    pub fn inner(&self) -> &Box<dyn Any> {\n        &self.event\n    }\n\n    pub fn downcast<T: 'static>(&self) -> Option<&T> {\n        self.event.downcast_ref::<T>()\n    }\n\n    pub fn downcast_mut<T: 'static>(&mut self) -> Option<&mut T> {\n        self.event.downcast_mut::<T>()\n    }\n\n    pub fn into_inner<T: 'static>(self) -> Option<T> {\n        self.event.downcast::<T>().ok().map(|e| *e)\n    }\n}\n\n/// A converter between a platform specific event and a general event. All code in a renderer that has a large binary size should be placed in this trait. Each of these functions should be snipped in high levels of optimization.\npub trait HtmlEventConverter: Send + Sync {\n    /// Convert a general event to an animation data event\n    fn convert_animation_data(&self, event: &PlatformEventData) -> AnimationData;\n    /// Convert a general event to a cancel data event\n    fn convert_cancel_data(&self, event: &PlatformEventData) -> CancelData;\n    /// Convert a general event to a clipboard data event\n    fn convert_clipboard_data(&self, event: &PlatformEventData) -> ClipboardData;\n    /// Convert a general event to a composition data event\n    fn convert_composition_data(&self, event: &PlatformEventData) -> CompositionData;\n    /// Convert a general event to a drag data event\n    fn convert_drag_data(&self, event: &PlatformEventData) -> DragData;\n    /// Convert a general event to a focus data event\n    fn convert_focus_data(&self, event: &PlatformEventData) -> FocusData;\n    /// Convert a general event to a form data event\n    fn convert_form_data(&self, event: &PlatformEventData) -> FormData;\n    /// Convert a general event to an image data event\n    fn convert_image_data(&self, event: &PlatformEventData) -> ImageData;\n    /// Convert a general event to a keyboard data event\n    fn convert_keyboard_data(&self, event: &PlatformEventData) -> KeyboardData;\n    /// Convert a general event to a media data event\n    fn convert_media_data(&self, event: &PlatformEventData) -> MediaData;\n    /// Convert a general event to a mounted data event\n    fn convert_mounted_data(&self, event: &PlatformEventData) -> MountedData;\n    /// Convert a general event to a mouse data event\n    fn convert_mouse_data(&self, event: &PlatformEventData) -> MouseData;\n    /// Convert a general event to a pointer data event\n    fn convert_pointer_data(&self, event: &PlatformEventData) -> PointerData;\n    /// Convert a general event to a resize data event\n    fn convert_resize_data(&self, event: &PlatformEventData) -> ResizeData;\n    /// Convert a general event to a scroll data event\n    fn convert_scroll_data(&self, event: &PlatformEventData) -> ScrollData;\n    /// Convert a general event to a selection data event\n    fn convert_selection_data(&self, event: &PlatformEventData) -> SelectionData;\n    /// Convert a general event to a toggle data event\n    fn convert_toggle_data(&self, event: &PlatformEventData) -> ToggleData;\n    /// Convert a general event to a touch data event\n    fn convert_touch_data(&self, event: &PlatformEventData) -> TouchData;\n    /// Convert a general event to a transition data event\n    fn convert_transition_data(&self, event: &PlatformEventData) -> TransitionData;\n    /// Convert a general event to a visible data event\n    fn convert_visible_data(&self, event: &PlatformEventData) -> VisibleData;\n    /// Convert a general event to a wheel data event\n    fn convert_wheel_data(&self, event: &PlatformEventData) -> WheelData;\n}\n\nimpl From<&PlatformEventData> for AnimationData {\n    fn from(val: &PlatformEventData) -> Self {\n        with_event_converter(|c| c.convert_animation_data(val))\n    }\n}\n\nimpl From<&PlatformEventData> for CancelData {\n    fn from(val: &PlatformEventData) -> Self {\n        with_event_converter(|c| c.convert_cancel_data(val))\n    }\n}\n\nimpl From<&PlatformEventData> for ClipboardData {\n    fn from(val: &PlatformEventData) -> Self {\n        with_event_converter(|c| c.convert_clipboard_data(val))\n    }\n}\n\nimpl From<&PlatformEventData> for CompositionData {\n    fn from(val: &PlatformEventData) -> Self {\n        with_event_converter(|c| c.convert_composition_data(val))\n    }\n}\n\nimpl From<&PlatformEventData> for DragData {\n    fn from(val: &PlatformEventData) -> Self {\n        with_event_converter(|c| c.convert_drag_data(val))\n    }\n}\n\nimpl From<&PlatformEventData> for FocusData {\n    fn from(val: &PlatformEventData) -> Self {\n        with_event_converter(|c| c.convert_focus_data(val))\n    }\n}\n\nimpl From<&PlatformEventData> for FormData {\n    fn from(val: &PlatformEventData) -> Self {\n        with_event_converter(|c| c.convert_form_data(val))\n    }\n}\n\nimpl From<&PlatformEventData> for ImageData {\n    fn from(val: &PlatformEventData) -> Self {\n        with_event_converter(|c| c.convert_image_data(val))\n    }\n}\n\nimpl From<&PlatformEventData> for KeyboardData {\n    fn from(val: &PlatformEventData) -> Self {\n        with_event_converter(|c| c.convert_keyboard_data(val))\n    }\n}\n\nimpl From<&PlatformEventData> for MediaData {\n    fn from(val: &PlatformEventData) -> Self {\n        with_event_converter(|c| c.convert_media_data(val))\n    }\n}\n\nimpl From<&PlatformEventData> for MountedData {\n    fn from(val: &PlatformEventData) -> Self {\n        with_event_converter(|c| c.convert_mounted_data(val))\n    }\n}\n\nimpl From<&PlatformEventData> for MouseData {\n    fn from(val: &PlatformEventData) -> Self {\n        with_event_converter(|c| c.convert_mouse_data(val))\n    }\n}\n\nimpl From<&PlatformEventData> for PointerData {\n    fn from(val: &PlatformEventData) -> Self {\n        with_event_converter(|c| c.convert_pointer_data(val))\n    }\n}\n\nimpl From<&PlatformEventData> for ResizeData {\n    fn from(val: &PlatformEventData) -> Self {\n        with_event_converter(|c| c.convert_resize_data(val))\n    }\n}\n\nimpl From<&PlatformEventData> for ScrollData {\n    fn from(val: &PlatformEventData) -> Self {\n        with_event_converter(|c| c.convert_scroll_data(val))\n    }\n}\n\nimpl From<&PlatformEventData> for SelectionData {\n    fn from(val: &PlatformEventData) -> Self {\n        with_event_converter(|c| c.convert_selection_data(val))\n    }\n}\n\nimpl From<&PlatformEventData> for ToggleData {\n    fn from(val: &PlatformEventData) -> Self {\n        with_event_converter(|c| c.convert_toggle_data(val))\n    }\n}\n\nimpl From<&PlatformEventData> for TouchData {\n    fn from(val: &PlatformEventData) -> Self {\n        with_event_converter(|c| c.convert_touch_data(val))\n    }\n}\n\nimpl From<&PlatformEventData> for TransitionData {\n    fn from(val: &PlatformEventData) -> Self {\n        with_event_converter(|c| c.convert_transition_data(val))\n    }\n}\n\nimpl From<&PlatformEventData> for VisibleData {\n    fn from(val: &PlatformEventData) -> Self {\n        with_event_converter(|c| c.convert_visible_data(val))\n    }\n}\n\nimpl From<&PlatformEventData> for WheelData {\n    fn from(val: &PlatformEventData) -> Self {\n        with_event_converter(|c| c.convert_wheel_data(val))\n    }\n}\n\nmod animation;\nmod cancel;\nmod clipboard;\nmod composition;\nmod drag;\nmod focus;\nmod form;\nmod image;\nmod keyboard;\nmod media;\nmod mounted;\nmod mouse;\nmod pointer;\nmod resize;\nmod scroll;\nmod selection;\nmod toggle;\nmod touch;\nmod transition;\nmod visible;\nmod wheel;\n\npub use animation::*;\npub use cancel::*;\npub use clipboard::*;\npub use composition::*;\npub use drag::*;\npub use focus::*;\npub use form::*;\npub use image::*;\npub use keyboard::*;\npub use media::*;\npub use mounted::*;\npub use mouse::*;\npub use pointer::*;\npub use resize::*;\npub use scroll::*;\npub use selection::*;\npub use toggle::*;\npub use touch::*;\npub use transition::*;\npub use visible::*;\npub use wheel::*;\n"
  },
  {
    "path": "packages/html/src/events/mounted.rs",
    "content": "//! Handles querying data from the renderer\n\nuse std::{\n    fmt::{Debug, Display, Formatter},\n    future::Future,\n    pin::Pin,\n};\n\n/// An Element that has been rendered and allows reading and modifying information about it.\n///\n/// Different platforms will have different implementations and different levels of support for this trait. Renderers that do not support specific features will return `None` for those queries.\n// we can not use async_trait here because it does not create a trait that is object safe\npub trait RenderedElementBacking: std::any::Any {\n    /// return self as Any\n    fn as_any(&self) -> &dyn std::any::Any;\n\n    /// Get the number of pixels that an element's content is scrolled\n    fn get_scroll_offset(&self) -> Pin<Box<dyn Future<Output = MountedResult<PixelsVector2D>>>> {\n        Box::pin(async { Err(MountedError::NotSupported) })\n    }\n\n    /// Get the size of an element's content, including content not visible on the screen due to overflow\n    #[allow(clippy::type_complexity)]\n    fn get_scroll_size(&self) -> Pin<Box<dyn Future<Output = MountedResult<PixelsSize>>>> {\n        Box::pin(async { Err(MountedError::NotSupported) })\n    }\n\n    /// Get the bounding rectangle of the element relative to the viewport (this does not include the scroll position)\n    #[allow(clippy::type_complexity)]\n    fn get_client_rect(&self) -> Pin<Box<dyn Future<Output = MountedResult<PixelsRect>>>> {\n        Box::pin(async { Err(MountedError::NotSupported) })\n    }\n\n    /// Scroll to make the element visible\n    fn scroll_to(\n        &self,\n        _options: ScrollToOptions,\n    ) -> Pin<Box<dyn Future<Output = MountedResult<()>>>> {\n        Box::pin(async { Err(MountedError::NotSupported) })\n    }\n\n    /// Scroll to the given element offsets\n    fn scroll(\n        &self,\n        _coordinates: PixelsVector2D,\n        _behavior: ScrollBehavior,\n    ) -> Pin<Box<dyn Future<Output = MountedResult<()>>>> {\n        Box::pin(async { Err(MountedError::NotSupported) })\n    }\n\n    /// Set the focus on the element\n    fn set_focus(&self, _focus: bool) -> Pin<Box<dyn Future<Output = MountedResult<()>>>> {\n        Box::pin(async { Err(MountedError::NotSupported) })\n    }\n}\n\nimpl RenderedElementBacking for () {\n    fn as_any(&self) -> &dyn std::any::Any {\n        self\n    }\n}\n\n/// The way that scrolling should be performed\n#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]\n#[cfg_attr(feature = \"serialize\", derive(serde::Serialize, serde::Deserialize))]\n#[doc(alias = \"ScrollIntoViewOptions\")]\npub enum ScrollBehavior {\n    /// Scroll to the element immediately\n    #[cfg_attr(feature = \"serialize\", serde(rename = \"instant\"))]\n    Instant,\n\n    /// Scroll to the element smoothly\n    #[default]\n    #[cfg_attr(feature = \"serialize\", serde(rename = \"smooth\"))]\n    Smooth,\n}\n\n/// The desired final position within the scrollable ancestor container for a given axis.\n#[cfg_attr(feature = \"serialize\", derive(serde::Serialize, serde::Deserialize))]\n#[doc(alias = \"ScrollIntoViewOptions\")]\npub enum ScrollLogicalPosition {\n    /// Aligns the element's start edge (top or left) with the start of the scrollable container,\n    /// making the element appear at the start of the visible area.\n    #[cfg_attr(feature = \"serialize\", serde(rename = \"start\"))]\n    Start,\n    /// Aligns the element at the center of the scrollable container,\n    /// positioning it in the middle of the visible area.\n    #[cfg_attr(feature = \"serialize\", serde(rename = \"center\"))]\n    Center,\n    /// Aligns the element's end edge (bottom or right) with the end of the scrollable container,\n    /// making the element appear at the end of the visible area\n    #[cfg_attr(feature = \"serialize\", serde(rename = \"end\"))]\n    End,\n    /// Scrolls the element to the nearest edge in the given axis.\n    /// This minimizes the scrolling distance.\n    #[cfg_attr(feature = \"serialize\", serde(rename = \"nearest\"))]\n    Nearest,\n}\n\n/// The way that scrolling should be performed\n#[cfg_attr(feature = \"serialize\", derive(serde::Serialize, serde::Deserialize))]\n#[doc(alias = \"ScrollIntoViewOptions\")]\npub struct ScrollToOptions {\n    pub behavior: ScrollBehavior,\n    pub vertical: ScrollLogicalPosition,\n    pub horizontal: ScrollLogicalPosition,\n}\nimpl Default for ScrollToOptions {\n    fn default() -> Self {\n        Self {\n            behavior: ScrollBehavior::Smooth,\n            vertical: ScrollLogicalPosition::Start,\n            horizontal: ScrollLogicalPosition::Center,\n        }\n    }\n}\n\n/// An Element that has been rendered and allows reading and modifying information about it.\n///\n/// Different platforms will have different implementations and different levels of support for this trait. Renderers that do not support specific features will return `None` for those queries.\npub struct MountedData {\n    inner: Box<dyn RenderedElementBacking>,\n}\n\nimpl Debug for MountedData {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_struct(\"MountedData\").finish()\n    }\n}\n\nimpl<E: RenderedElementBacking> From<E> for MountedData {\n    fn from(e: E) -> Self {\n        Self { inner: Box::new(e) }\n    }\n}\n\nimpl MountedData {\n    /// Create a new MountedData\n    pub fn new(registry: impl RenderedElementBacking + 'static) -> Self {\n        Self {\n            inner: Box::new(registry),\n        }\n    }\n\n    /// Get the number of pixels that an element's content is scrolled\n    #[doc(alias = \"scrollTop\")]\n    #[doc(alias = \"scrollLeft\")]\n    pub async fn get_scroll_offset(&self) -> MountedResult<PixelsVector2D> {\n        self.inner.get_scroll_offset().await\n    }\n\n    /// Get the size of an element's content, including content not visible on the screen due to overflow\n    #[doc(alias = \"scrollWidth\")]\n    #[doc(alias = \"scrollHeight\")]\n    pub async fn get_scroll_size(&self) -> MountedResult<PixelsSize> {\n        self.inner.get_scroll_size().await\n    }\n\n    /// Get the bounding rectangle of the element relative to the viewport (this does not include the scroll position)\n    #[doc(alias = \"getBoundingClientRect\")]\n    pub async fn get_client_rect(&self) -> MountedResult<PixelsRect> {\n        self.inner.get_client_rect().await\n    }\n\n    /// Scroll to make the element visible\n    #[doc(alias = \"scrollIntoView\")]\n    pub fn scroll_to(\n        &self,\n        behavior: ScrollBehavior,\n    ) -> Pin<Box<dyn Future<Output = MountedResult<()>>>> {\n        self.inner.scroll_to(ScrollToOptions {\n            behavior,\n            ..ScrollToOptions::default()\n        })\n    }\n\n    /// Scroll to make the element visible\n    #[doc(alias = \"scrollIntoView\")]\n    pub fn scroll_to_with_options(\n        &self,\n        options: ScrollToOptions,\n    ) -> Pin<Box<dyn Future<Output = MountedResult<()>>>> {\n        self.inner.scroll_to(options)\n    }\n\n    /// Scroll to the given element offsets\n    #[doc(alias = \"scrollTo\")]\n    pub fn scroll(\n        &self,\n        coordinates: PixelsVector2D,\n        behavior: ScrollBehavior,\n    ) -> Pin<Box<dyn Future<Output = MountedResult<()>>>> {\n        self.inner.scroll(coordinates, behavior)\n    }\n\n    /// Set the focus on the element\n    #[doc(alias = \"focus\")]\n    #[doc(alias = \"blur\")]\n    pub fn set_focus(&self, focus: bool) -> Pin<Box<dyn Future<Output = MountedResult<()>>>> {\n        self.inner.set_focus(focus)\n    }\n\n    /// Downcast this event to a concrete event type\n    #[inline(always)]\n    pub fn downcast<T: 'static>(&self) -> Option<&T> {\n        self.inner.as_any().downcast_ref::<T>()\n    }\n}\n\nuse dioxus_core::Event;\n\nuse crate::geometry::{PixelsRect, PixelsSize, PixelsVector2D};\n\npub type MountedEvent = Event<MountedData>;\n\nimpl_event! [\n    MountedData;\n\n    #[doc(alias = \"ref\")]\n    #[doc(alias = \"createRef\")]\n    #[doc(alias = \"useRef\")]\n    /// The onmounted event is fired when the element is first added to the DOM. This event gives you a [`MountedData`] object and lets you interact with the raw DOM element.\n    ///\n    /// This event is fired once per element. If you need to access the element multiple times, you can store the [`MountedData`] object in a [`use_signal`](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_signal.html) hook and use it as needed.\n    ///\n    /// # Examples\n    ///\n    /// ```rust, no_run\n    /// # use dioxus::prelude::*;\n    /// fn App() -> Element {\n    ///     let mut header_element = use_signal(|| None);\n    ///\n    ///     rsx! {\n    ///         div {\n    ///             h1 {\n    ///                 // The onmounted event will run the first time the h1 element is mounted\n    ///                 onmounted: move |element| header_element.set(Some(element.data())),\n    ///                 \"Scroll to top example\"\n    ///             }\n    ///\n    ///             for i in 0..100 {\n    ///                 div { \"Item {i}\" }\n    ///             }\n    ///\n    ///             button {\n    ///                 // When you click the button, if the header element has been mounted, we scroll to that element\n    ///                 onclick: move |_| async move {\n    ///                     if let Some(header) = header_element.cloned() {\n    ///                         let _ = header.scroll_to(ScrollBehavior::Smooth).await;\n    ///                     }\n    ///                 },\n    ///                 \"Scroll to top\"\n    ///             }\n    ///         }\n    ///     }\n    /// }\n    /// ```\n    ///\n    /// The `MountedData` struct contains cross platform APIs that work on the desktop, mobile, liveview and web platforms. For the web platform, you can also downcast the `MountedData` event to the `web-sys::Element` type for more web specific APIs:\n    ///\n    /// ```rust, ignore\n    /// use dioxus::prelude::*;\n    /// use dioxus_web::WebEventExt; // provides [`as_web_event()`] method\n    ///\n    /// fn App() -> Element {\n    ///     rsx! {\n    ///         div {\n    ///             id: \"some-id\",\n    ///             onmounted: move |element| {\n    ///                 // You can use the web_event trait to downcast the element to a web specific event. For the mounted event, this will be a web_sys::Element\n    ///                 let web_sys_element = element.as_web_event();\n    ///                 assert_eq!(web_sys_element.id(), \"some-id\");\n    ///             }\n    ///         }\n    ///     }\n    /// }\n    /// ```\n    onmounted\n];\n\npub use onmounted as onmount;\n\n/// The MountedResult type for the MountedData\npub type MountedResult<T> = Result<T, MountedError>;\n\n#[derive(Debug)]\n/// The error type for the MountedData\n#[non_exhaustive]\npub enum MountedError {\n    /// The renderer does not support the requested operation\n    NotSupported,\n    /// The element was not found\n    OperationFailed(Box<dyn std::error::Error>),\n}\n\nimpl Display for MountedError {\n    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {\n        match self {\n            MountedError::NotSupported => {\n                write!(f, \"The renderer does not support the requested operation\")\n            }\n            MountedError::OperationFailed(e) => {\n                write!(f, \"The operation failed: {}\", e)\n            }\n        }\n    }\n}\n\nimpl std::error::Error for MountedError {}\n"
  },
  {
    "path": "packages/html/src/events/mouse.rs",
    "content": "use crate::geometry::{ClientPoint, Coordinates, ElementPoint, PagePoint, ScreenPoint};\nuse crate::input_data::{MouseButton, MouseButtonSet};\nuse crate::*;\nuse dioxus_core::Event;\nuse keyboard_types::Modifiers;\n\npub type MouseEvent = Event<MouseData>;\n\n/// A synthetic event that wraps a web-style [`MouseEvent`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent)\n/// Data associated with a mouse event\npub struct MouseData {\n    inner: Box<dyn HasMouseData>,\n}\n\nimpl<E: HasMouseData + 'static> From<E> for MouseData {\n    fn from(e: E) -> Self {\n        Self { inner: Box::new(e) }\n    }\n}\n\nimpl std::fmt::Debug for MouseData {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_struct(\"MouseData\")\n            .field(\"coordinates\", &self.coordinates())\n            .field(\"modifiers\", &self.modifiers())\n            .field(\"held_buttons\", &self.held_buttons())\n            .field(\"trigger_button\", &self.trigger_button())\n            .finish()\n    }\n}\n\nimpl<E: HasMouseData> PartialEq<E> for MouseData {\n    fn eq(&self, other: &E) -> bool {\n        self.coordinates() == other.coordinates()\n            && self.modifiers() == other.modifiers()\n            && self.held_buttons() == other.held_buttons()\n            && self.trigger_button() == other.trigger_button()\n    }\n}\n\n/// A trait for any object that has the data for a mouse event\npub trait HasMouseData: PointerInteraction {\n    /// return self as Any\n    fn as_any(&self) -> &dyn std::any::Any;\n}\n\nimpl_event! {\n    MouseData;\n\n    /// Execute a callback when a button is clicked.\n    ///\n    /// ## Description\n    ///\n    /// An element receives a click event when a pointing device button (such as a mouse's primary mouse button)\n    /// is both pressed and released while the pointer is located inside the element.\n    ///\n    /// - Bubbles: Yes\n    /// - Cancelable: Yes\n    /// - Interface(InteData): [`MouseEvent`]\n    ///\n    /// If the button is pressed on one element and the pointer is moved outside the element before the button\n    /// is released, the event is fired on the most specific ancestor element that contained both elements.\n    /// `click` fires after both the `mousedown` and `mouseup` events have fired, in that order.\n    ///\n    /// ## Example\n    /// ```rust, ignore\n    /// rsx!( button { onclick: move |_| tracing::info!(\"Clicked!\"), \"click me\" } )\n    /// ```\n    ///\n    /// ## Reference\n    /// - <https://www.w3schools.com/tags/ev_onclick.asp>\n    /// - <https://developer.mozilla.org/en-US/docs/Web/API/Element/click_event>\n    onclick\n\n    /// oncontextmenu\n    oncontextmenu\n\n    #[deprecated(since = \"0.5.0\", note = \"use ondoubleclick instead\")]\n    ondblclick\n\n    ondoubleclick: \"ondblclick\"\n\n    /// onmousedown\n    onmousedown\n\n    /// onmouseenter\n    onmouseenter\n\n    /// onmouseleave\n    onmouseleave\n\n    /// onmousemove\n    onmousemove\n\n    /// onmouseout\n    onmouseout\n\n    /// onmouseover\n    ///\n    /// Triggered when the users's mouse hovers over an element.\n    onmouseover\n\n    /// onmouseup\n    onmouseup\n}\n\nimpl MouseData {\n    /// Create a new instance of MouseData\n    pub fn new(inner: impl HasMouseData + 'static) -> Self {\n        Self {\n            inner: Box::new(inner),\n        }\n    }\n\n    /// Downcast this event to a concrete event type\n    #[inline(always)]\n    pub fn downcast<T: 'static>(&self) -> Option<&T> {\n        self.inner.as_any().downcast_ref::<T>()\n    }\n}\n\nimpl InteractionLocation for MouseData {\n    fn client_coordinates(&self) -> ClientPoint {\n        self.inner.client_coordinates()\n    }\n\n    fn page_coordinates(&self) -> PagePoint {\n        self.inner.page_coordinates()\n    }\n\n    fn screen_coordinates(&self) -> ScreenPoint {\n        self.inner.screen_coordinates()\n    }\n}\n\nimpl InteractionElementOffset for MouseData {\n    fn element_coordinates(&self) -> ElementPoint {\n        self.inner.element_coordinates()\n    }\n\n    fn coordinates(&self) -> Coordinates {\n        self.inner.coordinates()\n    }\n}\n\nimpl ModifiersInteraction for MouseData {\n    /// The set of modifier keys which were pressed when the event occurred\n    fn modifiers(&self) -> Modifiers {\n        self.inner.modifiers()\n    }\n}\n\nimpl PointerInteraction for MouseData {\n    /// The set of mouse buttons which were held when the event occurred.\n    fn held_buttons(&self) -> MouseButtonSet {\n        self.inner.held_buttons()\n    }\n\n    /// The mouse button that triggered the event\n    ///\n    // todo the following is kind of bad; should we just return None when the trigger_button is unreliable (and frankly irrelevant)? i guess we would need the event_type here\n    /// This is only guaranteed to indicate which button was pressed during events caused by pressing or releasing a button. As such, it is not reliable for events such as mouseenter, mouseleave, mouseover, mouseout, or mousemove. For example, a value of MouseButton::Primary may also indicate that no button was pressed.\n    fn trigger_button(&self) -> Option<MouseButton> {\n        self.inner.trigger_button()\n    }\n}\n\nimpl PartialEq for MouseData {\n    fn eq(&self, other: &Self) -> bool {\n        self.coordinates() == other.coordinates()\n            && self.modifiers() == other.modifiers()\n            && self.held_buttons() == other.held_buttons()\n            && self.trigger_button() == other.trigger_button()\n    }\n}\n\n#[cfg(feature = \"serialize\")]\n/// A serialized version of [`MouseData`]\n#[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq, Clone, Default)]\npub struct SerializedMouseData {\n    /// Common data for all pointer/mouse events\n    #[serde(flatten)]\n    point_data: crate::point_interaction::SerializedPointInteraction,\n}\n\n#[cfg(feature = \"serialize\")]\nimpl SerializedMouseData {\n    /// Create a new instance of SerializedMouseData\n    pub fn new(\n        trigger_button: Option<MouseButton>,\n        held_buttons: MouseButtonSet,\n        coordinates: Coordinates,\n        modifiers: Modifiers,\n    ) -> Self {\n        Self {\n            point_data: crate::point_interaction::SerializedPointInteraction::new(\n                trigger_button,\n                held_buttons,\n                coordinates,\n                modifiers,\n            ),\n        }\n    }\n}\n\n#[cfg(feature = \"serialize\")]\nimpl From<&MouseData> for SerializedMouseData {\n    fn from(e: &MouseData) -> Self {\n        Self {\n            point_data: crate::point_interaction::SerializedPointInteraction::from(e),\n        }\n    }\n}\n\n#[cfg(feature = \"serialize\")]\nimpl HasMouseData for SerializedMouseData {\n    fn as_any(&self) -> &dyn std::any::Any {\n        self\n    }\n}\n\n#[cfg(feature = \"serialize\")]\nimpl InteractionLocation for SerializedMouseData {\n    fn client_coordinates(&self) -> ClientPoint {\n        self.point_data.client_coordinates()\n    }\n\n    fn page_coordinates(&self) -> PagePoint {\n        self.point_data.page_coordinates()\n    }\n\n    fn screen_coordinates(&self) -> ScreenPoint {\n        self.point_data.screen_coordinates()\n    }\n}\n\n#[cfg(feature = \"serialize\")]\nimpl InteractionElementOffset for SerializedMouseData {\n    fn element_coordinates(&self) -> ElementPoint {\n        self.point_data.element_coordinates()\n    }\n}\n\n#[cfg(feature = \"serialize\")]\nimpl ModifiersInteraction for SerializedMouseData {\n    fn modifiers(&self) -> Modifiers {\n        self.point_data.modifiers()\n    }\n}\n\n#[cfg(feature = \"serialize\")]\nimpl PointerInteraction for SerializedMouseData {\n    fn held_buttons(&self) -> MouseButtonSet {\n        self.point_data.held_buttons()\n    }\n\n    fn trigger_button(&self) -> Option<MouseButton> {\n        self.point_data.trigger_button()\n    }\n}\n\n#[cfg(feature = \"serialize\")]\nimpl serde::Serialize for MouseData {\n    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {\n        SerializedMouseData::from(self).serialize(serializer)\n    }\n}\n\n#[cfg(feature = \"serialize\")]\nimpl<'de> serde::Deserialize<'de> for MouseData {\n    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {\n        let data = SerializedMouseData::deserialize(deserializer)?;\n        Ok(Self {\n            inner: Box::new(data),\n        })\n    }\n}\n"
  },
  {
    "path": "packages/html/src/events/pointer.rs",
    "content": "use dioxus_core::Event;\nuse keyboard_types::Modifiers;\n\nuse crate::{geometry::*, input_data::*, *};\n\n/// A synthetic event that wraps a web-style [`PointerEvent`](https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent)\npub type PointerEvent = Event<PointerData>;\n\npub struct PointerData {\n    inner: Box<dyn HasPointerData>,\n}\n\nimpl PointerData {\n    /// Create a new PointerData\n    pub fn new(data: impl HasPointerData + 'static) -> Self {\n        Self::from(data)\n    }\n\n    /// Downcast this event to a concrete event type\n    #[inline(always)]\n    pub fn downcast<T: 'static>(&self) -> Option<&T> {\n        self.inner.as_any().downcast_ref::<T>()\n    }\n}\n\nimpl<E: HasPointerData + 'static> From<E> for PointerData {\n    fn from(e: E) -> Self {\n        Self { inner: Box::new(e) }\n    }\n}\n\nimpl std::fmt::Debug for PointerData {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_struct(\"PointerData\")\n            .field(\"pointer_id\", &self.pointer_id())\n            .field(\"width\", &self.width())\n            .field(\"height\", &self.height())\n            .field(\"pressure\", &self.pressure())\n            .field(\"tangential_pressure\", &self.tangential_pressure())\n            .field(\"tilt_x\", &self.tilt_x())\n            .field(\"tilt_y\", &self.tilt_y())\n            .field(\"twist\", &self.twist())\n            .field(\"pointer_type\", &self.pointer_type())\n            .field(\"is_primary\", &self.is_primary())\n            .field(\"coordinates\", &self.coordinates())\n            .field(\"modifiers\", &self.modifiers())\n            .field(\"held_buttons\", &self.held_buttons())\n            .field(\"trigger_button\", &self.trigger_button())\n            .finish()\n    }\n}\n\nimpl PartialEq for PointerData {\n    fn eq(&self, other: &Self) -> bool {\n        self.pointer_id() == other.pointer_id()\n            && self.width() == other.width()\n            && self.height() == other.height()\n            && self.pressure() == other.pressure()\n            && self.tangential_pressure() == other.tangential_pressure()\n            && self.tilt_x() == other.tilt_x()\n            && self.tilt_y() == other.tilt_y()\n            && self.twist() == other.twist()\n            && self.pointer_type() == other.pointer_type()\n            && self.is_primary() == other.is_primary()\n            && self.coordinates() == other.coordinates()\n            && self.modifiers() == other.modifiers()\n            && self.held_buttons() == other.held_buttons()\n            && self.trigger_button() == other.trigger_button()\n    }\n}\n\n/// A trait for any object that has the data for a pointer event\npub trait HasPointerData: PointerInteraction {\n    /// Gets the unique identifier of the pointer causing the event.\n    fn pointer_id(&self) -> i32;\n\n    /// Gets the width (magnitude on the X axis), in CSS pixels, of the contact geometry of the pointer.\n    fn width(&self) -> f64;\n\n    /// Gets the height (magnitude on the Y axis), in CSS pixels, of the contact geometry of the pointer.\n    fn height(&self) -> f64;\n\n    /// Gets the normalized pressure of the pointer input in the range of 0 to 1,\n    fn pressure(&self) -> f32;\n\n    /// Gets the normalized tangential pressure of the pointer input (also known as barrel pressure or cylinder stress) in the range -1 to 1,\n    fn tangential_pressure(&self) -> f32;\n\n    /// Gets the plane angle (in degrees, in the range of -90 to 90) between the Y-Z plane and the plane containing both the transducer (e.g. pen stylus) axis and the Y axis.\n    fn tilt_x(&self) -> i32;\n\n    /// Gets the plane angle (in degrees, in the range of -90 to 90) between the X-Z plane and the plane containing both the transducer (e.g. pen stylus) axis and the X axis.\n    fn tilt_y(&self) -> i32;\n\n    /// Gets the clockwise rotation of the pointer (e.g. pen stylus) around its major axis in degrees, with a value in the range 0 to 359.The clockwise rotation of the pointer (e.g. pen stylus) around its major axis in degrees, with a value in the range 0 to 359.\n    fn twist(&self) -> i32;\n\n    /// Gets the device type that caused the event (mouse, pen, touch, etc.).\n    fn pointer_type(&self) -> String;\n\n    /// Gets if the pointer represents the primary pointer of this pointer type.\n    fn is_primary(&self) -> bool;\n\n    /// return self as Any\n    fn as_any(&self) -> &dyn std::any::Any;\n}\n\nimpl_event![\n    PointerData;\n    /// pointerdown\n    onpointerdown\n\n    /// pointermove\n    onpointermove\n\n    /// pointerup\n    onpointerup\n\n    /// pointercancel\n    onpointercancel\n\n    /// gotpointercapture\n    ongotpointercapture\n\n    /// lostpointercapture\n    onlostpointercapture\n\n    /// pointerenter\n    onpointerenter\n\n    /// pointerleave\n    onpointerleave\n\n    /// pointerover\n    onpointerover\n\n    /// pointerout\n    onpointerout\n\n    /// auxclick\n    onauxclick\n];\n\nimpl PointerData {\n    /// Gets the unique identifier of the pointer causing the event.\n    pub fn pointer_id(&self) -> i32 {\n        self.inner.pointer_id()\n    }\n\n    /// Gets the width (magnitude on the X axis), in CSS pixels, of the contact geometry of the pointer.\n    pub fn width(&self) -> f64 {\n        self.inner.width()\n    }\n\n    /// Gets the height (magnitude on the Y axis), in CSS pixels, of the contact geometry of the pointer.\n    pub fn height(&self) -> f64 {\n        self.inner.height()\n    }\n\n    /// Gets the normalized pressure of the pointer input in the range of 0 to 1,\n    pub fn pressure(&self) -> f32 {\n        self.inner.pressure()\n    }\n\n    /// Gets the normalized tangential pressure of the pointer input (also known as barrel pressure or cylinder stress) in the range -1 to 1,\n    pub fn tangential_pressure(&self) -> f32 {\n        self.inner.tangential_pressure()\n    }\n\n    /// Gets the plane angle (in degrees, in the range of -90 to 90) between the Y-Z plane and the plane containing both the transducer (e.g. pen stylus) axis and the Y axis.\n    pub fn tilt_x(&self) -> i32 {\n        self.inner.tilt_x()\n    }\n\n    /// Gets the plane angle (in degrees, in the range of -90 to 90) between the X-Z plane and the plane containing both the transducer (e.g. pen stylus) axis and the X axis.\n    pub fn tilt_y(&self) -> i32 {\n        self.inner.tilt_y()\n    }\n\n    /// Gets the clockwise rotation of the pointer (e.g. pen stylus) around its major axis in degrees, with a value in the range 0 to 359.The clockwise rotation of the pointer (e.g. pen stylus) around its major axis in degrees, with a value in the range 0 to 359.\n    pub fn twist(&self) -> i32 {\n        self.inner.twist()\n    }\n\n    /// Gets the device type that caused the event (mouse, pen, touch, etc.).\n    pub fn pointer_type(&self) -> String {\n        self.inner.pointer_type()\n    }\n\n    /// Gets if the pointer represents the primary pointer of this pointer type.\n    pub fn is_primary(&self) -> bool {\n        self.inner.is_primary()\n    }\n}\n\nimpl InteractionLocation for PointerData {\n    fn client_coordinates(&self) -> ClientPoint {\n        self.inner.client_coordinates()\n    }\n\n    fn screen_coordinates(&self) -> ScreenPoint {\n        self.inner.screen_coordinates()\n    }\n\n    fn page_coordinates(&self) -> PagePoint {\n        self.inner.page_coordinates()\n    }\n}\n\nimpl InteractionElementOffset for PointerData {\n    fn element_coordinates(&self) -> ElementPoint {\n        self.inner.element_coordinates()\n    }\n}\n\nimpl ModifiersInteraction for PointerData {\n    fn modifiers(&self) -> Modifiers {\n        self.inner.modifiers()\n    }\n}\n\nimpl PointerInteraction for PointerData {\n    fn held_buttons(&self) -> MouseButtonSet {\n        self.inner.held_buttons()\n    }\n\n    fn trigger_button(&self) -> Option<MouseButton> {\n        self.inner.trigger_button()\n    }\n}\n\n#[cfg(feature = \"serialize\")]\n/// A serialized version of PointerData\n#[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq, Clone)]\npub struct SerializedPointerData {\n    /// Common data for all pointer/mouse events\n    #[serde(flatten)]\n    point_data: crate::point_interaction::SerializedPointInteraction,\n\n    /// The unique identifier of the pointer causing the event.\n    pointer_id: i32,\n\n    /// The width (magnitude on the X axis), in CSS pixels, of the contact geometry of the pointer.\n    width: f64,\n\n    /// The height (magnitude on the Y axis), in CSS pixels, of the contact geometry of the pointer.\n    height: f64,\n\n    /// The normalized pressure of the pointer input in the range of 0 to 1,\n    pressure: f32,\n\n    /// The normalized tangential pressure of the pointer input (also known as barrel pressure or cylinder stress) in the range -1 to 1,\n    tangential_pressure: f32,\n\n    /// The plane angle (in degrees, in the range of -90 to 90) between the Y-Z plane and the plane containing both the transducer (e.g. pen stylus) axis and the Y axis.\n    tilt_x: i32,\n\n    /// The plane angle (in degrees, in the range of -90 to 90) between the X-Z plane and the plane containing both the transducer (e.g. pen stylus) axis and the X axis.\n    tilt_y: i32,\n\n    /// The clockwise rotation of the pointer (e.g. pen stylus) around its major axis in degrees, with a value in the range 0 to 359.The clockwise rotation of the pointer (e.g. pen stylus) around its major axis in degrees, with a value in the range 0 to 359.\n    twist: i32,\n\n    /// Indicates the device type that caused the event (mouse, pen, touch, etc.).\n    pointer_type: String,\n\n    /// Indicates if the pointer represents the primary pointer of this pointer type.\n    is_primary: bool,\n}\n\n#[cfg(feature = \"serialize\")]\nimpl HasPointerData for SerializedPointerData {\n    fn pointer_id(&self) -> i32 {\n        self.pointer_id\n    }\n\n    fn width(&self) -> f64 {\n        self.width\n    }\n\n    fn height(&self) -> f64 {\n        self.height\n    }\n\n    fn pressure(&self) -> f32 {\n        self.pressure\n    }\n\n    fn tangential_pressure(&self) -> f32 {\n        self.tangential_pressure\n    }\n\n    fn tilt_x(&self) -> i32 {\n        self.tilt_x\n    }\n\n    fn tilt_y(&self) -> i32 {\n        self.tilt_y\n    }\n\n    fn twist(&self) -> i32 {\n        self.twist\n    }\n\n    fn pointer_type(&self) -> String {\n        self.pointer_type.clone()\n    }\n\n    fn is_primary(&self) -> bool {\n        self.is_primary\n    }\n\n    fn as_any(&self) -> &dyn std::any::Any {\n        self\n    }\n}\n\n#[cfg(feature = \"serialize\")]\nimpl InteractionLocation for SerializedPointerData {\n    fn client_coordinates(&self) -> ClientPoint {\n        self.point_data.client_coordinates()\n    }\n\n    fn screen_coordinates(&self) -> ScreenPoint {\n        self.point_data.screen_coordinates()\n    }\n\n    fn page_coordinates(&self) -> PagePoint {\n        self.point_data.page_coordinates()\n    }\n}\n\n#[cfg(feature = \"serialize\")]\nimpl InteractionElementOffset for SerializedPointerData {\n    fn element_coordinates(&self) -> ElementPoint {\n        self.point_data.element_coordinates()\n    }\n}\n\n#[cfg(feature = \"serialize\")]\nimpl ModifiersInteraction for SerializedPointerData {\n    fn modifiers(&self) -> Modifiers {\n        self.point_data.modifiers()\n    }\n}\n\n#[cfg(feature = \"serialize\")]\nimpl PointerInteraction for SerializedPointerData {\n    fn held_buttons(&self) -> MouseButtonSet {\n        self.point_data.held_buttons()\n    }\n\n    fn trigger_button(&self) -> Option<MouseButton> {\n        self.point_data.trigger_button()\n    }\n}\n\n#[cfg(feature = \"serialize\")]\nimpl From<&PointerData> for SerializedPointerData {\n    fn from(data: &PointerData) -> Self {\n        Self {\n            point_data: data.into(),\n            pointer_id: data.pointer_id(),\n            width: data.width(),\n            height: data.height(),\n            pressure: data.pressure(),\n            tangential_pressure: data.tangential_pressure(),\n            tilt_x: data.tilt_x(),\n            tilt_y: data.tilt_y(),\n            twist: data.twist(),\n            pointer_type: data.pointer_type().to_string(),\n            is_primary: data.is_primary(),\n        }\n    }\n}\n\n#[cfg(feature = \"serialize\")]\nimpl serde::Serialize for PointerData {\n    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {\n        SerializedPointerData::from(self).serialize(serializer)\n    }\n}\n\n#[cfg(feature = \"serialize\")]\nimpl<'de> serde::Deserialize<'de> for PointerData {\n    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {\n        let data = SerializedPointerData::deserialize(deserializer)?;\n        Ok(Self {\n            inner: Box::new(data),\n        })\n    }\n}\n"
  },
  {
    "path": "packages/html/src/events/resize.rs",
    "content": "use std::fmt::{Display, Formatter};\n\npub struct ResizeData {\n    inner: Box<dyn HasResizeData>,\n}\n\nimpl<E: HasResizeData> From<E> for ResizeData {\n    fn from(e: E) -> Self {\n        Self { inner: Box::new(e) }\n    }\n}\n\nimpl ResizeData {\n    /// Create a new ResizeData\n    pub fn new(inner: impl HasResizeData + 'static) -> Self {\n        Self {\n            inner: Box::new(inner),\n        }\n    }\n\n    /// Get the border box size of the observed element\n    pub fn get_border_box_size(&self) -> ResizeResult<PixelsSize> {\n        self.inner.get_border_box_size()\n    }\n\n    /// Get the content box size of the observed element\n    pub fn get_content_box_size(&self) -> ResizeResult<PixelsSize> {\n        self.inner.get_content_box_size()\n    }\n\n    /// Downcast this event to a concrete event type\n    pub fn downcast<T: 'static>(&self) -> Option<&T> {\n        self.inner.as_any().downcast_ref::<T>()\n    }\n}\n\nimpl std::fmt::Debug for ResizeData {\n    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {\n        f.debug_struct(\"ResizeData\")\n            .field(\"border_box_size\", &self.inner.get_border_box_size())\n            .field(\"content_box_size\", &self.inner.get_content_box_size())\n            .finish()\n    }\n}\n\nimpl PartialEq for ResizeData {\n    fn eq(&self, _: &Self) -> bool {\n        true\n    }\n}\n\n#[cfg(feature = \"serialize\")]\n/// A serialized version of ResizeData\n#[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq, Clone)]\npub struct SerializedResizeData {\n    pub border_box_size: PixelsSize,\n    pub content_box_size: PixelsSize,\n}\n\n#[cfg(feature = \"serialize\")]\nimpl SerializedResizeData {\n    /// Create a new SerializedResizeData\n    pub fn new(border_box_size: PixelsSize, content_box_size: PixelsSize) -> Self {\n        Self {\n            border_box_size,\n            content_box_size,\n        }\n    }\n}\n\n#[cfg(feature = \"serialize\")]\nimpl From<&ResizeData> for SerializedResizeData {\n    fn from(data: &ResizeData) -> Self {\n        Self::new(\n            data.get_border_box_size().unwrap(),\n            data.get_content_box_size().unwrap(),\n        )\n    }\n}\n\n#[cfg(feature = \"serialize\")]\nimpl HasResizeData for SerializedResizeData {\n    /// Get the border box size of the observed element\n    fn get_border_box_size(&self) -> ResizeResult<PixelsSize> {\n        Ok(self.border_box_size)\n    }\n\n    /// Get the content box size of the observed element\n    fn get_content_box_size(&self) -> ResizeResult<PixelsSize> {\n        Ok(self.content_box_size)\n    }\n\n    fn as_any(&self) -> &dyn std::any::Any {\n        self\n    }\n}\n\n#[cfg(feature = \"serialize\")]\nimpl serde::Serialize for ResizeData {\n    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {\n        SerializedResizeData::from(self).serialize(serializer)\n    }\n}\n\n#[cfg(feature = \"serialize\")]\nimpl<'de> serde::Deserialize<'de> for ResizeData {\n    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {\n        let data = SerializedResizeData::deserialize(deserializer)?;\n        Ok(Self {\n            inner: Box::new(data),\n        })\n    }\n}\n\npub trait HasResizeData: std::any::Any {\n    /// Get the border box size of the observed element\n    fn get_border_box_size(&self) -> ResizeResult<PixelsSize> {\n        Err(ResizeError::NotSupported)\n    }\n    /// Get the content box size of the observed element\n    fn get_content_box_size(&self) -> ResizeResult<PixelsSize> {\n        Err(ResizeError::NotSupported)\n    }\n\n    /// return self as Any\n    fn as_any(&self) -> &dyn std::any::Any;\n}\n\nuse dioxus_core::Event;\n\nuse crate::geometry::PixelsSize;\n\npub type ResizeEvent = Event<ResizeData>;\n\nimpl_event! {\n    ResizeData;\n\n    /// onresize\n    onresize\n}\n\n/// The ResizeResult type for the ResizeData\npub type ResizeResult<T> = Result<T, ResizeError>;\n\n#[derive(Debug)]\n/// The error type for the MountedData\n#[non_exhaustive]\npub enum ResizeError {\n    /// The renderer does not support the requested operation\n    NotSupported,\n    /// The element was not found\n    OperationFailed(Box<dyn std::error::Error>),\n}\n\nimpl Display for ResizeError {\n    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {\n        match self {\n            ResizeError::NotSupported => {\n                write!(f, \"The renderer does not support the requested operation\")\n            }\n            ResizeError::OperationFailed(e) => {\n                write!(f, \"The operation failed: {}\", e)\n            }\n        }\n    }\n}\n\nimpl std::error::Error for ResizeError {}\n"
  },
  {
    "path": "packages/html/src/events/scroll.rs",
    "content": "use dioxus_core::Event;\n\npub type ScrollEvent = Event<ScrollData>;\n\npub struct ScrollData {\n    inner: Box<dyn HasScrollData>,\n}\n\nimpl<E: HasScrollData> From<E> for ScrollData {\n    fn from(e: E) -> Self {\n        Self { inner: Box::new(e) }\n    }\n}\n\nimpl ScrollData {\n    /// Create a new ScrollData\n    pub fn new(inner: impl HasScrollData + 'static) -> Self {\n        Self {\n            inner: Box::new(inner),\n        }\n    }\n\n    /// Downcast this event to a concrete event type\n    #[inline(always)]\n    pub fn downcast<T: 'static>(&self) -> Option<&T> {\n        self.inner.as_any().downcast_ref::<T>()\n    }\n\n    pub fn scroll_top(&self) -> f64 {\n        self.inner.scroll_top()\n    }\n\n    pub fn scroll_left(&self) -> f64 {\n        self.inner.scroll_left()\n    }\n\n    pub fn scroll_width(&self) -> i32 {\n        self.inner.scroll_width()\n    }\n\n    pub fn scroll_height(&self) -> i32 {\n        self.inner.scroll_height()\n    }\n\n    pub fn client_width(&self) -> i32 {\n        self.inner.client_width()\n    }\n\n    pub fn client_height(&self) -> i32 {\n        self.inner.client_height()\n    }\n}\n\nimpl std::fmt::Debug for ScrollData {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_struct(\"ScrollData\")\n            .field(\"scroll_top\", &self.scroll_top())\n            .field(\"scroll_left\", &self.scroll_left())\n            .field(\"scroll_width\", &self.scroll_width())\n            .field(\"scroll_height\", &self.scroll_height())\n            .field(\"client_width\", &self.client_width())\n            .field(\"client_height\", &self.client_height())\n            .finish()\n    }\n}\n\nimpl PartialEq for ScrollData {\n    fn eq(&self, other: &Self) -> bool {\n        self.scroll_top() == other.scroll_top()\n            && self.scroll_left() == other.scroll_left()\n            && self.scroll_width() == other.scroll_width()\n            && self.scroll_height() == other.scroll_height()\n            && self.client_width() == other.client_width()\n            && self.client_height() == other.client_height()\n    }\n}\n\n#[cfg(feature = \"serialize\")]\n/// A serialized version of ScrollData\n#[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq, Clone)]\npub struct SerializedScrollData {\n    pub scroll_top: f64,\n    pub scroll_left: f64,\n    pub scroll_width: i32,\n    pub scroll_height: i32,\n    pub client_width: i32,\n    pub client_height: i32,\n}\n\n#[cfg(feature = \"serialize\")]\nimpl From<&ScrollData> for SerializedScrollData {\n    fn from(data: &ScrollData) -> Self {\n        Self {\n            scroll_top: data.inner.scroll_top(),\n            scroll_left: data.inner.scroll_left(),\n            scroll_width: data.inner.scroll_width(),\n            scroll_height: data.inner.scroll_height(),\n            client_width: data.inner.client_width(),\n            client_height: data.inner.client_height(),\n        }\n    }\n}\n\n#[cfg(feature = \"serialize\")]\nimpl HasScrollData for SerializedScrollData {\n    fn as_any(&self) -> &dyn std::any::Any {\n        self\n    }\n\n    fn scroll_top(&self) -> f64 {\n        self.scroll_top\n    }\n\n    fn scroll_left(&self) -> f64 {\n        self.scroll_left\n    }\n\n    fn scroll_width(&self) -> i32 {\n        self.scroll_width\n    }\n\n    fn scroll_height(&self) -> i32 {\n        self.scroll_height\n    }\n\n    fn client_width(&self) -> i32 {\n        self.client_width\n    }\n\n    fn client_height(&self) -> i32 {\n        self.client_height\n    }\n}\n\n#[cfg(feature = \"serialize\")]\nimpl serde::Serialize for ScrollData {\n    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {\n        SerializedScrollData::from(self).serialize(serializer)\n    }\n}\n\n#[cfg(feature = \"serialize\")]\nimpl<'de> serde::Deserialize<'de> for ScrollData {\n    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {\n        let data = SerializedScrollData::deserialize(deserializer)?;\n        Ok(Self {\n            inner: Box::new(data),\n        })\n    }\n}\n\npub trait HasScrollData: std::any::Any {\n    /// Return self as Any\n    fn as_any(&self) -> &dyn std::any::Any;\n\n    /// Get the vertical scroll position\n    fn scroll_top(&self) -> f64;\n\n    /// Get the horizontal scroll position\n    fn scroll_left(&self) -> f64;\n\n    /// Get the total scrollable width\n    fn scroll_width(&self) -> i32;\n\n    /// Get the total scrollable height\n    fn scroll_height(&self) -> i32;\n\n    /// Get the viewport width\n    fn client_width(&self) -> i32;\n\n    /// Get the viewport height\n    fn client_height(&self) -> i32;\n}\n\nimpl_event! {\n    ScrollData;\n\n    /// onscroll\n    onscroll\n\n    /// onscrollend\n    onscrollend\n}\n"
  },
  {
    "path": "packages/html/src/events/selection.rs",
    "content": "use dioxus_core::Event;\n\npub type SelectionEvent = Event<SelectionData>;\n\npub struct SelectionData {\n    inner: Box<dyn HasSelectionData>,\n}\n\nimpl SelectionData {\n    /// Create a new SelectionData\n    pub fn new(inner: impl HasSelectionData + 'static) -> Self {\n        Self {\n            inner: Box::new(inner),\n        }\n    }\n\n    /// Downcast this event to a concrete event type\n    #[inline(always)]\n    pub fn downcast<T: 'static>(&self) -> Option<&T> {\n        self.inner.as_any().downcast_ref::<T>()\n    }\n}\n\nimpl<E: HasSelectionData> From<E> for SelectionData {\n    fn from(e: E) -> Self {\n        Self { inner: Box::new(e) }\n    }\n}\n\nimpl std::fmt::Debug for SelectionData {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_struct(\"SelectionData\").finish()\n    }\n}\n\nimpl PartialEq for SelectionData {\n    fn eq(&self, _other: &Self) -> bool {\n        true\n    }\n}\n\n#[cfg(feature = \"serialize\")]\n/// A serialized version of SelectionData\n#[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq, Clone)]\npub struct SerializedSelectionData {}\n\n#[cfg(feature = \"serialize\")]\nimpl From<&SelectionData> for SerializedSelectionData {\n    fn from(_: &SelectionData) -> Self {\n        Self {}\n    }\n}\n\n#[cfg(feature = \"serialize\")]\nimpl HasSelectionData for SerializedSelectionData {\n    fn as_any(&self) -> &dyn std::any::Any {\n        self\n    }\n}\n\n#[cfg(feature = \"serialize\")]\nimpl serde::Serialize for SelectionData {\n    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {\n        SerializedSelectionData::from(self).serialize(serializer)\n    }\n}\n\n#[cfg(feature = \"serialize\")]\nimpl<'de> serde::Deserialize<'de> for SelectionData {\n    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {\n        let data = SerializedSelectionData::deserialize(deserializer)?;\n        Ok(Self {\n            inner: Box::new(data),\n        })\n    }\n}\n\npub trait HasSelectionData: std::any::Any {\n    /// return self as Any\n    fn as_any(&self) -> &dyn std::any::Any;\n}\n\nimpl_event! [\n    SelectionData;\n\n    /// select\n    onselect\n\n    /// selectstart\n    onselectstart\n\n    /// selectionchange\n    onselectionchange\n];\n"
  },
  {
    "path": "packages/html/src/events/toggle.rs",
    "content": "use dioxus_core::Event;\n\npub type ToggleEvent = Event<ToggleData>;\n\npub struct ToggleData {\n    inner: Box<dyn HasToggleData>,\n}\n\nimpl<E: HasToggleData> From<E> for ToggleData {\n    fn from(e: E) -> Self {\n        Self { inner: Box::new(e) }\n    }\n}\n\nimpl std::fmt::Debug for ToggleData {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_struct(\"ToggleData\").finish()\n    }\n}\n\nimpl PartialEq for ToggleData {\n    fn eq(&self, _other: &Self) -> bool {\n        true\n    }\n}\n\nimpl ToggleData {\n    /// Create a new ToggleData\n    pub fn new(inner: impl HasToggleData + 'static) -> Self {\n        Self {\n            inner: Box::new(inner),\n        }\n    }\n\n    /// Downcast this event to a concrete event type\n    #[inline(always)]\n    pub fn downcast<T: 'static>(&self) -> Option<&T> {\n        self.inner.as_any().downcast_ref::<T>()\n    }\n}\n\n#[cfg(feature = \"serialize\")]\n/// A serialized version of ToggleData\n#[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq, Clone)]\npub struct SerializedToggleData {}\n\n#[cfg(feature = \"serialize\")]\nimpl From<&ToggleData> for SerializedToggleData {\n    fn from(_: &ToggleData) -> Self {\n        Self {}\n    }\n}\n\n#[cfg(feature = \"serialize\")]\nimpl HasToggleData for SerializedToggleData {\n    fn as_any(&self) -> &dyn std::any::Any {\n        self\n    }\n}\n\n#[cfg(feature = \"serialize\")]\nimpl serde::Serialize for ToggleData {\n    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {\n        SerializedToggleData::from(self).serialize(serializer)\n    }\n}\n\n#[cfg(feature = \"serialize\")]\nimpl<'de> serde::Deserialize<'de> for ToggleData {\n    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {\n        let data = SerializedToggleData::deserialize(deserializer)?;\n        Ok(Self {\n            inner: Box::new(data),\n        })\n    }\n}\n\npub trait HasToggleData: std::any::Any {\n    /// return self as Any\n    fn as_any(&self) -> &dyn std::any::Any;\n}\n\nimpl_event! {\n    ToggleData;\n\n    /// ontoggle\n    ontoggle\n\n    /// onbeforetoggle\n    onbeforetoggle\n}\n"
  },
  {
    "path": "packages/html/src/events/touch.rs",
    "content": "use dioxus_core::Event;\nuse keyboard_types::Modifiers;\n\nuse crate::geometry::*;\nuse crate::{InteractionLocation, ModifiersInteraction};\n\npub type TouchEvent = Event<TouchData>;\npub struct TouchData {\n    inner: Box<dyn HasTouchData>,\n}\n\nimpl<E: HasTouchData> From<E> for TouchData {\n    fn from(e: E) -> Self {\n        Self { inner: Box::new(e) }\n    }\n}\n\nimpl std::fmt::Debug for TouchData {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_struct(\"TouchData\")\n            .field(\"modifiers\", &self.modifiers())\n            .field(\"touches\", &self.touches())\n            .field(\"touches_changed\", &self.touches_changed())\n            .field(\"target_touches\", &self.target_touches())\n            .finish()\n    }\n}\n\nimpl PartialEq for TouchData {\n    fn eq(&self, other: &Self) -> bool {\n        self.modifiers() == other.modifiers()\n    }\n}\n\nimpl TouchData {\n    /// Create a new TouchData\n    pub fn new(inner: impl HasTouchData + 'static) -> Self {\n        Self {\n            inner: Box::new(inner),\n        }\n    }\n\n    /// Get the pointers that are currently down\n    pub fn touches(&self) -> Vec<TouchPoint> {\n        self.inner.touches()\n    }\n\n    /// Get the touches that have changed since the last event\n    pub fn touches_changed(&self) -> Vec<TouchPoint> {\n        self.inner.touches_changed()\n    }\n\n    /// Get the touches that started and stayed on the element that triggered this event\n    pub fn target_touches(&self) -> Vec<TouchPoint> {\n        self.inner.target_touches()\n    }\n\n    /// Downcast this event to a concrete event type\n    #[inline(always)]\n    pub fn downcast<T: 'static>(&self) -> Option<&T> {\n        self.inner.as_any().downcast_ref::<T>()\n    }\n}\n\nimpl ModifiersInteraction for TouchData {\n    fn modifiers(&self) -> Modifiers {\n        self.inner.modifiers()\n    }\n}\n\n#[cfg(feature = \"serialize\")]\n/// A serialized version of TouchData\n#[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq, Clone)]\npub struct SerializedTouchData {\n    alt_key: bool,\n    ctrl_key: bool,\n    meta_key: bool,\n    shift_key: bool,\n    touches: Vec<SerializedTouchPoint>,\n    changed_touches: Vec<SerializedTouchPoint>,\n    target_touches: Vec<SerializedTouchPoint>,\n}\n\n#[cfg(feature = \"serialize\")]\nimpl From<&TouchData> for SerializedTouchData {\n    fn from(data: &TouchData) -> Self {\n        let modifiers = data.modifiers();\n        Self {\n            alt_key: modifiers.contains(Modifiers::ALT),\n            ctrl_key: modifiers.contains(Modifiers::CONTROL),\n            meta_key: modifiers.contains(Modifiers::META),\n            shift_key: modifiers.contains(Modifiers::SHIFT),\n            touches: data.touches().iter().map(|t| t.into()).collect(),\n            changed_touches: data.touches_changed().iter().map(|t| t.into()).collect(),\n            target_touches: data.target_touches().iter().map(|t| t.into()).collect(),\n        }\n    }\n}\n\n#[cfg(feature = \"serialize\")]\nimpl ModifiersInteraction for SerializedTouchData {\n    fn modifiers(&self) -> Modifiers {\n        let mut modifiers = Modifiers::default();\n        if self.alt_key {\n            modifiers.insert(Modifiers::ALT);\n        }\n        if self.ctrl_key {\n            modifiers.insert(Modifiers::CONTROL);\n        }\n        if self.meta_key {\n            modifiers.insert(Modifiers::META);\n        }\n        if self.shift_key {\n            modifiers.insert(Modifiers::SHIFT);\n        }\n        modifiers\n    }\n}\n\n#[cfg(feature = \"serialize\")]\nimpl HasTouchData for SerializedTouchData {\n    fn touches(&self) -> Vec<TouchPoint> {\n        self.touches.clone().into_iter().map(Into::into).collect()\n    }\n\n    fn touches_changed(&self) -> Vec<TouchPoint> {\n        self.changed_touches\n            .clone()\n            .into_iter()\n            .map(Into::into)\n            .collect()\n    }\n\n    fn target_touches(&self) -> Vec<TouchPoint> {\n        self.target_touches\n            .clone()\n            .into_iter()\n            .map(Into::into)\n            .collect()\n    }\n\n    fn as_any(&self) -> &dyn std::any::Any {\n        self\n    }\n}\n\n#[cfg(feature = \"serialize\")]\nimpl serde::Serialize for TouchData {\n    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {\n        SerializedTouchData::from(self).serialize(serializer)\n    }\n}\n\n#[cfg(feature = \"serialize\")]\nimpl<'de> serde::Deserialize<'de> for TouchData {\n    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {\n        let data = SerializedTouchData::deserialize(deserializer)?;\n        Ok(Self {\n            inner: Box::new(data),\n        })\n    }\n}\n\npub trait HasTouchData: ModifiersInteraction + std::any::Any {\n    /// Get the touches that are currently down\n    fn touches(&self) -> Vec<TouchPoint>;\n\n    /// Get the touches that have changed since the last event\n    fn touches_changed(&self) -> Vec<TouchPoint>;\n\n    /// Get the touches that started and stayed on the element that triggered this event\n    fn target_touches(&self) -> Vec<TouchPoint>;\n\n    /// return self as Any\n    fn as_any(&self) -> &dyn std::any::Any;\n}\n\npub struct TouchPoint {\n    inner: Box<dyn HasTouchPointData>,\n}\n\nimpl<E: HasTouchPointData> From<E> for TouchPoint {\n    fn from(e: E) -> Self {\n        Self { inner: Box::new(e) }\n    }\n}\n\nimpl TouchPoint {\n    /// Create a new TouchPoint\n    pub fn new(inner: impl HasTouchPointData + 'static) -> Self {\n        Self {\n            inner: Box::new(inner),\n        }\n    }\n\n    /// A unique identifier for this touch point that will be the same for the duration of the touch\n    fn identifier(&self) -> i32 {\n        self.inner.identifier()\n    }\n\n    /// the pressure of the touch\n    fn force(&self) -> f64 {\n        self.inner.force()\n    }\n\n    /// the radius of the touch\n    fn radius(&self) -> ScreenPoint {\n        self.inner.radius()\n    }\n\n    /// the rotation of the touch in degrees between 0 and 90\n    fn rotation(&self) -> f64 {\n        self.inner.rotation()\n    }\n\n    /// Downcast this event to a concrete event type\n    #[inline(always)]\n    pub fn downcast<T: 'static>(&self) -> Option<&T> {\n        self.inner.as_any().downcast_ref::<T>()\n    }\n}\n\nimpl std::fmt::Debug for TouchPoint {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_struct(\"TouchPoint\")\n            .field(\"client_coordinates\", &self.client_coordinates())\n            .field(\"page_coordinates\", &self.page_coordinates())\n            .field(\"screen_coordinates\", &self.screen_coordinates())\n            .field(\"identifier\", &self.identifier())\n            .field(\"force\", &self.force())\n            .field(\"radius\", &self.radius())\n            .field(\"rotation\", &self.rotation())\n            .finish()\n    }\n}\n\nimpl InteractionLocation for TouchPoint {\n    fn client_coordinates(&self) -> ClientPoint {\n        self.inner.client_coordinates()\n    }\n\n    fn page_coordinates(&self) -> PagePoint {\n        self.inner.page_coordinates()\n    }\n\n    fn screen_coordinates(&self) -> ScreenPoint {\n        self.inner.screen_coordinates()\n    }\n}\n\n/// A trait for touch point data\npub trait HasTouchPointData: InteractionLocation + std::any::Any {\n    /// A unique identifier for this touch point that will be the same for the duration of the touch\n    fn identifier(&self) -> i32;\n\n    /// the pressure of the touch\n    fn force(&self) -> f64;\n\n    /// the radius of the touch\n    fn radius(&self) -> ScreenPoint;\n\n    /// the rotation of the touch in degrees between 0 and 90\n    fn rotation(&self) -> f64;\n\n    /// return self as Any\n    fn as_any(&self) -> &dyn std::any::Any;\n}\n\n#[cfg(feature = \"serialize\")]\n/// A serialized version of TouchPoint\n#[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq, Clone)]\nstruct SerializedTouchPoint {\n    identifier: i32,\n    client_x: f64,\n    client_y: f64,\n    page_x: f64,\n    page_y: f64,\n    screen_x: f64,\n    screen_y: f64,\n    force: f64,\n    radius_x: f64,\n    radius_y: f64,\n    rotation_angle: f64,\n}\n\n#[cfg(feature = \"serialize\")]\nimpl From<&TouchPoint> for SerializedTouchPoint {\n    fn from(point: &TouchPoint) -> Self {\n        let client_coordinates = point.client_coordinates();\n\n        let page_coordinates = point.page_coordinates();\n        let screen_coordinates = point.screen_coordinates();\n        Self {\n            identifier: point.identifier(),\n            client_x: client_coordinates.x,\n            client_y: client_coordinates.y,\n            page_x: page_coordinates.x,\n            page_y: page_coordinates.y,\n            screen_x: screen_coordinates.x,\n            screen_y: screen_coordinates.y,\n            force: point.force(),\n            radius_x: point.radius().x,\n            radius_y: point.radius().y,\n            rotation_angle: point.rotation(),\n        }\n    }\n}\n\n#[cfg(feature = \"serialize\")]\nimpl HasTouchPointData for SerializedTouchPoint {\n    /// A unique identifier for this touch point that will be the same for the duration of the touch\n    fn identifier(&self) -> i32 {\n        self.identifier\n    }\n\n    /// the pressure of the touch\n    fn force(&self) -> f64 {\n        self.force\n    }\n\n    /// the radius of the touch\n    fn radius(&self) -> ScreenPoint {\n        ScreenPoint::new(self.radius_x, self.radius_y)\n    }\n\n    /// the rotation of the touch in degrees between 0 and 90\n    fn rotation(&self) -> f64 {\n        self.rotation_angle\n    }\n\n    /// return self as Any\n    fn as_any(&self) -> &dyn std::any::Any {\n        self\n    }\n}\n\n#[cfg(feature = \"serialize\")]\nimpl InteractionLocation for SerializedTouchPoint {\n    /// Gets the coordinates of the event relative to the browser viewport.\n    fn client_coordinates(&self) -> ClientPoint {\n        ClientPoint::new(self.client_x, self.client_y)\n    }\n\n    /// Gets the coordinates of the event relative to the screen.\n    fn screen_coordinates(&self) -> ScreenPoint {\n        ScreenPoint::new(self.screen_x, self.screen_y)\n    }\n\n    /// Gets the coordinates of the event relative to the page.\n    fn page_coordinates(&self) -> PagePoint {\n        PagePoint::new(self.page_x, self.page_y)\n    }\n}\n\nimpl_event! {\n    TouchData;\n    /// touchstart\n    ontouchstart\n\n    /// touchmove\n    ontouchmove\n\n    /// touchend\n    ontouchend\n\n    /// touchcancel\n    ontouchcancel\n}\n"
  },
  {
    "path": "packages/html/src/events/transition.rs",
    "content": "use dioxus_core::Event;\n\npub type TransitionEvent = Event<TransitionData>;\n\npub struct TransitionData {\n    inner: Box<dyn HasTransitionData>,\n}\n\nimpl<E: HasTransitionData> From<E> for TransitionData {\n    fn from(e: E) -> Self {\n        Self { inner: Box::new(e) }\n    }\n}\n\nimpl std::fmt::Debug for TransitionData {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_struct(\"TransitionData\")\n            .field(\"property_name\", &self.inner.property_name())\n            .field(\"pseudo_element\", &self.inner.pseudo_element())\n            .field(\"elapsed_time\", &self.inner.elapsed_time())\n            .finish()\n    }\n}\n\nimpl PartialEq for TransitionData {\n    fn eq(&self, other: &Self) -> bool {\n        self.inner.property_name() == other.inner.property_name()\n            && self.inner.pseudo_element() == other.inner.pseudo_element()\n            && self.inner.elapsed_time() == other.inner.elapsed_time()\n    }\n}\n\nimpl TransitionData {\n    /// Create a new TransitionData\n    pub fn new(inner: impl HasTransitionData + 'static) -> Self {\n        Self {\n            inner: Box::new(inner),\n        }\n    }\n\n    /// Downcast this event to a concrete event type\n    #[inline(always)]\n    pub fn downcast<T: 'static>(&self) -> Option<&T> {\n        self.inner.as_any().downcast_ref::<T>()\n    }\n}\n\n#[cfg(feature = \"serialize\")]\n/// A serialized version of TransitionData\n#[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq, Clone)]\npub struct SerializedTransitionData {\n    property_name: String,\n    pseudo_element: String,\n    elapsed_time: f32,\n}\n\n#[cfg(feature = \"serialize\")]\nimpl From<&TransitionData> for SerializedTransitionData {\n    fn from(data: &TransitionData) -> Self {\n        Self {\n            property_name: data.inner.property_name(),\n            pseudo_element: data.inner.pseudo_element(),\n            elapsed_time: data.inner.elapsed_time(),\n        }\n    }\n}\n\n#[cfg(feature = \"serialize\")]\nimpl HasTransitionData for SerializedTransitionData {\n    fn property_name(&self) -> String {\n        self.property_name.clone()\n    }\n\n    fn pseudo_element(&self) -> String {\n        self.pseudo_element.clone()\n    }\n\n    fn elapsed_time(&self) -> f32 {\n        self.elapsed_time\n    }\n\n    fn as_any(&self) -> &dyn std::any::Any {\n        self\n    }\n}\n\n#[cfg(feature = \"serialize\")]\nimpl serde::Serialize for TransitionData {\n    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {\n        SerializedTransitionData::from(self).serialize(serializer)\n    }\n}\n\n#[cfg(feature = \"serialize\")]\nimpl<'de> serde::Deserialize<'de> for TransitionData {\n    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {\n        let data = SerializedTransitionData::deserialize(deserializer)?;\n        Ok(Self {\n            inner: Box::new(data),\n        })\n    }\n}\n\npub trait HasTransitionData: std::any::Any {\n    fn property_name(&self) -> String;\n    fn pseudo_element(&self) -> String;\n    fn elapsed_time(&self) -> f32;\n    /// return self as Any\n    fn as_any(&self) -> &dyn std::any::Any;\n}\n\nimpl_event! {\n    TransitionData;\n\n    /// transitionend\n    ontransitionend\n}\n"
  },
  {
    "path": "packages/html/src/events/visible.rs",
    "content": "use std::{\n    fmt::{Display, Formatter},\n    time::SystemTime,\n};\n\npub struct VisibleData {\n    inner: Box<dyn HasVisibleData>,\n}\n\nimpl<E: HasVisibleData> From<E> for VisibleData {\n    fn from(e: E) -> Self {\n        Self { inner: Box::new(e) }\n    }\n}\n\nimpl VisibleData {\n    /// Create a new VisibleData\n    pub fn new(inner: impl HasVisibleData + 'static) -> Self {\n        Self {\n            inner: Box::new(inner),\n        }\n    }\n\n    /// Get the bounds rectangle of the target element\n    pub fn get_bounding_client_rect(&self) -> VisibleResult<PixelsRect> {\n        self.inner.get_bounding_client_rect()\n    }\n\n    /// Get the ratio of the intersectionRect to the boundingClientRect\n    pub fn get_intersection_ratio(&self) -> VisibleResult<f64> {\n        self.inner.get_intersection_ratio()\n    }\n\n    /// Get the rect representing the target's visible area\n    pub fn get_intersection_rect(&self) -> VisibleResult<PixelsRect> {\n        self.inner.get_intersection_rect()\n    }\n\n    /// Get if the target element intersects with the intersection observer's root\n    pub fn is_intersecting(&self) -> VisibleResult<bool> {\n        self.inner.is_intersecting()\n    }\n\n    /// Get the rect for the intersection observer's root\n    pub fn get_root_bounds(&self) -> VisibleResult<PixelsRect> {\n        self.inner.get_root_bounds()\n    }\n\n    /// Get a timestamp indicating the time at which the intersection was recorded\n    pub fn get_time(&self) -> VisibleResult<SystemTime> {\n        self.inner.get_time()\n    }\n\n    /// Downcast this event to a concrete event type\n    pub fn downcast<T: 'static>(&self) -> Option<&T> {\n        self.inner.as_any().downcast_ref::<T>()\n    }\n}\n\nimpl std::fmt::Debug for VisibleData {\n    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {\n        f.debug_struct(\"VisibleData\")\n            .field(\n                \"bounding_client_rect\",\n                &self.inner.get_bounding_client_rect(),\n            )\n            .field(\"intersection_ratio\", &self.inner.get_intersection_ratio())\n            .field(\"intersection_rect\", &self.inner.get_intersection_rect())\n            .field(\"is_intersecting\", &self.inner.is_intersecting())\n            .field(\"root_bounds\", &self.inner.get_root_bounds())\n            .field(\"time\", &self.inner.get_time())\n            .finish()\n    }\n}\n\nimpl PartialEq for VisibleData {\n    fn eq(&self, _: &Self) -> bool {\n        true\n    }\n}\n\n#[cfg(feature = \"serialize\")]\n#[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq, Clone)]\npub struct DOMRect {\n    bottom: f64, // The bottom coordinate value of the DOMRect (usually the same as y + height)\n    height: f64, // The height of the DOMRect\n    left: f64,   // The left coordinate value of the DOMRect (usually the same as x)\n    right: f64,  // The right coordinate value of the DOMRect (usually the same as x + width)\n    top: f64,    // The top coordinate value of the DOMRect (usually the same as y)\n    width: f64,  // The width of the DOMRect\n    x: f64,      // The x coordinate of the DOMRect's origin\n    y: f64,      // The y coordinate of the DOMRect's origin\n}\n\n#[cfg(feature = \"serialize\")]\nimpl From<PixelsRect> for DOMRect {\n    fn from(rect: PixelsRect) -> Self {\n        let x = rect.origin.x;\n        let y = rect.origin.y;\n        let height = rect.height();\n        let width = rect.width();\n\n        Self {\n            bottom: y + height,\n            height,\n            left: x,\n            right: x + width,\n            top: y,\n            width,\n            x,\n            y,\n        }\n    }\n}\n\n#[cfg(feature = \"serialize\")]\nimpl From<&DOMRect> for PixelsRect {\n    fn from(rect: &DOMRect) -> Self {\n        PixelsRect::new(\n            euclid::Point2D::new(rect.x, rect.y),\n            euclid::Size2D::new(rect.width, rect.height),\n        )\n    }\n}\n\n#[cfg(feature = \"serialize\")]\n/// A serialized version of VisibleData\n#[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq, Clone)]\npub struct SerializedVisibleData {\n    pub bounding_client_rect: DOMRect,\n    pub intersection_ratio: f64,\n    pub intersection_rect: DOMRect,\n    pub is_intersecting: bool,\n    pub root_bounds: DOMRect,\n    pub time_ms: u128,\n}\n\n#[cfg(feature = \"serialize\")]\nimpl SerializedVisibleData {\n    /// Create a new SerializedVisibleData\n    pub fn new(\n        bounding_client_rect: DOMRect,\n        intersection_ratio: f64,\n        intersection_rect: DOMRect,\n        is_intersecting: bool,\n        root_bounds: DOMRect,\n        time_ms: u128,\n    ) -> Self {\n        Self {\n            bounding_client_rect,\n            intersection_ratio,\n            intersection_rect,\n            is_intersecting,\n            root_bounds,\n            time_ms,\n        }\n    }\n}\n\n#[cfg(feature = \"serialize\")]\nimpl From<&VisibleData> for SerializedVisibleData {\n    fn from(data: &VisibleData) -> Self {\n        Self::new(\n            data.get_bounding_client_rect().unwrap().into(),\n            data.get_intersection_ratio().unwrap(),\n            data.get_intersection_rect().unwrap().into(),\n            data.is_intersecting().unwrap(),\n            data.get_root_bounds().unwrap().into(),\n            data.get_time().unwrap().elapsed().unwrap().as_millis(),\n        )\n    }\n}\n\n#[cfg(feature = \"serialize\")]\nimpl HasVisibleData for SerializedVisibleData {\n    /// Get the bounds rectangle of the target element\n    fn get_bounding_client_rect(&self) -> VisibleResult<PixelsRect> {\n        Ok((&self.bounding_client_rect).into())\n    }\n\n    /// Get the ratio of the intersectionRect to the boundingClientRect\n    fn get_intersection_ratio(&self) -> VisibleResult<f64> {\n        Ok(self.intersection_ratio)\n    }\n\n    /// Get the rect representing the target's visible area\n    fn get_intersection_rect(&self) -> VisibleResult<PixelsRect> {\n        Ok((&self.intersection_rect).into())\n    }\n\n    /// Get if the target element intersects with the intersection observer's root\n    fn is_intersecting(&self) -> VisibleResult<bool> {\n        Ok(self.is_intersecting)\n    }\n\n    /// Get the rect for the intersection observer's root\n    fn get_root_bounds(&self) -> VisibleResult<PixelsRect> {\n        Ok((&self.root_bounds).into())\n    }\n\n    /// Get a timestamp indicating the time at which the intersection was recorded\n    fn get_time(&self) -> VisibleResult<SystemTime> {\n        Ok(SystemTime::UNIX_EPOCH + std::time::Duration::from_millis(self.time_ms as u64))\n    }\n\n    fn as_any(&self) -> &dyn std::any::Any {\n        self\n    }\n}\n\n#[cfg(feature = \"serialize\")]\nimpl serde::Serialize for VisibleData {\n    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {\n        SerializedVisibleData::from(self).serialize(serializer)\n    }\n}\n\n#[cfg(feature = \"serialize\")]\nimpl<'de> serde::Deserialize<'de> for VisibleData {\n    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {\n        let data = SerializedVisibleData::deserialize(deserializer)?;\n        Ok(Self {\n            inner: Box::new(data),\n        })\n    }\n}\n\npub trait HasVisibleData: std::any::Any {\n    /// Get the bounds rectangle of the target element\n    fn get_bounding_client_rect(&self) -> VisibleResult<PixelsRect> {\n        Err(VisibleError::NotSupported)\n    }\n\n    /// Get the ratio of the intersectionRect to the boundingClientRect\n    fn get_intersection_ratio(&self) -> VisibleResult<f64> {\n        Err(VisibleError::NotSupported)\n    }\n\n    /// Get the rect representing the target's visible area\n    fn get_intersection_rect(&self) -> VisibleResult<PixelsRect> {\n        Err(VisibleError::NotSupported)\n    }\n\n    /// Get if the target element intersects with the intersection observer's root\n    fn is_intersecting(&self) -> VisibleResult<bool> {\n        Err(VisibleError::NotSupported)\n    }\n\n    /// Get the rect for the intersection observer's root\n    fn get_root_bounds(&self) -> VisibleResult<PixelsRect> {\n        Err(VisibleError::NotSupported)\n    }\n\n    /// Get a timestamp indicating the time at which the intersection was recorded\n    fn get_time(&self) -> VisibleResult<SystemTime> {\n        Err(VisibleError::NotSupported)\n    }\n\n    /// return self as Any\n    fn as_any(&self) -> &dyn std::any::Any;\n}\n\nuse dioxus_core::Event;\n\nuse crate::geometry::PixelsRect;\n\npub type VisibleEvent = Event<VisibleData>;\n\nimpl_event! {\n    VisibleData;\n\n    /// onvisible\n    onvisible\n}\n\n/// The VisibleResult type for the VisibleData\npub type VisibleResult<T> = Result<T, VisibleError>;\n\n#[derive(Debug)]\n/// The error type for the VisibleData\n#[non_exhaustive]\npub enum VisibleError {\n    /// The renderer does not support the requested operation\n    NotSupported,\n    /// The element was not found\n    OperationFailed(Box<dyn std::error::Error>),\n    /// The target element had no associated ElementId\n    NoElementId,\n}\n\nimpl Display for VisibleError {\n    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {\n        match self {\n            VisibleError::NotSupported => {\n                write!(f, \"The renderer does not support the requested operation\")\n            }\n            VisibleError::OperationFailed(e) => {\n                write!(f, \"The operation failed: {}\", e)\n            }\n            VisibleError::NoElementId => {\n                write!(f, \"The target had no associated ElementId\")\n            }\n        }\n    }\n}\n\nimpl std::error::Error for VisibleError {}\n"
  },
  {
    "path": "packages/html/src/events/wheel.rs",
    "content": "use dioxus_core::Event;\nuse keyboard_types::Modifiers;\nuse std::fmt::Formatter;\n\nuse crate::{geometry::*, InteractionLocation, ModifiersInteraction, PointerInteraction};\nuse crate::{\n    input_data::{MouseButton, MouseButtonSet},\n    InteractionElementOffset,\n};\n\nuse super::HasMouseData;\n\n/// A synthetic event that wraps a web-style\n/// [`WheelEvent`](https://developer.mozilla.org/en-US/docs/Web/API/WheelEvent)\npub type WheelEvent = Event<WheelData>;\n\n/// Data associated with a [WheelEvent]\npub struct WheelData {\n    inner: Box<dyn HasWheelData>,\n}\n\nimpl<E: HasWheelData> From<E> for WheelData {\n    fn from(e: E) -> Self {\n        Self { inner: Box::new(e) }\n    }\n}\n\nimpl std::fmt::Debug for WheelData {\n    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {\n        f.debug_struct(\"WheelData\")\n            .field(\"delta\", &self.delta())\n            .field(\"coordinates\", &self.coordinates())\n            .field(\"modifiers\", &self.modifiers())\n            .field(\"held_buttons\", &self.held_buttons())\n            .field(\"trigger_button\", &self.trigger_button())\n            .finish()\n    }\n}\n\nimpl PartialEq for WheelData {\n    fn eq(&self, other: &Self) -> bool {\n        self.inner.delta() == other.inner.delta()\n    }\n}\n\nimpl WheelData {\n    pub fn new(inner: impl HasWheelData + 'static) -> Self {\n        Self {\n            inner: Box::new(inner),\n        }\n    }\n\n    /// The amount of wheel movement\n    #[allow(deprecated)]\n    pub fn delta(&self) -> WheelDelta {\n        self.inner.delta()\n    }\n\n    /// Downcast this event to a concrete event type\n    #[inline(always)]\n    pub fn downcast<T: 'static>(&self) -> Option<&T> {\n        HasWheelData::as_any(&*self.inner).downcast_ref::<T>()\n    }\n}\n\nimpl InteractionLocation for WheelData {\n    fn client_coordinates(&self) -> ClientPoint {\n        self.inner.client_coordinates()\n    }\n\n    fn page_coordinates(&self) -> PagePoint {\n        self.inner.page_coordinates()\n    }\n\n    fn screen_coordinates(&self) -> ScreenPoint {\n        self.inner.screen_coordinates()\n    }\n}\n\nimpl InteractionElementOffset for WheelData {\n    fn element_coordinates(&self) -> ElementPoint {\n        self.inner.element_coordinates()\n    }\n\n    fn coordinates(&self) -> Coordinates {\n        self.inner.coordinates()\n    }\n}\n\nimpl ModifiersInteraction for WheelData {\n    fn modifiers(&self) -> Modifiers {\n        self.inner.modifiers()\n    }\n}\n\nimpl PointerInteraction for WheelData {\n    fn held_buttons(&self) -> MouseButtonSet {\n        self.inner.held_buttons()\n    }\n\n    // todo the following is kind of bad; should we just return None when the trigger_button is unreliable (and frankly irrelevant)? i guess we would need the event_type here\n    fn trigger_button(&self) -> Option<MouseButton> {\n        self.inner.trigger_button()\n    }\n}\n\n#[cfg(feature = \"serialize\")]\n/// A serialized version of WheelData\n#[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq, Clone)]\npub struct SerializedWheelData {\n    #[serde(flatten)]\n    pub mouse: crate::point_interaction::SerializedPointInteraction,\n\n    pub delta_mode: u32,\n    pub delta_x: f64,\n    pub delta_y: f64,\n    pub delta_z: f64,\n}\n\n#[cfg(feature = \"serialize\")]\nimpl SerializedWheelData {\n    /// Create a new SerializedWheelData\n    pub fn new(wheel: &WheelData) -> Self {\n        let delta_mode = match wheel.delta() {\n            WheelDelta::Pixels(_) => 0,\n            WheelDelta::Lines(_) => 1,\n            WheelDelta::Pages(_) => 2,\n        };\n        let delta_raw = wheel.delta().strip_units();\n        Self {\n            mouse: crate::point_interaction::SerializedPointInteraction::from(wheel),\n            delta_mode,\n            delta_x: delta_raw.x,\n            delta_y: delta_raw.y,\n            delta_z: delta_raw.z,\n        }\n    }\n}\n\n#[cfg(feature = \"serialize\")]\nimpl From<&WheelData> for SerializedWheelData {\n    fn from(data: &WheelData) -> Self {\n        Self::new(data)\n    }\n}\n\n#[cfg(feature = \"serialize\")]\nimpl HasWheelData for SerializedWheelData {\n    fn delta(&self) -> WheelDelta {\n        WheelDelta::from_web_attributes(self.delta_mode, self.delta_x, self.delta_y, self.delta_z)\n    }\n\n    fn as_any(&self) -> &dyn std::any::Any {\n        self\n    }\n}\n\n#[cfg(feature = \"serialize\")]\nimpl HasMouseData for SerializedWheelData {\n    fn as_any(&self) -> &dyn std::any::Any {\n        self\n    }\n}\n\n#[cfg(feature = \"serialize\")]\nimpl InteractionLocation for SerializedWheelData {\n    fn client_coordinates(&self) -> ClientPoint {\n        self.mouse.client_coordinates()\n    }\n\n    fn page_coordinates(&self) -> PagePoint {\n        self.mouse.page_coordinates()\n    }\n\n    fn screen_coordinates(&self) -> ScreenPoint {\n        self.mouse.screen_coordinates()\n    }\n}\n\n#[cfg(feature = \"serialize\")]\nimpl InteractionElementOffset for SerializedWheelData {\n    fn element_coordinates(&self) -> ElementPoint {\n        self.mouse.element_coordinates()\n    }\n\n    fn coordinates(&self) -> Coordinates {\n        self.mouse.coordinates()\n    }\n}\n\n#[cfg(feature = \"serialize\")]\nimpl ModifiersInteraction for SerializedWheelData {\n    fn modifiers(&self) -> Modifiers {\n        self.mouse.modifiers()\n    }\n}\n\n#[cfg(feature = \"serialize\")]\nimpl PointerInteraction for SerializedWheelData {\n    fn held_buttons(&self) -> MouseButtonSet {\n        self.mouse.held_buttons()\n    }\n\n    fn trigger_button(&self) -> Option<MouseButton> {\n        self.mouse.trigger_button()\n    }\n}\n\n#[cfg(feature = \"serialize\")]\nimpl serde::Serialize for WheelData {\n    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {\n        SerializedWheelData::from(self).serialize(serializer)\n    }\n}\n\n#[cfg(feature = \"serialize\")]\nimpl<'de> serde::Deserialize<'de> for WheelData {\n    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {\n        let data = SerializedWheelData::deserialize(deserializer)?;\n        Ok(Self {\n            inner: Box::new(data),\n        })\n    }\n}\n\nimpl_event![\n    WheelData;\n\n    /// Called when the mouse wheel is rotated over an element.\n    onwheel\n];\n\npub trait HasWheelData: HasMouseData + std::any::Any {\n    /// The amount of wheel movement\n    fn delta(&self) -> WheelDelta;\n\n    /// return self as Any\n    fn as_any(&self) -> &dyn std::any::Any;\n}\n"
  },
  {
    "path": "packages/html/src/file_data.rs",
    "content": "use bytes::Bytes;\nuse futures_util::Stream;\nuse std::{path::PathBuf, pin::Pin, prelude::rust_2024::Future};\n\n#[derive(Clone)]\npub struct FileData {\n    inner: std::sync::Arc<dyn NativeFileData>,\n}\n\nimpl FileData {\n    pub fn new(inner: impl NativeFileData + 'static) -> Self {\n        Self {\n            inner: std::sync::Arc::new(inner),\n        }\n    }\n\n    pub fn content_type(&self) -> Option<String> {\n        self.inner.content_type()\n    }\n\n    pub fn name(&self) -> String {\n        self.inner.name()\n    }\n\n    pub fn size(&self) -> u64 {\n        self.inner.size()\n    }\n\n    pub fn last_modified(&self) -> u64 {\n        self.inner.last_modified()\n    }\n\n    pub async fn read_bytes(&self) -> Result<Bytes, dioxus_core::CapturedError> {\n        self.inner.read_bytes().await\n    }\n\n    pub async fn read_string(&self) -> Result<String, dioxus_core::CapturedError> {\n        self.inner.read_string().await\n    }\n\n    pub fn byte_stream(\n        &self,\n    ) -> Pin<Box<dyn Stream<Item = Result<Bytes, dioxus_core::CapturedError>> + Send + 'static>>\n    {\n        self.inner.byte_stream()\n    }\n\n    pub fn inner(&self) -> &dyn std::any::Any {\n        self.inner.inner()\n    }\n\n    pub fn path(&self) -> PathBuf {\n        self.inner.path()\n    }\n}\n\nimpl PartialEq for FileData {\n    fn eq(&self, other: &Self) -> bool {\n        self.name() == other.name()\n            && self.size() == other.size()\n            && self.last_modified() == other.last_modified()\n            && self.path() == other.path()\n    }\n}\n\npub trait NativeFileData: Send + Sync {\n    fn name(&self) -> String;\n    fn size(&self) -> u64;\n    fn last_modified(&self) -> u64;\n    fn path(&self) -> PathBuf;\n    fn content_type(&self) -> Option<String>;\n    fn read_bytes(\n        &self,\n    ) -> Pin<Box<dyn Future<Output = Result<Bytes, dioxus_core::CapturedError>> + 'static>>;\n    fn byte_stream(\n        &self,\n    ) -> Pin<\n        Box<\n            dyn futures_util::Stream<Item = Result<Bytes, dioxus_core::CapturedError>>\n                + 'static\n                + Send,\n        >,\n    >;\n    fn read_string(\n        &self,\n    ) -> Pin<Box<dyn Future<Output = Result<String, dioxus_core::CapturedError>> + 'static>>;\n    fn inner(&self) -> &dyn std::any::Any;\n}\n\nimpl std::fmt::Debug for FileData {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_struct(\"FileData\")\n            .field(\"name\", &self.inner.name())\n            .field(\"size\", &self.inner.size())\n            .field(\"last_modified\", &self.inner.last_modified())\n            .finish()\n    }\n}\n\npub trait HasFileData: std::any::Any {\n    fn files(&self) -> Vec<FileData>;\n}\n\n#[cfg(feature = \"serialize\")]\npub use serialize::*;\n\n#[cfg(feature = \"serialize\")]\nmod serialize {\n    use super::*;\n\n    /// A serializable representation of file data\n    #[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq, Clone)]\n    pub struct SerializedFileData {\n        pub path: PathBuf,\n        pub size: u64,\n        pub last_modified: u64,\n        pub content_type: Option<String>,\n        pub contents: Option<bytes::Bytes>,\n    }\n\n    impl SerializedFileData {\n        /// Create a new empty serialized file data object\n        pub fn empty() -> Self {\n            Self {\n                path: PathBuf::new(),\n                size: 0,\n                last_modified: 0,\n                content_type: None,\n                contents: None,\n            }\n        }\n    }\n\n    impl NativeFileData for SerializedFileData {\n        fn name(&self) -> String {\n            self.path()\n                .file_name()\n                .unwrap()\n                .to_string_lossy()\n                .into_owned()\n        }\n\n        fn size(&self) -> u64 {\n            self.size\n        }\n\n        fn last_modified(&self) -> u64 {\n            self.last_modified\n        }\n\n        fn read_bytes(\n            &self,\n        ) -> Pin<Box<dyn Future<Output = Result<Bytes, dioxus_core::CapturedError>> + 'static>>\n        {\n            let contents = self.contents.clone();\n            let path = self.path.clone();\n\n            Box::pin(async move {\n                if let Some(contents) = contents {\n                    return Ok(contents);\n                }\n\n                #[cfg(not(target_arch = \"wasm32\"))]\n                if path.exists() {\n                    return Ok(std::fs::read(path).map(Bytes::from)?);\n                }\n\n                Err(dioxus_core::CapturedError::msg(\n                    \"File contents not available\",\n                ))\n            })\n        }\n\n        fn read_string(\n            &self,\n        ) -> Pin<Box<dyn Future<Output = Result<String, dioxus_core::CapturedError>> + 'static>>\n        {\n            let contents = self.contents.clone();\n            let path = self.path.clone();\n\n            Box::pin(async move {\n                if let Some(contents) = contents {\n                    return Ok(String::from_utf8(contents.to_vec())?);\n                }\n\n                #[cfg(not(target_arch = \"wasm32\"))]\n                if path.exists() {\n                    return Ok(std::fs::read_to_string(path)?);\n                }\n\n                Err(dioxus_core::CapturedError::msg(\n                    \"File contents not available\",\n                ))\n            })\n        }\n\n        fn byte_stream(\n            &self,\n        ) -> Pin<\n            Box<\n                dyn futures_util::Stream<Item = Result<Bytes, dioxus_core::CapturedError>>\n                    + 'static\n                    + Send,\n            >,\n        > {\n            let contents = self.contents.clone();\n            let path = self.path.clone();\n\n            Box::pin(futures_util::stream::once(async move {\n                if let Some(contents) = contents {\n                    return Ok(contents);\n                }\n\n                #[cfg(not(target_arch = \"wasm32\"))]\n                if path.exists() {\n                    return Ok(std::fs::read(path).map(Bytes::from)?);\n                }\n\n                Err(dioxus_core::CapturedError::msg(\n                    \"File contents not available\",\n                ))\n            }))\n        }\n\n        fn inner(&self) -> &dyn std::any::Any {\n            self\n        }\n\n        fn path(&self) -> PathBuf {\n            self.path.clone()\n        }\n\n        fn content_type(&self) -> Option<String> {\n            self.content_type.clone()\n        }\n    }\n\n    impl<'de> serde::Deserialize<'de> for FileData {\n        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>\n        where\n            D: serde::Deserializer<'de>,\n        {\n            let sfd = SerializedFileData::deserialize(deserializer)?;\n            Ok(FileData::new(sfd))\n        }\n    }\n}\n"
  },
  {
    "path": "packages/html/src/geometry.rs",
    "content": "//! Geometry primitives for representing e.g. mouse events\n\n/// A re-export of euclid, which we use for geometry primitives\npub use euclid;\n\nuse euclid::*;\n\n/// Coordinate space relative to the screen\npub struct ScreenSpace;\n/// A point in ScreenSpace\npub type ScreenPoint = Point2D<f64, ScreenSpace>;\n\n/// Coordinate space relative to the viewport\npub struct ClientSpace;\n/// A point in ClientSpace\npub type ClientPoint = Point2D<f64, ClientSpace>;\n\n/// Coordinate space relative to an element\npub struct ElementSpace;\n/// A point in ElementSpace\npub type ElementPoint = Point2D<f64, ElementSpace>;\n\n/// Coordinate space relative to the page\npub struct PageSpace;\n/// A point in PageSpace\npub type PagePoint = Point2D<f64, PageSpace>;\n\n/// A pixel unit: one unit corresponds to 1 pixel\npub struct Pixels;\n/// A size expressed in Pixels\npub type PixelsSize = Size2D<f64, Pixels>;\n/// A rectangle expressed in Pixels\npub type PixelsRect = Rect<f64, Pixels>;\n/// A 2D vector expressed in Pixels\npub type PixelsVector2D = Vector2D<f64, Pixels>;\n/// A 3D vector expressed in Pixels\npub type PixelsVector3D = Vector3D<f64, Pixels>;\n\n/// A unit in terms of Lines\n///\n/// One unit is relative to the size of one line\npub struct Lines;\n/// A vector expressed in Lines\npub type LinesVector = Vector3D<f64, Lines>;\n\n/// A unit in terms of Screens:\n///\n/// One unit is relative to the size of a page\npub struct Pages;\n/// A vector expressed in Pages\npub type PagesVector = Vector3D<f64, Pages>;\n\n/// A vector representing the amount the mouse wheel was moved\n///\n/// This may be expressed in Pixels, Lines or Pages\n#[derive(Copy, Clone, Debug, PartialEq)]\n#[cfg_attr(feature = \"serialize\", derive(serde::Serialize, serde::Deserialize))]\npub enum WheelDelta {\n    /// Movement in Pixels\n    Pixels(PixelsVector3D),\n    /// Movement in Lines\n    Lines(LinesVector),\n    /// Movement in Pages\n    Pages(PagesVector),\n}\n\nimpl WheelDelta {\n    /// Construct from the attributes of the web wheel event\n    pub fn from_web_attributes(delta_mode: u32, delta_x: f64, delta_y: f64, delta_z: f64) -> Self {\n        match delta_mode {\n            0 => WheelDelta::Pixels(PixelsVector3D::new(delta_x, delta_y, delta_z)),\n            1 => WheelDelta::Lines(LinesVector::new(delta_x, delta_y, delta_z)),\n            2 => WheelDelta::Pages(PagesVector::new(delta_x, delta_y, delta_z)),\n            _ => panic!(\"Invalid delta mode, {:?}\", delta_mode),\n        }\n    }\n\n    /// Convenience function for constructing a WheelDelta with pixel units\n    pub fn pixels(x: f64, y: f64, z: f64) -> Self {\n        WheelDelta::Pixels(PixelsVector3D::new(x, y, z))\n    }\n\n    /// Convenience function for constructing a WheelDelta with line units\n    pub fn lines(x: f64, y: f64, z: f64) -> Self {\n        WheelDelta::Lines(LinesVector::new(x, y, z))\n    }\n\n    /// Convenience function for constructing a WheelDelta with page units\n    pub fn pages(x: f64, y: f64, z: f64) -> Self {\n        WheelDelta::Pages(PagesVector::new(x, y, z))\n    }\n\n    /// Returns true iff there is no wheel movement\n    ///\n    /// i.e. the x, y and z delta is zero (disregards units)\n    pub fn is_zero(&self) -> bool {\n        self.strip_units() == Vector3D::new(0., 0., 0.)\n    }\n\n    /// A Vector3D proportional to the amount scrolled\n    ///\n    /// Note that this disregards the 3 possible units: this could be expressed in terms of pixels, lines, or pages.\n    ///\n    /// In most cases, to properly handle scrolling, you should handle all 3 possible enum variants instead of stripping units. Otherwise, if you assume that the units will always be pixels, the user may experience some unexpectedly slow scrolling if their mouse/OS sends values expressed in lines or pages.\n    pub fn strip_units(&self) -> Vector3D<f64, UnknownUnit> {\n        match self {\n            WheelDelta::Pixels(v) => v.cast_unit(),\n            WheelDelta::Lines(v) => v.cast_unit(),\n            WheelDelta::Pages(v) => v.cast_unit(),\n        }\n    }\n}\n\n/// Coordinates of a point in the app's interface\n#[derive(Debug, PartialEq)]\npub struct Coordinates {\n    screen: ScreenPoint,\n    client: ClientPoint,\n    element: ElementPoint,\n    page: PagePoint,\n}\n\nimpl Coordinates {\n    /// Construct new coordinates with the specified screen-, client-, element- and page-relative points\n    pub fn new(\n        screen: ScreenPoint,\n        client: ClientPoint,\n        element: ElementPoint,\n        page: PagePoint,\n    ) -> Self {\n        Self {\n            screen,\n            client,\n            element,\n            page,\n        }\n    }\n    /// Coordinates relative to the entire screen. This takes into account the window's offset.\n    pub fn screen(&self) -> ScreenPoint {\n        self.screen\n    }\n    /// Coordinates relative to the application's viewport (as opposed to the coordinate within the page).\n    ///\n    /// For example, clicking in the top left corner of the viewport will always result in a mouse event with client coordinates (0., 0.), regardless of whether the page is scrolled horizontally.\n    pub fn client(&self) -> ClientPoint {\n        self.client\n    }\n    /// Coordinates relative to the padding edge of the target element\n    ///\n    /// For example, clicking in the top left corner of an element will result in element coordinates (0., 0.)\n    pub fn element(&self) -> ElementPoint {\n        self.element\n    }\n    /// Coordinates relative to the entire document. This includes any portion of the document not currently visible.\n    ///\n    /// For example, if the page is scrolled 200 pixels to the right and 300 pixels down, clicking in the top left corner of the viewport would result in page coordinates (200., 300.)\n    pub fn page(&self) -> PagePoint {\n        self.page\n    }\n}\n"
  },
  {
    "path": "packages/html/src/input_data.rs",
    "content": "//! Data structures representing user input, such as modifier keys and mouse buttons\nuse enumset::{EnumSet, EnumSetType};\n\n/// A re-export of keyboard_types\npub use keyboard_types;\nuse keyboard_types::Location;\n\n/// A mouse button type (such as Primary/Secondary)\n// note: EnumSetType also derives Copy and Clone for some reason\n#[allow(clippy::unused_unit)]\n#[derive(EnumSetType, Debug, Default)]\n#[cfg_attr(feature = \"serialize\", derive(serde::Serialize, serde::Deserialize))]\npub enum MouseButton {\n    #[default]\n    /// Primary button (typically the left button)\n    Primary,\n    /// Secondary button (typically the right button)\n    Secondary,\n    /// Auxiliary button (typically the middle button)\n    Auxiliary,\n    /// Fourth button (typically the \"Browser Back\" button)\n    Fourth,\n    /// Fifth button (typically the \"Browser Forward\" button)\n    Fifth,\n    /// A button with an unknown code\n    Unknown,\n}\n\nimpl MouseButton {\n    /// Constructs a MouseButton for the specified button code\n    ///\n    /// E.g. 0 => Primary; 1 => Auxiliary\n    ///\n    /// Unknown codes get mapped to MouseButton::Unknown.\n    pub fn from_web_code(code: i16) -> Self {\n        match code {\n            0 => MouseButton::Primary,\n            // not a typo; auxiliary and secondary are swapped unlike in the `buttons` field.\n            // https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button\n            1 => MouseButton::Auxiliary,\n            2 => MouseButton::Secondary,\n            3 => MouseButton::Fourth,\n            4 => MouseButton::Fifth,\n            _ => MouseButton::Unknown,\n        }\n    }\n\n    /// Converts MouseButton into the corresponding button code\n    ///\n    /// MouseButton::Unknown will get mapped to -1\n    pub fn into_web_code(self) -> i16 {\n        match self {\n            MouseButton::Primary => 0,\n            // not a typo; auxiliary and secondary are swapped unlike in the `buttons` field.\n            // https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button\n            MouseButton::Auxiliary => 1,\n            MouseButton::Secondary => 2,\n            MouseButton::Fourth => 3,\n            MouseButton::Fifth => 4,\n            MouseButton::Unknown => -1,\n        }\n    }\n}\n\n/// A set of mouse buttons\npub type MouseButtonSet = EnumSet<MouseButton>;\n\npub fn decode_mouse_button_set(code: u16) -> MouseButtonSet {\n    let mut set = EnumSet::empty();\n\n    // https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/buttons\n    #[allow(deprecated)]\n    {\n        if code & 0b1 != 0 {\n            set |= MouseButton::Primary;\n        }\n        if code & 0b10 != 0 {\n            set |= MouseButton::Secondary;\n        }\n        if code & 0b100 != 0 {\n            set |= MouseButton::Auxiliary;\n        }\n        if code & 0b1000 != 0 {\n            set |= MouseButton::Fourth;\n        }\n        if code & 0b10000 != 0 {\n            set |= MouseButton::Fifth;\n        }\n        if code & (!0b11111) != 0 {\n            set |= MouseButton::Unknown;\n        }\n    }\n\n    set\n}\n\npub fn encode_mouse_button_set(set: MouseButtonSet) -> u16 {\n    let mut code = 0;\n\n    // https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/buttons\n    {\n        if set.contains(MouseButton::Primary) {\n            code |= 0b1;\n        }\n        if set.contains(MouseButton::Secondary) {\n            code |= 0b10;\n        }\n        if set.contains(MouseButton::Auxiliary) {\n            code |= 0b100;\n        }\n        if set.contains(MouseButton::Fourth) {\n            code |= 0b1000;\n        }\n        if set.contains(MouseButton::Fifth) {\n            code |= 0b10000;\n        }\n        if set.contains(MouseButton::Unknown) {\n            code |= 0b100000;\n        }\n    }\n\n    code\n}\n\npub fn decode_key_location(code: usize) -> Location {\n    match code {\n        0 => Location::Standard,\n        1 => Location::Left,\n        2 => Location::Right,\n        3 => Location::Numpad,\n        // keyboard_types doesn't yet support mobile/joystick locations\n        4 | 5 => Location::Standard,\n        // unknown location; Standard seems better than panicking\n        _ => Location::Standard,\n    }\n}\n\npub fn encode_key_location(location: Location) -> usize {\n    match location {\n        Location::Standard => 0,\n        Location::Left => 1,\n        Location::Right => 2,\n        Location::Numpad => 3,\n    }\n}\n"
  },
  {
    "path": "packages/html/src/lib.rs",
    "content": "#![doc = include_str!(\"../README.md\")]\n#![doc(html_logo_url = \"https://avatars.githubusercontent.com/u/79236386\")]\n#![doc(html_favicon_url = \"https://avatars.githubusercontent.com/u/79236386\")]\n#![allow(non_snake_case)]\n\n//! # Dioxus Namespace for HTML\n//!\n//! This crate provides a set of compile-time correct HTML elements that can be used with the Rsx and Html macros.\n//! This system allows users to easily build new tags, new types, and customize the output of the Rsx and Html macros.\n//!\n//! An added benefit of this approach is the ability to lend comprehensive documentation on how to use these elements inside\n//! of the Rsx and Html macros. Each element comes with a substantial amount of documentation on how to best use it, hopefully\n//! making the development cycle quick.\n//!\n//! All elements are used as zero-sized unit structs with trait impls.\n//!\n//! Currently, we don't validate for structures, but do validate attributes.\n\npub mod elements;\n#[cfg(feature = \"hot-reload-context\")]\npub use elements::HtmlCtx;\n#[cfg(feature = \"html-to-rsx\")]\npub use elements::{map_html_attribute_to_rsx, map_html_element_to_rsx};\npub mod events;\npub(crate) mod file_data;\npub use file_data::*;\nmod attribute_groups;\nmod data_transfer;\npub mod geometry;\npub mod input_data;\npub mod point_interaction;\nmod render_template;\npub use data_transfer::*;\n\npub use bytes;\n\n#[cfg(feature = \"serialize\")]\nmod transit;\n\n#[cfg(feature = \"serialize\")]\npub use transit::*;\n\npub use attribute_groups::*;\npub use elements::*;\npub use events::*;\npub use render_template::*;\n\npub use crate::attribute_groups::{GlobalAttributesExtension, SvgAttributesExtension};\npub use crate::elements::extensions::*;\npub use crate::point_interaction::*;\npub use keyboard_types::{self, Code, Key, Location, Modifiers};\n\npub mod traits {\n    pub use crate::events::*;\n    pub use crate::point_interaction::*;\n}\n\npub mod extensions {\n    pub use crate::attribute_groups::{GlobalAttributesExtension, SvgAttributesExtension};\n    pub use crate::elements::extensions::*;\n}\n"
  },
  {
    "path": "packages/html/src/point_interaction.rs",
    "content": "use keyboard_types::Modifiers;\n\nuse crate::{\n    geometry::{ClientPoint, Coordinates, ElementPoint, PagePoint, ScreenPoint},\n    input_data::{MouseButton, MouseButtonSet},\n};\n\n/// A interaction that contains data about the location of the event.\npub trait InteractionLocation {\n    /// Gets the coordinates of the event relative to the browser viewport.\n    fn client_coordinates(&self) -> ClientPoint;\n\n    /// Gets the coordinates of the event relative to the screen.\n    fn screen_coordinates(&self) -> ScreenPoint;\n\n    /// Gets the coordinates of the event relative to the page.\n    fn page_coordinates(&self) -> PagePoint;\n}\n\n/// A interaction that contains data about the location of the event.\npub trait InteractionElementOffset: InteractionLocation {\n    /// Gets the coordinates of the event.\n    fn coordinates(&self) -> Coordinates {\n        Coordinates::new(\n            self.screen_coordinates(),\n            self.client_coordinates(),\n            self.element_coordinates(),\n            self.page_coordinates(),\n        )\n    }\n\n    /// Gets the coordinates of the event relative to the target element.\n    fn element_coordinates(&self) -> ElementPoint;\n}\n\n/// A interaction that contains data about the pointer button(s) that triggered the event.\npub trait PointerInteraction: InteractionElementOffset + ModifiersInteraction {\n    /// Gets the button that triggered the event.\n    fn trigger_button(&self) -> Option<MouseButton>;\n\n    /// Gets the buttons that are currently held down.\n    fn held_buttons(&self) -> MouseButtonSet;\n}\n\n/// A interaction that contains data about the current state of the keyboard modifiers.\npub trait ModifiersInteraction {\n    /// Gets the modifiers of the pointer event.\n    fn modifiers(&self) -> Modifiers;\n}\n\n#[cfg(feature = \"serialize\")]\n#[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq, Clone, Default)]\npub struct SerializedPointInteraction {\n    pub alt_key: bool,\n\n    /// The button number that was pressed (if applicable) when the mouse event was fired.\n    pub button: i16,\n\n    /// Indicates which buttons are pressed on the mouse (or other input device) when a mouse event is triggered.\n    ///\n    /// Each button that can be pressed is represented by a given number (see below). If more than one button is pressed, the button values are added together to produce a new number. For example, if the secondary (2) and auxiliary (4) buttons are pressed simultaneously, the value is 6 (i.e., 2 + 4).\n    ///\n    /// - 1: Primary button (usually the left button)\n    /// - 2: Secondary button (usually the right button)\n    /// - 4: Auxiliary button (usually the mouse wheel button or middle button)\n    /// - 8: 4th button (typically the \"Browser Back\" button)\n    /// - 16 : 5th button (typically the \"Browser Forward\" button)\n    pub buttons: u16,\n\n    /// The horizontal coordinate within the application's viewport at which the event occurred (as opposed to the coordinate within the page).\n    ///\n    /// For example, clicking on the left edge of the viewport will always result in a mouse event with a clientX value of 0, regardless of whether the page is scrolled horizontally.\n    pub client_x: f64,\n\n    /// The vertical coordinate within the application's viewport at which the event occurred (as opposed to the coordinate within the page).\n    ///\n    /// For example, clicking on the top edge of the viewport will always result in a mouse event with a clientY value of 0, regardless of whether the page is scrolled vertically.\n    pub client_y: f64,\n\n    /// True if the control key was down when the mouse event was fired.\n    pub ctrl_key: bool,\n\n    /// True if the meta key was down when the mouse event was fired.\n    pub meta_key: bool,\n\n    /// The offset in the X coordinate of the mouse pointer between that event and the padding edge of the target node.\n    pub offset_x: f64,\n\n    /// The offset in the Y coordinate of the mouse pointer between that event and the padding edge of the target node.\n    pub offset_y: f64,\n\n    /// The X (horizontal) coordinate (in pixels) of the mouse, relative to the left edge of the entire document. This includes any portion of the document not currently visible.\n    ///\n    /// Being based on the edge of the document as it is, this property takes into account any horizontal scrolling of the page. For example, if the page is scrolled such that 200 pixels of the left side of the document are scrolled out of view, and the mouse is clicked 100 pixels inward from the left edge of the view, the value returned by pageX will be 300.\n    pub page_x: f64,\n\n    /// The Y (vertical) coordinate in pixels of the event relative to the whole document.\n    ///\n    /// See `page_x`.\n    pub page_y: f64,\n\n    /// The X coordinate of the mouse pointer in global (screen) coordinates.\n    pub screen_x: f64,\n\n    /// The Y coordinate of the mouse pointer in global (screen) coordinates.\n    pub screen_y: f64,\n\n    /// True if the shift key was down when the mouse event was fired.\n    pub shift_key: bool,\n}\n\n#[cfg(feature = \"serialize\")]\nimpl SerializedPointInteraction {\n    pub fn new(\n        trigger_button: Option<MouseButton>,\n        held_buttons: MouseButtonSet,\n        coordinates: Coordinates,\n        modifiers: Modifiers,\n    ) -> Self {\n        let alt_key = modifiers.contains(Modifiers::ALT);\n        let ctrl_key = modifiers.contains(Modifiers::CONTROL);\n        let meta_key = modifiers.contains(Modifiers::META);\n        let shift_key = modifiers.contains(Modifiers::SHIFT);\n\n        let [client_x, client_y]: [f64; 2] = coordinates.client().cast().into();\n        let [offset_x, offset_y]: [f64; 2] = coordinates.element().cast().into();\n        let [page_x, page_y]: [f64; 2] = coordinates.page().cast().into();\n        let [screen_x, screen_y]: [f64; 2] = coordinates.screen().cast().into();\n        Self {\n            button: trigger_button\n                .map_or(MouseButton::default(), |b| b)\n                .into_web_code(),\n            buttons: crate::input_data::encode_mouse_button_set(held_buttons),\n            meta_key,\n            ctrl_key,\n            shift_key,\n            alt_key,\n            client_x,\n            client_y,\n            screen_x,\n            screen_y,\n            offset_x,\n            offset_y,\n            page_x,\n            page_y,\n        }\n    }\n}\n\n#[cfg(feature = \"serialize\")]\nimpl<E: PointerInteraction> From<&E> for SerializedPointInteraction {\n    fn from(data: &E) -> Self {\n        let trigger_button = data.trigger_button();\n        let held_buttons = data.held_buttons();\n        let coordinates = data.coordinates();\n        let modifiers = data.modifiers();\n        Self::new(trigger_button, held_buttons, coordinates, modifiers)\n    }\n}\n\n#[cfg(feature = \"serialize\")]\nimpl PointerInteraction for SerializedPointInteraction {\n    fn held_buttons(&self) -> MouseButtonSet {\n        crate::input_data::decode_mouse_button_set(self.buttons)\n    }\n\n    fn trigger_button(&self) -> Option<MouseButton> {\n        Some(MouseButton::from_web_code(self.button))\n    }\n}\n\n#[cfg(feature = \"serialize\")]\nimpl ModifiersInteraction for SerializedPointInteraction {\n    fn modifiers(&self) -> Modifiers {\n        let mut modifiers = Modifiers::empty();\n\n        if self.alt_key {\n            modifiers.insert(Modifiers::ALT);\n        }\n        if self.ctrl_key {\n            modifiers.insert(Modifiers::CONTROL);\n        }\n        if self.meta_key {\n            modifiers.insert(Modifiers::META);\n        }\n        if self.shift_key {\n            modifiers.insert(Modifiers::SHIFT);\n        }\n\n        modifiers\n    }\n}\n\n#[cfg(feature = \"serialize\")]\nimpl InteractionLocation for SerializedPointInteraction {\n    fn client_coordinates(&self) -> ClientPoint {\n        ClientPoint::new(self.client_x, self.client_y)\n    }\n\n    fn screen_coordinates(&self) -> ScreenPoint {\n        ScreenPoint::new(self.screen_x, self.screen_y)\n    }\n\n    fn page_coordinates(&self) -> PagePoint {\n        PagePoint::new(self.page_x, self.page_y)\n    }\n}\n\n#[cfg(feature = \"serialize\")]\nimpl InteractionElementOffset for SerializedPointInteraction {\n    fn element_coordinates(&self) -> ElementPoint {\n        ElementPoint::new(self.offset_x, self.offset_y)\n    }\n}\n"
  },
  {
    "path": "packages/html/src/render_template.rs",
    "content": "use dioxus_core::{Template, TemplateAttribute, TemplateNode};\nuse std::fmt::Write;\n\n/// Render a template to an HTML string\n///\n/// Useful for sending over the wire. Can be used to with innerHtml to create templates with little work\npub fn render_template_to_html(template: &Template) -> String {\n    let mut out = String::new();\n\n    for root in template.roots {\n        render_template_node(root, &mut out).unwrap();\n    }\n\n    out\n}\n\nfn render_template_node(node: &TemplateNode, out: &mut String) -> std::fmt::Result {\n    match node {\n        TemplateNode::Element {\n            tag,\n            attrs,\n            children,\n            ..\n        } => {\n            write!(out, \"<{tag}\")?;\n            for attr in *attrs {\n                if let TemplateAttribute::Static { name, value, .. } = attr {\n                    write!(out, \"{name}=\\\"{value}\\\"\")?;\n                }\n            }\n            for child in *children {\n                render_template_node(child, out)?;\n            }\n            write!(out, \"</{tag}>\")?;\n        }\n        TemplateNode::Text { text: t } => write!(out, \"{t}\")?,\n        TemplateNode::Dynamic { id: _ } => write!(out, \"<!--placeholder-->\")?,\n    };\n    Ok(())\n}\n"
  },
  {
    "path": "packages/html/src/transit.rs",
    "content": "use std::{any::Any, rc::Rc};\n\nuse crate::events::*;\nuse dioxus_core::ElementId;\nuse serde::{Deserialize, Serialize};\n\n#[cfg(feature = \"serialize\")]\n#[derive(Serialize, Debug, PartialEq)]\npub struct HtmlEvent {\n    pub element: ElementId,\n    pub name: String,\n    pub bubbles: bool,\n    pub data: EventData,\n}\n\n#[cfg(feature = \"serialize\")]\nimpl<'de> Deserialize<'de> for HtmlEvent {\n    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>\n    where\n        D: serde::Deserializer<'de>,\n    {\n        #[derive(Deserialize, Debug, Clone)]\n        struct Inner {\n            element: ElementId,\n            name: String,\n            bubbles: bool,\n            data: serde_json::Value,\n        }\n\n        let Inner {\n            element,\n            name,\n            bubbles,\n            data,\n        } = Inner::deserialize(deserializer)?;\n\n        // in debug mode let's try and be helpful as to why the deserialization failed\n        let data = deserialize_raw(&name, &data).map_err(|e| {\n            serde::de::Error::custom(format!(\n                \"Failed to deserialize event data for event {}:  {}\\n'{:#?}'\",\n                name, e, data,\n            ))\n        })?;\n\n        Ok(HtmlEvent {\n            data,\n            element,\n            bubbles,\n            name,\n        })\n    }\n}\n\n#[cfg(feature = \"serialize\")]\nfn deserialize_raw(name: &str, data: &serde_json::Value) -> Result<EventData, serde_json::Error> {\n    use EventData::*;\n\n    // a little macro-esque thing to make the code below more readable\n    #[inline]\n    fn de<'de, F>(f: &'de serde_json::Value) -> Result<F, serde_json::Error>\n    where\n        F: Deserialize<'de>,\n    {\n        F::deserialize(f)\n    }\n\n    let data = match name {\n        // Cancel\n        \"cancel\" => Cancel(de(data)?),\n\n        // Mouse\n        \"click\" | \"contextmenu\" | \"dblclick\" | \"doubleclick\" | \"mousedown\" | \"mouseenter\"\n        | \"mouseleave\" | \"mousemove\" | \"mouseout\" | \"mouseover\" | \"mouseup\" => Mouse(de(data)?),\n\n        // Clipboard\n        \"copy\" | \"cut\" | \"paste\" => Clipboard(de(data)?),\n\n        // Composition\n        \"compositionend\" | \"compositionstart\" | \"compositionupdate\" => Composition(de(data)?),\n\n        // Keyboard\n        \"keydown\" | \"keypress\" | \"keyup\" => Keyboard(de(data)?),\n\n        // Focus\n        \"blur\" | \"focus\" | \"focusin\" | \"focusout\" => Focus(de(data)?),\n\n        // Form\n        \"change\" | \"input\" | \"invalid\" | \"reset\" | \"submit\" => Form(de(data)?),\n\n        // Drag\n        \"drag\" | \"dragend\" | \"dragenter\" | \"dragexit\" | \"dragleave\" | \"dragover\" | \"dragstart\"\n        | \"drop\" => Drag(de(data)?),\n\n        // Pointer\n        \"pointerlockchange\" | \"pointerlockerror\" | \"pointerdown\" | \"pointermove\" | \"pointerup\"\n        | \"pointerover\" | \"pointerout\" | \"pointerenter\" | \"pointerleave\" | \"gotpointercapture\"\n        | \"lostpointercapture\" | \"auxclick\" => Pointer(de(data)?),\n\n        // Selection\n        \"selectstart\" | \"selectionchange\" | \"select\" => Selection(de(data)?),\n\n        // Touch\n        \"touchcancel\" | \"touchend\" | \"touchmove\" | \"touchstart\" => Touch(de(data)?),\n\n        // Resize\n        \"resize\" => Resize(de(data)?),\n\n        // Scroll\n        \"scroll\" | \"scrollend\" => Scroll(de(data)?),\n\n        // Visible\n        \"visible\" => Visible(de(data)?),\n\n        // Wheel\n        \"wheel\" => Wheel(de(data)?),\n\n        // Media\n        \"abort\" | \"canplay\" | \"canplaythrough\" | \"durationchange\" | \"emptied\" | \"encrypted\"\n        | \"ended\" | \"interruptbegin\" | \"interruptend\" | \"loadeddata\" | \"loadedmetadata\"\n        | \"loadstart\" | \"pause\" | \"play\" | \"playing\" | \"progress\" | \"ratechange\" | \"seeked\"\n        | \"seeking\" | \"stalled\" | \"suspend\" | \"timeupdate\" | \"volumechange\" | \"waiting\"\n        | \"loadend\" | \"timeout\" => Media(de(data)?),\n\n        // Animation\n        \"animationstart\" | \"animationend\" | \"animationiteration\" => Animation(de(data)?),\n\n        // Transition\n        \"transitionend\" => Transition(de(data)?),\n\n        // Toggle\n        \"toggle\" => Toggle(de(data)?),\n\n        \"load\" | \"error\" => Image(de(data)?),\n\n        // Mounted\n        \"mounted\" => Mounted,\n\n        // OtherData => \"abort\" | \"afterprint\" | \"beforeprint\" | \"beforeunload\" | \"hashchange\" | \"languagechange\" | \"message\" | \"offline\" | \"online\" | \"pagehide\" | \"pageshow\" | \"popstate\" | \"rejectionhandled\" | \"storage\" | \"unhandledrejection\" | \"unload\" | \"userproximity\" | \"vrdisplayactivate\" | \"vrdisplayblur\" | \"vrdisplayconnect\" | \"vrdisplaydeactivate\" | \"vrdisplaydisconnect\" | \"vrdisplayfocus\" | \"vrdisplaypointerrestricted\" | \"vrdisplaypointerunrestricted\" | \"vrdisplaypresentchange\";\n        other => {\n            return Err(serde::de::Error::custom(format!(\n                \"Unknown event type: {other}\"\n            )))\n        }\n    };\n\n    Ok(data)\n}\n\n#[cfg(feature = \"serialize\")]\nimpl HtmlEvent {\n    pub fn bubbles(&self) -> bool {\n        self.bubbles\n    }\n}\n\n#[derive(Deserialize, Serialize, Debug, PartialEq)]\n#[serde(untagged)]\n#[non_exhaustive]\npub enum EventData {\n    Cancel(SerializedCancelData),\n    Mouse(SerializedMouseData),\n    Clipboard(SerializedClipboardData),\n    Composition(SerializedCompositionData),\n    Keyboard(SerializedKeyboardData),\n    Focus(SerializedFocusData),\n    Form(SerializedFormData),\n    Drag(SerializedDragData),\n    Pointer(SerializedPointerData),\n    Selection(SerializedSelectionData),\n    Touch(SerializedTouchData),\n    Resize(SerializedResizeData),\n    Scroll(SerializedScrollData),\n    Visible(SerializedVisibleData),\n    Wheel(SerializedWheelData),\n    Media(SerializedMediaData),\n    Animation(SerializedAnimationData),\n    Transition(SerializedTransitionData),\n    Toggle(SerializedToggleData),\n    Image(SerializedImageData),\n    Mounted,\n}\n\nimpl EventData {\n    pub fn into_any(self) -> Rc<dyn Any> {\n        match self {\n            EventData::Cancel(data) => {\n                Rc::new(PlatformEventData::new(Box::new(data))) as Rc<dyn Any>\n            }\n            EventData::Mouse(data) => {\n                Rc::new(PlatformEventData::new(Box::new(data))) as Rc<dyn Any>\n            }\n            EventData::Clipboard(data) => {\n                Rc::new(PlatformEventData::new(Box::new(data))) as Rc<dyn Any>\n            }\n            EventData::Composition(data) => {\n                Rc::new(PlatformEventData::new(Box::new(data))) as Rc<dyn Any>\n            }\n            EventData::Keyboard(data) => {\n                Rc::new(PlatformEventData::new(Box::new(data))) as Rc<dyn Any>\n            }\n            EventData::Focus(data) => {\n                Rc::new(PlatformEventData::new(Box::new(data))) as Rc<dyn Any>\n            }\n            EventData::Form(data) => Rc::new(PlatformEventData::new(Box::new(data))) as Rc<dyn Any>,\n            EventData::Drag(data) => Rc::new(PlatformEventData::new(Box::new(data))) as Rc<dyn Any>,\n            EventData::Pointer(data) => {\n                Rc::new(PlatformEventData::new(Box::new(data))) as Rc<dyn Any>\n            }\n            EventData::Selection(data) => {\n                Rc::new(PlatformEventData::new(Box::new(data))) as Rc<dyn Any>\n            }\n            EventData::Touch(data) => {\n                Rc::new(PlatformEventData::new(Box::new(data))) as Rc<dyn Any>\n            }\n            EventData::Resize(data) => {\n                Rc::new(PlatformEventData::new(Box::new(data))) as Rc<dyn Any>\n            }\n            EventData::Scroll(data) => {\n                Rc::new(PlatformEventData::new(Box::new(data))) as Rc<dyn Any>\n            }\n            EventData::Visible(data) => {\n                Rc::new(PlatformEventData::new(Box::new(data))) as Rc<dyn Any>\n            }\n            EventData::Wheel(data) => {\n                Rc::new(PlatformEventData::new(Box::new(data))) as Rc<dyn Any>\n            }\n            EventData::Media(data) => {\n                Rc::new(PlatformEventData::new(Box::new(data))) as Rc<dyn Any>\n            }\n            EventData::Animation(data) => {\n                Rc::new(PlatformEventData::new(Box::new(data))) as Rc<dyn Any>\n            }\n            EventData::Transition(data) => {\n                Rc::new(PlatformEventData::new(Box::new(data))) as Rc<dyn Any>\n            }\n            EventData::Toggle(data) => {\n                Rc::new(PlatformEventData::new(Box::new(data))) as Rc<dyn Any>\n            }\n            EventData::Image(data) => {\n                Rc::new(PlatformEventData::new(Box::new(data))) as Rc<dyn Any>\n            }\n            EventData::Mounted => {\n                Rc::new(PlatformEventData::new(Box::new(MountedData::new(())))) as Rc<dyn Any>\n            }\n        }\n    }\n}\n\n#[test]\nfn test_back_and_forth() {\n    let data = HtmlEvent {\n        element: ElementId(0),\n        data: EventData::Mouse(SerializedMouseData::default()),\n        name: \"click\".to_string(),\n        bubbles: true,\n    };\n\n    println!(\"{}\", serde_json::to_string_pretty(&data).unwrap());\n\n    let o = r#\"\n{\n  \"element\": 0,\n  \"name\": \"click\",\n  \"bubbles\": true,\n  \"data\": {\n    \"alt_key\": false,\n    \"button\": 0,\n    \"buttons\": 0,\n    \"client_x\": 0,\n    \"client_y\": 0,\n    \"ctrl_key\": false,\n    \"meta_key\": false,\n    \"offset_x\": 0,\n    \"offset_y\": 0,\n    \"page_x\": 0,\n    \"page_y\": 0,\n    \"screen_x\": 0,\n    \"screen_y\": 0,\n    \"shift_key\": false\n  }\n}\n    \"#;\n\n    let p: HtmlEvent = serde_json::from_str(o).unwrap();\n\n    assert_eq!(data, p);\n}\n\n/// A trait for converting from a serialized event to a concrete event type.\npub struct SerializedHtmlEventConverter;\n\nimpl HtmlEventConverter for SerializedHtmlEventConverter {\n    fn convert_animation_data(&self, event: &PlatformEventData) -> AnimationData {\n        event\n            .downcast::<SerializedAnimationData>()\n            .cloned()\n            .unwrap()\n            .into()\n    }\n\n    fn convert_cancel_data(&self, event: &PlatformEventData) -> CancelData {\n        event\n            .downcast::<SerializedCancelData>()\n            .cloned()\n            .unwrap()\n            .into()\n    }\n\n    fn convert_clipboard_data(&self, event: &PlatformEventData) -> ClipboardData {\n        event\n            .downcast::<SerializedClipboardData>()\n            .cloned()\n            .unwrap()\n            .into()\n    }\n\n    fn convert_composition_data(&self, event: &PlatformEventData) -> CompositionData {\n        event\n            .downcast::<SerializedCompositionData>()\n            .cloned()\n            .unwrap()\n            .into()\n    }\n\n    fn convert_drag_data(&self, event: &PlatformEventData) -> DragData {\n        event\n            .downcast::<SerializedDragData>()\n            .cloned()\n            .unwrap()\n            .into()\n    }\n\n    fn convert_focus_data(&self, event: &PlatformEventData) -> FocusData {\n        event\n            .downcast::<SerializedFocusData>()\n            .cloned()\n            .unwrap()\n            .into()\n    }\n\n    fn convert_form_data(&self, event: &PlatformEventData) -> FormData {\n        event\n            .downcast::<SerializedFormData>()\n            .cloned()\n            .unwrap()\n            .into()\n    }\n\n    fn convert_image_data(&self, event: &PlatformEventData) -> ImageData {\n        event\n            .downcast::<SerializedImageData>()\n            .cloned()\n            .unwrap()\n            .into()\n    }\n\n    fn convert_keyboard_data(&self, event: &PlatformEventData) -> KeyboardData {\n        event\n            .downcast::<SerializedKeyboardData>()\n            .cloned()\n            .unwrap()\n            .into()\n    }\n\n    fn convert_media_data(&self, event: &PlatformEventData) -> MediaData {\n        event\n            .downcast::<SerializedMediaData>()\n            .cloned()\n            .unwrap()\n            .into()\n    }\n\n    fn convert_mounted_data(&self, _: &PlatformEventData) -> MountedData {\n        MountedData::from(())\n    }\n\n    fn convert_mouse_data(&self, event: &PlatformEventData) -> MouseData {\n        event\n            .downcast::<SerializedMouseData>()\n            .cloned()\n            .unwrap()\n            .into()\n    }\n\n    fn convert_pointer_data(&self, event: &PlatformEventData) -> PointerData {\n        event\n            .downcast::<SerializedPointerData>()\n            .cloned()\n            .unwrap()\n            .into()\n    }\n    fn convert_resize_data(&self, event: &PlatformEventData) -> ResizeData {\n        event\n            .downcast::<SerializedResizeData>()\n            .cloned()\n            .unwrap()\n            .into()\n    }\n\n    fn convert_scroll_data(&self, event: &PlatformEventData) -> ScrollData {\n        event\n            .downcast::<SerializedScrollData>()\n            .cloned()\n            .unwrap()\n            .into()\n    }\n\n    fn convert_selection_data(&self, event: &PlatformEventData) -> SelectionData {\n        event\n            .downcast::<SerializedSelectionData>()\n            .cloned()\n            .unwrap()\n            .into()\n    }\n\n    fn convert_toggle_data(&self, event: &PlatformEventData) -> ToggleData {\n        event\n            .downcast::<SerializedToggleData>()\n            .cloned()\n            .unwrap()\n            .into()\n    }\n\n    fn convert_touch_data(&self, event: &PlatformEventData) -> TouchData {\n        event\n            .downcast::<SerializedTouchData>()\n            .cloned()\n            .unwrap()\n            .into()\n    }\n\n    fn convert_transition_data(&self, event: &PlatformEventData) -> TransitionData {\n        event\n            .downcast::<SerializedTransitionData>()\n            .cloned()\n            .unwrap()\n            .into()\n    }\n\n    fn convert_visible_data(&self, event: &PlatformEventData) -> VisibleData {\n        event\n            .downcast::<SerializedVisibleData>()\n            .cloned()\n            .unwrap()\n            .into()\n    }\n\n    fn convert_wheel_data(&self, event: &PlatformEventData) -> WheelData {\n        event\n            .downcast::<SerializedWheelData>()\n            .cloned()\n            .unwrap()\n            .into()\n    }\n}\n"
  },
  {
    "path": "packages/html/tsconfig.json",
    "content": "{\n    \"compilerOptions\": {\n        \"module\": \"CommonJS\",\n        \"lib\": [\n            \"ES2015\",\n            \"DOM\",\n            \"dom\",\n            \"dom.iterable\",\n            \"ESNext\"\n        ],\n        \"noImplicitAny\": true,\n        \"removeComments\": true,\n        \"preserveConstEnums\": true,\n    },\n    \"exclude\": [\n        \"**/*.spec.ts\"\n    ]\n}\n"
  },
  {
    "path": "packages/html-internal-macro/Cargo.toml",
    "content": "[package]\nname = \"dioxus-html-internal-macro\"\nversion = { workspace = true }\nedition = \"2021\"\nrepository = \"https://github.com/DioxusLabs/dioxus/\"\nhomepage = \"https://dioxuslabs.com\"\nkeywords = [\"dom\", \"ui\", \"gui\", \"react\", \"liveview\"]\nlicense = \"MIT OR Apache-2.0\"\ndescription = \"HTML function macros for Dioxus\"\n\n\n# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html\n\n[dependencies]\nproc-macro2 = { workspace = true }\nsyn = { workspace = true, features = [\"full\"] }\nquote = { workspace = true }\nconvert_case = { workspace = true }\n\n[lib]\nproc-macro = true\n\n[[test]]\nname = \"tests\"\npath = \"tests/progress.rs\"\n\n[dev-dependencies]\ntrybuild = { workspace = true, features = [\"diff\"] }\n"
  },
  {
    "path": "packages/html-internal-macro/src/lib.rs",
    "content": "use proc_macro::TokenStream;\n\nuse convert_case::{Case, Casing};\nuse proc_macro2::TokenStream as TokenStream2;\nuse quote::{quote, ToTokens, TokenStreamExt};\nuse syn::parse::{Parse, ParseStream};\nuse syn::punctuated::Punctuated;\nuse syn::{braced, parse_macro_input, Ident, Token};\n\n#[proc_macro]\npub fn impl_extension_attributes(input: TokenStream) -> TokenStream {\n    let input = parse_macro_input!(input as ImplExtensionAttributes);\n    input.to_token_stream().into()\n}\n\nstruct ImplExtensionAttributes {\n    name: Ident,\n    attrs: Punctuated<Ident, Token![,]>,\n}\n\nimpl Parse for ImplExtensionAttributes {\n    fn parse(input: ParseStream) -> syn::Result<Self> {\n        let content;\n\n        let name = input.parse()?;\n        braced!(content in input);\n        let attrs = content.parse_terminated(Ident::parse, Token![,])?;\n\n        Ok(ImplExtensionAttributes { name, attrs })\n    }\n}\n\nimpl ToTokens for ImplExtensionAttributes {\n    fn to_tokens(&self, tokens: &mut TokenStream2) {\n        let name = &self.name;\n        let name_string = name.to_string();\n        let camel_name = name_string\n            .strip_prefix(\"r#\")\n            .unwrap_or(&name_string)\n            .to_case(Case::UpperCamel);\n        let extension_name = Ident::new(format!(\"{}Extension\", &camel_name).as_str(), name.span());\n\n        let impls = self.attrs.iter().map(|ident| {\n            let d = quote! { #name::#ident };\n            quote! {\n                fn #ident(self, value: impl IntoAttributeValue) -> Self {\n                    let d = #d;\n                    self.push_attribute(d.0, d.1, value, d.2)\n                }\n            }\n        });\n        tokens.append_all(quote! {\n            pub trait #extension_name: HasAttributes + Sized {\n                #(#impls)*\n            }\n        });\n    }\n}\n"
  },
  {
    "path": "packages/html-internal-macro/tests/01-simple.rs",
    "content": "fn main() {}\n"
  },
  {
    "path": "packages/html-internal-macro/tests/progress.rs",
    "content": "#[test]\nfn tests() {\n    let t = trybuild::TestCases::new();\n    t.pass(\"tests/01-simple.rs\");\n}\n"
  },
  {
    "path": "packages/interpreter/.gitignore",
    "content": "gen2/\n"
  },
  {
    "path": "packages/interpreter/Cargo.toml",
    "content": "[package]\nname = \"dioxus-interpreter-js\"\nversion = { workspace = true }\nedition = \"2021\"\nauthors = [\"Jonathan Kelley\"]\ndescription = \"JS Interpreter for Dioxus - a concurrent renderer-agnostic Virtual DOM for interactive user experiences\"\nlicense = \"MIT OR Apache-2.0\"\nrepository = \"https://github.com/DioxusLabs/dioxus/\"\nhomepage = \"https://dioxuslabs.com\"\ndocumentation = \"https://docs.rs/dioxus\"\nkeywords = [\"dom\", \"ui\", \"gui\", \"react\", \"wasm\"]\n\n[dependencies]\nwasm-bindgen = { workspace = true, optional = true }\nwasm-bindgen-futures = { workspace = true, optional = true }\njs-sys = { workspace = true, optional = true }\nweb-sys = { workspace = true, optional = true, features = [\n    \"Element\",\n    \"Node\",\n] }\nsledgehammer_bindgen = { version = \"0.6.0\", default-features = false, optional = true }\nsledgehammer_utils = { version = \"0.3.1\", optional = true }\nserde = { workspace = true, features = [\"derive\"], optional = true }\nrustc-hash = { workspace = true, optional = true }\n\ndioxus-core = { workspace = true, optional = true }\ndioxus-core-types = { workspace = true, optional = true }\ndioxus-html = { workspace = true, optional = true }\n\n[build-dependencies]\nlazy-js-bundle = { workspace = true }\n\n[features]\ndefault = []\nserialize = [\"dep:serde\"]\nsledgehammer = [\"dep:sledgehammer_bindgen\", \"dep:sledgehammer_utils\", \"dep:rustc-hash\"]\nwebonly = [\n    \"sledgehammer\",\n    \"dep:wasm-bindgen\",\n    \"dep:wasm-bindgen-futures\",\n    \"dep:js-sys\",\n    \"dep:web-sys\",\n    \"sledgehammer_bindgen/web\",\n]\nbinary-protocol = [\"sledgehammer\", \"dep:dioxus-core\", \"dep:dioxus-core-types\", \"dep:dioxus-html\"]\nminimal_bindings = []\n"
  },
  {
    "path": "packages/interpreter/NOTES.md",
    "content": "# Notes on the web implementation\n\nHere's some useful resources if you ever need to splunk into the intricacies of how events are handled in HTML:\n\n\n- Not all event handlers are sync: https://w3c.github.io/uievents/#sync-async\n- Some attributes are truthy: https://github.com/facebook/react/blob/8b88ac2592c5f555f315f9440cbb665dd1e7457a/packages/react-dom/src/shared/DOMProperty.js#L352-L364\n"
  },
  {
    "path": "packages/interpreter/README.md",
    "content": "# `dioxus-interpreter-js`\n\n[![Crates.io][crates-badge]][crates-url]\n[![MIT licensed][mit-badge]][mit-url]\n[![Build Status][actions-badge]][actions-url]\n[![Discord chat][discord-badge]][discord-url]\n\n[crates-badge]: https://img.shields.io/crates/v/dioxus-interpreter-js.svg\n[crates-url]: https://crates.io/crates/dioxus-interpreter-js\n[mit-badge]: https://img.shields.io/badge/license-MIT-blue.svg\n[mit-url]: https://github.com/dioxuslabs/dioxus/blob/main/LICENSE-MIT\n[actions-badge]: https://github.com/dioxuslabs/dioxus/actions/workflows/main.yml/badge.svg\n[actions-url]: https://github.com/dioxuslabs/dioxus/actions?query=workflow%3ACI+branch%3Amaster\n[discord-badge]: https://img.shields.io/discord/899851952891002890.svg?logo=discord&style=flat-square\n[discord-url]: https://discord.gg/XgGxMSkvUM\n\n[Website](https://dioxuslabs.com) |\n[Guides](https://dioxuslabs.com/learn/0.7/) |\n[API Docs](https://docs.rs/dioxus-interpreter-js/latest/dioxus_interpreter_js) |\n[Chat](https://discord.gg/XgGxMSkvUM)\n\n## Overview\n\n`dioxus-interpreter-js` provides the high-performance JavaScript glue that interprets the stream of edits produced by the Dioxus VirtualDom and converts them into mutations on the actual web DOM.\n\nThis crate features bindings for the web and sledgehammer for increased performance.\n\n## Architecture\n\nWe use TypeScript to write the bindings and a very simple build.rs along with bun to convert them to javascript, minify them, and glue them into the rest of the project.\n\nNot every snippet of JS will be used, so we split out the snippets from the core interpreter.\n\nIn theory, we _could_ use Rust in the browser to do everything these bindings are doing. In reality, we want to stick with JS to skip the need for a WASM build step when running the LiveView and WebView renderers. We also want to use JS to prevent diverging behavior of things like canceling events, uploading files, and collecting form inputs. These details are tough to ensure 1:1 compatibility when implementing them in two languages.\n\nIf you want to contribute to the bindings, you'll need to have the typescript compiler installed on your machine as well as bun:\n\n<https://bun.sh/docs/installation>\n\n## Contributing\n\n- Report issues on our [issue tracker](https://github.com/dioxuslabs/dioxus/issues).\n- Join the discord and ask questions!\n\n## License\n\nThis project is licensed under the [MIT license].\n\n[mit license]: https://github.com/dioxuslabs/dioxus/blob/main/LICENSE-MIT\n\nUnless you explicitly state otherwise, any contribution intentionally submitted\nfor inclusion in Dioxus by you shall be licensed as MIT without any additional\nterms or conditions.\n"
  },
  {
    "path": "packages/interpreter/build.rs",
    "content": "fn main() {\n    // If any TS files change, re-run the build script\n    lazy_js_bundle::LazyTypeScriptBindings::new()\n        .with_watching(\"./src/ts\")\n        .with_binding(\"./src/ts/set_attribute.ts\", \"./src/js/set_attribute.js\")\n        .with_binding(\"./src/ts/native.ts\", \"./src/js/native.js\")\n        .with_binding(\"./src/ts/core.ts\", \"./src/js/core.js\")\n        .with_binding(\"./src/ts/hydrate.ts\", \"./src/js/hydrate.js\")\n        .with_binding(\"./src/ts/patch_console.ts\", \"./src/js/patch_console.js\")\n        .with_binding(\n            \"./src/ts/initialize_streaming.ts\",\n            \"./src/js/initialize_streaming.js\",\n        )\n        .run();\n}\n"
  },
  {
    "path": "packages/interpreter/src/js/common.js",
    "content": "function setAttributeInner(node,field,value,ns){if(ns===\"style\"){node.style.setProperty(field,value);return}if(ns){node.setAttributeNS(ns,field,value);return}switch(field){case\"value\":if(node.tagName===\"OPTION\")setAttributeDefault(node,field,value);else if(node.value!==value)node.value=value;break;case\"initial_value\":node.defaultValue=value;break;case\"checked\":node.checked=truthy(value);break;case\"initial_checked\":node.defaultChecked=truthy(value);break;case\"selected\":node.selected=truthy(value);break;case\"initial_selected\":node.defaultSelected=truthy(value);break;case\"dangerous_inner_html\":node.innerHTML=value;break;case\"style\":let existingStyles={};for(let i=0;i<node.style.length;i++){let prop=node.style[i];existingStyles[prop]=node.style.getPropertyValue(prop)}node.setAttribute(field,value);for(let prop in existingStyles)if(!node.style.getPropertyValue(prop))node.style.setProperty(prop,existingStyles[prop]);break;case\"multiple\":if(setAttributeDefault(node,field,value),node.options!==null&&node.options!==void 0){let options=node.options;for(let option of options)option.selected=option.defaultSelected}break;default:setAttributeDefault(node,field,value)}}function setAttributeDefault(node,field,value){if(!truthy(value)&&isBoolAttr(field))node.removeAttribute(field);else node.setAttribute(field,value)}function truthy(val){return val===\"true\"||val===!0}function isBoolAttr(field){switch(field){case\"allowfullscreen\":case\"allowpaymentrequest\":case\"async\":case\"autofocus\":case\"autoplay\":case\"checked\":case\"controls\":case\"default\":case\"defer\":case\"disabled\":case\"formnovalidate\":case\"hidden\":case\"ismap\":case\"itemscope\":case\"loop\":case\"multiple\":case\"muted\":case\"nomodule\":case\"novalidate\":case\"open\":case\"playsinline\":case\"readonly\":case\"required\":case\"reversed\":case\"selected\":case\"truespeed\":case\"webkitdirectory\":return!0;default:return!1}}function retrieveFormValues(form){let formData=new FormData(form),contents=[];return formData.forEach((value,key)=>{if(value instanceof File){let fileData={name:value.name,path:value.webkitRelativePath,size:value.size,last_modified:value.lastModified,content_type:value.type};contents.push([key,fileData])}else contents.push([key,value])}),{valid:form.checkValidity(),values:contents}}export{setAttributeInner,retrieveFormValues};\n"
  },
  {
    "path": "packages/interpreter/src/js/core.js",
    "content": "function setAttributeInner(node,field,value,ns){if(ns===\"style\"){node.style.setProperty(field,value);return}if(ns){node.setAttributeNS(ns,field,value);return}switch(field){case\"value\":if(node.tagName===\"OPTION\")setAttributeDefault(node,field,value);else if(node.value!==value)node.value=value;break;case\"initial_value\":node.defaultValue=value;break;case\"checked\":node.checked=truthy(value);break;case\"initial_checked\":node.defaultChecked=truthy(value);break;case\"selected\":node.selected=truthy(value);break;case\"initial_selected\":node.defaultSelected=truthy(value);break;case\"dangerous_inner_html\":node.innerHTML=value;break;case\"style\":let existingStyles={};for(let i=0;i<node.style.length;i++){let prop=node.style[i];existingStyles[prop]=node.style.getPropertyValue(prop)}node.setAttribute(field,value);for(let prop in existingStyles)if(!node.style.getPropertyValue(prop))node.style.setProperty(prop,existingStyles[prop]);break;case\"multiple\":if(setAttributeDefault(node,field,value),node.options!==null&&node.options!==void 0){let options=node.options;for(let option of options)option.selected=option.defaultSelected}break;default:setAttributeDefault(node,field,value)}}function setAttributeDefault(node,field,value){if(!truthy(value)&&isBoolAttr(field))node.removeAttribute(field);else node.setAttribute(field,value)}function truthy(val){return val===\"true\"||val===!0}function isBoolAttr(field){switch(field){case\"allowfullscreen\":case\"allowpaymentrequest\":case\"async\":case\"autofocus\":case\"autoplay\":case\"checked\":case\"controls\":case\"default\":case\"defer\":case\"disabled\":case\"formnovalidate\":case\"hidden\":case\"ismap\":case\"itemscope\":case\"loop\":case\"multiple\":case\"muted\":case\"nomodule\":case\"novalidate\":case\"open\":case\"playsinline\":case\"readonly\":case\"required\":case\"reversed\":case\"selected\":case\"truespeed\":case\"webkitdirectory\":return!0;default:return!1}}class BaseInterpreter{global;local;root;handler;resizeObserver;intersectionObserver;nodes;stack;templates;m;constructor(){}initialize(root,handler=null){this.global={},this.local={},this.root=root,this.nodes=[root],this.stack=[root],this.templates={},this.handler=handler,root.setAttribute(\"data-dioxus-id\",\"0\")}handleResizeEvent(entry){let target=entry.target,event=new CustomEvent(\"resize\",{bubbles:!1,detail:entry});target.dispatchEvent(event)}createResizeObserver(element){if(!this.resizeObserver)this.resizeObserver=new ResizeObserver((entries)=>{for(let entry of entries)this.handleResizeEvent(entry)});this.resizeObserver.observe(element)}removeResizeObserver(element){if(this.resizeObserver)this.resizeObserver.unobserve(element)}handleIntersectionEvent(entry){let target=entry.target,event=new CustomEvent(\"visible\",{bubbles:!1,detail:entry});target.dispatchEvent(event)}createIntersectionObserver(element){if(!this.intersectionObserver)this.intersectionObserver=new IntersectionObserver((entries)=>{for(let entry of entries)this.handleIntersectionEvent(entry)});this.intersectionObserver.observe(element)}removeIntersectionObserver(element){if(this.intersectionObserver)this.intersectionObserver.unobserve(element)}createListener(event_name,element,bubbles){if(event_name==\"resize\")this.createResizeObserver(element);else if(event_name==\"visible\")this.createIntersectionObserver(element);if(bubbles)if(this.global[event_name]===void 0)this.global[event_name]={active:1,callback:this.handler},this.root.addEventListener(event_name,this.handler);else this.global[event_name].active++;else{let id=element.getAttribute(\"data-dioxus-id\");if(!this.local[id])this.local[id]={};element.addEventListener(event_name,this.handler)}}removeListener(element,event_name,bubbles){if(event_name==\"resize\")this.removeResizeObserver(element);else if(event_name==\"visible\")this.removeIntersectionObserver(element);else if(bubbles)this.removeBubblingListener(event_name);else this.removeNonBubblingListener(element,event_name)}removeBubblingListener(event_name){if(this.global[event_name].active--,this.global[event_name].active===0)this.root.removeEventListener(event_name,this.global[event_name].callback),delete this.global[event_name]}removeNonBubblingListener(element,event_name){let id=element.getAttribute(\"data-dioxus-id\");if(delete this.local[id][event_name],Object.keys(this.local[id]).length===0)delete this.local[id];element.removeEventListener(event_name,this.handler)}removeAllNonBubblingListeners(element){let id=element.getAttribute(\"data-dioxus-id\");delete this.local[id]}getNode(id){return this.nodes[id]}pushRoot(node){this.stack.push(node)}appendChildren(id,many){let root=this.nodes[id],els=this.stack.splice(this.stack.length-many);for(let k=0;k<many;k++)root.appendChild(els[k])}loadChild(ptr,len){let node=this.stack[this.stack.length-1],ptr_end=ptr+len;for(;ptr<ptr_end;ptr++){let end=this.m.getUint8(ptr);for(node=node.firstChild;end>0;end--)node=node.nextSibling}return node}saveTemplate(nodes,tmpl_id){this.templates[tmpl_id]=nodes}hydrate_node(hydrateNode,ids){let split=hydrateNode.getAttribute(\"data-node-hydration\").split(\",\"),id=ids[parseInt(split[0])];if(this.nodes[id]=hydrateNode,split.length>1){hydrateNode.listening=split.length-1,hydrateNode.setAttribute(\"data-dioxus-id\",id.toString());for(let j=1;j<split.length;j++){let split2=split[j].split(\":\"),event_name=split2[0],bubbles=split2[1]===\"1\";this.createListener(event_name,hydrateNode,bubbles)}}}hydrate(ids,underNodes){for(let i=0;i<underNodes.length;i++){let under=underNodes[i];if(under instanceof HTMLElement){if(under.getAttribute(\"data-node-hydration\"))this.hydrate_node(under,ids);let hydrateNodes=under.querySelectorAll(\"[data-node-hydration]\");for(let i2=0;i2<hydrateNodes.length;i2++)this.hydrate_node(hydrateNodes[i2],ids)}let treeWalker=document.createTreeWalker(under,NodeFilter.SHOW_COMMENT),nextSibling=under.nextSibling,continueToNextNode=()=>{if(!treeWalker.nextNode())return!1;return treeWalker.currentNode!==nextSibling};while(treeWalker.currentNode){let currentNode=treeWalker.currentNode;if(currentNode.nodeType===Node.COMMENT_NODE){let id=currentNode.textContent,placeholderSplit=id.split(\"placeholder\");if(placeholderSplit.length>1){if(this.nodes[ids[parseInt(placeholderSplit[1])]]=currentNode,!continueToNextNode())break;continue}let textNodeSplit=id.split(\"node-id\");if(textNodeSplit.length>1){let next=currentNode.nextSibling;currentNode.remove();let commentAfterText,textNode;if(next.nodeType===Node.COMMENT_NODE){let newText=next.parentElement.insertBefore(document.createTextNode(\"\"),next);commentAfterText=next,textNode=newText}else textNode=next,commentAfterText=textNode.nextSibling;treeWalker.currentNode=commentAfterText,this.nodes[ids[parseInt(textNodeSplit[1])]]=textNode;let exit=currentNode===under||!continueToNextNode();if(commentAfterText.remove(),exit)break;continue}}if(!continueToNextNode())break}}}setAttributeInner(node,field,value,ns){setAttributeInner(node,field,value,ns)}}export{BaseInterpreter};\n"
  },
  {
    "path": "packages/interpreter/src/js/hash.txt",
    "content": "[17669692872757955279, 11420464406527728232, 3770103091118609057, 5444526391971481782, 8889858244860485542, 5052021921702764563, 11493752756395680038, 11339769846046015954]"
  },
  {
    "path": "packages/interpreter/src/js/hydrate.js",
    "content": "function register_rehydrate_chunk_for_streaming(callback){return register_rehydrate_chunk_for_streaming_debug(callback)}function register_rehydrate_chunk_for_streaming_debug(callback){window.hydration_callback=callback;for(let i=0;i<window.hydrate_queue.length;i++){let[id,data,debug_types,debug_locations]=window.hydrate_queue[i];window.hydration_callback(id,data,debug_types,debug_locations)}}export{register_rehydrate_chunk_for_streaming_debug,register_rehydrate_chunk_for_streaming};\n"
  },
  {
    "path": "packages/interpreter/src/js/initialize_streaming.js",
    "content": "window.hydrate_queue=[];window.dx_hydrate=(id,data,debug_types,debug_locations)=>{let decoded=atob(data),bytes=Uint8Array.from(decoded,(c)=>c.charCodeAt(0));if(window.hydration_callback)window.hydration_callback(id,bytes,debug_types,debug_locations);else window.hydrate_queue.push([id,bytes,debug_types,debug_locations])};\n"
  },
  {
    "path": "packages/interpreter/src/js/native.js",
    "content": "function serializeEvent(event,target){let contents={},extend=(obj)=>contents={...contents,...obj};if(event instanceof WheelEvent)extend(serializeWheelEvent(event));if(event instanceof MouseEvent)extend(serializeMouseEvent(event));if(event instanceof KeyboardEvent)extend(serializeKeyboardEvent(event));if(event instanceof InputEvent)extend(serializeInputEvent(event,target));if(event instanceof PointerEvent)extend(serializePointerEvent(event));if(event instanceof AnimationEvent)extend(serializeAnimationEvent(event));if(event instanceof TransitionEvent)extend({property_name:event.propertyName,elapsed_time:event.elapsedTime,pseudo_element:event.pseudoElement});if(event instanceof CompositionEvent)extend({data:event.data});if(event instanceof DragEvent)extend(serializeDragEvent(event));if(event instanceof FocusEvent)extend({});if(event instanceof ClipboardEvent)extend({});if(event instanceof CustomEvent){let detail=event.detail;if(detail instanceof ResizeObserverEntry)extend(serializeResizeEventDetail(detail));else if(detail instanceof IntersectionObserverEntry)extend(serializeIntersectionEventDetail(detail))}if(typeof TouchEvent<\"u\"&&event instanceof TouchEvent)extend(serializeTouchEvent(event));if(event.type===\"submit\"||event.type===\"reset\"||event.type===\"click\"||event.type===\"change\"||event.type===\"input\")extend(serializeInputEvent(event,target));if(event instanceof DragEvent){let files=[];if(event.dataTransfer&&event.dataTransfer.files)for(let i=0;i<event.dataTransfer.files.length;i++){let file=event.dataTransfer.files[i],data={path:file.name,size:file.size,last_modified:file.lastModified,content_type:file.type};files.push({key:file.name,file:data})}extend({files})}if(event.type===\"scroll\"||event.type===\"scrollend\")extend(serializeScrollEvent(event));return contents}function toSerializableResizeObserverSize(size,is_inline_width){return[is_inline_width?size.inlineSize:size.blockSize,is_inline_width?size.blockSize:size.inlineSize]}function serializeResizeEventDetail(detail){let is_inline_width=!0;if(detail.target instanceof HTMLElement){if(window.getComputedStyle(detail.target).getPropertyValue(\"writing-mode\")!==\"horizontal-tb\")is_inline_width=!1}return{border_box_size:detail.borderBoxSize!==void 0?toSerializableResizeObserverSize(detail.borderBoxSize[0],is_inline_width):detail.contentRect,content_box_size:detail.contentBoxSize!==void 0?toSerializableResizeObserverSize(detail.contentBoxSize[0],is_inline_width):detail.contentRect,content_rect:detail.contentRect}}function serializeIntersectionEventDetail(detail){return{bounding_client_rect:detail.boundingClientRect,intersection_ratio:detail.intersectionRatio,intersection_rect:detail.intersectionRect,is_intersecting:detail.isIntersecting,root_bounds:detail.rootBounds,time_ms:Math.floor(Date.now()+detail.time)}}function serializeInputEvent(event,target){let contents={};if(target instanceof HTMLElement){let values=extractSerializedFormValues(event,target);contents.values=values.values,contents.valid=values.valid}if(event.target instanceof HTMLInputElement){let target2=event.target,value=target2.value??target2.textContent??\"\";if(target2.type===\"checkbox\")value=target2.checked?\"true\":\"false\";else if(target2.type===\"radio\")value=target2.value;contents.value=value}if(event.target instanceof HTMLTextAreaElement)contents.value=event.target.value;if(event.target instanceof HTMLSelectElement)contents.value=retrieveSelectValue(event.target).join(\",\");if(contents.value===void 0)contents.value=\"\";return contents}function serializeWheelEvent(event){return{delta_x:event.deltaX,delta_y:event.deltaY,delta_z:event.deltaZ,delta_mode:event.deltaMode}}function serializeTouchEvent(event){return{alt_key:event.altKey,ctrl_key:event.ctrlKey,meta_key:event.metaKey,shift_key:event.shiftKey,changed_touches:serializeTouchList(event.changedTouches),target_touches:serializeTouchList(event.targetTouches),touches:serializeTouchList(event.touches)}}function serializePointerEvent(event){return{alt_key:event.altKey,button:event.button,buttons:event.buttons,client_x:event.clientX,client_y:event.clientY,ctrl_key:event.ctrlKey,meta_key:event.metaKey,page_x:event.pageX,page_y:event.pageY,screen_x:event.screenX,screen_y:event.screenY,shift_key:event.shiftKey,pointer_id:event.pointerId,width:event.width,height:event.height,pressure:event.pressure,tangential_pressure:event.tangentialPressure,tilt_x:event.tiltX,tilt_y:event.tiltY,twist:event.twist,pointer_type:event.pointerType,is_primary:event.isPrimary}}function serializeTouchList(touchList){let serializedTouches=[];for(let i=0;i<touchList.length;i++){let touch=touchList[i];serializedTouches.push({identifier:touch.identifier,client_x:touch.clientX,client_y:touch.clientY,page_x:touch.pageX,page_y:touch.pageY,screen_x:touch.screenX,screen_y:touch.screenY,radius_x:touch.radiusX,radius_y:touch.radiusY,rotation_angle:touch.rotationAngle,force:touch.force})}return serializedTouches}function serializeMouseEvent(event){return{alt_key:event.altKey,button:event.button,buttons:event.buttons,client_x:event.clientX,client_y:event.clientY,ctrl_key:event.ctrlKey,meta_key:event.metaKey,offset_x:event.offsetX,offset_y:event.offsetY,page_x:event.pageX,page_y:event.pageY,screen_x:event.screenX,screen_y:event.screenY,shift_key:event.shiftKey}}function serializeKeyboardEvent(event){return{char_code:event.charCode,is_composing:event.isComposing,key:event.key,alt_key:event.altKey,ctrl_key:event.ctrlKey,meta_key:event.metaKey,key_code:event.keyCode,shift_key:event.shiftKey,location:event.location,repeat:event.repeat,which:event.which,code:event.code}}function serializeAnimationEvent(event){return{animation_name:event.animationName,elapsed_time:event.elapsedTime,pseudo_element:event.pseudoElement}}function serializeDragEvent(event){let data_transfer=event.dataTransfer||new DataTransfer,items=[],files=[],effect_allowed=data_transfer.effectAllowed,drop_effect=data_transfer.dropEffect;for(let i=0;i<data_transfer.items.length;i++){let item=data_transfer.items[i],data;if(item.kind===\"string\")data=data_transfer.getData(item.type);else data=item.getAsFile()?.name||\"\";items.push({kind:item.kind,type_:item.type,data})}for(let i=0;i<data_transfer.files.length;i++){let file=data_transfer.files[i];files.push({name:file.name,path:file.name,size:file.size,last_modified:file.lastModified,content_type:file.type,contents:void 0})}return{mouse:{alt_key:event.altKey,ctrl_key:event.ctrlKey,meta_key:event.metaKey,shift_key:event.shiftKey,...serializeMouseEvent(event)},data_transfer:{items,files,effect_allowed,drop_effect}}}function serializeScrollEvent(event){let scrollLeft=0,scrollTop=0,scrollWidth=0,scrollHeight=0,clientWidth=0,clientHeight=0;if(event.target instanceof Element)scrollLeft=event.target.scrollLeft,scrollTop=event.target.scrollTop,scrollWidth=event.target.scrollWidth,scrollHeight=event.target.scrollHeight,clientWidth=event.target.clientWidth,clientHeight=event.target.clientHeight;else if(event.target===document)scrollLeft=window.scrollX||document.documentElement.scrollLeft,scrollTop=window.scrollY||document.documentElement.scrollTop,scrollWidth=document.documentElement.scrollWidth,scrollHeight=document.documentElement.scrollHeight,clientWidth=document.documentElement.clientWidth,clientHeight=document.documentElement.clientHeight;return{scroll_left:scrollLeft,scroll_top:scrollTop,scroll_width:scrollWidth,scroll_height:scrollHeight,client_width:clientWidth,client_height:clientHeight}}function extractSerializedFormValues(event,target){let contents={values:[]},form=target.closest(\"form\");if(form){if(event.type===\"input\"||event.type===\"change\"||event.type===\"submit\"||event.type===\"reset\"||event.type===\"click\")contents=retrieveFormValues(form)}return contents}function retrieveFormValues(form){let formData=new FormData(form),contents=[];return formData.forEach((value,key)=>{if(value instanceof File){let fileData={path:value.name,size:value.size,last_modified:value.lastModified,content_type:value.type};contents.push({key,file:fileData})}else contents.push({key,text:value})}),{valid:form.checkValidity(),values:contents}}function retrieveSelectValue(target){let options=target.selectedOptions,values=[];for(let i=0;i<options.length;i++)values.push(options[i].value);return values}var JSChannel_;if(RawInterpreter!==void 0&&RawInterpreter!==null)JSChannel_=RawInterpreter;class NativeInterpreter extends JSChannel_{intercept_link_redirects;ipc;edits;baseUri;eventsPath;headless;kickStylesheets;queuedBytes=[];liveview;constructor(baseUri,headless){super();this.baseUri=baseUri,this.eventsPath=`${baseUri}/__events`,this.kickStylesheets=!1,this.headless=headless}initialize(root){this.intercept_link_redirects=!0,this.liveview=!1,window.addEventListener(\"dragover\",function(e){if(e.target instanceof Element&&e.target.tagName!=\"INPUT\")e.preventDefault()},!1),window.addEventListener(\"drop\",function(e){if(!(e.target instanceof Element))return;e.preventDefault()},!1),window.addEventListener(\"click\",(event)=>{let target=event.target;if(target instanceof HTMLInputElement&&target.getAttribute(\"type\")===\"file\"){let target_id=getTargetId(target);if(target_id!==null){if(target instanceof HTMLInputElement&&target.getAttribute(\"type\")===\"file\"){event.preventDefault();let contents=serializeEvent(event,target),target_name=target.getAttribute(\"name\")||\"\",requestData={event:\"change&input\",accept:target.getAttribute(\"accept\"),directory:target.getAttribute(\"webkitdirectory\")===\"true\",multiple:target.hasAttribute(\"multiple\"),target:target_id,bubbles:event.bubbles,target_name,values:contents.values};this.fetchAgainstHost(\"__file_dialog\",requestData).then((response)=>response.json()).then((resp)=>{let formObjects=resp.values,dataTransfer=new DataTransfer;for(let formObject of formObjects)if(formObject.key==target_name&&formObject.file!=null){let file=new File([],formObject.file.path,{type:formObject.file.content_type,lastModified:formObject.file.last_modified});dataTransfer.items.add(file)}target.files=dataTransfer.files;let body={data:contents,element:target_id,bubbles:event.bubbles};contents.values=formObjects,this.sendSerializedEvent({...body,name:\"input\"}),this.sendSerializedEvent({...body,name:\"change\"})});return}}}}),this.ipc=window.ipc;let handler=(event)=>this.handleEvent(event,event.type,!0);super.initialize(root,handler)}fetchAgainstHost(path,data){let encoded_data=new TextEncoder().encode(JSON.stringify(data)),base64data=btoa(String.fromCharCode.apply(null,Array.from(encoded_data)));return fetch(`${this.baseUri}/${path}`,{method:\"GET\",headers:{\"x-dioxus-data\":base64data}})}sendIpcMessage(method,params={}){let body=JSON.stringify({method,params});this.ipc.postMessage(body)}scrollTo(id,options){let node=this.nodes[id];if(node instanceof HTMLElement)return node.scrollIntoView(options),!0;return!1}scroll(id,x,y,behavior){let node=this.nodes[id];if(node instanceof HTMLElement)return node.scroll({top:y,left:x,behavior}),!0;return!1}getScrollHeight(id){let node=this.nodes[id];if(node instanceof HTMLElement)return node.scrollHeight}getScrollLeft(id){let node=this.nodes[id];if(node instanceof HTMLElement)return node.scrollLeft}getScrollTop(id){let node=this.nodes[id];if(node instanceof HTMLElement)return node.scrollTop}getScrollWidth(id){let node=this.nodes[id];if(node instanceof HTMLElement)return node.scrollWidth}getClientRect(id){let node=this.nodes[id];if(node instanceof HTMLElement){let rect=node.getBoundingClientRect();return{type:\"GetClientRect\",origin:[rect.x,rect.y],size:[rect.width,rect.height]}}}setFocus(id,focus){let node=this.nodes[id];if(node instanceof HTMLElement)if(focus)node.focus();else node.blur()}handleWindowsDragDrop(){if(window.dxDragLastElement){let dragLeaveEvent=new DragEvent(\"dragleave\",{bubbles:!0,cancelable:!0});window.dxDragLastElement.dispatchEvent(dragLeaveEvent);let data=new DataTransfer,file=new File([\"content\"],\"file.txt\",{type:\"text/plain\"});data.items.add(file);let dragDropEvent=new DragEvent(\"drop\",{bubbles:!0,cancelable:!0,dataTransfer:data});window.dxDragLastElement.dispatchEvent(dragDropEvent),window.dxDragLastElement=null}}handleWindowsDragOver(xPos,yPos){let displayScaleFactor=window.devicePixelRatio||1;xPos/=displayScaleFactor,yPos/=displayScaleFactor;let element=document.elementFromPoint(xPos,yPos);if(element!=window.dxDragLastElement){if(window.dxDragLastElement){let dragLeaveEvent=new DragEvent(\"dragleave\",{bubbles:!0,cancelable:!0});window.dxDragLastElement.dispatchEvent(dragLeaveEvent)}let dragOverEvent=new DragEvent(\"dragover\",{bubbles:!0,cancelable:!0});element.dispatchEvent(dragOverEvent),window.dxDragLastElement=element}}handleWindowsDragLeave(){if(window.dxDragLastElement){let dragLeaveEvent=new DragEvent(\"dragleave\",{bubbles:!0,cancelable:!0});window.dxDragLastElement.dispatchEvent(dragLeaveEvent),window.dxDragLastElement=null}}loadChild(array){let node=this.stack[this.stack.length-1];for(let i=0;i<array.length;i++){let end=array[i];for(node=node.firstChild;end>0;end--)node=node.nextSibling}return node}appendChildren(id,many){let root=this.nodes[id],els=this.stack.splice(this.stack.length-many);for(let k=0;k<many;k++)root.appendChild(els[k])}handleEvent(event,name,bubbles){let target=event.target,element=getTargetId(target),contents=serializeEvent(event,target),body={name,data:contents,element,bubbles};if(this.liveview&&target instanceof HTMLInputElement&&(event.type===\"change\"||event.type===\"input\")){if(target.getAttribute(\"type\")===\"file\"){this.readFiles(target,contents,bubbles,element,name);return}}let response=this.sendSerializedEvent(body);if(response){if(response.preventDefault)event.preventDefault();else if(target instanceof Element&&event.type===\"click\")this.handleClickNavigate(event,target);if(response.stopPropagation)event.stopPropagation()}}sendSerializedEvent(body){if(this.liveview)this.sendIpcMessage(\"user_event\",body);else return handleVirtualdomEventSync(this.eventsPath,JSON.stringify(body))}handleClickNavigate(event,target){if(!this.intercept_link_redirects)return;let a_element=target.closest(\"a\");if(a_element){event.preventDefault();let href=a_element.getAttribute(\"href\");if(href!==\"\"&&href!==null&&href!==void 0)this.sendIpcMessage(\"browser_open\",{href})}}enqueueBytes(bytes){this.queuedBytes.push(bytes)}flushQueuedBytes(){let byteArray=this.queuedBytes;this.queuedBytes=[];for(let bytes of byteArray)this.run_from_bytes(bytes)}rafEdits(bytes){if(this.headless)this.run_from_bytes(bytes),this.markEditsFinished();else this.enqueueBytes(bytes),requestAnimationFrame(()=>{this.flushQueuedBytes(),this.markEditsFinished()})}waitForRequest(editsPath,required_server_key){this.edits=new WebSocket(editsPath);let authenticated=!1;this.edits.onclose=()=>{setTimeout(()=>{if(this.edits.url!=editsPath)return;this.waitForRequest(editsPath,required_server_key)},100)},this.edits.onmessage=(event)=>{let data=event.data;if(data instanceof Blob){if(!authenticated)return;data.arrayBuffer().then((buffer)=>{this.rafEdits(buffer)})}else if(typeof data===\"string\"){if(data===required_server_key){authenticated=!0;return}}}}markEditsFinished(){this.edits.send(new ArrayBuffer(0))}kickAllStylesheetsOnPage(){let stylesheets=document.querySelectorAll(\"link[rel=stylesheet]\");for(let i=0;i<stylesheets.length;i++){let sheet=stylesheets[i],splitByQuery=sheet.href.split(\"?\"),url=splitByQuery[0],query=splitByQuery[1];if(!query)query=\"\";let queryParams=new URLSearchParams(query);queryParams.delete(\"dx_force_reload\"),queryParams.append(\"dx_force_reload\",Math.random().toString()),sheet.href=`${url}?${queryParams}`}}async readFiles(target,contents,bubbles,realId,name){let files=target.files,file_contents={};for(let i=0;i<files.length;i++){let file=files[i];file_contents[file.name]=Array.from(new Uint8Array(await file.arrayBuffer()))}contents.files={files:file_contents};let message=this.sendSerializedEvent({name,element:realId,data:contents,bubbles});this.ipc.postMessage(message)}}function handleVirtualdomEventSync(endpoint,contents){let xhr=new XMLHttpRequest;xhr.open(\"POST\",endpoint,!1),xhr.setRequestHeader(\"Content-Type\",\"application/json\");let contents_bytes=new TextEncoder().encode(contents),contents_base64=btoa(String.fromCharCode.apply(null,contents_bytes));return xhr.setRequestHeader(\"dioxus-data\",contents_base64),xhr.send(),JSON.parse(xhr.responseText)}function getTargetId(target){if(!(target instanceof Node))return null;let ourTarget=target,realId=null;while(realId==null){if(ourTarget===null)return null;if(ourTarget instanceof Element)realId=ourTarget.getAttribute(\"data-dioxus-id\");ourTarget=ourTarget.parentNode}return parseInt(realId)}export{NativeInterpreter};\n"
  },
  {
    "path": "packages/interpreter/src/js/patch_console.js",
    "content": "function monkeyPatchConsole(ws){let console=window.console,log=console.log,info=console.info,warn=console.warn,error=console.error,debug=console.debug;console.log=function(...args){if(ws.readyState===WebSocket.OPEN)ws.send(JSON.stringify({Log:{level:\"log\",messages:args}}));log.apply(console,args)},console.info=function(...args){if(ws.readyState===WebSocket.OPEN)ws.send(JSON.stringify({Log:{level:\"info\",messages:args}}));info.apply(console,args)},console.warn=function(...args){if(ws.readyState===WebSocket.OPEN)ws.send(JSON.stringify({Log:{level:\"warn\",messages:args}}));warn.apply(console,args)},console.error=function(...args){if(ws.readyState===WebSocket.OPEN)ws.send(JSON.stringify({Log:{level:\"error\",messages:args}}));error.apply(console,args)},console.debug=function(...args){if(ws.readyState===WebSocket.OPEN)ws.send(JSON.stringify({Log:{level:\"debug\",messages:args}}));debug.apply(console,args)}}export{monkeyPatchConsole};\n"
  },
  {
    "path": "packages/interpreter/src/js/set_attribute.js",
    "content": "function setAttributeInner(node,field,value,ns){if(ns===\"style\"){node.style.setProperty(field,value);return}if(ns){node.setAttributeNS(ns,field,value);return}switch(field){case\"value\":if(node.tagName===\"OPTION\")setAttributeDefault(node,field,value);else if(node.value!==value)node.value=value;break;case\"initial_value\":node.defaultValue=value;break;case\"checked\":node.checked=truthy(value);break;case\"initial_checked\":node.defaultChecked=truthy(value);break;case\"selected\":node.selected=truthy(value);break;case\"initial_selected\":node.defaultSelected=truthy(value);break;case\"dangerous_inner_html\":node.innerHTML=value;break;case\"style\":let existingStyles={};for(let i=0;i<node.style.length;i++){let prop=node.style[i];existingStyles[prop]=node.style.getPropertyValue(prop)}node.setAttribute(field,value);for(let prop in existingStyles)if(!node.style.getPropertyValue(prop))node.style.setProperty(prop,existingStyles[prop]);break;case\"multiple\":if(setAttributeDefault(node,field,value),node.options!==null&&node.options!==void 0){let options=node.options;for(let option of options)option.selected=option.defaultSelected}break;default:setAttributeDefault(node,field,value)}}function setAttributeDefault(node,field,value){if(!truthy(value)&&isBoolAttr(field))node.removeAttribute(field);else node.setAttribute(field,value)}function truthy(val){return val===\"true\"||val===!0}function isBoolAttr(field){switch(field){case\"allowfullscreen\":case\"allowpaymentrequest\":case\"async\":case\"autofocus\":case\"autoplay\":case\"checked\":case\"controls\":case\"default\":case\"defer\":case\"disabled\":case\"formnovalidate\":case\"hidden\":case\"ismap\":case\"itemscope\":case\"loop\":case\"multiple\":case\"muted\":case\"nomodule\":case\"novalidate\":case\"open\":case\"playsinline\":case\"readonly\":case\"required\":case\"reversed\":case\"selected\":case\"truespeed\":case\"webkitdirectory\":return!0;default:return!1}}export{setAttributeInner};\n"
  },
  {
    "path": "packages/interpreter/src/lib.rs",
    "content": "#![allow(clippy::empty_docs)]\n#![doc = include_str!(\"../README.md\")]\n#![doc(html_logo_url = \"https://avatars.githubusercontent.com/u/79236386\")]\n#![doc(html_favicon_url = \"https://avatars.githubusercontent.com/u/79236386\")]\n\n/// The base class that the JS channel will extend\npub static INTERPRETER_JS: &str = include_str!(\"./js/core.js\");\n\n/// The code explicitly for desktop/liveview that bridges the eval gap between the two\npub static NATIVE_JS: &str = include_str!(\"./js/native.js\");\n\n/// The code that handles initializing data used for fullstack data streaming\npub static INITIALIZE_STREAMING_JS: &str = include_str!(\"./js/initialize_streaming.js\");\n\n#[cfg(all(feature = \"binary-protocol\", feature = \"sledgehammer\"))]\nmod write_native_mutations;\n\n#[cfg(all(feature = \"binary-protocol\", feature = \"sledgehammer\"))]\npub use write_native_mutations::*;\n\n#[cfg(feature = \"sledgehammer\")]\npub mod unified_bindings;\n\n#[cfg(feature = \"sledgehammer\")]\npub use unified_bindings::*;\n\n// Common bindings for minimal usage.\n#[cfg(all(feature = \"minimal_bindings\", feature = \"webonly\"))]\npub mod minimal_bindings {\n    use wasm_bindgen::{prelude::wasm_bindgen, JsValue};\n\n    /// Some useful snippets that we use to share common functionality between the different platforms we support.\n    ///\n    /// This maintains some sort of consistency between web, desktop, and liveview\n    #[wasm_bindgen(module = \"/src/js/set_attribute.js\")]\n    extern \"C\" {\n        /// Set the attribute of the node\n        pub fn setAttributeInner(node: JsValue, name: &str, value: JsValue, ns: Option<&str>);\n    }\n\n    #[wasm_bindgen(module = \"/src/js/hydrate.js\")]\n    extern \"C\" {\n        /// Register a callback that will be called to hydrate a node at the given id with data from the server\n        pub fn register_rehydrate_chunk_for_streaming(\n            closure: &wasm_bindgen::closure::Closure<dyn FnMut(Vec<u32>, js_sys::Uint8Array)>,\n        );\n\n        /// Register a callback that will be called to hydrate a node at the given id with data from the server\n        pub fn register_rehydrate_chunk_for_streaming_debug(\n            closure: &wasm_bindgen::closure::Closure<\n                dyn FnMut(Vec<u32>, js_sys::Uint8Array, Option<Vec<String>>, Option<Vec<String>>),\n            >,\n        );\n    }\n\n    #[wasm_bindgen(module = \"/src/js/patch_console.js\")]\n    extern \"C\" {\n        pub fn monkeyPatchConsole(ws: JsValue);\n    }\n}\n"
  },
  {
    "path": "packages/interpreter/src/ts/.gitignore",
    "content": "# please dont accidentally run tsc and commit your js in this dir.\n*.js\n\n"
  },
  {
    "path": "packages/interpreter/src/ts/core.ts",
    "content": "// The root interpreter class that holds state about the mapping between DOM and VirtualDom\n// This always lives in the JS side of things, and is extended by the native and web interpreters\n\nimport { setAttributeInner } from \"./set_attribute\";\n\nexport type NodeId = number;\n\nexport class BaseInterpreter {\n  // non bubbling events listen at the element the listener was created at\n  global: {\n    [key: string]: { active: number; callback: EventListener };\n  };\n  // bubbling events can listen at the root element\n  local: {\n    [key: string]: {\n      [key: string]: EventListener;\n    };\n  };\n\n  root: HTMLElement;\n  handler: EventListener;\n  resizeObserver: ResizeObserver;\n  intersectionObserver: IntersectionObserver;\n\n  nodes: Node[];\n  stack: Node[];\n  templates: {\n    [key: number]: Node[];\n  };\n\n  // sledgehammer is generating this...\n  m: any;\n\n  constructor() {}\n\n  initialize(root: HTMLElement, handler: EventListener | null = null) {\n    this.global = {};\n    this.local = {};\n    this.root = root;\n\n    this.nodes = [root];\n    this.stack = [root];\n    this.templates = {};\n\n    this.handler = handler;\n\n    // make sure to set the root element's ID so it still registers events\n    root.setAttribute(\"data-dioxus-id\", \"0\");\n  }\n\n  handleResizeEvent(entry: ResizeObserverEntry) {\n    const target = entry.target;\n\n    let event = new CustomEvent<ResizeObserverEntry>(\"resize\", {\n      bubbles: false,\n      detail: entry,\n    });\n\n    target.dispatchEvent(event);\n  }\n\n  createResizeObserver(element: HTMLElement) {\n    // Lazily create the resize observer\n    if (!this.resizeObserver) {\n      this.resizeObserver = new ResizeObserver((entries) => {\n        for (const entry of entries) {\n          this.handleResizeEvent(entry);\n        }\n      });\n    }\n    this.resizeObserver.observe(element);\n  }\n\n  removeResizeObserver(element: HTMLElement) {\n    if (this.resizeObserver) {\n      this.resizeObserver.unobserve(element);\n    }\n  }\n\n  handleIntersectionEvent(entry: IntersectionObserverEntry) {\n    const target = entry.target;\n\n    let event = new CustomEvent<IntersectionObserverEntry>(\"visible\", {\n      bubbles: false,\n      detail: entry,\n    });\n\n    target.dispatchEvent(event);\n  }\n\n  createIntersectionObserver(element: HTMLElement) {\n    /// Lazily create the intersection observer\n    if (!this.intersectionObserver) {\n      this.intersectionObserver = new IntersectionObserver((entries) => {\n        for (const entry of entries) {\n          this.handleIntersectionEvent(entry);\n        }\n      });\n    }\n    this.intersectionObserver.observe(element);\n  }\n\n  removeIntersectionObserver(element: HTMLElement) {\n    if (this.intersectionObserver) {\n      this.intersectionObserver.unobserve(element);\n    }\n  }\n\n  createListener(event_name: string, element: HTMLElement, bubbles: boolean) {\n    if (event_name == \"resize\") {\n      this.createResizeObserver(element);\n    } else if (event_name == \"visible\") {\n      this.createIntersectionObserver(element);\n    }\n\n    if (bubbles) {\n      if (this.global[event_name] === undefined) {\n        this.global[event_name] = { active: 1, callback: this.handler };\n        this.root.addEventListener(event_name, this.handler);\n      } else {\n        this.global[event_name].active++;\n      }\n    } else {\n      const id = element.getAttribute(\"data-dioxus-id\");\n      if (!this.local[id]) {\n        this.local[id] = {};\n      }\n      element.addEventListener(event_name, this.handler);\n    }\n  }\n\n  removeListener(element: HTMLElement, event_name: string, bubbles: boolean) {\n    if (event_name == \"resize\") {\n      this.removeResizeObserver(element);\n    } else if (event_name == \"visible\") {\n      this.removeIntersectionObserver(element);\n    } else if (bubbles) {\n      this.removeBubblingListener(event_name);\n    } else {\n      this.removeNonBubblingListener(element, event_name);\n    }\n  }\n\n  removeBubblingListener(event_name: string) {\n    this.global[event_name].active--;\n    if (this.global[event_name].active === 0) {\n      this.root.removeEventListener(\n        event_name,\n        this.global[event_name].callback\n      );\n      delete this.global[event_name];\n    }\n  }\n\n  removeNonBubblingListener(element: HTMLElement, event_name: string) {\n    const id = element.getAttribute(\"data-dioxus-id\");\n    delete this.local[id][event_name];\n    if (Object.keys(this.local[id]).length === 0) {\n      delete this.local[id];\n    }\n    element.removeEventListener(event_name, this.handler);\n  }\n\n  removeAllNonBubblingListeners(element: HTMLElement) {\n    const id = element.getAttribute(\"data-dioxus-id\");\n    delete this.local[id];\n  }\n\n  getNode(id: NodeId): Node {\n    return this.nodes[id];\n  }\n\n  pushRoot(node: Node) {\n    this.stack.push(node);\n  }\n\n  appendChildren(id: NodeId, many: number) {\n    const root = this.nodes[id];\n    const els = this.stack.splice(this.stack.length - many);\n    for (let k = 0; k < many; k++) {\n      root.appendChild(els[k]);\n    }\n  }\n\n  loadChild(ptr: number, len: number): Node {\n    // iterate through each number and get that child\n    let node = this.stack[this.stack.length - 1] as Node;\n    let ptr_end = ptr + len;\n\n    for (; ptr < ptr_end; ptr++) {\n      let end = this.m.getUint8(ptr);\n      for (node = node.firstChild; end > 0; end--) {\n        node = node.nextSibling;\n      }\n    }\n\n    return node;\n  }\n\n  saveTemplate(nodes: HTMLElement[], tmpl_id: number) {\n    this.templates[tmpl_id] = nodes;\n  }\n\n  hydrate_node(hydrateNode: HTMLElement, ids: { [key: number]: number }) {\n    const hydration = hydrateNode.getAttribute(\"data-node-hydration\");\n    const split = hydration!.split(\",\");\n    const id = ids[parseInt(split[0])];\n\n    this.nodes[id] = hydrateNode;\n\n    if (split.length > 1) {\n      // @ts-ignore\n      hydrateNode.listening = split.length - 1;\n      hydrateNode.setAttribute(\"data-dioxus-id\", id.toString());\n      for (let j = 1; j < split.length; j++) {\n        const listener = split[j];\n        const split2 = listener.split(\":\");\n        const event_name = split2[0];\n        const bubbles = split2[1] === \"1\";\n        this.createListener(event_name, hydrateNode, bubbles);\n      }\n    }\n  }\n\n  hydrate(ids: { [key: number]: number }, underNodes: Node[]) {\n    for (let i = 0; i < underNodes.length; i++) {\n      const under = underNodes[i];\n      if (under instanceof HTMLElement) {\n        if (under.getAttribute(\"data-node-hydration\")) {\n          this.hydrate_node(under, ids);\n        }\n        const hydrateNodes = under.querySelectorAll(\"[data-node-hydration]\");\n\n        for (let i = 0; i < hydrateNodes.length; i++) {\n          this.hydrate_node(hydrateNodes[i] as HTMLElement, ids);\n        }\n      }\n\n      const treeWalker = document.createTreeWalker(\n        under,\n        NodeFilter.SHOW_COMMENT\n      );\n\n      let nextSibling = under.nextSibling;\n      // Continue to the next node. Returns false if we should stop traversing because we've reached the end of the children\n      // of the root\n      let continueToNextNode = () => {\n        // stop traversing if there are no more nodes\n        if (!treeWalker.nextNode()) {\n          return false;\n        }\n        // stop traversing if we have reached the next sibling of the root node\n        return treeWalker.currentNode !== nextSibling;\n      };\n\n      while (treeWalker.currentNode) {\n        const currentNode = treeWalker.currentNode as ChildNode;\n        if (currentNode.nodeType === Node.COMMENT_NODE) {\n          const id = currentNode.textContent!;\n\n          // First try to hydrate the comment node as a placeholder\n          const placeholderSplit = id.split(\"placeholder\");\n\n          if (placeholderSplit.length > 1) {\n            this.nodes[ids[parseInt(placeholderSplit[1])]] = currentNode;\n            if (!continueToNextNode()) {\n              break;\n            }\n            continue;\n          }\n\n          // Then try to hydrate the comment node as a marker for the next text node\n          const textNodeSplit = id.split(\"node-id\");\n\n          if (textNodeSplit.length > 1) {\n            // For most text nodes, this should be text\n            let next = currentNode.nextSibling;\n            // remove the comment node\n            currentNode.remove();\n\n            let commentAfterText;\n            let textNode;\n\n            // If we are hydrating an empty text node, we may see two comment nodes in a row instead of a comment node, text node and then comment node\n            if (next.nodeType === Node.COMMENT_NODE) {\n              const newText = next.parentElement.insertBefore(\n                document.createTextNode(\"\"),\n                next\n              );\n              commentAfterText = next;\n              textNode = newText;\n            } else {\n              // The node after text should be a comment node marking the end of the text node\n              textNode = next;\n              commentAfterText = textNode.nextSibling;\n            }\n            treeWalker.currentNode = commentAfterText;\n            this.nodes[ids[parseInt(textNodeSplit[1])]] = textNode;\n            // Stop traversing if we started on a comment node (which has no children)\n            // or the next sibling stops the walk\n            let exit = currentNode === under || !continueToNextNode();\n            // remove the comment node after the text node\n            commentAfterText.remove();\n            if (exit) {\n              break;\n            }\n            continue;\n          }\n        }\n        if (!continueToNextNode()) {\n          break;\n        }\n      }\n    }\n  }\n\n  setAttributeInner(\n    node: HTMLElement,\n    field: string,\n    value: string,\n    ns: string\n  ) {\n    setAttributeInner(node, field, value, ns);\n  }\n}\n"
  },
  {
    "path": "packages/interpreter/src/ts/hydrate.ts",
    "content": "import \"./hydrate_types\";\nimport { HydrationCallback } from \"./hydrate_types\";\n\nexport function register_rehydrate_chunk_for_streaming(callback: HydrationCallback): void {\n  return register_rehydrate_chunk_for_streaming_debug(callback);\n}\n\nexport function register_rehydrate_chunk_for_streaming_debug(\n  callback: HydrationCallback\n): void {\n  window.hydration_callback = callback;\n  for (let i = 0; i < window.hydrate_queue.length; i++) {\n    const [id, data, debug_types, debug_locations] = window.hydrate_queue[i];\n    window.hydration_callback(id, data, debug_types, debug_locations);\n  }\n}\n"
  },
  {
    "path": "packages/interpreter/src/ts/hydrate_types.ts",
    "content": "export { };\n\nexport type HydrationCallback = (\n  id: number[],\n  data: Uint8Array,\n  debug_types: string[] | null,\n  debug_locations: string[] | null\n) => void;\n\ndeclare global {\n  interface Window {\n    hydrate_queue: [number[], Uint8Array, string[] | null, string[] | null][];\n    hydration_callback:\n    | null\n    | HydrationCallback;\n  }\n}\n"
  },
  {
    "path": "packages/interpreter/src/ts/initialize_streaming.ts",
    "content": "import \"./hydrate_types\";\n\n// Chunks may load before the wasm has loaded and the callback to hydrate the dom is registered. We queue up hydration ids and data until the wasm is ready\nwindow.hydrate_queue = [];\n\n// @ts-ignore\nwindow.dx_hydrate = (\n  id: number[],\n  data: string,\n  debug_types: string[] | null,\n  debug_locations: string[] | null\n) => {\n  // First convert the base64 encoded string to a Uint8Array\n  const decoded = atob(data);\n  const bytes = Uint8Array.from(decoded, (c) => c.charCodeAt(0));\n  if (window.hydration_callback) {\n    window.hydration_callback(id, bytes, debug_types, debug_locations);\n  } else {\n    window.hydrate_queue.push([id, bytes, debug_types, debug_locations]);\n  }\n};\n"
  },
  {
    "path": "packages/interpreter/src/ts/native.ts",
    "content": "// This file provides an extended variant of the interpreter used for desktop and liveview interaction\n//\n// This file lives on the renderer, not the host. It's basically a polyfill over functionality that the host can't\n// provide since it doesn't have access to the dom.\n\nimport { BaseInterpreter, NodeId } from \"./core\";\nimport { SerializedEvent, serializeEvent, SerializedFileData, extractSerializedFormValues, SerializedFormObject } from \"./serialize\";\n\n// okay so, we've got this JSChannel thing from sledgehammer, implicitly imported into our scope\n// we want to extend it, and it technically extends base interpreter. To make typescript happy,\n// we're going to bind the JSChannel_ object to the JSChannel object, and then extend it\nvar JSChannel_: typeof BaseInterpreter;\n\n// @ts-ignore - this is coming from the host\nif (RawInterpreter !== undefined && RawInterpreter !== null) {\n  // @ts-ignore - this is coming from the host\n  JSChannel_ = RawInterpreter;\n}\n\nexport class NativeInterpreter extends JSChannel_ {\n  intercept_link_redirects: boolean;\n  ipc: any;\n  edits: WebSocket;\n  baseUri: string;\n  eventsPath: string;\n  headless: boolean;\n  kickStylesheets: boolean;\n  queuedBytes: ArrayBuffer[] = [];\n\n  // eventually we want to remove liveview and build it into the server-side-events of fullstack\n  // however, for now we need to support it since WebSockets in fullstack doesn't exist yet\n  liveview: boolean;\n\n  constructor(baseUri: string, headless: boolean) {\n    super();\n    this.baseUri = baseUri;\n    this.eventsPath = `${baseUri}/__events`;\n    this.kickStylesheets = false;\n    this.headless = headless;\n  }\n\n  initialize(root: HTMLElement): void {\n    this.intercept_link_redirects = true;\n    this.liveview = false;\n\n    // attach an event listener on the body that prevents file drops from navigating\n    // this is because the browser will try to navigate to the file if it's dropped on the window\n    window.addEventListener(\n      \"dragover\",\n      function (e) {\n        // // check which element is our target\n        if (e.target instanceof Element && e.target.tagName != \"INPUT\") {\n          e.preventDefault();\n        }\n      },\n      false\n    );\n\n    window.addEventListener(\n      \"drop\",\n      function (e) {\n        let target = e.target;\n\n        if (!(target instanceof Element)) {\n          return;\n        }\n\n        // Dropping a file on the window will navigate to the file, which we don't want\n        e.preventDefault();\n      },\n      false\n    );\n\n    // attach a listener to the route that listens for clicks and prevents the default file dialog\n    window.addEventListener(\"click\", (event) => {\n      const target = event.target;\n      if (\n        target instanceof HTMLInputElement &&\n        target.getAttribute(\"type\") === \"file\"\n      ) {\n        // Send a message to the host to open the file dialog if the target is a file input and has a dioxus id attached to it\n        let target_id = getTargetId(target);\n        if (target_id !== null) {\n          // Handle file inputs specifically, since we want to get the real file inputs from the native\n          // dialog and then set the form inputs accordingly.\n          if (\n            target instanceof HTMLInputElement &&\n            target.getAttribute(\"type\") === \"file\"\n          ) {\n            // Send a message to the host to open the file dialog if the target is a file input and has a dioxus id attached to it\n            event.preventDefault();\n\n            const contents = serializeEvent(event, target);\n\n            const target_name = target.getAttribute(\"name\") || \"\";\n\n            let requestData = {\n              event: \"change&input\",\n              accept: target.getAttribute(\"accept\"),\n              directory: target.getAttribute(\"webkitdirectory\") === \"true\",\n              multiple: target.hasAttribute(\"multiple\"),\n              target: target_id,\n              bubbles: event.bubbles,\n              target_name,\n              values: contents.values,\n            };\n\n            this.fetchAgainstHost(\"__file_dialog\", requestData).then(response => response.json()).then(resp => {\n              const formObjects: SerializedFormObject[] = resp.values;\n\n              // Create a new DataTransfer to hold the files\n              const dataTransfer = new DataTransfer();\n\n              // We name the file the path, so we can just use the path as the name later on.\n              for (let formObject of formObjects) {\n                if (formObject.key == target_name && formObject.file != null) {\n                  const file = new File([], formObject.file.path, {\n                    type: formObject.file.content_type,\n                    lastModified: formObject.file.last_modified,\n                  });\n                  dataTransfer.items.add(file);\n                }\n              }\n\n              // Set the files on the input\n              target.files = dataTransfer.files;\n\n              let body = {\n                data: contents,\n                element: target_id,\n                bubbles: event.bubbles,\n              };\n\n              // And then dispatch the actual event against the dom\n              contents.values = formObjects;\n              this.sendSerializedEvent({\n                ...body,\n                name: \"input\",\n              });\n              this.sendSerializedEvent({\n                ...body,\n                name: \"change\",\n              });\n            });\n\n            return;\n          }\n\n        }\n      }\n    });\n\n\n    // @ts-ignore - wry gives us this\n    this.ipc = window.ipc;\n\n    // make sure we pass the handler to the base interpreter\n    const handler: EventListener = (event) =>\n      this.handleEvent(event, event.type, true);\n\n    super.initialize(root, handler);\n  }\n\n  fetchAgainstHost(path: string, data: { [key: string]: any }): Promise<Response> {\n    let encoded_data = new TextEncoder().encode(JSON.stringify(data));\n    const base64data = btoa(\n      String.fromCharCode.apply(null, Array.from(encoded_data))\n    );\n\n    return fetch(`${this.baseUri}/${path}`, {\n      method: \"GET\",\n      headers: {\n        \"x-dioxus-data\": base64data\n      }\n    });\n  }\n\n  sendIpcMessage(method: string, params = {}) {\n    const body = JSON.stringify({ method, params });\n    this.ipc.postMessage(body);\n  }\n\n  scrollTo(id: NodeId, options: ScrollIntoViewOptions): boolean {\n    const node = this.nodes[id];\n    if (node instanceof HTMLElement) {\n      node.scrollIntoView(options);\n      return true;\n    }\n    return false;\n  }\n\n  scroll(id: NodeId, x: number, y: number, behavior: ScrollBehavior): boolean {\n    const node = this.nodes[id];\n    if (node instanceof HTMLElement) {\n      node.scroll({ top: y, left: x, behavior });\n      return true;\n    }\n    return false;\n  }\n\n  getScrollHeight(id: NodeId): number | undefined {\n    const node = this.nodes[id];\n    if (node instanceof HTMLElement) {\n      return node.scrollHeight;\n    }\n  }\n\n  getScrollLeft(id: NodeId): number | undefined {\n    const node = this.nodes[id];\n    if (node instanceof HTMLElement) {\n      return node.scrollLeft;\n    }\n  }\n\n  getScrollTop(id: NodeId): number | undefined {\n    const node = this.nodes[id];\n    if (node instanceof HTMLElement) {\n      return node.scrollTop;\n    }\n  }\n\n  getScrollWidth(id: NodeId): number | undefined {\n    const node = this.nodes[id];\n    if (node instanceof HTMLElement) {\n      return node.scrollWidth;\n    }\n  }\n\n  getClientRect(\n    id: NodeId\n  ): { type: string; origin: number[]; size: number[] } | undefined {\n    const node = this.nodes[id];\n    if (node instanceof HTMLElement) {\n      const rect = node.getBoundingClientRect();\n      return {\n        type: \"GetClientRect\",\n        origin: [rect.x, rect.y],\n        size: [rect.width, rect.height],\n      };\n    }\n  }\n\n  setFocus(id: NodeId, focus: boolean) {\n    const node = this.nodes[id];\n\n    if (node instanceof HTMLElement) {\n      if (focus) {\n        node.focus();\n      } else {\n        node.blur();\n      }\n    }\n  }\n\n  // Windows drag-n-drop fix code. Called by wry drag-n-drop handler over the event loop.\n  handleWindowsDragDrop() {\n    // @ts-ignore\n    if (window.dxDragLastElement) {\n      const dragLeaveEvent = new DragEvent(\"dragleave\", {\n        bubbles: true,\n        cancelable: true,\n      });\n\n      // @ts-ignore\n      window.dxDragLastElement.dispatchEvent(dragLeaveEvent);\n\n      let data = new DataTransfer();\n\n      // We need to mimic that there are actually files in this event for our native file engine to pick it up.\n      const file = new File([\"content\"], \"file.txt\", { type: \"text/plain\" });\n      data.items.add(file);\n\n      const dragDropEvent = new DragEvent(\"drop\", {\n        bubbles: true,\n        cancelable: true,\n        dataTransfer: data,\n      });\n\n      // @ts-ignore\n      window.dxDragLastElement.dispatchEvent(dragDropEvent);\n      // @ts-ignore\n      window.dxDragLastElement = null;\n    }\n  }\n\n  handleWindowsDragOver(xPos: number, yPos: number) {\n    const displayScaleFactor = window.devicePixelRatio || 1;\n    xPos /= displayScaleFactor;\n    yPos /= displayScaleFactor;\n    const element = document.elementFromPoint(xPos, yPos);\n\n    // @ts-ignore\n    if (element != window.dxDragLastElement) {\n      // @ts-ignore\n      if (window.dxDragLastElement) {\n        const dragLeaveEvent = new DragEvent(\"dragleave\", {\n          bubbles: true,\n          cancelable: true,\n        });\n        // @ts-ignore\n        window.dxDragLastElement.dispatchEvent(dragLeaveEvent);\n      }\n\n      const dragOverEvent = new DragEvent(\"dragover\", {\n        bubbles: true,\n        cancelable: true,\n      });\n      element.dispatchEvent(dragOverEvent);\n      // @ts-ignore\n      window.dxDragLastElement = element;\n    }\n  }\n\n  handleWindowsDragLeave() {\n    // @ts-ignore\n    if (window.dxDragLastElement) {\n      const dragLeaveEvent = new DragEvent(\"dragleave\", {\n        bubbles: true,\n        cancelable: true,\n      });\n      // @ts-ignore\n      window.dxDragLastElement.dispatchEvent(dragLeaveEvent);\n      // @ts-ignore\n      window.dxDragLastElement = null;\n    }\n  }\n\n  // ignore the fact the base interpreter uses ptr + len but we use array...\n  // @ts-ignore\n  loadChild(array: number[]) {\n    // iterate through each number and get that child\n    let node = this.stack[this.stack.length - 1];\n\n    for (let i = 0; i < array.length; i++) {\n      let end = array[i];\n      for (node = node.firstChild; end > 0; end--) {\n        node = node.nextSibling;\n      }\n    }\n\n    return node;\n  }\n\n  appendChildren(id: NodeId, many: number) {\n    const root = this.nodes[id];\n    const els = this.stack.splice(this.stack.length - many);\n\n    for (let k = 0; k < many; k++) {\n      root.appendChild(els[k]);\n    }\n  }\n\n  handleEvent(event: Event, name: string, bubbles: boolean) {\n    const target = event.target!;\n    const element = getTargetId(target)!;\n    const contents = serializeEvent(event, target);\n\n    // Handle the event on the virtualdom and then preventDefault if it also preventsDefault\n    // Some listeners\n    let body = {\n      name,\n      data: contents,\n      element,\n      bubbles,\n    };\n\n    // liveview does not have synchronous event handling, so we need to send the event to the host\n    if (\n      this.liveview &&\n      target instanceof HTMLInputElement &&\n      (event.type === \"change\" || event.type === \"input\")\n    ) {\n      if (target.getAttribute(\"type\") === \"file\") {\n        this.readFiles(target, contents, bubbles, element, name);\n        return;\n      }\n    }\n\n    const response = this.sendSerializedEvent(body);\n    // capture/prevent default of the event if the virtualdom wants to\n    if (response) {\n      if (response.preventDefault) {\n        event.preventDefault();\n      } else {\n        // Attempt to intercept if the event is a click and the default action was not prevented\n        if (target instanceof Element && event.type === \"click\") {\n          this.handleClickNavigate(event, target);\n        }\n      }\n\n      if (response.stopPropagation) {\n        event.stopPropagation();\n      }\n    }\n  }\n\n  sendSerializedEvent(body: {\n    name: string;\n    element: number;\n    data: any;\n    bubbles: boolean;\n  }): EventSyncResult | void {\n    if (this.liveview) {\n      this.sendIpcMessage(\"user_event\", body);\n    } else {\n      // Run the event handler on the virtualdom\n      return handleVirtualdomEventSync(this.eventsPath, JSON.stringify(body));\n    }\n  }\n\n  handleClickNavigate(event: Event, target: Element) {\n    // todo call prevent default if it's the right type of event\n    if (!this.intercept_link_redirects) {\n      return;\n    }\n\n    // If the target is an anchor tag, we want to intercept the click too, to prevent the browser from navigating\n    let a_element = target.closest(\"a\");\n    if (a_element) {\n      event.preventDefault();\n\n      const href = a_element.getAttribute(\"href\");\n      if (href !== \"\" && href !== null && href !== undefined) {\n        this.sendIpcMessage(\"browser_open\", { href })\n      }\n    }\n  }\n\n  enqueueBytes(bytes: ArrayBuffer) {\n    this.queuedBytes.push(bytes);\n  }\n\n  flushQueuedBytes() {\n    // drain the queuedBytes\n    const byteArray = this.queuedBytes;\n    this.queuedBytes = [];\n\n    for (let bytes of byteArray) {\n      // @ts-ignore\n      this.run_from_bytes(bytes);\n    }\n  }\n\n  // Run the edits the next animation frame\n  rafEdits(bytes: ArrayBuffer) {\n    // In headless mode, the requestAnimationFrame callback is never called, so we need to run the bytes directly\n    if (this.headless) {\n      // @ts-ignore\n      this.run_from_bytes(bytes);\n      this.markEditsFinished();\n    } else {\n      this.enqueueBytes(bytes);\n      requestAnimationFrame(() => {\n        this.flushQueuedBytes();\n        this.markEditsFinished();\n      });\n    }\n  }\n\n  waitForRequest(editsPath: string, required_server_key: string) {\n    this.edits = new WebSocket(editsPath);\n    // Only trust the websocket once it sends us the required server key\n    let authenticated = false;\n    // Reconnect if the websocket closes. This may happen on ios when the app is suspended\n    // in the background: https://github.com/DioxusLabs/dioxus/issues/4374\n    this.edits.onclose = () => {\n      setTimeout(() => {\n        // If the edits path has changed, we don't want to reconnect to the old one\n        if (this.edits.url != editsPath) {\n          return;\n        }\n        this.waitForRequest(editsPath, required_server_key);\n      }, 100);\n    };\n    this.edits.onmessage = (event) => {\n      const data = event.data;\n      if (data instanceof Blob) {\n        if (!authenticated) {\n          return;\n        }\n        // If the data is a blob, we need to convert it to an ArrayBuffer\n        data.arrayBuffer().then((buffer) => {\n          this.rafEdits(buffer);\n        });\n      } else if (typeof data === \"string\") {\n        if (data === required_server_key) {\n          // If the data is the required server key, we can trust the websocket\n          authenticated = true;\n          return;\n        }\n      }\n    };\n  }\n\n  markEditsFinished() {\n    // Send an empty ArrayBuffer to the edits websocket to signal that the edits are finished\n    // This is used to signal that the edits are done and the next request can be processed\n    this.edits.send(new ArrayBuffer(0));\n  }\n\n  kickAllStylesheetsOnPage() {\n    // If this function is being called and we have not explicitly set kickStylesheets to true, then we should\n    // force kick the stylesheets, regardless if they have a dioxus attribute or not\n    // This happens when any hotreload happens.\n    let stylesheets = document.querySelectorAll(\"link[rel=stylesheet]\");\n    for (let i = 0; i < stylesheets.length; i++) {\n      let sheet = stylesheets[i] as HTMLLinkElement;\n      // Split up the url and add a extra random query param to force the browser to reload he asset\n      const splitByQuery = sheet.href.split(\"?\");\n      let url = splitByQuery[0];\n      let query = splitByQuery[1];\n      if (!query) {\n        query = \"\";\n      }\n      let queryParams = new URLSearchParams(query);\n      // Delete the existing dx_force_reload entry if it exists\n      queryParams.delete(\"dx_force_reload\");\n      // And add a new random dx_force_reload query param to force the browser to reload the asset\n      queryParams.append(\"dx_force_reload\", Math.random().toString());\n      sheet.href = `${url}?${queryParams}`;\n    }\n  }\n\n  //  A liveview only function\n  // Desktop will intercept the event before it hits this\n  async readFiles(\n    target: HTMLInputElement,\n    contents: SerializedEvent,\n    bubbles: boolean,\n    realId: NodeId,\n    name: string\n  ) {\n    let files = target.files!;\n    let file_contents: { [name: string]: number[] } = {};\n\n    for (let i = 0; i < files.length; i++) {\n      const file = files[i];\n      file_contents[file.name] = Array.from(\n        new Uint8Array(await file.arrayBuffer())\n      );\n    }\n\n    contents.files = { files: file_contents };\n\n    const message = this.sendSerializedEvent({\n      name: name,\n      element: realId,\n      data: contents,\n      bubbles,\n    });\n\n    this.ipc.postMessage(message);\n  }\n}\n\ntype EventSyncResult = {\n  preventDefault: boolean;\n  stopPropagation: boolean;\n};\n\n// This function sends the event to the virtualdom and then waits for the virtualdom to process it\n//\n// However, it's not really suitable for liveview, because it's synchronous and will block the main thread\n// We should definitely consider using a websocket if we want to block... or just not block on liveview\n// Liveview is a little bit of a tricky beast\nfunction handleVirtualdomEventSync(\n  endpoint: string,\n  contents: string\n): EventSyncResult {\n  // Handle the event on the virtualdom and then process whatever its output was\n  const xhr = new XMLHttpRequest();\n\n  // Serialize the event and send it to the custom protocol in the Rust side of things\n  xhr.open(\"POST\", endpoint, false);\n  xhr.setRequestHeader(\"Content-Type\", \"application/json\");\n\n  // hack for android since we CANT SEND BODIES (because wry is using shouldInterceptRequest)\n  //\n  // https://issuetracker.google.com/issues/119844519\n  // https://stackoverflow.com/questions/43273640/android-webviewclient-how-to-get-post-request-body\n  // https://developer.android.com/reference/android/webkit/WebViewClient#shouldInterceptRequest(android.webkit.WebView,%20android.webkit.WebResourceRequest)\n  //\n  // the issue here isn't that big, tbh, but there's a small chance we lose the event due to header max size (16k per header, 32k max)\n  const contents_bytes = new TextEncoder().encode(contents);\n  const contents_base64 = btoa(String.fromCharCode.apply(null, contents_bytes));\n  xhr.setRequestHeader(\"dioxus-data\", contents_base64);\n  xhr.send();\n\n  // Deserialize the response, and then prevent the default/capture the event if the virtualdom wants to\n  return JSON.parse(xhr.responseText);\n}\n\nfunction getTargetId(target: EventTarget): NodeId | null {\n  // Ensure that the target is a node, sometimes it's nota\n  if (!(target instanceof Node)) {\n    return null;\n  }\n\n  let ourTarget = target;\n  let realId = null;\n\n  while (realId == null) {\n    if (ourTarget === null) {\n      return null;\n    }\n\n    if (ourTarget instanceof Element) {\n      realId = ourTarget.getAttribute(`data-dioxus-id`);\n    }\n\n    ourTarget = ourTarget.parentNode;\n  }\n\n  return parseInt(realId);\n}\n"
  },
  {
    "path": "packages/interpreter/src/ts/patch_console.ts",
    "content": "\nexport function monkeyPatchConsole(ws: WebSocket) {\n  const console = window.console;\n  const log = console.log;\n  const info = console.info;\n  const warn = console.warn;\n  const error = console.error;\n  const debug = console.debug;\n\n  console.log = function (...args: any[]) {\n    if (ws.readyState === WebSocket.OPEN) {\n      ws.send(JSON.stringify({\n        \"Log\": { level: \"log\", messages: args }\n      }));\n    }\n    log.apply(console, args);\n  };\n\n  console.info = function (...args: any[]) {\n    if (ws.readyState === WebSocket.OPEN) {\n      ws.send(JSON.stringify({\n        \"Log\": { level: \"info\", messages: args }\n      }));\n    }\n    info.apply(console, args);\n  };\n\n  console.warn = function (...args: any[]) {\n    if (ws.readyState === WebSocket.OPEN) {\n      ws.send(JSON.stringify({\n        \"Log\": { level: \"warn\", messages: args }\n      }));\n    }\n    warn.apply(console, args);\n  };\n\n  console.error = function (...args: any[]) {\n    if (ws.readyState === WebSocket.OPEN) {\n      ws.send(JSON.stringify({\n        \"Log\": { level: \"error\", messages: args }\n      }));\n    }\n    error.apply(console, args);\n  };\n\n  console.debug = function (...args: any[]) {\n    if (ws.readyState === WebSocket.OPEN) {\n      ws.send(JSON.stringify({\n        \"Log\": { level: \"debug\", messages: args }\n      }));\n    }\n    debug.apply(console, args);\n  };\n}\n"
  },
  {
    "path": "packages/interpreter/src/ts/serialize.ts",
    "content": "// Handle serialization of the event data across the IPC boundary\n//\n// Note that for files, we don't serialize their contents, expecting the renderer itself to handle\n// reading the file contents if needed. Reading files is an async operation, and should be done lazily\n// if the app needs it.\n\nexport type AppTouchEvent = TouchEvent;\n\nexport type SerializedEvent = {\n  values?: SerializedFormObject[];\n  value?: string;\n  [key: string]: any;\n};\n\nexport function serializeEvent(\n  event: Event,\n  target: EventTarget\n): SerializedEvent {\n  let contents = {};\n\n  // merge the object into the contents\n  let extend = (obj: SerializedEvent) => (contents = { ...contents, ...obj });\n\n  if (event instanceof WheelEvent) {\n    extend(serializeWheelEvent(event));\n  }\n  if (event instanceof MouseEvent) {\n    extend(serializeMouseEvent(event));\n  }\n  if (event instanceof KeyboardEvent) {\n    extend(serializeKeyboardEvent(event));\n  }\n\n  if (event instanceof InputEvent) {\n    extend(serializeInputEvent(event, target));\n  }\n  if (event instanceof PointerEvent) {\n    extend(serializePointerEvent(event));\n  }\n  if (event instanceof AnimationEvent) {\n    extend(serializeAnimationEvent(event));\n  }\n  if (event instanceof TransitionEvent) {\n    extend({\n      property_name: event.propertyName,\n      elapsed_time: event.elapsedTime,\n      pseudo_element: event.pseudoElement,\n    });\n  }\n  if (event instanceof CompositionEvent) {\n    extend({ data: event.data });\n  }\n  if (event instanceof DragEvent) {\n    extend(serializeDragEvent(event));\n  }\n  if (event instanceof FocusEvent) {\n    extend({});\n  }\n  if (event instanceof ClipboardEvent) {\n    extend({});\n  }\n\n  if (event instanceof CustomEvent) {\n    const detail = event.detail;\n    if (detail instanceof ResizeObserverEntry) {\n      extend(serializeResizeEventDetail(detail));\n    } else if (detail instanceof IntersectionObserverEntry) {\n      extend(serializeIntersectionEventDetail(detail));\n    }\n  }\n\n  // safari is quirky and doesn't have TouchEvent\n  if (typeof TouchEvent !== \"undefined\" && event instanceof TouchEvent) {\n    extend(serializeTouchEvent(event));\n  }\n\n  if (\n    event.type === \"submit\" ||\n    event.type === \"reset\" ||\n    event.type === \"click\" ||\n    event.type === \"change\" ||\n    event.type === \"input\"\n  ) {\n    extend(serializeInputEvent(event as InputEvent, target));\n  }\n\n  // If there's any files, we need to serialize them\n  if (event instanceof DragEvent) {\n    let files: SerializedFormObject[] = [];\n    if (event.dataTransfer && event.dataTransfer.files) {\n      for (let i = 0; i < event.dataTransfer.files.length; i++) {\n        let file = event.dataTransfer.files[i];\n        let data: SerializedFileData = {\n          path: file.name,\n          size: file.size,\n          last_modified: file.lastModified,\n          content_type: file.type,\n        }\n        files.push({ key: file.name, file: data });\n      }\n    }\n    extend({ files });\n  }\n\n  if (event.type === \"scroll\" || event.type === \"scrollend\") {\n    extend(serializeScrollEvent(event));\n  }\n\n  return contents;\n}\n\nfunction toSerializableResizeObserverSize(\n  size: ResizeObserverSize,\n  is_inline_width: boolean\n): Object {\n  return [\n    is_inline_width ? size.inlineSize : size.blockSize,\n    is_inline_width ? size.blockSize : size.inlineSize,\n  ];\n}\n\nexport function serializeResizeEventDetail(\n  detail: ResizeObserverEntry\n): SerializedEvent {\n  let is_inline_width = true;\n  if (detail.target instanceof HTMLElement) {\n    let target_style = window.getComputedStyle(detail.target);\n    let target_writing_mode = target_style.getPropertyValue(\"writing-mode\");\n    if (target_writing_mode !== \"horizontal-tb\") {\n      is_inline_width = false;\n    }\n  }\n\n  return {\n    border_box_size:\n      detail.borderBoxSize !== undefined\n        ? toSerializableResizeObserverSize(\n          detail.borderBoxSize[0],\n          is_inline_width\n        )\n        : detail.contentRect,\n    content_box_size:\n      detail.contentBoxSize !== undefined\n        ? toSerializableResizeObserverSize(\n          detail.contentBoxSize[0],\n          is_inline_width\n        )\n        : detail.contentRect,\n    content_rect: detail.contentRect,\n  };\n}\n\nexport function serializeIntersectionEventDetail(\n  detail: IntersectionObserverEntry): SerializedEvent {\n  return {\n    bounding_client_rect: detail.boundingClientRect,\n    intersection_ratio: detail.intersectionRatio,\n    intersection_rect: detail.intersectionRect,\n    is_intersecting: detail.isIntersecting,\n    root_bounds: detail.rootBounds,\n\n    // math.floor removes the floating point extra such that we serialize into integer\n    // this *might* become an issue longer term\n    time_ms: Math.floor(Date.now() + detail.time),\n  }\n}\n\nfunction serializeInputEvent(\n  event: InputEvent,\n  target: EventTarget\n): SerializedEvent {\n  let contents: SerializedEvent = {};\n\n  // Attempt to retrieve the values from the form\n  if (target instanceof HTMLElement) {\n    let values = extractSerializedFormValues(event, target);\n    contents.values = values.values;\n    contents.valid = values.valid;\n  }\n\n  if (event.target instanceof HTMLInputElement) {\n    let target = event.target;\n    let value = target.value ?? target.textContent ?? \"\";\n\n    if (target.type === \"checkbox\") {\n      value = target.checked ? \"true\" : \"false\";\n    } else if (target.type === \"radio\") {\n      value = target.value;\n    }\n\n    contents.value = value;\n  }\n\n  if (event.target instanceof HTMLTextAreaElement) {\n    contents.value = event.target.value;\n  }\n\n  if (event.target instanceof HTMLSelectElement) {\n    contents.value = retrieveSelectValue(event.target).join(\",\");\n  }\n\n  // Ensure the serializer isn't quirky\n  if (contents.value === undefined) {\n    contents.value = \"\";\n  }\n\n  return contents;\n}\n\nfunction serializeWheelEvent(event: WheelEvent): SerializedEvent {\n  return {\n    delta_x: event.deltaX,\n    delta_y: event.deltaY,\n    delta_z: event.deltaZ,\n    delta_mode: event.deltaMode,\n  };\n}\n\nfunction serializeTouchEvent(event: TouchEvent): SerializedEvent {\n  return {\n    alt_key: event.altKey,\n    ctrl_key: event.ctrlKey,\n    meta_key: event.metaKey,\n    shift_key: event.shiftKey,\n    changed_touches: serializeTouchList(event.changedTouches),\n    target_touches: serializeTouchList(event.targetTouches),\n    touches: serializeTouchList(event.touches),\n  };\n}\n\nfunction serializePointerEvent(event: PointerEvent): SerializedEvent {\n  return {\n    alt_key: event.altKey,\n    button: event.button,\n    buttons: event.buttons,\n    client_x: event.clientX,\n    client_y: event.clientY,\n    ctrl_key: event.ctrlKey,\n    meta_key: event.metaKey,\n    page_x: event.pageX,\n    page_y: event.pageY,\n    screen_x: event.screenX,\n    screen_y: event.screenY,\n    shift_key: event.shiftKey,\n    pointer_id: event.pointerId,\n    width: event.width,\n    height: event.height,\n    pressure: event.pressure,\n    tangential_pressure: event.tangentialPressure,\n    tilt_x: event.tiltX,\n    tilt_y: event.tiltY,\n    twist: event.twist,\n    pointer_type: event.pointerType,\n    is_primary: event.isPrimary,\n  };\n}\n\nfunction serializeTouchList(touchList: TouchList) {\n  const serializedTouches = [];\n  for (let i = 0; i < touchList.length; i++) {\n    const touch = touchList[i];\n    serializedTouches.push({\n      identifier: touch.identifier,\n      client_x: touch.clientX,\n      client_y: touch.clientY,\n      page_x: touch.pageX,\n      page_y: touch.pageY,\n      screen_x: touch.screenX,\n      screen_y: touch.screenY,\n      radius_x: (touch as any).radiusX,\n      radius_y: (touch as any).radiusY,\n      rotation_angle: (touch as any).rotationAngle,\n      force: (touch as any).force,\n    });\n  }\n  return serializedTouches;\n}\n\nfunction serializeMouseEvent(event: MouseEvent): SerializedEvent {\n  return {\n    alt_key: event.altKey,\n    button: event.button,\n    buttons: event.buttons,\n    client_x: event.clientX,\n    client_y: event.clientY,\n    ctrl_key: event.ctrlKey,\n    meta_key: event.metaKey,\n    offset_x: event.offsetX,\n    offset_y: event.offsetY,\n    page_x: event.pageX,\n    page_y: event.pageY,\n    screen_x: event.screenX,\n    screen_y: event.screenY,\n    shift_key: event.shiftKey,\n  };\n}\n\nfunction serializeKeyboardEvent(event: KeyboardEvent): SerializedEvent {\n  return {\n    char_code: event.charCode,\n    is_composing: event.isComposing,\n    key: event.key,\n    alt_key: event.altKey,\n    ctrl_key: event.ctrlKey,\n    meta_key: event.metaKey,\n    key_code: event.keyCode,\n    shift_key: event.shiftKey,\n    location: event.location,\n    repeat: event.repeat,\n    which: event.which,\n    code: event.code,\n  };\n}\n\nfunction serializeAnimationEvent(event: AnimationEvent): SerializedEvent {\n  return {\n    animation_name: event.animationName,\n    elapsed_time: event.elapsedTime,\n    pseudo_element: event.pseudoElement,\n  };\n}\n\nfunction serializeDragEvent(event: DragEvent): SerializedEvent {\n  let data_transfer = event.dataTransfer || new DataTransfer();\n  let items = [];\n  let files = [];\n  let effect_allowed = data_transfer.effectAllowed;\n  let drop_effect = data_transfer.dropEffect;\n\n  for (let i = 0; i < data_transfer.items.length; i++) {\n    let item = data_transfer.items[i];\n    let data;\n    if (item.kind === \"string\") {\n      // we can only get the data if we do this synchronously\n      // which is a bit sad, but oh well.\n      data = data_transfer.getData(item.type);\n    } else {\n      data = item.getAsFile()?.name || \"\";\n    }\n\n    items.push({\n      kind: item.kind,\n      type_: item.type,\n      data\n    });\n  }\n\n  for (let i = 0; i < data_transfer.files.length; i++) {\n    let file = data_transfer.files[i];\n    files.push({\n      name: file.name,\n      path: file.name,\n      size: file.size,\n      last_modified: file.lastModified,\n      content_type: file.type,\n      contents: undefined, // we don't serialize contents here\n    });\n  }\n\n  return {\n    mouse: {\n      alt_key: event.altKey,\n      ctrl_key: event.ctrlKey,\n      meta_key: event.metaKey,\n      shift_key: event.shiftKey,\n      ...serializeMouseEvent(event),\n    },\n    data_transfer: {\n      items,\n      files,\n      effect_allowed,\n      drop_effect,\n    },\n  };\n}\n\nfunction serializeScrollEvent(event: Event): SerializedEvent {\n  let scrollLeft = 0;\n  let scrollTop = 0;\n  let scrollWidth = 0;\n  let scrollHeight = 0;\n  let clientWidth = 0;\n  let clientHeight = 0;\n\n  if (event.target instanceof Element) {\n    scrollLeft = event.target.scrollLeft;\n    scrollTop = event.target.scrollTop;\n    scrollWidth = event.target.scrollWidth;\n    scrollHeight = event.target.scrollHeight;\n    clientWidth = event.target.clientWidth;\n    clientHeight = event.target.clientHeight;\n  } else if (event.target === document) {\n    scrollLeft = window.scrollX || document.documentElement.scrollLeft;\n    scrollTop = window.scrollY || document.documentElement.scrollTop;\n    scrollWidth = document.documentElement.scrollWidth;\n    scrollHeight = document.documentElement.scrollHeight;\n    clientWidth = document.documentElement.clientWidth;\n    clientHeight = document.documentElement.clientHeight;\n  }\n\n  return {\n    scroll_left: scrollLeft,\n    scroll_top: scrollTop,\n    scroll_width: scrollWidth,\n    scroll_height: scrollHeight,\n    client_width: clientWidth,\n    client_height: clientHeight,\n  };\n}\n\n\nexport type SerializedFormData = {\n  valid?: boolean;\n  values: SerializedFormObject[];\n}\n\nexport type SerializedFormObject = {\n  key: string;\n  text?: string;\n  file?: SerializedFileData;\n}\nexport type SerializedFileData = {\n  // we store the real path of the file as the name.\n  // This makes it possible to simply read the file during event handling\n  path: string;\n  size?: number;\n  last_modified?: number;\n  content_type?: string;\n  contents?: string; // base64 encoded, if present. not required to br present\n};\n\nexport function extractSerializedFormValues(event: Event, target: HTMLElement): SerializedFormData {\n  let contents: SerializedFormData = {\n    values: []\n  };\n\n  // If there's a form...\n  let form = target.closest(\"form\");\n\n  // If the target is an input, and the event is input or change, we want to get the value without going through the form\n  if (form) {\n    if (\n      event.type === \"input\"\n      || event.type === \"change\"\n      || event.type === \"submit\"\n      || event.type === \"reset\"\n      || event.type === \"click\"\n    ) {\n      contents = retrieveFormValues(form);\n    }\n  }\n\n  return contents;\n}\n\n// todo: maybe encode spaces or something?\n// We encode select multiple as a comma separated list which breaks... when there's commas in the values\nfunction retrieveFormValues(form: HTMLFormElement): SerializedFormData {\n  const formData = new FormData(form);\n  const contents: SerializedFormObject[] = [];\n\n  formData.forEach((value, key) => {\n    if (value instanceof File) {\n      let fileData: SerializedFileData = {\n        path: value.name,\n        size: value.size,\n        last_modified: value.lastModified,\n        content_type: value.type,\n      };\n      contents.push({ key, file: fileData });\n    } else {\n      contents.push({ key, text: value });\n    }\n  });\n\n  return {\n    valid: form.checkValidity(),\n    values: contents\n  };\n}\n\nexport function retrieveSelectValue(target: HTMLSelectElement): string[] {\n  // there might be multiple...\n  let options = target.selectedOptions;\n  let values = [];\n  for (let i = 0; i < options.length; i++) {\n    values.push(options[i].value);\n  }\n  return values;\n}\n\nexport type SerializedDataTransfer = {}\nexport type SerializedDataTransferItem = {}\n"
  },
  {
    "path": "packages/interpreter/src/ts/set_attribute.ts",
    "content": "// A unified interface for setting attributes on a node\n\n// this function should try and stay fast, if possible\nexport function setAttributeInner(\n  node: HTMLElement,\n  field: string,\n  value: string,\n  ns: string\n) {\n  // we support a single namespace by default: style\n  if (ns === \"style\") {\n    node.style.setProperty(field, value);\n    return;\n  }\n\n  // If there's a namespace, use setAttributeNS (svg, mathml, etc.)\n  if (!!ns) {\n    node.setAttributeNS(ns, field, value);\n    return;\n  }\n\n  // A few attributes are need to be set with either boolean values or require some sort of translation\n  switch (field) {\n    case \"value\":\n      // If this is a option element, set the attribute normally\n      if (node.tagName === \"OPTION\") {\n        setAttributeDefault(node, field, value);\n      } else {\n        // Otherwise, set the property if it has changed\n        // @ts-ignore\n        if (node.value !== value) {\n          // @ts-ignore\n          node.value = value;\n        }\n      }\n      break;\n\n    case \"initial_value\":\n      // @ts-ignore\n      node.defaultValue = value;\n      break;\n\n    case \"checked\":\n      // @ts-ignore\n      node.checked = truthy(value);\n      break;\n\n    case \"initial_checked\":\n      // @ts-ignore\n      node.defaultChecked = truthy(value);\n      break;\n\n    case \"selected\":\n      // @ts-ignore\n      node.selected = truthy(value);\n      break;\n\n    case \"initial_selected\":\n      // @ts-ignore\n      node.defaultSelected = truthy(value);\n      break;\n\n    case \"dangerous_inner_html\":\n      node.innerHTML = value;\n      break;\n\n    case \"style\":\n      // Save the existing styles\n      const existingStyles: Record<string, string> = {};\n\n      for (let i = 0; i < node.style.length; i++) {\n        const prop = node.style[i];\n        existingStyles[prop] = node.style.getPropertyValue(prop);\n      }\n      // Override all styles\n      node.setAttribute(field, value);\n      // Restore the old styles\n      for (const prop in existingStyles) {\n        // If it wasn't overridden, restore it\n        if (!node.style.getPropertyValue(prop)) {\n          node.style.setProperty(prop, existingStyles[prop]);\n        }\n      }\n      break;\n\n    case \"multiple\":\n      setAttributeDefault(node, field, value);\n      // reset the selected value whenever multiple changes\n      // @ts-ignore\n      if (node.options !== null && node.options !== undefined) {\n        // @ts-ignore\n        let options = node.options;\n        for (const option of options) {\n          option.selected = option.defaultSelected;\n        }\n      }\n      break;\n\n    default:\n      setAttributeDefault(node, field, value);\n  }\n}\n\nfunction setAttributeDefault(node: HTMLElement, field: string, value: string) {\n  // The presence of a an attribute is enough to set it to true, provided the value is being set to a truthy value\n  // Again, kinda ugly and would prefer this logic to be baked into dioxus-html at compile time\n  // https://github.com/facebook/react/blob/8b88ac2592c5f555f315f9440cbb665dd1e7457a/packages/react-dom/src/shared/DOMProperty.js#L352-L364\n  if (!truthy(value) && isBoolAttr(field)) {\n    node.removeAttribute(field);\n  } else {\n    node.setAttribute(field, value);\n  }\n}\n\nfunction truthy(val: string | boolean) {\n  return val === \"true\" || val === true;\n}\n\nfunction isBoolAttr(field: string): boolean {\n  switch (field) {\n    case \"allowfullscreen\":\n    case \"allowpaymentrequest\":\n    case \"async\":\n    case \"autofocus\":\n    case \"autoplay\":\n    case \"checked\":\n    case \"controls\":\n    case \"default\":\n    case \"defer\":\n    case \"disabled\":\n    case \"formnovalidate\":\n    case \"hidden\":\n    case \"ismap\":\n    case \"itemscope\":\n    case \"loop\":\n    case \"multiple\":\n    case \"muted\":\n    case \"nomodule\":\n    case \"novalidate\":\n    case \"open\":\n    case \"playsinline\":\n    case \"readonly\":\n    case \"required\":\n    case \"reversed\":\n    case \"selected\":\n    case \"truespeed\":\n    case \"webkitdirectory\":\n      return true;\n    default:\n      return false;\n  }\n}\n"
  },
  {
    "path": "packages/interpreter/src/unified_bindings.rs",
    "content": "#[cfg(feature = \"webonly\")]\nuse web_sys::Node;\n\npub const SLEDGEHAMMER_JS: &str = GENERATED_JS;\n\n#[cfg(feature = \"webonly\")]\n#[wasm_bindgen::prelude::wasm_bindgen]\nextern \"C\" {\n    pub type BaseInterpreter;\n\n    #[wasm_bindgen(method)]\n    pub fn initialize(this: &BaseInterpreter, root: Node, handler: &js_sys::Function);\n\n    #[wasm_bindgen(method, js_name = \"saveTemplate\")]\n    pub fn save_template(this: &BaseInterpreter, nodes: Vec<Node>, tmpl_id: u16);\n\n    #[wasm_bindgen(method)]\n    pub fn hydrate(this: &BaseInterpreter, ids: Vec<u32>, under: Vec<Node>);\n\n    #[wasm_bindgen(method, js_name = \"getNode\")]\n    pub fn get_node(this: &BaseInterpreter, id: u32) -> Node;\n\n    #[wasm_bindgen(method, js_name = \"pushRoot\")]\n    pub fn push_root(this: &BaseInterpreter, node: Node);\n}\n\n// Note that this impl is for the sledgehammer interpreter to allow us dropping down to the base interpreter\n// During hydration and initialization we need to the base interpreter methods\n#[cfg(feature = \"webonly\")]\nimpl Interpreter {\n    /// Convert the interpreter to its baseclass, giving\n    pub fn base(&self) -> &BaseInterpreter {\n        use wasm_bindgen::prelude::JsCast;\n        self.js_channel().unchecked_ref()\n    }\n}\n\n#[sledgehammer_bindgen::bindgen(module)]\nmod js {\n    // Extend the web base class\n    const BASE: &str = \"./src/js/core.js\";\n\n    /// The interpreter extends the core interpreter which contains the state for the interpreter along with some functions that all platforms use like `AppendChildren`.\n    #[extends(BaseInterpreter)]\n    pub struct Interpreter;\n\n    fn push_root(root: u32) {\n        \"{this.pushRoot(this.nodes[$root$]);}\"\n    }\n    fn append_children(id: u32, many: u16) {\n        \"{this.appendChildren($id$, $many$);}\"\n    }\n    fn pop_root() {\n        \"{this.stack.pop();}\"\n    }\n    fn replace_with(id: u32, n: u16) {\n        \"{const root = this.nodes[$id$]; let els = this.stack.splice(this.stack.length-$n$); if (root.listening) { this.removeAllNonBubblingListeners(root); } root.replaceWith(...els);}\"\n    }\n    fn insert_after(id: u32, n: u16) {\n        \"{let node = this.nodes[$id$];node.after(...this.stack.splice(this.stack.length-$n$));}\"\n    }\n    fn insert_before(id: u32, n: u16) {\n        \"{let node = this.nodes[$id$];node.before(...this.stack.splice(this.stack.length-$n$));}\"\n    }\n    fn remove(id: u32) {\n        \"{let node = this.nodes[$id$]; if (node !== undefined) { if (node.listening) { this.removeAllNonBubblingListeners(node); } node.remove(); }}\"\n    }\n    fn create_raw_text(text: &str) {\n        \"{this.stack.push(document.createTextNode($text$));}\"\n    }\n    fn create_text_node(text: &str, id: u32) {\n        \"{let node = document.createTextNode($text$); this.nodes[$id$] = node; this.stack.push(node);}\"\n    }\n    fn create_placeholder(id: u32) {\n        \"{let node = document.createComment('placeholder'); this.stack.push(node); this.nodes[$id$] = node;}\"\n    }\n\n    fn new_event_listener(event_name: &str<u8, evt>, id: u32, bubbles: u8) {\n        r#\"\n            const node = this.nodes[id];\n            if(node.listening){node.listening += 1;}else{node.listening = 1;}\n            node.setAttribute('data-dioxus-id', `\\${id}`);\n            this.createListener($event_name$, node, $bubbles$);\n        \"#\n    }\n    fn remove_event_listener(event_name: &str<u8, evt>, id: u32, bubbles: u8) {\n        \"{let node = this.nodes[$id$]; node.listening -= 1; node.removeAttribute('data-dioxus-id'); this.removeListener(node, $event_name$, $bubbles$);}\"\n    }\n    fn set_text(id: u32, text: &str) {\n        \"{this.nodes[$id$].textContent = $text$;}\"\n    }\n    fn set_attribute(id: u32, field: &str<u8, attr>, value: &str, ns: &str<u8, ns_cache>) {\n        \"{let node = this.nodes[$id$]; this.setAttributeInner(node, $field$, $value$, $ns$);}\"\n    }\n    fn remove_attribute(id: u32, field: &str<u8, attr>, ns: &str<u8, ns_cache>) {\n        r#\"{\n            let node = this.nodes[$id$];\n            if (!ns) {\n                switch (field) {\n                    case \"value\":\n                        node.value = \"\";\n                        node.removeAttribute(\"value\");\n                        break;\n                    case \"checked\":\n                        node.checked = false;\n                        break;\n                    case \"selected\":\n                        node.selected = false;\n                        break;\n                    case \"dangerous_inner_html\":\n                        node.innerHTML = \"\";\n                        break;\n                    default:\n                        node.removeAttribute(field);\n                        break;\n                }\n            } else if (ns == \"style\") {\n                node.style.removeProperty(field);\n            } else {\n                node.removeAttributeNS(ns, field);\n            }\n        }\"#\n    }\n    fn assign_id(ptr: u32, len: u8, id: u32) {\n        \"{this.nodes[$id$] = this.loadChild($ptr$, $len$);}\"\n    }\n    fn replace_placeholder(ptr: u32, len: u8, n: u16) {\n        \"{let els = this.stack.splice(this.stack.length - $n$); let node = this.loadChild($ptr$, $len$); node.replaceWith(...els);}\"\n    }\n    fn load_template(tmpl_id: u16, index: u16, id: u32) {\n        \"{let node = this.templates[$tmpl_id$][$index$].cloneNode(true); this.nodes[$id$] = node; this.stack.push(node);}\"\n    }\n\n    #[cfg(feature = \"binary-protocol\")]\n    fn append_children_to_top(many: u16) {\n        \"{\n            let root = this.stack[this.stack.length-many-1];\n            let els = this.stack.splice(this.stack.length-many);\n            for (let k = 0; k < many; k++) {\n                root.appendChild(els[k]);\n            }\n        }\"\n    }\n\n    #[cfg(feature = \"binary-protocol\")]\n    fn set_top_attribute(field: &str<u8, attr>, value: &str, ns: &str<u8, ns_cache>) {\n        \"{this.setAttributeInner(this.stack[this.stack.length-1], $field$, $value$, $ns$);}\"\n    }\n\n    #[cfg(feature = \"binary-protocol\")]\n    fn add_placeholder() {\n        \"{let node = document.createComment('placeholder'); this.stack.push(node);}\"\n    }\n\n    #[cfg(feature = \"binary-protocol\")]\n    fn create_element(element: &'static str<u8, el>) {\n        \"{this.stack.push(document.createElement($element$))}\"\n    }\n\n    #[cfg(feature = \"binary-protocol\")]\n    fn create_element_ns(element: &'static str<u8, el>, ns: &'static str<u8, namespace>) {\n        \"{this.stack.push(document.createElementNS($ns$, $element$))}\"\n    }\n\n    #[cfg(feature = \"binary-protocol\")]\n    fn add_templates(tmpl_id: u16, len: u16) {\n        \"{this.templates[$tmpl_id$] = this.stack.splice(this.stack.length-$len$);}\"\n    }\n\n    #[cfg(feature = \"binary-protocol\")]\n    fn foreign_event_listener(event: &str<u8, evt>, id: u32, bubbles: u8) {\n        r#\"\n    bubbles = bubbles == 1;\n    let this_node = this.nodes[id];\n    if(this_node.listening){\n        this_node.listening += 1;\n    } else {\n        this_node.listening = 1;\n    }\n    this_node.setAttribute('data-dioxus-id', `\\${id}`);\n    const event_name = $event$;\n\n    // if this is a mounted listener, we send the event immediately\n    if (event_name === \"mounted\") {\n        window.ipc.postMessage(\n            this.sendSerializedEvent({\n                name: event_name,\n                element: id,\n                data: null,\n                bubbles,\n            })\n        );\n    } else {\n        this.createListener(event_name, this_node, bubbles, (event) => {\n            this.handler(event, event_name, bubbles);\n        });\n    }\"#\n    }\n\n    /// Assign the ID\n    #[cfg(feature = \"binary-protocol\")]\n    fn assign_id_ref(array: &[u8], id: u32) {\n        \"{this.nodes[$id$] = this.loadChild($array$);}\"\n    }\n\n    #[cfg(feature = \"binary-protocol\")]\n    fn replace_placeholder_ref(array: &[u8], n: u16) {\n        \"{let els = this.stack.splice(this.stack.length - $n$); let node = this.loadChild($array$); node.replaceWith(...els);}\"\n    }\n}\n"
  },
  {
    "path": "packages/interpreter/src/write_native_mutations.rs",
    "content": "use crate::unified_bindings::Interpreter as Channel;\nuse dioxus_core::{Template, TemplateAttribute, TemplateNode, WriteMutations};\nuse dioxus_core_types::event_bubbles;\nuse rustc_hash::FxHashMap;\n\n/// The state needed to apply mutations to a channel. This state should be kept across all mutations for the app\n#[derive(Default)]\npub struct MutationState {\n    /// The currently registered templates with the template ids\n    templates: FxHashMap<Template, u16>,\n\n    /// The channel that we are applying mutations to\n    channel: Channel,\n}\n\nimpl MutationState {\n    pub fn new() -> Self {\n        Self::default()\n    }\n\n    pub fn export_memory(&mut self) -> Vec<u8> {\n        let bytes: Vec<_> = self.channel.export_memory().collect();\n        self.channel.reset();\n        bytes\n    }\n\n    pub fn write_memory_into(&mut self, buffer: &mut Vec<u8>) {\n        buffer.extend(self.channel.export_memory());\n        self.channel.reset();\n    }\n\n    pub fn channel(&mut self) -> &mut Channel {\n        &mut self.channel\n    }\n\n    pub fn channel_mut(&mut self) -> &mut Channel {\n        &mut self.channel\n    }\n\n    fn create_template_node(&mut self, node: &'static TemplateNode) {\n        use TemplateNode::*;\n        match node {\n            Element {\n                tag,\n                namespace,\n                attrs,\n                children,\n                ..\n            } => {\n                // Push the current node onto the stack\n                match namespace {\n                    Some(ns) => self.channel.create_element_ns(tag, ns),\n                    None => self.channel.create_element(tag),\n                }\n                // Set attributes on the current node\n                for attr in *attrs {\n                    if let TemplateAttribute::Static {\n                        name,\n                        value,\n                        namespace,\n                    } = attr\n                    {\n                        self.channel\n                            .set_top_attribute(name, value, namespace.unwrap_or_default())\n                    }\n                }\n                // Add each child to the stack\n                for child in *children {\n                    self.create_template_node(child);\n                }\n                // Add all children to the parent\n                self.channel.append_children_to_top(children.len() as u16);\n            }\n            Text { text } => self.channel.create_raw_text(text),\n            Dynamic { .. } => self.channel.add_placeholder(),\n        }\n    }\n}\n\nimpl WriteMutations for MutationState {\n    fn append_children(&mut self, id: dioxus_core::ElementId, m: usize) {\n        self.channel.append_children(id.0 as u32, m as u16);\n    }\n\n    fn assign_node_id(&mut self, path: &'static [u8], id: dioxus_core::ElementId) {\n        self.channel.assign_id_ref(path, id.0 as u32);\n    }\n\n    fn create_placeholder(&mut self, id: dioxus_core::ElementId) {\n        self.channel.create_placeholder(id.0 as u32);\n    }\n\n    fn create_text_node(&mut self, value: &str, id: dioxus_core::ElementId) {\n        self.channel.create_text_node(value, id.0 as u32);\n    }\n\n    fn load_template(&mut self, template: Template, index: usize, id: dioxus_core::ElementId) {\n        // Get the template or create it if we haven't seen it before\n        let tmpl_id = self.templates.get(&template).cloned().unwrap_or_else(|| {\n            let tmpl_id = self.templates.len() as u16;\n            self.templates.insert(template, tmpl_id);\n\n            for root in template.roots.iter() {\n                self.create_template_node(root);\n            }\n\n            let len = template.roots.len() as u16;\n            self.channel.add_templates(tmpl_id, len);\n\n            tmpl_id\n        });\n\n        self.channel\n            .load_template(tmpl_id, index as u16, id.0 as u32);\n    }\n\n    fn replace_node_with(&mut self, id: dioxus_core::ElementId, m: usize) {\n        self.channel.replace_with(id.0 as u32, m as u16);\n    }\n\n    fn replace_placeholder_with_nodes(&mut self, path: &'static [u8], m: usize) {\n        self.channel.replace_placeholder_ref(path, m as u16);\n    }\n\n    fn insert_nodes_after(&mut self, id: dioxus_core::ElementId, m: usize) {\n        self.channel.insert_after(id.0 as u32, m as u16);\n    }\n\n    fn insert_nodes_before(&mut self, id: dioxus_core::ElementId, m: usize) {\n        self.channel.insert_before(id.0 as u32, m as u16);\n    }\n\n    fn set_attribute(\n        &mut self,\n        name: &'static str,\n        ns: Option<&'static str>,\n        value: &dioxus_core::AttributeValue,\n        id: dioxus_core::ElementId,\n    ) {\n        match value {\n            dioxus_core::AttributeValue::Text(txt) => {\n                self.channel\n                    .set_attribute(id.0 as u32, name, txt, ns.unwrap_or_default())\n            }\n            dioxus_core::AttributeValue::Float(f) => self.channel.set_attribute(\n                id.0 as u32,\n                name,\n                &f.to_string(),\n                ns.unwrap_or_default(),\n            ),\n            dioxus_core::AttributeValue::Int(n) => self.channel.set_attribute(\n                id.0 as u32,\n                name,\n                &n.to_string(),\n                ns.unwrap_or_default(),\n            ),\n            dioxus_core::AttributeValue::Bool(b) => self.channel.set_attribute(\n                id.0 as u32,\n                name,\n                if *b { \"true\" } else { \"false\" },\n                ns.unwrap_or_default(),\n            ),\n            dioxus_core::AttributeValue::None => {\n                self.channel\n                    .remove_attribute(id.0 as u32, name, ns.unwrap_or_default())\n            }\n            _ => unreachable!(\"Any attributes are not supported by the current renderer\"),\n        }\n    }\n\n    fn set_node_text(&mut self, value: &str, id: dioxus_core::ElementId) {\n        self.channel.set_text(id.0 as u32, value);\n    }\n\n    fn create_event_listener(&mut self, name: &'static str, id: dioxus_core::ElementId) {\n        // note that we use the foreign event listener here instead of the native one\n        // the native method assumes we have direct access to the dom, which we don't.\n        self.channel\n            .foreign_event_listener(name, id.0 as u32, event_bubbles(name) as u8);\n    }\n\n    fn remove_event_listener(&mut self, name: &'static str, id: dioxus_core::ElementId) {\n        self.channel\n            .remove_event_listener(name, id.0 as u32, event_bubbles(name) as u8);\n    }\n\n    fn remove_node(&mut self, id: dioxus_core::ElementId) {\n        self.channel.remove(id.0 as u32);\n    }\n\n    fn push_root(&mut self, id: dioxus_core::ElementId) {\n        self.channel.push_root(id.0 as _);\n    }\n}\n"
  },
  {
    "path": "packages/interpreter/tests/e2e.rs",
    "content": "//! Ensure that the interpreter loads, has no errors, and writes mutations\n"
  },
  {
    "path": "packages/interpreter/tests/serialize.rs",
    "content": "//! Ensure that events are serialized and deserialized correctly.\n"
  },
  {
    "path": "packages/interpreter/tsconfig.json",
    "content": "{\n    \"compilerOptions\": {\n        \"module\": \"CommonJS\",\n        \"lib\": [\n            \"ES2015\",\n            \"DOM\",\n            \"dom\",\n            \"dom.iterable\",\n            \"ESNext\"\n        ],\n        \"noImplicitAny\": true,\n        \"removeComments\": true,\n        \"preserveConstEnums\": true,\n        \"typeRoots\": [\".src/ts/types\"]\n    },\n    \"exclude\": [\n        \"**/*.spec.ts\"\n    ]\n}\n"
  },
  {
    "path": "packages/lazy-js-bundle/Cargo.toml",
    "content": "[package]\nname = \"lazy-js-bundle\"\nversion = { workspace = true }\nedition = \"2021\"\nauthors = [\"Jonathan Kelley\"]\ndescription = \"A codegen library to bundle TypeScript into JavaScript without requiring a bundler to be installed\"\nlicense = \"MIT OR Apache-2.0\"\nrepository = \"https://github.com/DioxusLabs/dioxus/\"\nhomepage = \"https://dioxuslabs.com\"\ndocumentation = \"https://docs.rs/dioxus\"\nkeywords = [\"ts\", \"codegen\", \"typescript\", \"javascript\", \"wasm\"]\n"
  },
  {
    "path": "packages/lazy-js-bundle/src/lib.rs",
    "content": "use std::collections::hash_map::DefaultHasher;\nuse std::path::{Path, PathBuf};\nuse std::{hash::Hasher, process::Command};\n\nstruct Binding {\n    input_path: PathBuf,\n    output_path: PathBuf,\n}\n\n/// A builder for generating TypeScript bindings lazily\n#[derive(Default)]\npub struct LazyTypeScriptBindings {\n    binding: Vec<Binding>,\n    minify_level: MinifyLevel,\n    watching: Vec<PathBuf>,\n}\n\nimpl LazyTypeScriptBindings {\n    /// Create a new builder for generating TypeScript bindings that inputs from the given path and outputs javascript to the given path\n    pub fn new() -> Self {\n        Self::default()\n    }\n\n    /// Add a binding to generate\n    pub fn with_binding(\n        mut self,\n        input_path: impl AsRef<Path>,\n        output_path: impl AsRef<Path>,\n    ) -> Self {\n        let input_path = input_path.as_ref();\n        let output_path = output_path.as_ref();\n\n        self.binding.push(Binding {\n            input_path: input_path.to_path_buf(),\n            output_path: output_path.to_path_buf(),\n        });\n\n        self\n    }\n\n    /// Set the minify level for the bindings\n    pub fn with_minify_level(mut self, minify_level: MinifyLevel) -> Self {\n        self.minify_level = minify_level;\n        self\n    }\n\n    /// Watch any .js or .ts files in a directory and re-generate the bindings when they change\n    // TODO: we should watch any files that get bundled by bun by reading the source map\n    pub fn with_watching(mut self, path: impl AsRef<Path>) -> Self {\n        let path = path.as_ref();\n        self.watching.push(path.to_path_buf());\n        self\n    }\n\n    /// Run the bindings\n    pub fn run(&self) {\n        // If any TS changes, re-run the build script\n        let mut watching_paths = Vec::new();\n        for path in &self.watching {\n            if let Ok(dir) = std::fs::read_dir(path) {\n                for entry in dir.flatten() {\n                    let path = entry.path();\n                    if path\n                        .extension()\n                        .map(|ext| ext == \"ts\" || ext == \"js\")\n                        .unwrap_or(false)\n                    {\n                        watching_paths.push(path);\n                    }\n                }\n            } else {\n                watching_paths.push(path.to_path_buf());\n            }\n        }\n        for path in &watching_paths {\n            println!(\"cargo:rerun-if-changed={}\", path.display());\n        }\n\n        // Compute the hash of the input files\n        let hashes = hash_files(watching_paths);\n\n        let hash_location = PathBuf::from(\"./src/js/\");\n        std::fs::create_dir_all(&hash_location).unwrap_or_else(|err| {\n            panic!(\n                \"Failed to create directory for hash file: {} in {}\",\n                err,\n                hash_location.display()\n            )\n        });\n        let hash_location = hash_location.join(\"hash.txt\");\n\n        // If the hash matches the one on disk, we're good and don't need to update bindings\n        let fs_hash_string = std::fs::read_to_string(&hash_location);\n        let expected = fs_hash_string\n            .as_ref()\n            .map(|s| s.trim())\n            .unwrap_or_default();\n        let hashes_string = format!(\"{hashes:?}\");\n        if expected == hashes_string {\n            return;\n        }\n\n        // Otherwise, generate the bindings and write the new hash to disk\n        for path in &self.binding {\n            gen_bindings(&path.input_path, &path.output_path, self.minify_level);\n        }\n\n        std::fs::write(hash_location, hashes_string).unwrap();\n    }\n}\n\n/// The level of minification to apply to the bindings\n#[derive(Copy, Clone, Debug, Default)]\npub enum MinifyLevel {\n    /// Don't minify the bindings\n    None,\n    /// Minify whitespace\n    Whitespace,\n    /// Minify whitespace and syntax\n    #[default]\n    Syntax,\n    /// Minify whitespace, syntax, and identifiers\n    Identifiers,\n}\n\nimpl MinifyLevel {\n    fn as_args(&self) -> &'static [&'static str] {\n        match self {\n            MinifyLevel::None => &[],\n            MinifyLevel::Whitespace => &[\"--minify-whitespace\"],\n            MinifyLevel::Syntax => &[\"--minify-whitespace\", \"--minify-syntax\"],\n            MinifyLevel::Identifiers => &[\n                \"--minify-whitespace\",\n                \"--minify-syntax\",\n                \"--minify-identifiers\",\n            ],\n        }\n    }\n}\n\n/// Hashes the contents of a directory\nfn hash_files(mut files: Vec<PathBuf>) -> Vec<u64> {\n    // Different systems will read the files in different orders, so we sort them to make sure the hash is consistent\n    files.sort();\n    let mut hashes = Vec::new();\n    for file in files {\n        let mut hash = DefaultHasher::new();\n        let Ok(contents) = std::fs::read_to_string(file) else {\n            continue;\n        };\n        // windows + git does a weird thing with line endings, so we need to normalize them\n        for line in contents.lines() {\n            hash.write(line.as_bytes());\n        }\n        hashes.push(hash.finish());\n    }\n    hashes\n}\n\n// okay...... so bun might fail if the user doesn't have it installed\n// we don't really want to fail if that's the case\n// but if you started *editing* the .ts files, you're gonna have a bad time\n// so.....\n// we need to hash each of the .ts files and add that hash to the JS files\n// if the hashes don't match, we need to fail the build\n// that way we also don't need\nfn gen_bindings(input_path: &Path, output_path: &Path, minify_level: MinifyLevel) {\n    // If the file is generated, and the hash is different, we need to generate it\n    let status = Command::new(\"bun\")\n        .arg(\"build\")\n        .arg(input_path)\n        .arg(\"--outfile\")\n        .arg(output_path)\n        .args(minify_level.as_args())\n        .status();\n\n    let status = match status {\n        Ok(status) => status,\n        Err(error) => {\n            if Command::new(\"bun\").status().is_ok() {\n                panic!(\n                    \"Bun failed to generated bindings for {:?}. Error:\\n{:?}\",\n                    input_path, error\n                );\n            } else {\n                panic!(\"Make sure you have Bun installed. Failed to generate bindings for {:?}. Error:\\n{:?}\", input_path, error);\n            }\n        }\n    };\n\n    if !status.success() {\n        panic!(\"Bun failed to generate bindings for {:?}.\", input_path);\n    }\n}\n"
  },
  {
    "path": "packages/liveview/Cargo.toml",
    "content": "[package]\nname = \"dioxus-liveview\"\nversion = { workspace = true }\nedition = \"2021\"\nrepository = \"https://github.com/DioxusLabs/dioxus/\"\nhomepage = \"https://dioxuslabs.com/learn/0.7/getting_started\"\nkeywords = [\"dom\", \"ui\", \"gui\", \"react\", \"liveview\"]\ndescription = \"Build server-side apps with Dioxus\"\nauthors = [\"Jonathan Kelley\", \"Evan Almloff\"]\nlicense = \"MIT OR Apache-2.0\"\n\n[dependencies]\nthiserror = { workspace = true }\ntracing = { workspace = true }\nslab = { workspace = true }\nfutures-util = { workspace = true, default-features = false, features = [\n    \"sink\",\n] }\nfutures-channel = { workspace = true }\ntokio = { workspace = true, features = [\"time\", \"macros\"] }\ntokio-stream = { workspace = true, features = [\"net\"] }\ntokio-util = { workspace = true, features = [\"rt\"] }\nserde = { workspace = true, features = [\"derive\"] }\nserde_json = { workspace = true }\ndioxus-html = { workspace = true, features = [\"serialize\"] }\ndioxus-document = { workspace = true }\ndioxus-history = { workspace = true }\nrustc-hash = { workspace = true }\ndioxus-core = { workspace = true, features = [\"serialize\"] }\ndioxus-interpreter-js = { workspace = true, features = [\"binary-protocol\"] }\ndioxus-devtools = { workspace = true, optional = true }\ndioxus-cli-config = { workspace = true }\ngenerational-box = { workspace = true }\n\n# axum\naxum = { workspace = true, optional = true, default-features = true, features = [\"ws\"] }\n\n[dev-dependencies]\ntokio = { workspace = true, features = [\"full\"] }\naxum = { workspace = true, features = [\"ws\"] }\ntower = { workspace = true }\ndioxus = { workspace = true }\n\n[features]\ndefault = [\"devtools\", \"multi-thread\"]\naxum = [\"dep:axum\"]\nmulti-thread = [\"tokio/rt-multi-thread\"]\ndevtools = [\"dep:dioxus-devtools\"]\n\n[[example]]\nname = \"axum\"\nrequired-features = [\"axum\"]\ndoc-scrape-examples = true\n\n[[example]]\nname = \"axum_stress\"\nrequired-features = [\"axum\"]\ndoc-scrape-examples = true\n"
  },
  {
    "path": "packages/liveview/README.md",
    "content": "# Dioxus Liveview\n\n[![Crates.io][crates-badge]][crates-url]\n[![MIT licensed][mit-badge]][mit-url]\n[![Build Status][actions-badge]][actions-url]\n[![Discord chat][discord-badge]][discord-url]\n\n[crates-badge]: https://img.shields.io/crates/v/dioxus-liveview.svg\n[crates-url]: https://crates.io/crates/dioxus-liveview\n[mit-badge]: https://img.shields.io/badge/license-MIT-blue.svg\n[mit-url]: https://github.com/dioxuslabs/dioxus/blob/main/LICENSE-MIT\n[actions-badge]: https://github.com/dioxuslabs/dioxus/actions/workflows/main.yml/badge.svg\n[actions-url]: https://github.com/dioxuslabs/dioxus/actions?query=workflow%3ACI+branch%3Amaster\n[discord-badge]: https://img.shields.io/discord/899851952891002890.svg?logo=discord&style=flat-square\n[discord-url]: https://discord.gg/XgGxMSkvUM\n\n[Website](https://dioxuslabs.com) |\n[Guides](https://dioxuslabs.com/learn/0.7/) |\n[API Docs](https://docs.rs/dioxus-liveview/latest/dioxus_liveview) |\n[Chat](https://discord.gg/XgGxMSkvUM)\n\n## Overview\n\n`dioxus-liveview` provides adapters for running the Dioxus VirtualDom over a WebSocket connection.\n\nThe current backend frameworks supported include:\n\n- Axum\n\nDioxus-LiveView exports some primitives to wire up an app into an existing backend framework.\n\n- A ThreadPool for spawning the `!Send` VirtualDom and interacting with it from WebSockets\n- An adapter for transforming various socket types into the `LiveViewSocket` type\n- The glue to load the interpreter into your app\n\n## Contributing\n\n- Report issues on our [issue tracker](https://github.com/dioxuslabs/dioxus/issues).\n- Join the discord and ask questions!\n\n## License\n\nThis project is licensed under the [MIT license].\n\n[mit license]: https://github.com/dioxuslabs/dioxus/blob/main/LICENSE-MIT\n\nUnless you explicitly state otherwise, any contribution intentionally submitted\nfor inclusion in Dioxus by you shall be licensed as MIT without any additional\nterms or conditions.\n"
  },
  {
    "path": "packages/liveview/examples/axum.rs",
    "content": "use axum::Router;\nuse dioxus::prelude::*;\nuse dioxus_liveview::LiveviewRouter;\n\nfn app() -> Element {\n    let mut num = use_signal(|| 0);\n\n    rsx! {\n        div {\n            \"hello axum! {num}\"\n            button { onclick: move |_| num += 1, \"Increment\" }\n        }\n    }\n}\n\n#[tokio::main]\nasync fn main() {\n    dioxus::logger::initialize_default();\n\n    let addr: std::net::SocketAddr = ([127, 0, 0, 1], 3030).into();\n\n    let app = Router::new().with_app(\"/\", app);\n\n    println!(\"Listening on http://{addr}\");\n\n    let listener = tokio::net::TcpListener::bind(&addr).await.unwrap();\n    axum::serve(listener, app.into_make_service())\n        .await\n        .unwrap();\n}\n"
  },
  {
    "path": "packages/liveview/examples/axum_stress.rs",
    "content": "use axum::{extract::ws::WebSocketUpgrade, response::Html, routing::get, Router};\nuse dioxus::prelude::*;\n\nfn app() -> Element {\n    let mut state = use_signal(|| 0);\n    use_future(move || async move {\n        loop {\n            state += 1;\n            tokio::time::sleep(std::time::Duration::from_millis(1)).await;\n        }\n    });\n\n    rsx! {\n        for _ in 0..10000 {\n            div {\n                \"hello axum! {state}\"\n            }\n        }\n    }\n}\n\n#[tokio::main]\nasync fn main() {\n    dioxus::logger::initialize_default();\n\n    let addr: std::net::SocketAddr = ([127, 0, 0, 1], 3030).into();\n\n    let view = dioxus_liveview::LiveViewPool::new();\n\n    let app = Router::new()\n        .route(\n            \"/\",\n            get(move || async move {\n                Html(format!(\n                    r#\"\n            <!DOCTYPE html>\n            <html>\n                <head> <title>Dioxus LiveView with axum</title>  </head>\n                <body> <div id=\"main\"></div> </body>\n                {glue}\n            </html>\n            \"#,\n                    glue = dioxus_liveview::interpreter_glue(&format!(\"ws://{addr}/ws\"))\n                ))\n            }),\n        )\n        .route(\n            \"/ws\",\n            get(move |ws: WebSocketUpgrade| async move {\n                ws.on_upgrade(move |socket| async move {\n                    _ = view.launch(dioxus_liveview::axum_socket(socket), app).await;\n                })\n            }),\n        );\n\n    println!(\"Listening on http://{addr}\");\n\n    let listener = tokio::net::TcpListener::bind(&addr).await.unwrap();\n    axum::serve(listener, app.into_make_service())\n        .await\n        .unwrap();\n}\n"
  },
  {
    "path": "packages/liveview/src/adapters/axum_adapter.rs",
    "content": "use std::sync::Arc;\n\nuse crate::{interpreter_glue, LiveViewError, LiveViewSocket, LiveviewRouter};\nuse axum::{\n    extract::{\n        ws::{Message, WebSocket},\n        WebSocketUpgrade,\n    },\n    response::Html,\n    routing::*,\n    Router,\n};\nuse futures_util::{SinkExt, StreamExt};\n\n/// Convert an Axum WebSocket into a `LiveViewSocket`.\n///\n/// This is required to launch a LiveView app using the Axum web framework.\npub fn axum_socket(ws: WebSocket) -> impl LiveViewSocket {\n    ws.map(transform_rx)\n        .with(transform_tx)\n        .sink_map_err(|_| LiveViewError::SendingFailed)\n}\n\nfn transform_rx(message: Result<Message, axum::Error>) -> Result<Vec<u8>, LiveViewError> {\n    message\n        .map_err(|_| LiveViewError::SendingFailed)?\n        .into_text()\n        .map(|s| s.as_str().into())\n        .map_err(|_| LiveViewError::SendingFailed)\n}\n\nasync fn transform_tx(message: Vec<u8>) -> Result<Message, axum::Error> {\n    Ok(Message::Binary(message.into()))\n}\n\nimpl LiveviewRouter for Router {\n    fn create_default_liveview_router() -> Self {\n        Router::new()\n    }\n\n    fn with_virtual_dom(\n        self,\n        route: &str,\n        app: impl Fn() -> dioxus_core::VirtualDom + Send + Sync + 'static,\n    ) -> Self {\n        let view = crate::LiveViewPool::new();\n\n        let ws_path = format!(\"{}/ws\", route.trim_start_matches('/'));\n        let title = crate::app_title();\n\n        let index_page_with_glue = move |glue: &str| {\n            Html(format!(\n                r#\"\n        <!DOCTYPE html>\n        <html>\n            <head><title>{title}</title></head>\n            <body><div id=\"main\"></div></body>\n            {glue}\n        </html>\n        \"#,\n            ))\n        };\n\n        let app = Arc::new(app);\n        // Add an extra catch all segment to the route\n        let mut route = route.trim_matches('/').to_string();\n        if route.is_empty() {\n            route = \"/{*route}\".to_string();\n        } else {\n            route = format!(\"/{route}/{{*route}}\");\n        }\n\n        self.route(\n            &ws_path,\n            get(move |ws: WebSocketUpgrade| async move {\n                let app = app.clone();\n                ws.on_upgrade(move |socket| async move {\n                    _ = view\n                        .launch_virtualdom(axum_socket(socket), move || app())\n                        .await;\n                })\n            }),\n        )\n        .route(\n            &route,\n            get(move || async move { index_page_with_glue(&interpreter_glue(&ws_path)) }),\n        )\n    }\n\n    async fn start(self, address: impl Into<std::net::SocketAddr>) {\n        let listener = tokio::net::TcpListener::bind(address.into()).await.unwrap();\n        if let Err(err) = axum::serve(listener, self.into_make_service()).await {\n            eprintln!(\"Failed to start axum server: {}\", err);\n        }\n    }\n}\n"
  },
  {
    "path": "packages/liveview/src/adapters/mod.rs",
    "content": "use std::future::Future;\n\nuse dioxus_core::{Element, VirtualDom};\n\n#[cfg(feature = \"axum\")]\npub mod axum_adapter;\n#[cfg(feature = \"axum\")]\npub use axum_adapter::*;\n\n/// A trait for servers that can be used to host a LiveView app.\npub trait LiveviewRouter {\n    /// Create a new router.\n    fn create_default_liveview_router() -> Self;\n\n    /// Add a liveview route to the server from a component\n    fn with_app(self, route: &str, app: fn() -> Element) -> Self\n    where\n        Self: Sized,\n    {\n        self.with_virtual_dom(route, move || VirtualDom::new(app))\n    }\n\n    /// Add a liveview route to the server from a virtual dom.\n    fn with_virtual_dom(\n        self,\n        route: &str,\n        app: impl Fn() -> VirtualDom + Send + Sync + 'static,\n    ) -> Self;\n\n    /// Start the server on an address.\n    fn start(self, address: impl Into<std::net::SocketAddr>) -> impl Future<Output = ()>;\n}\n"
  },
  {
    "path": "packages/liveview/src/config.rs",
    "content": "use dioxus_core::{LaunchConfig, VirtualDom};\n\nuse crate::LiveviewRouter;\n\npub(crate) fn app_title() -> String {\n    dioxus_cli_config::app_title().unwrap_or_else(|| \"Dioxus Liveview App\".to_string())\n}\n\n/// A configuration for the LiveView server.\npub struct Config<R: LiveviewRouter> {\n    router: R,\n    address: std::net::SocketAddr,\n    route: String,\n}\n\nimpl<R: LiveviewRouter + 'static> LaunchConfig for Config<R> {}\n\nimpl<R: LiveviewRouter> Default for Config<R> {\n    fn default() -> Self {\n        Self {\n            address: dioxus_cli_config::fullstack_address_or_localhost(),\n            router: R::create_default_liveview_router(),\n            route: \"/\".to_string(),\n        }\n    }\n}\n\nimpl<R: LiveviewRouter> Config<R> {\n    /// Set the route to use for the LiveView server.\n    pub fn route(mut self, route: impl Into<String>) -> Self {\n        self.route = route.into();\n        self\n    }\n\n    /// Create a new configuration for the LiveView server.\n    pub fn with_app(mut self, app: fn() -> dioxus_core::Element) -> Self {\n        self.router = self.router.with_app(&self.route, app);\n        self\n    }\n\n    /// Create a new configuration for the LiveView server.\n    pub fn with_virtual_dom(\n        mut self,\n        virtual_dom: impl Fn() -> VirtualDom + Send + Sync + 'static,\n    ) -> Self {\n        self.router = self.router.with_virtual_dom(&self.route, virtual_dom);\n        self\n    }\n\n    /// Set the address to listen on.\n    pub fn address(mut self, address: impl Into<std::net::SocketAddr>) -> Self {\n        self.address = address.into();\n        self\n    }\n\n    /// Launch the LiveView server.\n    pub async fn launch(self) {\n        println!(\"{} started on http://{}\", app_title(), self.address);\n        self.router.start(self.address).await\n    }\n}\n"
  },
  {
    "path": "packages/liveview/src/document.rs",
    "content": "use dioxus_core::queue_effect;\nuse dioxus_core::ScopeId;\nuse dioxus_document::{\n    create_element_in_head, Document, Eval, EvalError, Evaluator, LinkProps, MetaProps,\n    ScriptProps, StyleProps,\n};\nuse dioxus_history::History;\nuse generational_box::{AnyStorage, GenerationalBox, UnsyncStorage};\nuse std::rc::Rc;\n\nuse crate::history::LiveviewHistory;\nuse crate::query::{Query, QueryEngine};\n\n/// Represents a liveview-target's JavaScript evaluator.\npub(crate) struct LiveviewEvaluator {\n    query: Query<serde_json::Value>,\n}\n\nimpl LiveviewEvaluator {\n    /// Creates a new evaluator for liveview-based targets.\n    pub fn create(query_engine: QueryEngine, js: String) -> GenerationalBox<Box<dyn Evaluator>> {\n        let query = query_engine.new_query(&js);\n        // We create a generational box that is owned by the query slot so that when we drop the query slot, the generational box is also dropped.\n        let owner = UnsyncStorage::owner();\n        let query_id = query.id;\n        let query = owner.insert(Box::new(LiveviewEvaluator { query }) as Box<dyn Evaluator>);\n        query_engine.active_requests.slab.borrow_mut()[query_id].owner = Some(owner);\n\n        query\n    }\n}\n\nimpl Evaluator for LiveviewEvaluator {\n    /// # Panics\n    /// This will panic if the query is currently being awaited.\n    fn poll_join(\n        &mut self,\n        context: &mut std::task::Context<'_>,\n    ) -> std::task::Poll<Result<serde_json::Value, EvalError>> {\n        self.query\n            .poll_result(context)\n            .map_err(|e| EvalError::Communication(e.to_string()))\n    }\n\n    /// Sends a message to the evaluated JavaScript.\n    fn send(&self, data: serde_json::Value) -> Result<(), EvalError> {\n        if let Err(e) = self.query.send(data) {\n            return Err(EvalError::Communication(e.to_string()));\n        }\n        Ok(())\n    }\n\n    /// Gets an UnboundedReceiver to receive messages from the evaluated JavaScript.\n    ///\n    /// # Panics\n    /// This will panic if the query is currently being awaited.\n    fn poll_recv(\n        &mut self,\n        context: &mut std::task::Context<'_>,\n    ) -> std::task::Poll<Result<serde_json::Value, EvalError>> {\n        self.query\n            .poll_recv(context)\n            .map_err(|e| EvalError::Communication(e.to_string()))\n    }\n}\n\n/// Provides the LiveviewDocument through [`dioxus_core::Runtime::provide_context`].\npub fn init_document() {\n    let rt = dioxus_core::Runtime::current();\n    let query = rt.consume_context::<QueryEngine>(ScopeId::ROOT).unwrap();\n    let provider: Rc<dyn Document> = Rc::new(LiveviewDocument {\n        query: query.clone(),\n    });\n    rt.provide_context(ScopeId::ROOT, provider);\n    let history = LiveviewHistory::new(Rc::new(move |script: &str| {\n        Eval::new(LiveviewEvaluator::create(query.clone(), script.to_string()))\n    }));\n    let history: Rc<dyn History> = Rc::new(history);\n    rt.provide_context(ScopeId::ROOT, history);\n}\n\n/// Reprints the liveview-target's provider of evaluators.\n#[derive(Clone)]\npub struct LiveviewDocument {\n    query: QueryEngine,\n}\n\nimpl Document for LiveviewDocument {\n    fn eval(&self, js: String) -> Eval {\n        Eval::new(LiveviewEvaluator::create(self.query.clone(), js))\n    }\n\n    /// Set the title of the document\n    fn set_title(&self, title: String) {\n        let myself = self.clone();\n        queue_effect(move || {\n            myself.eval(format!(\"document.title = {title:?};\"));\n        });\n    }\n\n    /// Create a new meta tag in the head\n    fn create_meta(&self, props: MetaProps) {\n        let myself = self.clone();\n        queue_effect(move || {\n            myself.eval(create_element_in_head(\"meta\", &props.attributes(), None));\n        });\n    }\n\n    /// Create a new script tag in the head\n    fn create_script(&self, props: ScriptProps) {\n        let myself = self.clone();\n        queue_effect(move || {\n            myself.eval(create_element_in_head(\n                \"script\",\n                &props.attributes(),\n                props.script_contents().ok(),\n            ));\n        });\n    }\n\n    /// Create a new style tag in the head\n    fn create_style(&self, props: StyleProps) {\n        let myself = self.clone();\n        queue_effect(move || {\n            myself.eval(create_element_in_head(\n                \"style\",\n                &props.attributes(),\n                props.style_contents().ok(),\n            ));\n        });\n    }\n\n    /// Create a new link tag in the head\n    fn create_link(&self, props: LinkProps) {\n        let myself = self.clone();\n        queue_effect(move || {\n            myself.eval(create_element_in_head(\"link\", &props.attributes(), None));\n        });\n    }\n}\n"
  },
  {
    "path": "packages/liveview/src/element.rs",
    "content": "use dioxus_core::ElementId;\nuse dioxus_html::{\n    geometry::{PixelsRect, PixelsSize, PixelsVector2D},\n    MountedResult, RenderedElementBacking,\n};\n\nuse crate::query::QueryEngine;\n\n/// A mounted element passed to onmounted events\n#[derive(Clone)]\npub struct LiveviewElement {\n    id: ElementId,\n    query: QueryEngine,\n}\n\nimpl LiveviewElement {\n    pub(crate) fn new(id: ElementId, query: QueryEngine) -> Self {\n        Self { id, query }\n    }\n}\n\nmacro_rules! scripted_getter {\n    ($meth_name:ident, $script:literal, $output_type:path) => {\n        fn $meth_name(\n            &self,\n        ) -> std::pin::Pin<\n            Box<dyn futures_util::Future<Output = dioxus_html::MountedResult<$output_type>>>,\n        > {\n            let script = format!($script, id = self.id.0);\n\n            let fut = self\n                .query\n                .new_query::<Option<$output_type>>(&script)\n                .resolve();\n            Box::pin(async move {\n                match fut.await {\n                    Ok(Some(res)) => Ok(res),\n                    Ok(None) => MountedResult::Err(dioxus_html::MountedError::OperationFailed(\n                        Box::new(DesktopQueryError::FailedToQuery),\n                    )),\n                    Err(err) => MountedResult::Err(dioxus_html::MountedError::OperationFailed(\n                        Box::new(err),\n                    )),\n                }\n            })\n        }\n    };\n}\n\nimpl RenderedElementBacking for LiveviewElement {\n    fn as_any(&self) -> &dyn std::any::Any {\n        self\n    }\n\n    scripted_getter!(\n        get_scroll_offset,\n        \"return [window.interpreter.getScrollLeft({id}), window.interpreter.getScrollTop({id})]\",\n        PixelsVector2D\n    );\n\n    scripted_getter!(\n        get_scroll_size,\n        \"return [window.interpreter.getScrollWidth({id}), window.interpreter.getScrollHeight({id})]\",\n        PixelsSize\n    );\n\n    scripted_getter!(\n        get_client_rect,\n        \"return window.interpreter.getClientRect({id});\",\n        PixelsRect\n    );\n\n    fn scroll_to(\n        &self,\n        options: dioxus_html::ScrollToOptions,\n    ) -> std::pin::Pin<Box<dyn futures_util::Future<Output = dioxus_html::MountedResult<()>>>> {\n        let script = format!(\n            \"return window.interpreter.scrollTo({}, {});\",\n            self.id.0,\n            serde_json::to_string(&options).expect(\"Failed to serialize ScrollToOptions\")\n        );\n\n        let fut = self.query.new_query::<bool>(&script).resolve();\n        Box::pin(async move {\n            match fut.await {\n                Ok(true) => Ok(()),\n                Ok(false) => MountedResult::Err(dioxus_html::MountedError::OperationFailed(\n                    Box::new(DesktopQueryError::FailedToQuery),\n                )),\n                Err(err) => {\n                    MountedResult::Err(dioxus_html::MountedError::OperationFailed(Box::new(err)))\n                }\n            }\n        })\n    }\n\n    fn scroll(\n        &self,\n        coordinates: PixelsVector2D,\n        behavior: dioxus_html::ScrollBehavior,\n    ) -> std::pin::Pin<Box<dyn std::future::Future<Output = MountedResult<()>>>> {\n        let script = format!(\n            \"return window.interpreter.scroll({}, {}, {}, {});\",\n            self.id.0,\n            coordinates.x,\n            coordinates.y,\n            serde_json::to_string(&behavior).expect(\"Failed to serialize ScrollBehavior\")\n        );\n\n        let fut = self.query.new_query::<bool>(&script).resolve();\n        Box::pin(async move {\n            match fut.await {\n                Ok(true) => Ok(()),\n                Ok(false) => MountedResult::Err(dioxus_html::MountedError::OperationFailed(\n                    Box::new(DesktopQueryError::FailedToQuery),\n                )),\n                Err(err) => {\n                    MountedResult::Err(dioxus_html::MountedError::OperationFailed(Box::new(err)))\n                }\n            }\n        })\n    }\n\n    fn set_focus(\n        &self,\n        focus: bool,\n    ) -> std::pin::Pin<Box<dyn futures_util::Future<Output = dioxus_html::MountedResult<()>>>> {\n        let script = format!(\n            \"return window.interpreter.setFocus({}, {});\",\n            self.id.0, focus\n        );\n\n        let fut = self.query.new_query::<bool>(&script).resolve();\n\n        Box::pin(async move {\n            match fut.await {\n                Ok(true) => Ok(()),\n                Ok(false) => MountedResult::Err(dioxus_html::MountedError::OperationFailed(\n                    Box::new(DesktopQueryError::FailedToQuery),\n                )),\n                Err(err) => {\n                    MountedResult::Err(dioxus_html::MountedError::OperationFailed(Box::new(err)))\n                }\n            }\n        })\n    }\n}\n\n#[derive(Debug)]\nenum DesktopQueryError {\n    FailedToQuery,\n}\n\nimpl std::fmt::Display for DesktopQueryError {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        match self {\n            DesktopQueryError::FailedToQuery => write!(f, \"Failed to query the element\"),\n        }\n    }\n}\n\nimpl std::error::Error for DesktopQueryError {}\n"
  },
  {
    "path": "packages/liveview/src/events.rs",
    "content": "//! Convert a serialized event to an event trigger\n\nuse dioxus_html::*;\n\nuse crate::element::LiveviewElement;\n\npub(crate) struct SerializedHtmlEventConverter;\n\nimpl HtmlEventConverter for SerializedHtmlEventConverter {\n    fn convert_animation_data(&self, event: &PlatformEventData) -> AnimationData {\n        event\n            .downcast::<SerializedAnimationData>()\n            .cloned()\n            .unwrap()\n            .into()\n    }\n\n    fn convert_cancel_data(&self, event: &PlatformEventData) -> CancelData {\n        event\n            .downcast::<SerializedCancelData>()\n            .cloned()\n            .unwrap()\n            .into()\n    }\n\n    fn convert_clipboard_data(&self, event: &PlatformEventData) -> ClipboardData {\n        event\n            .downcast::<SerializedClipboardData>()\n            .cloned()\n            .unwrap()\n            .into()\n    }\n\n    fn convert_composition_data(&self, event: &PlatformEventData) -> CompositionData {\n        event\n            .downcast::<SerializedCompositionData>()\n            .cloned()\n            .unwrap()\n            .into()\n    }\n\n    fn convert_drag_data(&self, event: &PlatformEventData) -> DragData {\n        event\n            .downcast::<SerializedDragData>()\n            .cloned()\n            .unwrap()\n            .into()\n    }\n\n    fn convert_focus_data(&self, event: &PlatformEventData) -> FocusData {\n        event\n            .downcast::<SerializedFocusData>()\n            .cloned()\n            .unwrap()\n            .into()\n    }\n\n    fn convert_form_data(&self, event: &PlatformEventData) -> FormData {\n        event\n            .downcast::<SerializedFormData>()\n            .cloned()\n            .unwrap()\n            .into()\n    }\n\n    fn convert_image_data(&self, event: &PlatformEventData) -> ImageData {\n        event\n            .downcast::<SerializedImageData>()\n            .cloned()\n            .unwrap()\n            .into()\n    }\n\n    fn convert_keyboard_data(&self, event: &PlatformEventData) -> KeyboardData {\n        event\n            .downcast::<SerializedKeyboardData>()\n            .cloned()\n            .unwrap()\n            .into()\n    }\n\n    fn convert_media_data(&self, event: &PlatformEventData) -> MediaData {\n        event\n            .downcast::<SerializedMediaData>()\n            .cloned()\n            .unwrap()\n            .into()\n    }\n\n    fn convert_mounted_data(&self, event: &PlatformEventData) -> MountedData {\n        event.downcast::<LiveviewElement>().cloned().unwrap().into()\n    }\n\n    fn convert_mouse_data(&self, event: &PlatformEventData) -> MouseData {\n        event\n            .downcast::<SerializedMouseData>()\n            .cloned()\n            .unwrap()\n            .into()\n    }\n\n    fn convert_pointer_data(&self, event: &PlatformEventData) -> PointerData {\n        event\n            .downcast::<SerializedPointerData>()\n            .cloned()\n            .unwrap()\n            .into()\n    }\n\n    fn convert_resize_data(&self, event: &PlatformEventData) -> ResizeData {\n        event\n            .downcast::<SerializedResizeData>()\n            .cloned()\n            .unwrap()\n            .into()\n    }\n\n    fn convert_scroll_data(&self, event: &PlatformEventData) -> ScrollData {\n        event\n            .downcast::<SerializedScrollData>()\n            .cloned()\n            .unwrap()\n            .into()\n    }\n\n    fn convert_selection_data(&self, event: &PlatformEventData) -> SelectionData {\n        event\n            .downcast::<SerializedSelectionData>()\n            .cloned()\n            .unwrap()\n            .into()\n    }\n\n    fn convert_toggle_data(&self, event: &PlatformEventData) -> ToggleData {\n        event\n            .downcast::<SerializedToggleData>()\n            .cloned()\n            .unwrap()\n            .into()\n    }\n\n    fn convert_touch_data(&self, event: &PlatformEventData) -> TouchData {\n        event\n            .downcast::<SerializedTouchData>()\n            .cloned()\n            .unwrap()\n            .into()\n    }\n\n    fn convert_transition_data(&self, event: &PlatformEventData) -> TransitionData {\n        event\n            .downcast::<SerializedTransitionData>()\n            .cloned()\n            .unwrap()\n            .into()\n    }\n\n    fn convert_visible_data(&self, event: &PlatformEventData) -> VisibleData {\n        event\n            .downcast::<SerializedVisibleData>()\n            .cloned()\n            .unwrap()\n            .into()\n    }\n\n    fn convert_wheel_data(&self, event: &PlatformEventData) -> WheelData {\n        event\n            .downcast::<SerializedWheelData>()\n            .cloned()\n            .unwrap()\n            .into()\n    }\n}\n"
  },
  {
    "path": "packages/liveview/src/history.rs",
    "content": "use dioxus_core::spawn;\nuse dioxus_document::Eval;\nuse dioxus_history::History;\nuse serde::{Deserialize, Serialize};\nuse std::rc::Rc;\nuse std::sync::{Mutex, RwLock};\nuse std::{collections::BTreeMap, sync::Arc};\n\n/// A [`History`] that evaluates history through JS.\npub(crate) struct LiveviewHistory {\n    action_tx: tokio::sync::mpsc::UnboundedSender<Action>,\n    timeline: Arc<Mutex<Timeline>>,\n    updater_callback: Arc<RwLock<Arc<dyn Fn() + Send + Sync>>>,\n}\n\nstruct Timeline {\n    current_index: usize,\n    routes: BTreeMap<usize, String>,\n}\n\n#[derive(Serialize, Deserialize, Debug)]\nstruct State {\n    index: usize,\n}\n\n#[derive(Serialize, Deserialize, Debug)]\nstruct Session {\n    #[serde(with = \"routes\")]\n    routes: BTreeMap<usize, String>,\n    last_visited: usize,\n}\n\nenum Action {\n    GoBack,\n    GoForward,\n    Push(String),\n    Replace(String),\n    External(String),\n}\n\nimpl Timeline {\n    fn new(initial_path: String) -> Self {\n        Self {\n            current_index: 0,\n            routes: BTreeMap::from([(0, initial_path)]),\n        }\n    }\n\n    fn init(\n        &mut self,\n        route: String,\n        state: Option<State>,\n        session: Option<Session>,\n        depth: usize,\n    ) -> State {\n        if let Some(session) = session {\n            self.routes = session.routes;\n            if state.is_none() {\n                // top of stack\n                let last_visited = session.last_visited;\n                self.routes.retain(|&lhs, _| lhs <= last_visited);\n            }\n        };\n        let state = match state {\n            Some(state) => {\n                self.current_index = state.index;\n                state\n            }\n            None => {\n                let index = depth - 1;\n                self.current_index = index;\n                State { index }\n            }\n        };\n        self.routes.insert(state.index, route);\n        state\n    }\n\n    fn update(&mut self, route: String, state: Option<State>) -> State {\n        if let Some(state) = state {\n            self.current_index = state.index;\n            self.routes.insert(self.current_index, route);\n            state\n        } else {\n            self.push(route)\n        }\n    }\n\n    fn push(&mut self, route: String) -> State {\n        // top of stack\n        let index = self.current_index + 1;\n        self.current_index = index;\n        self.routes.insert(index, route);\n        self.routes.retain(|&rhs, _| index >= rhs);\n        State {\n            index: self.current_index,\n        }\n    }\n\n    fn replace(&mut self, route: String) -> State {\n        self.routes.insert(self.current_index, route);\n        State {\n            index: self.current_index,\n        }\n    }\n\n    fn current_route(&self) -> &str {\n        &self.routes[&self.current_index]\n    }\n\n    fn session(&self) -> Session {\n        Session {\n            routes: self.routes.clone(),\n            last_visited: self.current_index,\n        }\n    }\n}\n\nimpl LiveviewHistory {\n    /// Create a [`LiveviewHistory`] in the given scope.\n    /// When using a [`LiveviewHistory`] in combination with use_eval, history must be untampered with.\n    ///\n    /// # Panics\n    ///\n    /// Panics if the function is not called in a dioxus runtime with a Liveview context.\n    pub(crate) fn new(eval: Rc<dyn Fn(&str) -> Eval>) -> Self {\n        Self::new_with_initial_path(\n            \"/\".parse().unwrap_or_else(|err| {\n                panic!(\"index route does not exist:\\n{}\\n use LiveviewHistory::new_with_initial_path to set a custom path\", err)\n            }),\n            eval\n        )\n    }\n\n    /// Create a [`LiveviewHistory`] in the given scope, starting at `initial_path`.\n    /// When using a [`LiveviewHistory`] in combination with use_eval, history must be untampered with.\n    ///\n    /// # Panics\n    ///\n    /// Panics if the function is not called in a dioxus runtime with a Liveview context.\n    fn new_with_initial_path(initial_path: String, eval: Rc<dyn Fn(&str) -> Eval>) -> Self {\n        let (action_tx, mut action_rx) = tokio::sync::mpsc::unbounded_channel::<Action>();\n\n        let timeline = Arc::new(Mutex::new(Timeline::new(initial_path)));\n        let updater_callback: Arc<RwLock<Arc<dyn Fn() + Send + Sync>>> =\n            Arc::new(RwLock::new(Arc::new(|| {})));\n\n        // Listen to server actions\n        spawn({\n            let timeline = timeline.clone();\n            let create_eval = eval.clone();\n            async move {\n                loop {\n                    let eval = action_rx.recv().await.expect(\"sender to exist\");\n\n                    let _ = match eval {\n                        Action::GoBack => create_eval(\n                            r#\"\n                                // this triggers a PopState event\n                                history.back();\n                            \"#,\n                        ),\n                        Action::GoForward => create_eval(\n                            r#\"\n                                // this triggers a PopState event\n                                history.forward();\n                            \"#,\n                        ),\n                        Action::Push(route) => {\n                            let mut timeline = timeline.lock().expect(\"unpoisoned mutex\");\n                            let state = timeline.push(route.clone());\n                            let state = serde_json::to_string(&state).expect(\"serializable state\");\n                            let session = serde_json::to_string(&timeline.session())\n                                .expect(\"serializable session\");\n                            create_eval(&format!(\n                                r#\"\n                                // this does not trigger a PopState event\n                                history.pushState({state}, \"\", \"{route}\");\n                                sessionStorage.setItem(\"liveview\", '{session}');\n                            \"#\n                            ))\n                        }\n                        Action::Replace(route) => {\n                            let mut timeline = timeline.lock().expect(\"unpoisoned mutex\");\n                            let state = timeline.replace(route.clone());\n                            let state = serde_json::to_string(&state).expect(\"serializable state\");\n                            let session = serde_json::to_string(&timeline.session())\n                                .expect(\"serializable session\");\n                            create_eval(&format!(\n                                r#\"\n                                // this does not trigger a PopState event\n                                history.replaceState({state}, \"\", \"{route}\");\n                                sessionStorage.setItem(\"liveview\", '{session}');\n                            \"#\n                            ))\n                        }\n                        Action::External(url) => create_eval(&format!(\n                            r#\"\n                                location.href = \"{url}\";\n                            \"#\n                        )),\n                    };\n                }\n            }\n        });\n\n        // Listen to browser actions\n        spawn({\n            let updater = updater_callback.clone();\n            let timeline = timeline.clone();\n            let create_eval = eval.clone();\n            async move {\n                let mut popstate_eval = {\n                    let init_eval = create_eval(\n                        r#\"\n                        return [\n                          document.location.pathname + \"?\" + document.location.search + \"\\#\" + document.location.hash,\n                          history.state,\n                          JSON.parse(sessionStorage.getItem(\"liveview\")),\n                          history.length,\n                        ];\n                    \"#,\n                    ).await.expect(\"serializable state\");\n                    let (route, state, session, depth) =\n                        serde_json::from_value::<(String, Option<State>, Option<Session>, usize)>(\n                            init_eval,\n                        )\n                        .expect(\"serializable state\");\n                    let mut timeline = timeline.lock().expect(\"unpoisoned mutex\");\n                    let state = timeline.init(route.clone(), state, session, depth);\n                    let state = serde_json::to_string(&state).expect(\"serializable state\");\n                    let session =\n                        serde_json::to_string(&timeline.session()).expect(\"serializable session\");\n\n                    // Call the updater callback\n                    (updater.read().unwrap())();\n\n                    create_eval(&format!(\n                        r#\"\n                        // this does not trigger a PopState event\n                        history.replaceState({state}, \"\", \"{route}\");\n                        sessionStorage.setItem(\"liveview\", '{session}');\n\n                        window.addEventListener(\"popstate\", (event) => {{\n                          dioxus.send([\n                            document.location.pathname + \"?\" + document.location.search + \"\\#\" + document.location.hash,\n                            event.state,\n                          ]);\n                        }});\n                    \"#\n                    ))\n                };\n\n                loop {\n                    let event = match popstate_eval.recv().await {\n                        Ok(event) => event,\n                        Err(_) => continue,\n                    };\n                    let (route, state) = serde_json::from_value::<(String, Option<State>)>(event)\n                        .expect(\"serializable state\");\n                    let mut timeline = timeline.lock().expect(\"unpoisoned mutex\");\n                    let state = timeline.update(route.clone(), state);\n                    let state = serde_json::to_string(&state).expect(\"serializable state\");\n                    let session =\n                        serde_json::to_string(&timeline.session()).expect(\"serializable session\");\n\n                    let _ = create_eval(&format!(\n                        r#\"\n                        // this does not trigger a PopState event\n                        history.replaceState({state}, \"\", \"{route}\");\n                        sessionStorage.setItem(\"liveview\", '{session}');\n                    \"#\n                    ));\n\n                    // Call the updater callback\n                    (updater.read().unwrap())();\n                }\n            }\n        });\n\n        Self {\n            action_tx,\n            timeline,\n            updater_callback,\n        }\n    }\n}\n\nimpl History for LiveviewHistory {\n    fn go_back(&self) {\n        let _ = self.action_tx.send(Action::GoBack);\n    }\n\n    fn go_forward(&self) {\n        let _ = self.action_tx.send(Action::GoForward);\n    }\n\n    fn push(&self, route: String) {\n        let _ = self.action_tx.send(Action::Push(route));\n    }\n\n    fn replace(&self, route: String) {\n        let _ = self.action_tx.send(Action::Replace(route));\n    }\n\n    fn external(&self, url: String) -> bool {\n        let _ = self.action_tx.send(Action::External(url));\n        true\n    }\n\n    fn current_route(&self) -> String {\n        let timeline = self.timeline.lock().expect(\"unpoisoned mutex\");\n        timeline.current_route().to_string()\n    }\n\n    fn can_go_back(&self) -> bool {\n        let timeline = self.timeline.lock().expect(\"unpoisoned mutex\");\n        // Check if the one before is contiguous (i.e., not an external page)\n        let visited_indices: Vec<usize> = timeline.routes.keys().cloned().collect();\n        visited_indices\n            .iter()\n            .position(|&rhs| timeline.current_index == rhs)\n            .is_some_and(|index| {\n                index > 0 && visited_indices[index - 1] == timeline.current_index - 1\n            })\n    }\n\n    fn can_go_forward(&self) -> bool {\n        let timeline = self.timeline.lock().expect(\"unpoisoned mutex\");\n        // Check if the one after is contiguous (i.e., not an external page)\n        let visited_indices: Vec<usize> = timeline.routes.keys().cloned().collect();\n        visited_indices\n            .iter()\n            .rposition(|&rhs| timeline.current_index == rhs)\n            .is_some_and(|index| {\n                index < visited_indices.len() - 1\n                    && visited_indices[index + 1] == timeline.current_index + 1\n            })\n    }\n\n    fn updater(&self, callback: Arc<dyn Fn() + Send + Sync>) {\n        let mut updater_callback = self.updater_callback.write().unwrap();\n        *updater_callback = callback;\n    }\n\n    fn include_prevent_default(&self) -> bool {\n        true\n    }\n}\n\nmod routes {\n    use serde::de::{MapAccess, Visitor};\n    use serde::{ser::SerializeMap, Deserializer, Serializer};\n    use std::collections::BTreeMap;\n\n    pub fn serialize<S>(routes: &BTreeMap<usize, String>, serializer: S) -> Result<S::Ok, S::Error>\n    where\n        S: Serializer,\n    {\n        let mut map = serializer.serialize_map(Some(routes.len()))?;\n        for (index, route) in routes.iter() {\n            map.serialize_entry(&index.to_string(), &route.to_string())?;\n        }\n        map.end()\n    }\n\n    pub fn deserialize<'de, D>(deserializer: D) -> Result<BTreeMap<usize, String>, D::Error>\n    where\n        D: Deserializer<'de>,\n    {\n        struct BTreeMapVisitor {}\n\n        impl<'de> Visitor<'de> for BTreeMapVisitor {\n            type Value = BTreeMap<usize, String>;\n\n            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {\n                formatter.write_str(\"a map with indices and routable values\")\n            }\n\n            fn visit_map<M>(self, mut map: M) -> Result<Self::Value, M::Error>\n            where\n                M: MapAccess<'de>,\n            {\n                let mut routes = BTreeMap::new();\n                while let Some((index, route)) = map.next_entry::<String, String>()? {\n                    let index = index.parse::<usize>().map_err(serde::de::Error::custom)?;\n                    routes.insert(index, route);\n                }\n                Ok(routes)\n            }\n        }\n\n        deserializer.deserialize_map(BTreeMapVisitor {})\n    }\n}\n"
  },
  {
    "path": "packages/liveview/src/index.html",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <title>Dioxus app</title>\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n  </head>\n  <body>\n    <div id=\"main\"></div>\n    <script>\n      import(\"./index.js\").then(function (module) {\n        module.main();\n      });\n    </script>\n  </body>\n</html>\n"
  },
  {
    "path": "packages/liveview/src/launch.rs",
    "content": "use dioxus_core::*;\nuse std::any::Any;\n\npub type Config = crate::Config<axum::Router>;\n\n/// Launches the WebView and runs the event loop, with configuration and root props.\npub fn launch(\n    root: fn() -> Element,\n    contexts: Vec<Box<dyn Fn() -> Box<dyn Any> + Send + Sync>>,\n    platform_configs: Vec<Box<dyn Any>>,\n) -> ! {\n    #[cfg(feature = \"multi-thread\")]\n    let mut builder = tokio::runtime::Builder::new_multi_thread();\n    #[cfg(not(feature = \"multi-thread\"))]\n    let mut builder = tokio::runtime::Builder::new_current_thread();\n\n    let config = platform_configs\n        .into_iter()\n        .find_map(|cfg| cfg.downcast::<Config>().ok().map(|cfg| *cfg))\n        .unwrap_or_default();\n\n    builder.enable_all().build().unwrap().block_on(async move {\n        config\n            .with_virtual_dom(move || {\n                let mut virtual_dom = VirtualDom::new(root);\n\n                for context in &contexts {\n                    virtual_dom.insert_any_root_context(context());\n                }\n\n                virtual_dom\n            })\n            .launch()\n            .await;\n    });\n\n    panic!(\"Launching a liveview app should never return\")\n}\n"
  },
  {
    "path": "packages/liveview/src/lib.rs",
    "content": "#![doc = include_str!(\"../README.md\")]\n#![doc(html_logo_url = \"https://avatars.githubusercontent.com/u/79236386\")]\n#![doc(html_favicon_url = \"https://avatars.githubusercontent.com/u/79236386\")]\n\nmod adapters;\n#[allow(unused_imports)]\npub use adapters::*;\n\nmod element;\npub mod pool;\nmod query;\nuse dioxus_interpreter_js::NATIVE_JS;\nuse futures_util::{SinkExt, StreamExt};\npub use pool::*;\nmod config;\nmod document;\nmod events;\nmod history;\npub use config::*;\n#[cfg(feature = \"axum\")]\npub mod launch;\n\npub trait WebsocketTx: SinkExt<String, Error = LiveViewError> {}\nimpl<T> WebsocketTx for T where T: SinkExt<String, Error = LiveViewError> {}\n\npub trait WebsocketRx: StreamExt<Item = Result<String, LiveViewError>> {}\nimpl<T> WebsocketRx for T where T: StreamExt<Item = Result<String, LiveViewError>> {}\n\n#[derive(Debug, thiserror::Error)]\n#[non_exhaustive]\npub enum LiveViewError {\n    #[error(\"Sending to client error\")]\n    SendingFailed,\n}\n\nfn handle_edits_code() -> String {\n    use dioxus_interpreter_js::unified_bindings::SLEDGEHAMMER_JS;\n\n    let serialize_file_uploads = r#\"if (\n        target.tagName === \"INPUT\" &&\n        (event.type === \"change\" || event.type === \"input\")\n      ) {\n        const type = target.getAttribute(\"type\");\n        if (type === \"file\") {\n          async function read_files() {\n            const files = target.files;\n            const file_contents = {};\n\n            for (let i = 0; i < files.length; i++) {\n              const file = files[i];\n\n              file_contents[file.name] = Array.from(\n                new Uint8Array(await file.arrayBuffer())\n              );\n            }\n            let file_engine = {\n              files: file_contents,\n            };\n            contents.files = file_engine;\n\n            if (realId === null) {\n              return;\n            }\n            const message = window.interpreter.sendSerializedEvent({\n              name: name,\n              element: parseInt(realId),\n              data: contents,\n              bubbles,\n            });\n            window.ipc.postMessage(message);\n          }\n          read_files();\n          return;\n        }\n      }\"#;\n    let mut interpreter = format!(\n        r#\"\n    // Bring the sledgehammer code\n    {SLEDGEHAMMER_JS}\n\n    // And then extend it with our native bindings\n    {NATIVE_JS}\n    \"#\n    )\n    .replace(\"/*POST_EVENT_SERIALIZATION*/\", serialize_file_uploads)\n    .replace(\"export\", \"\");\n    while let Some(import_start) = interpreter.find(\"import\") {\n        let import_end = interpreter[import_start..]\n            .find([';', '\\n'])\n            .map(|i| i + import_start)\n            .unwrap_or_else(|| interpreter.len());\n        interpreter.replace_range(import_start..import_end, \"\");\n    }\n    let main_js = include_str!(\"./main.js\");\n    let js = format!(\"{interpreter}\\n{main_js}\");\n    js\n}\n\n/// This script that gets injected into your app connects this page to the websocket endpoint\n///\n/// Once the endpoint is connected, it will send the initial state of the app, and then start\n/// processing user events and returning edits to the liveview instance.\n///\n/// You can pass a relative path prefixed with \"/\", or enter a full URL including the protocol\n/// (`ws:` or `wss:`) as an argument.\n///\n/// If you enter a relative path, the web client automatically prefixes the host address in\n/// `window.location` when creating a web socket to LiveView.\n///\n/// ```rust\n/// use dioxus_liveview::interpreter_glue;\n///\n/// // Creates websocket connection to same host as current page\n/// interpreter_glue(\"/api/liveview\");\n///\n/// // Creates websocket connection to specified url\n/// interpreter_glue(\"ws://localhost:8080/api/liveview\");\n/// ```\npub fn interpreter_glue(url_or_path: &str) -> String {\n    // If the url starts with a `/`, generate glue which reuses current host\n    let get_ws_url = if url_or_path.starts_with('/') {\n        r#\"\n  let loc = window.location;\n  let new_url = \"\";\n  if (loc.protocol === \"https:\") {{\n      new_url = \"wss:\";\n  }} else {{\n      new_url = \"ws:\";\n  }}\n  new_url += \"//\" + loc.host + path;\n  return new_url;\n      \"#\n    } else {\n        \"return path;\"\n    };\n\n    let handle_edits = handle_edits_code();\n\n    format!(\n        r#\"\n<script>\n    function __dioxusGetWsUrl(path) {{\n      {get_ws_url}\n    }}\n\n    var WS_ADDR = __dioxusGetWsUrl(\"{url_or_path}\");\n    {handle_edits}\n</script>\n    \"#\n    )\n}\n"
  },
  {
    "path": "packages/liveview/src/main.js",
    "content": "const intercept_link_redirects = false;\n\nfunction main() {\n  let root = window.document.getElementById(\"main\");\n  if (root != null) {\n    window.ipc = new IPC(root);\n  }\n}\n\nclass IPC {\n  constructor(root) {\n    window.interpreter = new NativeInterpreter();\n    window.interpreter.initialize(root);\n    window.interpreter.liveview = true;\n    window.interpreter.ipc = this;\n    const ws = new WebSocket(WS_ADDR);\n    ws.binaryType = \"arraybuffer\";\n\n    function ping() {\n      ws.send(\"__ping__\");\n    }\n\n    ws.onopen = () => {\n      // we ping every 30 seconds to keep the websocket alive\n      setInterval(ping, 30000);\n    };\n\n    ws.onerror = (err) => {\n      // todo: retry the connection\n    };\n\n    ws.onmessage = (message) => {\n      const u8view = new Uint8Array(message.data);\n      const binaryFrame = u8view[0] == 1;\n      const messageData = message.data.slice(1);\n      // The first byte tells the shim if this is a binary of text frame\n      if (binaryFrame) {\n        // binary frame\n        window.interpreter.run_from_bytes(messageData);\n      } else {\n        // text frame\n        let decoder = new TextDecoder(\"utf-8\");\n\n        // Using decode method to get string output\n        let str = decoder.decode(messageData);\n        // Ignore pongs\n        if (str != \"__pong__\") {\n          const event = JSON.parse(str);\n          switch (event.type) {\n            case \"query\":\n              Function(\"Eval\", `\"use strict\";${event.data};`)();\n              break;\n          }\n        }\n      }\n    };\n\n    this.ws = ws;\n  }\n\n  postMessage(msg) {\n    this.ws.send(msg);\n  }\n}\n\nmain();\n"
  },
  {
    "path": "packages/liveview/src/pool.rs",
    "content": "use crate::{\n    document::init_document,\n    element::LiveviewElement,\n    events::SerializedHtmlEventConverter,\n    query::{QueryEngine, QueryResult},\n    LiveViewError,\n};\n\nuse dioxus_core::{provide_context, Element, Event, ScopeId, VirtualDom};\nuse dioxus_html::{EventData, HtmlEvent, PlatformEventData};\nuse dioxus_interpreter_js::MutationState;\nuse futures_util::{pin_mut, SinkExt, StreamExt};\nuse serde::Serialize;\nuse std::{any::Any, rc::Rc};\nuse tokio_util::task::LocalPoolHandle;\n\n#[derive(Clone)]\npub struct LiveViewPool {\n    pub(crate) pool: LocalPoolHandle,\n}\n\nimpl Default for LiveViewPool {\n    fn default() -> Self {\n        Self::new()\n    }\n}\n\nimpl LiveViewPool {\n    pub fn new() -> Self {\n        // Set the event converter\n        dioxus_html::set_event_converter(Box::new(SerializedHtmlEventConverter));\n\n        LiveViewPool {\n            pool: LocalPoolHandle::new(\n                std::thread::available_parallelism()\n                    .map(usize::from)\n                    .unwrap_or(1),\n            ),\n        }\n    }\n\n    pub async fn launch(\n        &self,\n        ws: impl LiveViewSocket,\n        app: fn() -> Element,\n    ) -> Result<(), LiveViewError> {\n        self.launch_with_props(ws, |app| app(), app).await\n    }\n\n    pub async fn launch_with_props<T: Clone + Send + 'static>(\n        &self,\n        ws: impl LiveViewSocket,\n        app: fn(T) -> Element,\n        props: T,\n    ) -> Result<(), LiveViewError> {\n        self.launch_virtualdom(ws, move || VirtualDom::new_with_props(app, props))\n            .await\n    }\n\n    pub async fn launch_virtualdom<F: FnOnce() -> VirtualDom + Send + 'static>(\n        &self,\n        ws: impl LiveViewSocket,\n        make_app: F,\n    ) -> Result<(), LiveViewError> {\n        match self.pool.spawn_pinned(move || run(make_app(), ws)).await {\n            Ok(Ok(_)) => Ok(()),\n            Ok(Err(e)) => Err(e),\n            Err(_) => Err(LiveViewError::SendingFailed),\n        }\n    }\n}\n\n/// A LiveViewSocket is a Sink and Stream of Strings that Dioxus uses to communicate with the client\n///\n/// Most websockets from most HTTP frameworks can be converted into a LiveViewSocket using the appropriate adapter.\n///\n/// You can also convert your own socket into a LiveViewSocket by implementing this trait. This trait is an auto trait,\n/// meaning that as long as your type implements Stream and Sink, you can use it as a LiveViewSocket.\n///\n/// For example, the axum implementation is a really small transform:\n///\n/// ```rust, ignore\n/// pub fn axum_socket(ws: WebSocket) -> impl LiveViewSocket {\n///     ws.map(transform_rx)\n///         .with(transform_tx)\n///         .sink_map_err(|_| LiveViewError::SendingFailed)\n/// }\n///\n/// fn transform_rx(message: Result<Message, axum::Error>) -> Result<String, LiveViewError> {\n///     message\n///         .map_err(|_| LiveViewError::SendingFailed)?\n///         .into_text()\n///         .map_err(|_| LiveViewError::SendingFailed)\n/// }\n///\n/// async fn transform_tx(message: String) -> Result<Message, axum::Error> {\n///     Ok(Message::Text(message))\n/// }\n/// ```\npub trait LiveViewSocket:\n    SinkExt<Vec<u8>, Error = LiveViewError>\n    + StreamExt<Item = Result<Vec<u8>, LiveViewError>>\n    + Send\n    + 'static\n{\n}\n\nimpl<S> LiveViewSocket for S where\n    S: SinkExt<Vec<u8>, Error = LiveViewError>\n        + StreamExt<Item = Result<Vec<u8>, LiveViewError>>\n        + Send\n        + 'static\n{\n}\n\n/// The primary event loop for the VirtualDom waiting for user input\n///\n/// This function makes it easy to integrate Dioxus LiveView with any socket-based framework.\n///\n/// As long as your framework can provide a Sink and Stream of Bytes, you can use this function.\n///\n/// You might need to transform the error types of the web backend into the LiveView error type.\npub async fn run(mut vdom: VirtualDom, ws: impl LiveViewSocket) -> Result<(), LiveViewError> {\n    #[cfg(all(feature = \"devtools\", debug_assertions))]\n    let mut hot_reload_rx = {\n        let (tx, rx) = tokio::sync::mpsc::unbounded_channel();\n        dioxus_devtools::connect(move |template| _ = tx.send(template));\n        rx\n    };\n\n    let mut mutations = MutationState::default();\n\n    // Create the a proxy for query engine\n    let (query_tx, mut query_rx) = tokio::sync::mpsc::unbounded_channel();\n    let query_engine = QueryEngine::new(query_tx);\n    vdom.runtime().in_scope(ScopeId::ROOT, || {\n        provide_context(query_engine.clone());\n        init_document();\n    });\n\n    // pin the futures so we can use select!\n    pin_mut!(ws);\n\n    if let Some(edits) = {\n        vdom.rebuild(&mut mutations);\n        take_edits(&mut mutations)\n    } {\n        // send the initial render to the client\n        ws.send(edits).await?;\n    }\n\n    // desktop uses this wrapper struct thing around the actual event itself\n    // this is sorta driven by tao/wry\n    #[derive(serde::Deserialize, Debug)]\n    #[serde(tag = \"method\", content = \"params\")]\n    enum IpcMessage {\n        #[serde(rename = \"user_event\")]\n        Event(Box<HtmlEvent>),\n        #[serde(rename = \"query\")]\n        Query(QueryResult),\n    }\n\n    loop {\n        #[cfg(all(feature = \"devtools\", debug_assertions))]\n        let hot_reload_wait = hot_reload_rx.recv();\n        #[cfg(not(all(feature = \"devtools\", debug_assertions)))]\n        let hot_reload_wait: std::future::Pending<Option<()>> = std::future::pending();\n\n        tokio::select! {\n            // poll any futures or suspense\n            _ = vdom.wait_for_work() => {}\n\n            evt = ws.next() => {\n                match evt.as_ref().map(|o| o.as_deref()) {\n                    // respond with a pong every ping to keep the websocket alive\n                    Some(Ok(b\"__ping__\")) => {\n                        ws.send(text_frame(\"__pong__\")).await?;\n                    }\n                    Some(Ok(evt)) => {\n                        if let Ok(message) = serde_json::from_str::<IpcMessage>(&String::from_utf8_lossy(evt)) {\n                            match message {\n                                IpcMessage::Event(evt) => {\n                                    // Intercept the mounted event and insert a custom element type\n                                    let event = if let EventData::Mounted = &evt.data {\n                                        let element = LiveviewElement::new(evt.element, query_engine.clone());\n                                        Event::new(\n                                            Rc::new(PlatformEventData::new(Box::new(element))) as Rc<dyn Any>,\n                                            evt.bubbles,\n                                        )\n                                    } else {\n                                        Event::new(\n                                            evt.data.into_any(),\n                                            evt.bubbles,\n                                        )\n                                    };\n                                    vdom.runtime().handle_event(\n                                        &evt.name,\n                                        event,\n                                        evt.element,\n                                    );\n                                }\n                                IpcMessage::Query(result) => {\n                                    query_engine.send(result);\n                                },\n                            }\n                        }\n                    }\n                    // log this I guess? when would we get an error here?\n                    Some(Err(_e)) => {}\n                    None => return Ok(()),\n                }\n            }\n\n            // handle any new queries\n            Some(query) = query_rx.recv() => {\n                ws.send(text_frame(&serde_json::to_string(&ClientUpdate::Query(query)).unwrap())).await?;\n            }\n\n            Some(msg) = hot_reload_wait => {\n                #[cfg(all(feature = \"devtools\", debug_assertions))]\n                match msg {\n                    dioxus_devtools::DevserverMsg::HotReload(msg)=> {\n                        dioxus_devtools::apply_changes(&vdom, &msg);\n                    }\n                    dioxus_devtools::DevserverMsg::Shutdown => {\n                        std::process::exit(0);\n                    },\n                    dioxus_devtools::DevserverMsg::FullReloadCommand\n                    | dioxus_devtools::DevserverMsg::FullReloadStart\n                    | dioxus_devtools::DevserverMsg::FullReloadFailed => {\n                        // usually only web gets this message - what are we supposed to do?\n                        // Maybe we could just binary patch ourselves in place without losing window state?\n                    },\n                    _ => {}\n                }\n                #[cfg(not(all(feature = \"devtools\", debug_assertions)))]\n                let () = msg;\n            }\n        }\n\n        // render the vdom\n        vdom.render_immediate(&mut mutations);\n\n        if let Some(edits) = take_edits(&mut mutations) {\n            ws.send(edits).await?;\n        }\n    }\n}\n\nfn text_frame(text: &str) -> Vec<u8> {\n    let mut bytes = vec![0];\n    bytes.extend(text.as_bytes());\n    bytes\n}\n\nfn take_edits(mutations: &mut MutationState) -> Option<Vec<u8>> {\n    // Add an extra one at the beginning to tell the shim this is a binary frame\n    let mut bytes = vec![1];\n    mutations.write_memory_into(&mut bytes);\n    (bytes.len() > 1).then_some(bytes)\n}\n\n#[derive(Serialize)]\n#[serde(tag = \"type\", content = \"data\")]\nenum ClientUpdate {\n    #[serde(rename = \"query\")]\n    Query(String),\n}\n"
  },
  {
    "path": "packages/liveview/src/query.rs",
    "content": "use std::{cell::RefCell, rc::Rc};\n\nuse futures_util::FutureExt;\nuse generational_box::{Owner, UnsyncStorage};\nuse serde::{de::DeserializeOwned, Deserialize};\nuse serde_json::Value;\nuse slab::Slab;\nuse thiserror::Error;\nuse tokio::sync::broadcast::error::RecvError;\n\nconst DIOXUS_CODE: &str = r#\"\nlet dioxus = {\n    recv: function () {\n        return new Promise((resolve, _reject) => {\n            // Ever 50 ms check for new data\n            let timeout = setTimeout(() => {\n                let __msg = null;\n                while (true) {\n                    let __data = _message_queue.shift();\n                    if (__data) {\n                        __msg = __data;\n                        break;\n                    }\n                }\n                clearTimeout(timeout);\n                resolve(__msg);\n            }, 50);\n        });\n    },\n\n    send: function (value) {\n        window.ipc.postMessage(\n            JSON.stringify({\n                \"method\":\"query\",\n                \"params\": {\n                    \"id\": _request_id,\n                    \"data\": value,\n                    \"returned_value\": false\n                }\n            })\n        );\n    }\n}\"#;\n\n/// Tracks what query ids are currently active\npub(crate) struct SharedSlab<T = ()> {\n    pub(crate) slab: Rc<RefCell<Slab<T>>>,\n}\n\nimpl<T> Clone for SharedSlab<T> {\n    fn clone(&self) -> Self {\n        Self {\n            slab: self.slab.clone(),\n        }\n    }\n}\n\nimpl<T> Default for SharedSlab<T> {\n    fn default() -> Self {\n        SharedSlab {\n            slab: Rc::new(RefCell::new(Slab::new())),\n        }\n    }\n}\n\npub(crate) struct QueryEntry {\n    channel_sender: tokio::sync::mpsc::UnboundedSender<Value>,\n    return_sender: Option<tokio::sync::oneshot::Sender<Value>>,\n    pub(crate) owner: Option<Owner<UnsyncStorage>>,\n}\n\nconst QUEUE_NAME: &str = \"__msg_queues\";\n\n/// Handles sending and receiving arbitrary queries from the webview. Queries can be resolved non-sequentially, so we use ids to track them.\n#[derive(Clone)]\npub(crate) struct QueryEngine {\n    pub(crate) active_requests: SharedSlab<QueryEntry>,\n    query_tx: tokio::sync::mpsc::UnboundedSender<String>,\n}\n\nimpl QueryEngine {\n    pub(crate) fn new(query_tx: tokio::sync::mpsc::UnboundedSender<String>) -> Self {\n        Self {\n            active_requests: Default::default(),\n            query_tx,\n        }\n    }\n\n    /// Creates a new query and returns a handle to it. The query will be resolved when the webview returns a result with the same id.\n    pub fn new_query<V: DeserializeOwned>(&self, script: &str) -> Query<V> {\n        let (tx, rx) = tokio::sync::mpsc::unbounded_channel();\n        let (return_tx, return_rx) = tokio::sync::oneshot::channel();\n        let request_id = self.active_requests.slab.borrow_mut().insert(QueryEntry {\n            channel_sender: tx,\n            return_sender: Some(return_tx),\n            owner: None,\n        });\n\n        // start the query\n        // We embed the return of the eval in a function so we can send it back to the main thread\n        if let Err(err) = self.query_tx.send(format!(\n            r#\"(function(){{\n                (async (resolve, _reject) => {{\n                    {DIOXUS_CODE}\n                    if (!window.{QUEUE_NAME}) {{\n                        window.{QUEUE_NAME} = [];\n                    }}\n\n                    let _request_id = {request_id};\n\n                    if (!window.{QUEUE_NAME}[{request_id}]) {{\n                        window.{QUEUE_NAME}[{request_id}] = [];\n                    }}\n                    let _message_queue = window.{QUEUE_NAME}[{request_id}];\n\n                    {script}\n                }})().then((result)=>{{\n                    let returned_value = {{\n                        \"method\":\"query\",\n                        \"params\": {{\n                            \"id\": {request_id},\n                            \"data\": result,\n                            \"returned_value\": true\n                        }}\n                    }};\n                    window.ipc.postMessage(\n                        JSON.stringify(returned_value)\n                    );\n                }})\n            }})();\"#\n        )) {\n            tracing::warn!(\"Query error: {err}\");\n        }\n\n        Query {\n            query_engine: self.clone(),\n            id: request_id,\n            receiver: rx,\n            return_receiver: Some(return_rx),\n            phantom: std::marker::PhantomData,\n        }\n    }\n\n    /// Send a query channel message to the correct query\n    pub fn send(&self, data: QueryResult) {\n        let QueryResult {\n            id,\n            data,\n            returned_value,\n        } = data;\n        let mut slab = self.active_requests.slab.borrow_mut();\n        if let Some(entry) = slab.get_mut(id) {\n            if returned_value {\n                if let Some(sender) = entry.return_sender.take() {\n                    let _ = sender.send(data);\n                }\n            } else {\n                let _ = entry.channel_sender.send(data);\n            }\n        }\n    }\n}\n\npub(crate) struct Query<V: DeserializeOwned> {\n    query_engine: QueryEngine,\n    pub receiver: tokio::sync::mpsc::UnboundedReceiver<Value>,\n    pub return_receiver: Option<tokio::sync::oneshot::Receiver<Value>>,\n    pub id: usize,\n    phantom: std::marker::PhantomData<V>,\n}\n\nimpl<V: DeserializeOwned> Query<V> {\n    /// Resolve the query\n    pub async fn resolve(mut self) -> Result<V, QueryError> {\n        V::deserialize(self.result().await?).map_err(QueryError::Deserialize)\n    }\n\n    /// Send a message to the query\n    pub fn send<S: ToString>(&self, message: S) -> Result<(), QueryError> {\n        let queue_id = self.id;\n\n        let data = message.to_string();\n        let script = format!(\n            r#\"\n            if (!window.{QUEUE_NAME}) {{\n                window.{QUEUE_NAME} = [];\n            }}\n\n            if (!window.{QUEUE_NAME}[{queue_id}]) {{\n                window.{QUEUE_NAME}[{queue_id}] = [];\n            }}\n            window.{QUEUE_NAME}[{queue_id}].push({data});\n            \"#\n        );\n\n        self.query_engine\n            .query_tx\n            .send(script)\n            .map_err(|e| QueryError::Send(e.to_string()))?;\n\n        Ok(())\n    }\n\n    /// Poll the query for a message\n    pub fn poll_recv(\n        &mut self,\n        cx: &mut std::task::Context<'_>,\n    ) -> std::task::Poll<Result<Value, QueryError>> {\n        self.receiver\n            .poll_recv(cx)\n            .map(|result| result.ok_or(QueryError::Recv(RecvError::Closed)))\n    }\n\n    /// Receive the result of the query\n    pub async fn result(&mut self) -> Result<Value, QueryError> {\n        match self.return_receiver.take() {\n            Some(receiver) => receiver\n                .await\n                .map_err(|_| QueryError::Recv(RecvError::Closed)),\n            None => Err(QueryError::Finished),\n        }\n    }\n\n    /// Poll the query for a result\n    pub fn poll_result(\n        &mut self,\n        cx: &mut std::task::Context<'_>,\n    ) -> std::task::Poll<Result<Value, QueryError>> {\n        match self.return_receiver.as_mut() {\n            Some(receiver) => receiver\n                .poll_unpin(cx)\n                .map_err(|_| QueryError::Recv(RecvError::Closed)),\n            None => std::task::Poll::Ready(Err(QueryError::Finished)),\n        }\n    }\n}\n\nimpl<V: DeserializeOwned> Drop for Query<V> {\n    fn drop(&mut self) {\n        self.query_engine\n            .active_requests\n            .slab\n            .borrow_mut()\n            .remove(self.id);\n        let queue_id = self.id;\n\n        _ = self.query_engine.query_tx.send(format!(\n            r#\"\n            if (!window.{QUEUE_NAME}) {{\n                window.{QUEUE_NAME} = [];\n            }}\n\n            if (window.{QUEUE_NAME}[{queue_id}]) {{\n                window.{QUEUE_NAME}[{queue_id}] = [];\n            }}\n            \"#\n        ));\n    }\n}\n\n#[derive(Error, Debug)]\n#[non_exhaustive]\npub enum QueryError {\n    #[error(\"Error receiving query result: {0}\")]\n    Recv(RecvError),\n    #[error(\"Error sending message to query: {0}\")]\n    Send(String),\n    #[error(\"Error deserializing query result: {0}\")]\n    Deserialize(serde_json::Error),\n    #[error(\"Query has already been resolved\")]\n    Finished,\n}\n\n#[derive(Clone, Debug, Deserialize)]\npub(crate) struct QueryResult {\n    id: usize,\n    #[serde(default)]\n    data: Value,\n    #[serde(default)]\n    returned_value: bool,\n}\n"
  },
  {
    "path": "packages/logger/Cargo.toml",
    "content": "[package]\nname = \"dioxus-logger\"\nversion = { workspace = true }\nedition = \"2021\"\ndescription = \"A logging utility to provide a standard interface whether you're targeting web desktop, fullstack, and more.\"\nauthors = [\"DogeDark\", \"Jonathan Kelley\"]\nrepository = \"https://github.com/dioxuslabs/dioxus\"\nhomepage = \"https://github.com/dioxuslabs/dioxus\"\nreadme = \"README.md\"\nlicense = \"MIT\"\nkeywords = [\"dioxus\", \"log\", \"logging\"]\ncategories = [\"development-tools::debugging\"]\n\n\n[dependencies]\ndioxus-cli-config = { workspace = true }\ntracing = { workspace = true }\ntracing-subscriber = { workspace = true, features = [\"registry\", \"std\", \"env-filter\"] }\n\n[features]\ndefault = []\n\n[target.'cfg(target_arch = \"wasm32\")'.dependencies]\ntracing-wasm = { workspace = true }\n\n[target.'cfg(not(target_arch = \"wasm32\"))'.dependencies]\ntracing-subscriber = { workspace = true, features = [\"fmt\"] }\n\n[dev-dependencies]\ndioxus = { workspace = true, features = [\"fullstack\"] }\n"
  },
  {
    "path": "packages/logger/README.md",
    "content": "<div align=\"center\">\n  <h1>📡 Dioxus Logger 🛰️</h1>\n  <p><strong>A logging utility to provide a standard interface whether you're targeting web, desktop, fullstack, and more.</strong></p>\n</div>\n\n<div align=\"center\">\n  <!-- Crates version -->\n  <a href=\"https://crates.io/crates/dioxus-logger\">\n    <img src=\"https://img.shields.io/crates/v/dioxus-logger.svg?style=flat-square\"\n    alt=\"Crates.io version\" />\n  </a>\n  <!-- Downloads -->\n  <a href=\"https://crates.io/crates/dioxus-logger\">\n    <img src=\"https://img.shields.io/crates/d/dioxus-logger.svg?style=flat-square\"\n      alt=\"Download\" />\n  </a>\n  <!-- docs -->\n  <a href=\"https://docs.rs/dioxus-logger\">\n    <img src=\"https://img.shields.io/badge/docs-latest-blue.svg?style=flat-square\"\n      alt=\"docs.rs docs\" />\n  </a>\n</div>\n\n-----\n\n`dioxus-logger` is a basic cross-platform facade for logging in [Dioxus](https://dioxuslabs.com/) that uses the [tracing](https://crates.io/crates/tracing) crate.\n\n\n```rust\nuse dioxus::prelude::*;\nuse dioxus_logger::tracing::{Level, info};\n\nfn main() {\n  dioxus_logger::init(Level::INFO).expect(\"logger failed to init\");\n  dioxus::launch(App);\n}\n\n#[component]\nfn App() -> Element {\n  info!(\"App rendered\");\n  rsx! {\n    p { \"hi\" }\n  }\n}\n```\n\n## Dioxus support\n\nAs of v0.6, dioxus_logger is part of dioxus itself. Dioxus will call `init` with a default Level, though you can still override the default with `init`.\n\n```rust\nuse dioxus::prelude::*;\nuse dioxus::logger::tracing::{Level, info};\n\nfn main() {\n  dioxus::logger::init(Level::INFO).expect(\"logger failed to init\");\n  dioxus::launch(App);\n}\n\n#[component]\nfn App() -> Element {\n  info!(\"App rendered\");\n  rsx! {\n    p { \"hi\" }\n  }\n}\n```\nFor non-wasm targets, runtime filtering is based on the `RUST_LOG` environment variable. e.g. for `RUST_LOG=none,crateName=trace` only logs trace and above for `crateName` will be captured. See [here](https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#directives) for syntax. For crates with `-` in the name, these need to be changed to `_` in `RUST_LOG`.\n\n## Platform Support\nDioxus logger will eventually support every target that Dioxus does. Currently mobile and TUI are not supported.\n\n## Installation\n`dioxus_logger` is part of Dioxus v0.6. If you're using Dioxus v0.6, then no installation is required!\n\n\nIf you're on Dioxus v0.5 and below, you can add `dioxus-logger` to your application by adding it to your dependencies.\n```toml\n[dependencies]\ndioxus-logger = \"0.5\"\n```\n\n\n## License\nThis project is licensed under the [MIT license].\n\n[mit license]: ./LICENSE\n\nEvery contribution intentionally submitted for inclusion in `dioxus-logger` by you, shall be licensed as MIT, without any additional terms or conditions.\n"
  },
  {
    "path": "packages/logger/src/lib.rs",
    "content": "use tracing::{\n    subscriber::{set_global_default, SetGlobalDefaultError},\n    Level,\n};\n\npub use tracing;\n\n/// Attempt to initialize the subscriber if it doesn't already exist, with default settings.\n///\n/// See [`crate::init`] for more info.\n///\n/// If you're doing setup before your `dioxus::launch` function that requires lots of logging, then\n/// it might be worth calling this earlier than launch.\n///\n/// `dioxus::launch` calls this for you automatically and won't replace any facade you've already set.\n///\n/// # Example\n///\n/// ```rust, ignore\n/// use dioxus::prelude::*;\n/// use tracing::info;\n///\n/// fn main() {\n///     dioxus::logger::initialize_default();\n///\n///     info!(\"Doing some work before launching...\");\n///\n///     dioxus::launch(App);\n/// }\n///\n/// #[component]\n/// fn App() -> Element {\n///     info!(\"App rendered\");\n///     rsx! {\n///         p { \"hi\" }\n///     }\n/// }\n/// ```\npub fn initialize_default() {\n    if tracing::dispatcher::has_been_set() {\n        return;\n    }\n\n    if cfg!(debug_assertions) {\n        _ = init(Level::DEBUG);\n    } else {\n        _ = init(Level::INFO);\n    }\n}\n\n/// Initialize `dioxus-logger` with a specified max filter.\n///\n/// Generally it is best to initialize the logger before launching your Dioxus app.\n/// Works on Web, Desktop, Fullstack, and Liveview.\n///\n/// # Example\n///\n/// ```rust, ignore\n/// use dioxus::prelude::*;\n/// use dioxus::logger::tracing::{Level, info};\n///\n/// fn main() {\n///     dioxus::logger::init(Level::INFO).expect(\"logger failed to init\");\n///     dioxus::launch(App);\n/// }\n///\n/// #[component]\n/// fn App() -> Element {\n///     info!(\"App rendered\");\n///     rsx! {\n///         p { \"hi\" }\n///     }\n/// }\n/// ```\npub fn init(level: Level) -> Result<(), SetGlobalDefaultError> {\n    /*\n    The default logger is currently set to log in fmt mode (meaning print directly to stdout)\n\n    Eventually we want to change the output mode to be `json` when running under `dx`. This would let\n    use re-format the tracing spans to be better integrated with `dx`\n    */\n\n    #[cfg(target_arch = \"wasm32\")]\n    {\n        use tracing_subscriber::layer::SubscriberExt;\n        use tracing_subscriber::Registry;\n\n        let layer_config = tracing_wasm::WASMLayerConfigBuilder::new()\n            .set_max_level(level)\n            .build();\n        let layer = tracing_wasm::WASMLayer::new(layer_config);\n        let reg = Registry::default().with(layer);\n\n        set_global_default(reg)\n    }\n\n    #[cfg(not(target_arch = \"wasm32\"))]\n    {\n        let sub = tracing_subscriber::FmtSubscriber::builder()\n            .with_max_level(level)\n            .with_env_filter(\n                tracing_subscriber::EnvFilter::builder()\n                    .with_default_directive(level.into())\n                    .from_env_lossy()\n                    .add_directive(\"hyper_util=warn\".parse().unwrap()), // hyper has `debug!` sitting around in some places that are spammy\n            );\n\n        if !dioxus_cli_config::is_cli_enabled() {\n            return set_global_default(sub.finish());\n        }\n\n        // todo(jon): this is a small hack to clean up logging when running under the CLI\n        // eventually we want to emit everything as json and let the CLI manage the parsing + display\n        set_global_default(sub.without_time().with_target(false).finish())\n    }\n}\n"
  },
  {
    "path": "packages/manganis/manganis/Cargo.toml",
    "content": "[package]\n# Manganese is a rusting catalyst. Manganis makes it faster to collect rust assets (and has almost no google search results)\nname = \"manganis\"\nversion.workspace = true\nauthors = [\"Evan Almloff\"]\nedition = \"2021\"\ndescription = \"Ergonomic, automatic, cross crate asset collection and optimization\"\nlicense = \"MIT OR Apache-2.0\"\nrepository = \"https://github.com/DioxusLabs/dioxus/tree/main/packages/manganis/manganis\"\nhomepage = \"https://dioxuslabs.com\"\nkeywords = [\"assets\"]\n\n[lib]\n\n[dependencies]\nconst-serialize = { workspace = true }\nmanganis-core = { workspace = true }\nmanganis-macro = { workspace = true }\nthiserror = { workspace = true }\nconst-serialize-07 = { package = \"const-serialize\", version = \"=0.7.2\" }\n\n[target.'cfg(target_os = \"android\")'.dependencies]\njni = \"0.21\"\nndk-context = \"0.1.1\"\n\n[target.'cfg(target_os = \"ios\")'.dependencies]\nobjc2 = \"0.6.3\"\n\n[target.'cfg(target_os = \"macos\")'.dependencies]\nobjc2 = \"0.6.3\"\n\n[features]\ndefault = []\ndioxus = [\"manganis-core/dioxus\"]\n"
  },
  {
    "path": "packages/manganis/manganis/README.md",
    "content": "# Manganis\n\nManganis is a tool for submitting assets and native source files to the program linker. It makes it easy to self-host assets and native plugins that are distributed throughout your libraries.\n\n## Assets\n\nIncluding assets is as simple as using the `asset!()` macro:\n\n```rust\nuse manganis::{Asset, asset};\nconst STYLE: Asset = asset!(\"/assets/style.css\");\n```\n\nAfter cargo builds your app, the asset path is embedded directly in the data section. A tool like the Dioxus CLI can extract this metadata and post-process these assets.\n\n```rust\nuse manganis::{ImageFormat, Asset, asset, ImageSize, AssetOptions};\n// You can collect arbitrary files. Absolute paths are resolved relative to the package root\nconst _: Asset = asset!(\"/assets/script.js\");\n\n// You can collect images which will be automatically optimized\npub const PNG_ASSET: Asset =\n    asset!(\"/assets/image.png\");\n// Resize the image at compile time to make the assets smaller\npub const RESIZED_PNG_ASSET: Asset =\n    asset!(\"/assets/image.png\", AssetOptions::image().with_size(ImageSize::Manual { width: 52, height: 52 }));\n// Or convert the image at compile time to a web friendly format\npub const AVIF_ASSET: Asset = asset!(\"/assets/image.png\", AssetOptions::image().with_format(ImageFormat::Avif));\n```\n\n## option_asset\n\nIf you have assets that may not always be bundled, you can fall back gracefully with `option_asset!`:\n\n```rust\nuse manganis::{Asset, asset, option_asset};\nconst REQUIRED: Asset = asset!(\"/assets/style.css\");\nconst OPTIONAL: Option<Asset> = option_asset!(\"/assets/missing.css\");\n```\n\n## Native FFI Bindings\n\nManganis provides the `#[ffi]` attribute macro for generating direct FFI bindings between Rust and native platforms (Swift/Kotlin). See the `geolocation-native-plugin` example for usage.\n"
  },
  {
    "path": "packages/manganis/manganis/assets/script.js",
    "content": ""
  },
  {
    "path": "packages/manganis/manganis/assets/style.css",
    "content": ""
  },
  {
    "path": "packages/manganis/manganis/src/android/activity.rs",
    "content": "use jni::{objects::JObject, JNIEnv, JavaVM};\nuse std::sync::OnceLock;\n\n/// Cached reference to the Android activity.\nstatic ACTIVITY: OnceLock<jni::objects::GlobalRef> = OnceLock::new();\nstatic JAVA_VM: OnceLock<JavaVM> = OnceLock::new();\n\n/// Execute a JNI operation with a cached activity reference.\n///\n/// This function handles the boilerplate of getting the JavaVM and Activity\n/// references, caching them for subsequent calls. It's the foundation for\n/// most Android mobile API operations.\n///\n/// # Arguments\n///\n/// * `f` - A closure that receives a mutable JNIEnv and the Activity JObject\n///\n/// # Returns\n///\n/// Returns `Some(R)` if the operation succeeds, `None` if there's an error\n/// getting the VM or Activity references.\n///\n/// # Example\n///\n/// ```rust,no_run\n/// use dioxus_platform_bridge::android::with_activity;\n///\n/// let result = with_activity(|env, activity| {\n///     // Your JNI operations here\n///     Some(42)\n/// });\n/// ```\npub fn with_activity<F, R>(f: F) -> Option<R>\nwhere\n    F: FnOnce(&mut JNIEnv<'_>, &JObject<'_>) -> Option<R>,\n{\n    let ctx = ndk_context::android_context();\n    let vm = if let Some(vm) = JAVA_VM.get() {\n        vm\n    } else {\n        let raw_vm = unsafe { JavaVM::from_raw(ctx.vm().cast()) }.ok()?;\n        let _ = JAVA_VM.set(raw_vm);\n        JAVA_VM.get()?\n    };\n    let mut env = vm.attach_current_thread().ok()?;\n\n    let activity = if let Some(activity) = ACTIVITY.get() {\n        activity\n    } else {\n        let raw_activity = unsafe { JObject::from_raw(ctx.context() as jni::sys::jobject) };\n        let global = env.new_global_ref(&raw_activity).ok()?;\n        match ACTIVITY.set(global) {\n            Ok(()) => ACTIVITY.get().unwrap(),\n            Err(global) => {\n                drop(global);\n                ACTIVITY.get()?\n            }\n        }\n    };\n\n    let activity_obj = activity.as_obj();\n    f(&mut env, &activity_obj)\n}\n"
  },
  {
    "path": "packages/manganis/manganis/src/android/callback.rs",
    "content": "use jni::{\n    objects::{GlobalRef, JClass, JObject, JValue},\n    sys::jlong,\n    JNIEnv, NativeMethod,\n};\n\nuse crate::android::java::Result;\n\n/// Generic callback system for loading DEX classes and registering native methods\npub struct CallbackSystem {\n    bytecode: &'static [u8],\n    class_name: &'static str,\n    callback_name: &'static str,\n    callback_signature: &'static str,\n}\n\nimpl CallbackSystem {\n    /// Create a new callback system\n    ///\n    /// # Arguments\n    ///\n    /// * `bytecode` - The compiled DEX bytecode\n    /// * `class_name` - The fully qualified Java class name\n    /// * `callback_name` - The name of the native callback method\n    /// * `callback_signature` - The JNI signature of the callback method\n    pub fn new(\n        bytecode: &'static [u8],\n        class_name: &'static str,\n        callback_name: &'static str,\n        callback_signature: &'static str,\n    ) -> Self {\n        Self {\n            bytecode,\n            class_name,\n            callback_name,\n            callback_signature,\n        }\n    }\n\n    /// Load the DEX class and register the native callback method\n    ///\n    /// This function handles the boilerplate of:\n    /// 1. Creating an InMemoryDexClassLoader\n    /// 2. Loading the specified class\n    /// 3. Registering the native callback method\n    ///\n    /// # Returns\n    ///\n    /// Returns a `GlobalRef` to the loaded class, or an error if loading fails\n    pub fn load_and_register(&self, env: &mut JNIEnv<'_>) -> Result<GlobalRef> {\n        let callback_class = self.load_dex_class(env)?;\n        self.register_native_callback(env, &callback_class)?;\n        let global = env.new_global_ref(callback_class)?;\n        Ok(global)\n    }\n\n    /// Load the DEX class from bytecode\n    fn load_dex_class<'a>(&self, env: &mut JNIEnv<'a>) -> Result<JClass<'a>> {\n        const IN_MEMORY_LOADER: &str = \"dalvik/system/InMemoryDexClassLoader\";\n\n        // Create a ByteBuffer from our DEX bytecode\n        let byte_buffer = unsafe {\n            env.new_direct_byte_buffer(self.bytecode.as_ptr() as *mut u8, self.bytecode.len())\n        }?;\n\n        // Create an InMemoryDexClassLoader with our DEX bytecode\n        let dex_class_loader = env.new_object(\n            IN_MEMORY_LOADER,\n            \"(Ljava/nio/ByteBuffer;Ljava/lang/ClassLoader;)V\",\n            &[\n                JValue::Object(&byte_buffer),\n                JValue::Object(&JObject::null()),\n            ],\n        )?;\n\n        // Load our class\n        let class_name = env.new_string(self.class_name)?;\n        let class = env\n            .call_method(\n                &dex_class_loader,\n                \"loadClass\",\n                \"(Ljava/lang/String;)Ljava/lang/Class;\",\n                &[JValue::Object(&class_name)],\n            )?\n            .l()?;\n\n        Ok(class.into())\n    }\n\n    /// Register the native callback method with the Java class\n    fn register_native_callback<'a>(\n        &self,\n        env: &mut JNIEnv<'a>,\n        callback_class: &JClass<'a>,\n    ) -> Result<()> {\n        env.register_native_methods(\n            callback_class,\n            &[NativeMethod {\n                name: self.callback_name.into(),\n                sig: self.callback_signature.into(),\n                fn_ptr: rust_callback as *mut _,\n            }],\n        )?;\n        Ok(())\n    }\n}\n\n/// Generic native callback function called from Java\n///\n/// SAFETY: This function is called from Java and must maintain proper memory safety.\n/// The handler pointer is valid as long as the Manager exists (see Drop implementation).\n#[no_mangle]\nunsafe extern \"C\" fn rust_callback<'a>(\n    mut env: JNIEnv<'a>,\n    _class: JObject<'a>,\n    handler_ptr_high: jlong,\n    handler_ptr_low: jlong,\n    location: JObject<'a>,\n) {\n    // Reconstruct the pointer from two i64 values (for 64-bit pointers)\n    #[cfg(not(target_pointer_width = \"64\"))]\n    compile_error!(\"Only 64-bit Android targets are supported\");\n\n    let handler_ptr_raw: usize =\n        ((handler_ptr_high as u64) << 32 | handler_ptr_low as u64) as usize;\n\n    // Convert to our callback function pointer\n    let callback: fn(&mut JNIEnv, JObject) = unsafe { std::mem::transmute(handler_ptr_raw) };\n\n    // Use the local reference for this JNI frame; avoid leaking a global ref.\n    callback(&mut env, location);\n}\n"
  },
  {
    "path": "packages/manganis/manganis/src/android/java.rs",
    "content": "use jni::{\n    objects::{JClass, JObject, JObjectArray, JString, JValue, JValueOwned},\n    JNIEnv,\n};\n\n/// Result type for JNI operations\npub type Result<T> = std::result::Result<T, jni::errors::Error>;\n\n/// Helper functions for common JNI operations\n\n/// Create a new Java string tied to the current JNI frame\npub fn new_string<'env>(env: &mut JNIEnv<'env>, s: &str) -> Result<JString<'env>> {\n    env.new_string(s)\n}\n\n/// Create a new object array\npub fn new_object_array<'env>(\n    env: &mut JNIEnv<'env>,\n    len: i32,\n    element_class: &str,\n) -> Result<JObjectArray<'env>> {\n    env.new_object_array(len, element_class, &JObject::null())\n}\n\n/// Set an element in an object array\npub fn set_object_array_element<'env>(\n    env: &mut JNIEnv<'env>,\n    array: &JObjectArray<'env>,\n    index: i32,\n    element: JString<'env>,\n) -> Result<()> {\n    env.set_object_array_element(array, index, element)\n}\n\n/// Call a static method on a class\npub fn call_static_method<'env, 'obj>(\n    env: &mut JNIEnv<'env>,\n    class: &JClass<'env>,\n    method_name: &str,\n    signature: &str,\n    args: &[JValue<'env, 'obj>],\n) -> Result<JValueOwned<'env>> {\n    env.call_static_method(class, method_name, signature, args)\n}\n\n/// Call an instance method on an object\npub fn call_method<'env, 'obj>(\n    env: &mut JNIEnv<'env>,\n    obj: &JObject<'env>,\n    method_name: &str,\n    signature: &str,\n    args: &[JValue<'env, 'obj>],\n) -> Result<JValueOwned<'env>> {\n    env.call_method(obj, method_name, signature, args)\n}\n\n/// Find a Java class by name\npub fn find_class<'env>(env: &mut JNIEnv<'env>, class_name: &str) -> Result<JClass<'env>> {\n    env.find_class(class_name)\n}\n\n/// Create a new object instance\npub fn new_object<'env, 'obj>(\n    env: &mut JNIEnv<'env>,\n    class_name: &str,\n    signature: &str,\n    args: &[JValue<'env, 'obj>],\n) -> Result<JObject<'env>> {\n    env.new_object(class_name, signature, args)\n}\n\n/// Check if a permission is granted (Activity.checkSelfPermission)\npub fn check_self_permission(\n    env: &mut JNIEnv,\n    activity: &JObject,\n    permission: &str,\n) -> Result<bool> {\n    let permission_string = new_string(env, permission)?;\n    let status = env.call_method(\n        activity,\n        \"checkSelfPermission\",\n        \"(Ljava/lang/String;)I\",\n        &[JValue::Object(&permission_string)],\n    )?;\n\n    const PERMISSION_GRANTED: i32 = 0;\n    Ok(status.i()? == PERMISSION_GRANTED)\n}\n\n/// Request permissions via a helper class's static method\n///\n/// This uses PermissionsHelper.requestPermissionsOnUiThread(pattern)\n/// to request permissions on the UI thread.\npub fn request_permissions_via_helper(\n    env: &mut JNIEnv,\n    helper_class: &JClass,\n    activity: &JObject,\n    permissions: JObjectArray,\n    request_code: i32,\n) -> Result<()> {\n    env.call_static_method(\n        helper_class,\n        \"requestPermissionsOnUiThread\",\n        \"(Landroid/app/Activity;[Ljava/lang/String;I)V\",\n        &[\n            JValue::Object(activity),\n            JValue::Object(&permissions.into()),\n            JValue::Int(request_code),\n        ],\n    )?;\n    Ok(())\n}\n\n/// Load a Java class from the APK's classloader\npub fn load_class_from_classloader<'env>(\n    env: &mut JNIEnv<'env>,\n    class_name: &str,\n) -> Result<JClass<'env>> {\n    let class_name_jstring = new_string(env, class_name)?;\n    let class = env.call_static_method(\n        \"java/lang/Class\",\n        \"forName\",\n        \"(Ljava/lang/String;)Ljava/lang/Class;\",\n        &[JValue::Object(&class_name_jstring)],\n    )?;\n    Ok(class.l()?.into())\n}\n"
  },
  {
    "path": "packages/manganis/manganis/src/android/metadata.rs",
    "content": "//! Android metadata wrappers for linker-based collection.\n\npub use manganis_core::AndroidArtifactMetadata;\n\npub type AndroidMetadataBuffer = crate::macro_helpers::ConstVec<u8, 4096>;\n\npub const fn serialize_android_metadata(meta: &AndroidArtifactMetadata) -> AndroidMetadataBuffer {\n    crate::macro_helpers::serialize_android_artifact(meta)\n}\n"
  },
  {
    "path": "packages/manganis/manganis/src/android/mod.rs",
    "content": "//! Android-specific utilities for mobile APIs\n\n#[cfg(target_os = \"android\")]\npub mod activity;\n#[cfg(target_os = \"android\")]\npub mod callback;\n#[cfg(target_os = \"android\")]\npub mod java;\n#[cfg(target_os = \"android\")]\npub mod metadata;\n\n#[doc(hidden)]\npub mod macro_helpers {\n    //! Helper functions for macro expansion\n    //!\n    //! These functions are used internally by the `android_plugin!()` macro\n    //! and should not be used directly.\n\n    /// Copy a slice into a constant sized buffer at compile time\n    pub const fn copy_bytes<const N: usize>(bytes: &[u8]) -> [u8; N] {\n        let mut out = [0; N];\n        let mut i = 0;\n        while i < N {\n            out[i] = bytes[i];\n            i += 1;\n        }\n        out\n    }\n}\n\n#[cfg(target_os = \"android\")]\npub use activity::*;\n#[cfg(target_os = \"android\")]\npub use callback::*;\n#[cfg(target_os = \"android\")]\npub use java::*;\n\n#[cfg(target_os = \"android\")]\npub use metadata::*;\n"
  },
  {
    "path": "packages/manganis/manganis/src/darwin/mod.rs",
    "content": "//! Darwin (iOS/macOS) shared utilities for objc2-based APIs\n//!\n//! This module provides shared utilities for both iOS and macOS platforms\n//! since they share the same Objective-C runtime and threading requirements\n//! through objc2.\n\n/// Re-export MainThreadMarker for convenience\npub use objc2::MainThreadMarker;\n\n/// Macro helpers for FFI code generation\npub mod macro_helpers {\n    pub use crate::macro_helpers::copy_bytes;\n}\n\npub use manganis_core::SwiftPackageMetadata;\n\npub mod metadata {\n    use manganis_core::SwiftPackageMetadata;\n\n    /// Buffer type for serialized Swift metadata\n    #[doc(hidden)]\n    pub type SwiftMetadataBuffer = crate::macro_helpers::ConstVec<u8, 4096>;\n\n    /// Serialize Swift package metadata for linker embedding\n    #[doc(hidden)]\n    pub const fn serialize_swift_metadata(meta: &SwiftPackageMetadata) -> SwiftMetadataBuffer {\n        crate::macro_helpers::serialize_swift_package(meta)\n    }\n}\n"
  },
  {
    "path": "packages/manganis/manganis/src/lib.rs",
    "content": "#![doc = include_str!(\"../README.md\")]\n\n#[doc(hidden)]\npub mod macro_helpers;\npub use manganis_macro::asset;\npub use manganis_macro::css_module;\npub use manganis_macro::option_asset;\n\npub use manganis_core::{\n    // Core asset types\n    Asset,\n    AssetOptions,\n    AssetVariant,\n    BundledAsset,\n    // Standard asset options\n    CssAssetOptions,\n    CssModuleAssetOptions,\n    FolderAssetOptions,\n    ImageAssetOptions,\n    ImageFormat,\n    ImageSize,\n    JsAssetOptions,\n};\n\n// Re-export metadata types for FFI and sidecar macros\npub use manganis_core::SwiftPackageMetadata;\npub use manganis_core::SymbolData;\n\n// FFI utilities and plugin metadata for Dioxus mobile platform APIs\n//\n// This crate provides common patterns and utilities for implementing\n// mobile platform APIs in Dioxus applications. It handles the\n// boilerplate for JNI (Android) and objc2 (iOS/macOS) bindings, build scripts,\n// and platform-specific resource management.\n\n/// Android platform utilities\n#[doc(hidden)]\npub mod android;\n\n/// Darwin (iOS/macOS) platform utilities\n#[doc(hidden)]\n#[cfg(any(target_os = \"ios\", target_os = \"macos\"))]\npub mod darwin;\n\n#[cfg(target_os = \"android\")]\npub use android::*;\n\n// Export darwin module for iOS, macOS, and when metadata feature is enabled (for FFI macro)\n#[cfg(any(target_os = \"ios\", target_os = \"macos\"))]\npub use darwin::*;\n\n/// Re-export commonly used types for convenience\n#[cfg(target_os = \"android\")]\npub use jni;\n\n// Re-export objc2 for FFI macro generated code\n#[cfg(any(target_os = \"ios\", target_os = \"macos\"))]\npub use objc2;\n\n/// Re-export the ffi attribute macro for native FFI bindings\n/// This macro generates direct FFI bindings between Rust and native platforms (Swift/Kotlin)\npub use manganis_macro::ffi;\n"
  },
  {
    "path": "packages/manganis/manganis/src/macro_helpers.rs",
    "content": "// Re-export const_serialize types for generated code.\npub use const_serialize;\npub use const_serialize::{ConstStr, ConstVec, SerializeConst};\npub use const_serialize_07;\n\nuse const_serialize_07::ConstVec as ConstVec07;\nuse manganis_core::{\n    AndroidArtifactMetadata, AssetOptions, BundledAsset, SwiftPackageMetadata, SymbolData,\n};\n\n/// Copy a slice into a constant sized buffer at compile time\n///\n/// This is a generic utility that works with any byte slice and can be used\n/// in const contexts to create fixed-size arrays from dynamic slices.\npub const fn copy_bytes<const N: usize>(bytes: &[u8]) -> [u8; N] {\n    let mut out = [0; N];\n    let mut i = 0;\n    while i < N {\n        out[i] = bytes[i];\n        i += 1;\n    }\n    out\n}\n\n/// Serialize a SymbolData value into a const buffer\n///\n/// This is used by the widget!() macro and other symbol-based macros to embed\n/// metadata into the binary using the 4096-byte buffer format.\npub const fn serialize_symbol_data(symbol_data: &SymbolData) -> ConstVec<u8, 4096> {\n    dx_macro_helpers::serialize_to_const_with_max_padded::<4096>(symbol_data)\n}\n\n/// Serialize Android artifact metadata (wrapped in `SymbolData::AndroidArtifact`).\npub const fn serialize_android_artifact(meta: &AndroidArtifactMetadata) -> ConstVec<u8, 4096> {\n    serialize_symbol_data(&SymbolData::AndroidArtifact(*meta))\n}\n\n/// Serialize Swift package metadata (wrapped in `SymbolData::SwiftPackage`).\npub const fn serialize_swift_package(meta: &SwiftPackageMetadata) -> ConstVec<u8, 4096> {\n    serialize_symbol_data(&SymbolData::SwiftPackage(*meta))\n}\n\n/// Create a bundled asset from the input path, the content hash, and the asset options\npub const fn create_bundled_asset(input_path: &str, asset_config: AssetOptions) -> BundledAsset {\n    BundledAsset::new(input_path, BundledAsset::PLACEHOLDER_HASH, asset_config)\n}\n\n/// Create a bundled asset from the input path, the content hash, and the asset options with a relative asset deprecation warning\n///\n/// This method is deprecated and will be removed in a future release.\n#[deprecated(\n    note = \"Relative asset!() paths are not supported. Use a path like `/assets/myfile.png` instead of `./assets/myfile.png`\"\n)]\npub const fn create_bundled_asset_relative(\n    input_path: &str,\n    asset_config: AssetOptions,\n) -> BundledAsset {\n    create_bundled_asset(input_path, asset_config)\n}\n\n/// Serialize an asset to a const buffer\n///\n/// Serializes the asset directly (not wrapped in SymbolData) for simplicity.\n/// Uses a 4096-byte buffer and pads to the full size to match linker section size.\npub const fn serialize_asset(asset: &BundledAsset) -> ConstVec<u8, 4096> {\n    dx_macro_helpers::serialize_to_const_with_max_padded::<4096>(asset)\n}\n\n/// Serialize an asset to a const buffer in the legacy 0.7 format\npub const fn serialize_asset_07(asset: &BundledAsset) -> ConstVec07<u8> {\n    dx_macro_helpers::serialize_to_const_with_layout_padded_07(asset)\n}\n\n/// Deserialize a const buffer into a BundledAsset\npub const fn deserialize_asset(bytes: &[u8]) -> BundledAsset {\n    match const_serialize::deserialize_const!(BundledAsset, bytes) {\n        Some((_, asset)) => asset,\n        None => panic!(\"Failed to deserialize asset. This may be caused by a mismatch between your dioxus and dioxus-cli versions\"),\n    }\n}\n\npub mod dx_macro_helpers {\n    use const_serialize::{ConstVec, SerializeConst};\n\n    /// Serialize a value to a const buffer, padding to the specified size\n    ///\n    /// This is a generic helper that works with any type implementing `SerializeConst`.\n    /// It serializes the value and then pads the buffer to the specified memory layout size.\n    pub const fn serialize_to_const<T: SerializeConst>(\n        value: &T,\n        memory_layout_size: usize,\n    ) -> ConstVec<u8> {\n        let data = ConstVec::new();\n        let mut data = const_serialize::serialize_const(value, data);\n        // Reserve the maximum size of the type\n        while data.len() < memory_layout_size {\n            data = data.push(0);\n        }\n        data\n    }\n\n    /// Serialize a value to a const buffer with a fixed maximum size, padding to the specified size\n    ///\n    /// This variant uses a `ConstVec` with a fixed maximum size (e.g., `ConstVec<u8, 4096>`)\n    /// and then pads to the specified memory layout size.\n    ///\n    /// This function serializes directly into the larger buffer to avoid overflow issues\n    /// when the serialized data exceeds the default 1024-byte buffer size.\n    pub const fn serialize_to_const_with_max<const MAX_SIZE: usize>(\n        value: &impl SerializeConst,\n        memory_layout_size: usize,\n    ) -> ConstVec<u8, MAX_SIZE> {\n        // Serialize using the default buffer, then copy into the larger buffer\n        let serialized = const_serialize::serialize_const(value, ConstVec::new());\n        let mut data: ConstVec<u8, MAX_SIZE> = ConstVec::new_with_max_size();\n        data = data.extend(serialized.as_ref());\n        // Reserve the maximum size of the type (pad to MEMORY_LAYOUT size)\n        while data.len() < memory_layout_size {\n            data = data.push(0);\n        }\n        data\n    }\n\n    /// Serialize a value to a const buffer and pad to the full buffer size\n    ///\n    /// This is useful for linker section generation that expects a fixed-size buffer.\n    pub const fn serialize_to_const_with_max_padded<const MAX_SIZE: usize>(\n        value: &impl SerializeConst,\n    ) -> ConstVec<u8, MAX_SIZE> {\n        let serialized = const_serialize::serialize_const(value, ConstVec::new());\n        let mut data: ConstVec<u8, MAX_SIZE> = ConstVec::new_with_max_size();\n        data = data.extend(serialized.as_ref());\n        while data.len() < MAX_SIZE {\n            data = data.push(0);\n        }\n        data\n    }\n\n    /// Serialize a value using the legacy 0.7 const-serialize format and pad to layout size\n    ///\n    /// Note: The legacy ConstVec has a 1024-byte limit. If MEMORY_LAYOUT.size() exceeds this,\n    /// we pad only up to the buffer limit to avoid overflow.\n    pub const fn serialize_to_const_with_layout_padded_07<T: const_serialize_07::SerializeConst>(\n        value: &T,\n    ) -> const_serialize_07::ConstVec<u8> {\n        let data = const_serialize_07::ConstVec::new();\n        let mut data = const_serialize_07::serialize_const(value, data);\n        // Pad to MEMORY_LAYOUT size, but cap at 1024 bytes (the legacy buffer limit)\n        let target_size = if T::MEMORY_LAYOUT.size() > 1024 {\n            1024\n        } else {\n            T::MEMORY_LAYOUT.size()\n        };\n        while data.len() < target_size {\n            data = data.push(0);\n        }\n        data\n    }\n}\n"
  },
  {
    "path": "packages/manganis/manganis-07/Cargo.toml",
    "content": "[package]\nname = \"manganis-core-07\"\nedition = \"2021\"\nversion = \"0.7.2\"\nauthors = [\"Jonathan Kelley\"]\ndescription = \"Legacy manganis-core v0.7.2 for backwards compatibility during 0.7.3 release\"\nlicense = \"MIT OR Apache-2.0\"\nrepository = \"https://github.com/DioxusLabs/dioxus/\"\nhomepage = \"https://dioxuslabs.com\"\nkeywords = [\"dom\", \"ui\", \"gui\", \"assets\"]\n\n# be careful with dependencies you add here - these need to get compiled for the proc macro and therefore\n# prevent the main code from compiling!\n[dependencies]\nserde = { workspace = true, features = [\"derive\"] }\nconst-serialize-07 = { package = \"const-serialize\", version = \"=0.7.2\", features = [\"serde\"] }\n"
  },
  {
    "path": "packages/manganis/manganis-07/src/asset.rs",
    "content": "use crate::AssetOptions;\nuse const_serialize_07::{ConstStr, SerializeConst};\nuse std::{fmt::Debug, hash::Hash};\n\n#[derive(Debug, Eq, Clone, Copy, SerializeConst, serde::Serialize, serde::Deserialize)]\npub struct BundledAsset {\n    absolute_source_path: ConstStr,\n    bundled_path: ConstStr,\n    options: AssetOptions,\n}\n\nimpl PartialEq for BundledAsset {\n    fn eq(&self, other: &Self) -> bool {\n        self.absolute_source_path == other.absolute_source_path\n            && self.bundled_path == other.bundled_path\n            && self.options == other.options\n    }\n}\n\nimpl PartialOrd for BundledAsset {\n    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {\n        match self\n            .absolute_source_path\n            .partial_cmp(&other.absolute_source_path)\n        {\n            Some(core::cmp::Ordering::Equal) => {}\n            ord => return ord,\n        }\n        match self.bundled_path.partial_cmp(&other.bundled_path) {\n            Some(core::cmp::Ordering::Equal) => {}\n            ord => return ord,\n        }\n        self.options.partial_cmp(&other.options)\n    }\n}\n\nimpl Hash for BundledAsset {\n    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {\n        self.absolute_source_path.hash(state);\n        self.bundled_path.hash(state);\n        self.options.hash(state);\n    }\n}\n\nimpl BundledAsset {\n    #[doc(hidden)]\n    pub const fn new(\n        absolute_source_path: &str,\n        bundled_path: &str,\n        options: AssetOptions,\n    ) -> Self {\n        Self {\n            absolute_source_path: ConstStr::new(absolute_source_path),\n            bundled_path: ConstStr::new(bundled_path),\n            options,\n        }\n    }\n\n    pub fn bundled_path(&self) -> &str {\n        self.bundled_path.as_str()\n    }\n\n    pub fn absolute_source_path(&self) -> &str {\n        self.absolute_source_path.as_str()\n    }\n\n    pub const fn options(&self) -> &AssetOptions {\n        &self.options\n    }\n}\n"
  },
  {
    "path": "packages/manganis/manganis-07/src/css.rs",
    "content": "use crate::{AssetOptions, AssetOptionsBuilder, AssetVariant};\nuse const_serialize_07::SerializeConst;\n\n#[derive(\n    Debug,\n    Eq,\n    PartialEq,\n    PartialOrd,\n    Clone,\n    Copy,\n    Hash,\n    SerializeConst,\n    serde::Serialize,\n    serde::Deserialize,\n)]\npub struct CssAssetOptions {\n    minify: bool,\n    preload: bool,\n    static_head: bool,\n}\n\nimpl Default for CssAssetOptions {\n    fn default() -> Self {\n        Self::default()\n    }\n}\n\nimpl CssAssetOptions {\n    pub const fn new() -> AssetOptionsBuilder<CssAssetOptions> {\n        AssetOptions::css()\n    }\n\n    pub const fn default() -> Self {\n        Self {\n            preload: false,\n            minify: true,\n            static_head: false,\n        }\n    }\n\n    pub const fn preloaded(&self) -> bool {\n        self.preload\n    }\n\n    pub const fn static_head(&self) -> bool {\n        self.static_head\n    }\n\n    pub const fn minified(&self) -> bool {\n        self.minify\n    }\n}\n\nimpl AssetOptions {\n    pub const fn css() -> AssetOptionsBuilder<CssAssetOptions> {\n        AssetOptionsBuilder::variant(CssAssetOptions::default())\n    }\n}\n\nimpl AssetOptionsBuilder<CssAssetOptions> {\n    pub const fn with_minify(mut self, minify: bool) -> Self {\n        self.variant.minify = minify;\n        self\n    }\n\n    #[allow(unused)]\n    pub const fn with_static_head(mut self, static_head: bool) -> Self {\n        self.variant.static_head = static_head;\n        self\n    }\n\n    pub const fn with_preload(mut self, preload: bool) -> Self {\n        self.variant.preload = preload;\n        self\n    }\n\n    pub const fn into_asset_options(self) -> AssetOptions {\n        AssetOptions {\n            add_hash: true,\n            variant: AssetVariant::Css(self.variant),\n        }\n    }\n}\n"
  },
  {
    "path": "packages/manganis/manganis-07/src/css_module.rs",
    "content": "use crate::{AssetOptions, AssetOptionsBuilder, AssetVariant};\nuse const_serialize_07::SerializeConst;\nuse std::collections::HashSet;\n\n#[derive(\n    Debug,\n    Eq,\n    PartialEq,\n    PartialOrd,\n    Clone,\n    Copy,\n    Hash,\n    SerializeConst,\n    serde::Serialize,\n    serde::Deserialize,\n)]\n#[non_exhaustive]\n#[doc(hidden)]\npub struct CssModuleAssetOptions {\n    minify: bool,\n    preload: bool,\n}\n\nimpl Default for CssModuleAssetOptions {\n    fn default() -> Self {\n        Self::default()\n    }\n}\n\nimpl CssModuleAssetOptions {\n    pub const fn new() -> AssetOptionsBuilder<CssModuleAssetOptions> {\n        AssetOptions::css_module()\n    }\n\n    pub const fn default() -> Self {\n        Self {\n            preload: false,\n            minify: true,\n        }\n    }\n\n    pub const fn minified(&self) -> bool {\n        self.minify\n    }\n\n    pub const fn preloaded(&self) -> bool {\n        self.preload\n    }\n}\n\nimpl AssetOptions {\n    pub const fn css_module() -> AssetOptionsBuilder<CssModuleAssetOptions> {\n        AssetOptionsBuilder::variant(CssModuleAssetOptions::default())\n    }\n}\n\nimpl AssetOptionsBuilder<CssModuleAssetOptions> {\n    pub const fn with_minify(mut self, minify: bool) -> Self {\n        self.variant.minify = minify;\n        self\n    }\n\n    pub const fn with_preload(mut self, preload: bool) -> Self {\n        self.variant.preload = preload;\n        self\n    }\n\n    pub const fn into_asset_options(self) -> AssetOptions {\n        AssetOptions {\n            add_hash: self.add_hash,\n            variant: AssetVariant::CssModule(self.variant),\n        }\n    }\n}\n\npub fn collect_css_idents(css: &str) -> (HashSet<String>, HashSet<String>) {\n    const ALLOWED: &str = \"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-\";\n\n    let mut classes = HashSet::new();\n    let mut ids = HashSet::new();\n\n    let mut start: Option<(String, bool)> = None;\n\n    let mut comment_start = false;\n    let mut comment_end = false;\n    let mut in_comment_scope = false;\n\n    let mut in_block_scope = false;\n\n    for (_byte_index, c) in css.char_indices() {\n        if let Some(ident) = start.as_mut() {\n            if ALLOWED.find(c).is_some() {\n                if ident.0.is_empty() && c.is_numeric() {\n                    start = None;\n                    continue;\n                }\n\n                ident.0.push(c);\n            } else {\n                match ident.1 {\n                    true => ids.insert(ident.0.clone()),\n                    false => classes.insert(ident.0.clone()),\n                };\n\n                start = None;\n            }\n        } else {\n            match c {\n                '*' if comment_start => {\n                    comment_start = false;\n                    in_comment_scope = true;\n                }\n                '*' if in_comment_scope => comment_end = true,\n                '/' if !in_comment_scope => {\n                    comment_start = true;\n                }\n                '/' if comment_end => {\n                    in_comment_scope = false;\n                    comment_start = false;\n                    comment_end = false;\n                }\n                '{' => in_block_scope = true,\n                '}' => in_block_scope = false,\n                _ => {\n                    comment_start = false;\n                    comment_end = false;\n                }\n            }\n\n            if in_comment_scope || in_block_scope {\n                continue;\n            }\n\n            match c {\n                '.' => start = Some((String::new(), false)),\n                '#' => start = Some((String::new(), true)),\n                _ => {}\n            }\n        }\n    }\n\n    (classes, ids)\n}\n"
  },
  {
    "path": "packages/manganis/manganis-07/src/folder.rs",
    "content": "use const_serialize_07::SerializeConst;\n\nuse crate::{AssetOptions, AssetOptionsBuilder};\n\n#[derive(\n    Debug,\n    Eq,\n    PartialEq,\n    PartialOrd,\n    Clone,\n    Copy,\n    Hash,\n    SerializeConst,\n    serde::Serialize,\n    serde::Deserialize,\n)]\npub struct FolderAssetOptions {}\n\nimpl Default for FolderAssetOptions {\n    fn default() -> Self {\n        Self::default()\n    }\n}\n\nimpl FolderAssetOptions {\n    pub const fn new() -> AssetOptionsBuilder<FolderAssetOptions> {\n        AssetOptions::folder()\n    }\n\n    pub const fn default() -> Self {\n        Self {}\n    }\n}\n\nimpl AssetOptions {\n    pub const fn folder() -> AssetOptionsBuilder<FolderAssetOptions> {\n        AssetOptionsBuilder::variant(FolderAssetOptions::default())\n    }\n}\n\nimpl AssetOptionsBuilder<FolderAssetOptions> {\n    pub const fn into_asset_options(self) -> AssetOptions {\n        AssetOptions {\n            add_hash: false,\n            variant: crate::AssetVariant::Folder(self.variant),\n        }\n    }\n}\n"
  },
  {
    "path": "packages/manganis/manganis-07/src/images.rs",
    "content": "use const_serialize_07::SerializeConst;\n\nuse crate::{AssetOptions, AssetOptionsBuilder, AssetVariant};\n\n#[derive(\n    Debug,\n    Eq,\n    PartialEq,\n    PartialOrd,\n    Clone,\n    Copy,\n    Hash,\n    SerializeConst,\n    serde::Serialize,\n    serde::Deserialize,\n)]\n#[repr(u8)]\npub enum ImageFormat {\n    Png,\n    Jpg,\n    Webp,\n    Avif,\n    Unknown,\n}\n\n#[derive(\n    Debug,\n    Eq,\n    PartialEq,\n    PartialOrd,\n    Clone,\n    Copy,\n    Hash,\n    SerializeConst,\n    serde::Serialize,\n    serde::Deserialize,\n)]\n#[repr(C, u8)]\npub enum ImageSize {\n    Manual { width: u32, height: u32 },\n    Automatic,\n}\n\n#[derive(\n    Debug,\n    Eq,\n    PartialEq,\n    PartialOrd,\n    Clone,\n    Copy,\n    Hash,\n    SerializeConst,\n    serde::Serialize,\n    serde::Deserialize,\n)]\npub struct ImageAssetOptions {\n    ty: ImageFormat,\n    low_quality_preview: bool,\n    size: ImageSize,\n    preload: bool,\n}\n\nimpl Default for ImageAssetOptions {\n    fn default() -> Self {\n        Self::default()\n    }\n}\n\nimpl ImageAssetOptions {\n    pub const fn new() -> AssetOptionsBuilder<ImageAssetOptions> {\n        AssetOptions::image()\n    }\n\n    pub const fn default() -> Self {\n        Self {\n            ty: ImageFormat::Unknown,\n            low_quality_preview: false,\n            size: ImageSize::Automatic,\n            preload: false,\n        }\n    }\n\n    pub const fn preloaded(&self) -> bool {\n        self.preload\n    }\n\n    pub const fn format(&self) -> ImageFormat {\n        self.ty\n    }\n\n    pub const fn size(&self) -> ImageSize {\n        self.size\n    }\n\n    pub(crate) const fn extension(&self) -> Option<&'static str> {\n        match self.ty {\n            ImageFormat::Png => Some(\"png\"),\n            ImageFormat::Jpg => Some(\"jpg\"),\n            ImageFormat::Webp => Some(\"webp\"),\n            ImageFormat::Avif => Some(\"avif\"),\n            ImageFormat::Unknown => None,\n        }\n    }\n}\n\nimpl AssetOptions {\n    pub const fn image() -> AssetOptionsBuilder<ImageAssetOptions> {\n        AssetOptionsBuilder::variant(ImageAssetOptions::default())\n    }\n}\n\nimpl AssetOptionsBuilder<ImageAssetOptions> {\n    pub const fn with_preload(mut self, preload: bool) -> Self {\n        self.variant.preload = preload;\n        self\n    }\n\n    pub const fn with_format(mut self, format: ImageFormat) -> Self {\n        self.variant.ty = format;\n        self\n    }\n\n    pub const fn with_avif(self) -> Self {\n        self.with_format(ImageFormat::Avif)\n    }\n\n    pub const fn with_webp(self) -> Self {\n        self.with_format(ImageFormat::Webp)\n    }\n\n    pub const fn with_jpg(self) -> Self {\n        self.with_format(ImageFormat::Jpg)\n    }\n\n    pub const fn with_png(self) -> Self {\n        self.with_format(ImageFormat::Png)\n    }\n\n    pub const fn with_size(mut self, size: ImageSize) -> Self {\n        self.variant.size = size;\n        self\n    }\n\n    pub const fn into_asset_options(self) -> AssetOptions {\n        AssetOptions {\n            add_hash: self.add_hash,\n            variant: AssetVariant::Image(self.variant),\n        }\n    }\n}\n"
  },
  {
    "path": "packages/manganis/manganis-07/src/js.rs",
    "content": "use const_serialize_07::SerializeConst;\n\nuse crate::{AssetOptions, AssetOptionsBuilder, AssetVariant};\n\n#[derive(\n    Debug,\n    Eq,\n    PartialEq,\n    PartialOrd,\n    Clone,\n    Copy,\n    Hash,\n    SerializeConst,\n    serde::Serialize,\n    serde::Deserialize,\n)]\npub struct JsAssetOptions {\n    minify: bool,\n    preload: bool,\n    static_head: bool,\n}\n\nimpl Default for JsAssetOptions {\n    fn default() -> Self {\n        Self::default()\n    }\n}\n\nimpl JsAssetOptions {\n    pub const fn new() -> AssetOptionsBuilder<JsAssetOptions> {\n        AssetOptions::js()\n    }\n\n    pub const fn default() -> Self {\n        Self {\n            preload: false,\n            minify: true,\n            static_head: false,\n        }\n    }\n\n    pub const fn preloaded(&self) -> bool {\n        self.preload\n    }\n\n    pub const fn static_head(&self) -> bool {\n        self.static_head\n    }\n\n    pub const fn minified(&self) -> bool {\n        self.minify\n    }\n}\n\nimpl AssetOptions {\n    pub const fn js() -> AssetOptionsBuilder<JsAssetOptions> {\n        AssetOptionsBuilder::variant(JsAssetOptions::default())\n    }\n}\n\nimpl AssetOptionsBuilder<JsAssetOptions> {\n    #[allow(unused)]\n    pub const fn with_minify(mut self, minify: bool) -> Self {\n        self.variant.minify = minify;\n        self\n    }\n\n    #[allow(unused)]\n    pub const fn with_static_head(mut self, static_head: bool) -> Self {\n        self.variant.static_head = static_head;\n        self\n    }\n\n    #[allow(unused)]\n    pub const fn with_preload(mut self, preload: bool) -> Self {\n        self.variant.preload = preload;\n        self\n    }\n\n    pub const fn into_asset_options(self) -> AssetOptions {\n        AssetOptions {\n            add_hash: self.add_hash,\n            variant: AssetVariant::Js(self.variant),\n        }\n    }\n}\n"
  },
  {
    "path": "packages/manganis/manganis-07/src/lib.rs",
    "content": "// Re-export const-serialize under the expected name for the derive macro\nextern crate const_serialize_07 as const_serialize;\n\nmod folder;\npub use folder::*;\n\nmod images;\npub use images::*;\n\nmod options;\npub use options::*;\n\nmod css;\npub use css::*;\n\nmod js;\npub use js::*;\n\nmod asset;\npub use asset::*;\n\nmod css_module;\npub use css_module::*;\n"
  },
  {
    "path": "packages/manganis/manganis-07/src/options.rs",
    "content": "use const_serialize_07::SerializeConst;\n\nuse crate::{\n    CssAssetOptions, CssModuleAssetOptions, FolderAssetOptions, ImageAssetOptions, JsAssetOptions,\n};\n\n#[derive(\n    Debug,\n    Eq,\n    PartialEq,\n    PartialOrd,\n    Clone,\n    Copy,\n    Hash,\n    SerializeConst,\n    serde::Serialize,\n    serde::Deserialize,\n)]\n#[non_exhaustive]\npub struct AssetOptions {\n    pub(crate) add_hash: bool,\n    pub(crate) variant: AssetVariant,\n}\n\nimpl AssetOptions {\n    pub const fn builder() -> AssetOptionsBuilder<()> {\n        AssetOptionsBuilder::new()\n    }\n\n    pub const fn variant(&self) -> &AssetVariant {\n        &self.variant\n    }\n\n    pub const fn hash_suffix(&self) -> bool {\n        self.add_hash\n    }\n\n    pub const fn extension(&self) -> Option<&'static str> {\n        match self.variant {\n            AssetVariant::Image(image) => image.extension(),\n            AssetVariant::Css(_) => Some(\"css\"),\n            AssetVariant::CssModule(_) => Some(\"css\"),\n            AssetVariant::Js(_) => Some(\"js\"),\n            AssetVariant::Folder(_) => None,\n            AssetVariant::Unknown => None,\n        }\n    }\n\n    pub const fn into_asset_options(self) -> AssetOptions {\n        self\n    }\n}\n\npub struct AssetOptionsBuilder<T> {\n    pub(crate) add_hash: bool,\n    pub(crate) variant: T,\n}\n\nimpl Default for AssetOptionsBuilder<()> {\n    fn default() -> Self {\n        Self::default()\n    }\n}\n\nimpl AssetOptionsBuilder<()> {\n    pub const fn new() -> Self {\n        Self {\n            add_hash: true,\n            variant: (),\n        }\n    }\n\n    pub const fn default() -> Self {\n        Self::new()\n    }\n\n    pub const fn into_asset_options(self) -> AssetOptions {\n        AssetOptions {\n            add_hash: self.add_hash,\n            variant: AssetVariant::Unknown,\n        }\n    }\n}\n\nimpl<T> AssetOptionsBuilder<T> {\n    pub(crate) const fn variant(variant: T) -> Self {\n        Self {\n            add_hash: true,\n            variant,\n        }\n    }\n\n    pub const fn with_hash_suffix(mut self, add_hash: bool) -> Self {\n        self.add_hash = add_hash;\n        self\n    }\n}\n\n#[derive(\n    Debug,\n    Eq,\n    PartialEq,\n    PartialOrd,\n    Clone,\n    Copy,\n    Hash,\n    SerializeConst,\n    serde::Serialize,\n    serde::Deserialize,\n)]\n#[repr(C, u8)]\n#[non_exhaustive]\npub enum AssetVariant {\n    Image(ImageAssetOptions),\n    Folder(FolderAssetOptions),\n    Css(CssAssetOptions),\n    CssModule(CssModuleAssetOptions),\n    Js(JsAssetOptions),\n    Unknown,\n}\n"
  },
  {
    "path": "packages/manganis/manganis-core/Cargo.toml",
    "content": "[package]\nname = \"manganis-core\"\nedition = \"2021\"\nversion = { workspace = true }\nauthors = [\"Jonathan Kelley\"]\ndescription = \"core for manganis\"\nlicense = \"MIT OR Apache-2.0\"\nrepository = \"https://github.com/DioxusLabs/dioxus/tree/main/packages/manganis/manganis-core\"\nhomepage = \"https://dioxuslabs.com\"\nkeywords = [\"dom\", \"ui\", \"gui\", \"assets\"]\nreadme = \"../manganis/README.md\"\n\n# be careful with dependencies you add here - these need to get compiled for the proc macro and therefore\n# prevent the main code from compiling!\n[dependencies]\nserde = { workspace = true, features = [\"derive\"] }\nconst-serialize-08 = { package = \"const-serialize\", path = \"../../const-serialize\", version = \"0.8.0-alpha.0\", features = [\"serde\", \"const-serialize-07\"] }\nconst-serialize-07 = { package = \"const-serialize\", version = \"=0.7.2\" }\ndioxus-core-types = { workspace = true, optional = true }\ndioxus-cli-config = { workspace = true, optional = true }\nwinnow = { workspace = true }\n\n[dev-dependencies]\nmanganis = { workspace = true }\n\n[features]\ndioxus = [\"dep:dioxus-core-types\", \"dep:dioxus-cli-config\"]\n"
  },
  {
    "path": "packages/manganis/manganis-core/assets/script.js",
    "content": ""
  },
  {
    "path": "packages/manganis/manganis-core/assets/style.css",
    "content": ""
  },
  {
    "path": "packages/manganis/manganis-core/src/asset.rs",
    "content": "use crate::AssetOptions;\nuse const_serialize_07 as const_serialize;\nuse const_serialize_08::{deserialize_const, ConstStr, SerializeConst};\nuse std::{fmt::Debug, hash::Hash, path::PathBuf};\n\n/// An asset that should be copied by the bundler with some options. This type will be\n/// serialized into the binary.\n/// CLIs that support manganis, should pull out the assets from the link section, optimize,\n/// and write them to the filesystem at [`BundledAsset::bundled_path`] for the application\n/// to use.\n#[derive(\n    Debug,\n    Eq,\n    Clone,\n    Copy,\n    SerializeConst,\n    const_serialize::SerializeConst,\n    serde::Serialize,\n    serde::Deserialize,\n)]\n#[const_serialize(crate = const_serialize_08)]\npub struct BundledAsset {\n    /// The absolute path of the asset\n    absolute_source_path: ConstStr,\n\n    /// The bundled path of the asset\n    bundled_path: ConstStr,\n\n    /// The options for the asset\n    options: AssetOptions,\n}\n\nimpl PartialEq for BundledAsset {\n    fn eq(&self, other: &Self) -> bool {\n        self.absolute_source_path == other.absolute_source_path\n            && self.bundled_path == other.bundled_path\n            && self.options == other.options\n    }\n}\n\nimpl PartialOrd for BundledAsset {\n    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {\n        match self\n            .absolute_source_path\n            .partial_cmp(&other.absolute_source_path)\n        {\n            Some(core::cmp::Ordering::Equal) => {}\n            ord => return ord,\n        }\n        match self.bundled_path.partial_cmp(&other.bundled_path) {\n            Some(core::cmp::Ordering::Equal) => {}\n            ord => return ord,\n        }\n        self.options.partial_cmp(&other.options)\n    }\n}\n\nimpl Hash for BundledAsset {\n    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {\n        self.absolute_source_path.hash(state);\n        self.bundled_path.hash(state);\n        self.options.hash(state);\n    }\n}\n\nimpl BundledAsset {\n    pub const PLACEHOLDER_HASH: &str = \"This should be replaced by dx as part of the build process. If you see this error, make sure you are using a matching version of dx and dioxus and you are not stripping symbols from your binary.\";\n\n    #[doc(hidden)]\n    /// This should only be called from the macro\n    /// Create a new asset\n    pub const fn new(\n        absolute_source_path: &str,\n        bundled_path: &str,\n        options: AssetOptions,\n    ) -> Self {\n        Self {\n            absolute_source_path: ConstStr::new(absolute_source_path),\n            bundled_path: ConstStr::new(bundled_path),\n            options,\n        }\n    }\n\n    /// Get the bundled name of the asset. This identifier cannot be used to read the asset directly\n    pub fn bundled_path(&self) -> &str {\n        self.bundled_path.as_str()\n    }\n\n    /// Get the absolute path of the asset source. This path will not be available when the asset is bundled\n    pub fn absolute_source_path(&self) -> &str {\n        self.absolute_source_path.as_str()\n    }\n\n    /// Get the options for the asset\n    pub const fn options(&self) -> &AssetOptions {\n        &self.options\n    }\n}\n\n/// A bundled asset with some options. The asset can be used in rsx! to reference the asset.\n/// It should not be read directly with [`std::fs::read`] because the path needs to be resolved\n/// relative to the bundle\n///\n/// ```rust, ignore\n/// # use manganis::{asset, Asset};\n/// # use dioxus::prelude::*;\n/// const ASSET: Asset = asset!(\"/assets/image.png\");\n/// rsx! {\n///     img { src: ASSET }\n/// };\n/// ```\n#[allow(unpredictable_function_pointer_comparisons)]\n#[derive(PartialEq, Clone, Copy)]\npub struct Asset {\n    /// A function that returns a pointer to the bundled asset. This will be resolved after the linker has run and\n    /// put into the lazy asset. We use a function instead of using the pointer directly to force the compiler to\n    /// read the static __LINK_SECTION at runtime which will be offset by the hot reloading engine instead\n    /// of at compile time which can't be offset\n    ///\n    /// WARNING: Don't read this directly. Reads can get optimized away at compile time before\n    /// the data for this is filled in by the CLI after the binary is built. Instead, use\n    /// [`std::ptr::read_volatile`] to read the data.\n    bundled: fn() -> &'static [u8],\n    /// The legacy version of [`Self::bundled`]. This is only used for backwards compatibility with older versions of the CLI\n    legacy: fn() -> &'static [u8],\n}\n\nimpl Debug for Asset {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        self.resolve().fmt(f)\n    }\n}\n\nunsafe impl Send for Asset {}\nunsafe impl Sync for Asset {}\n\nimpl Asset {\n    #[doc(hidden)]\n    /// This should only be called from the macro\n    /// Create a new asset from the bundled form of the asset and the link section\n    pub const fn new(\n        bundled: extern \"Rust\" fn() -> &'static [u8],\n        legacy: extern \"Rust\" fn() -> &'static [u8],\n    ) -> Self {\n        Self { bundled, legacy }\n    }\n\n    /// Get the bundled asset\n    pub fn bundled(&self) -> BundledAsset {\n        // Read the slice using volatile reads to prevent the compiler from optimizing\n        // away the read at compile time\n        fn read_slice_volatile(bundled: &'static [u8]) -> Vec<u8> {\n            let ptr = bundled as *const [u8] as *const u8;\n            let len = bundled.len();\n            if ptr.is_null() {\n                panic!(\"Tried to use an asset that was not bundled. Make sure you are compiling dx as the linker\");\n            }\n            let mut bytes = Vec::with_capacity(len);\n            for byte in 0..len {\n                // SAFETY: We checked that the pointer was not null above. The pointer is valid for reads and\n                // since we are reading a u8 there are no alignment requirements\n                let byte = unsafe { std::ptr::read_volatile(ptr.add(byte)) };\n                bytes.push(byte);\n            }\n            bytes\n        }\n\n        let bundled = (self.bundled)();\n        let bytes = read_slice_volatile(bundled);\n        let read = bytes.as_slice();\n        let asset = deserialize_const!(BundledAsset, read).expect(\"Failed to deserialize asset. Make sure you built with the matching version of the Dioxus CLI\").1;\n\n        // If the asset wasn't bundled with the newer format, try the legacy format\n        if asset.bundled_path() == BundledAsset::PLACEHOLDER_HASH {\n            let bundled = (self.legacy)();\n            let bytes = read_slice_volatile(bundled);\n            let read = const_serialize_07::ConstReadBuffer::new(bytes.as_ref());\n            let asset = const_serialize_07::deserialize_const!(BundledAsset, read).expect(\"Failed to deserialize asset. Make sure you built with the matching version of the Dioxus CLI\").1;\n            asset\n        } else {\n            asset\n        }\n    }\n\n    /// Return a canonicalized path to the asset\n    ///\n    /// Attempts to resolve it against an `assets` folder in the current directory.\n    /// If that doesn't exist, it will resolve against the cargo manifest dir\n    pub fn resolve(&self) -> PathBuf {\n        #[cfg(feature = \"dioxus\")]\n        // If the asset is relative, we resolve the asset at the current directory\n        if !dioxus_core_types::is_bundled_app() {\n            return PathBuf::from(self.bundled().absolute_source_path.as_str());\n        }\n\n        #[cfg(feature = \"dioxus\")]\n        let bundle_root = {\n            let base_path = dioxus_cli_config::base_path();\n            let base_path = base_path\n                .as_deref()\n                .map(|base_path| {\n                    let trimmed = base_path.trim_matches('/');\n                    format!(\"/{trimmed}\")\n                })\n                .unwrap_or_default();\n            PathBuf::from(format!(\"{base_path}/assets/\"))\n        };\n        #[cfg(not(feature = \"dioxus\"))]\n        let bundle_root = PathBuf::from(\"/assets/\");\n\n        // Otherwise presumably we're bundled and we can use the bundled path\n        bundle_root.join(PathBuf::from(\n            self.bundled().bundled_path.as_str().trim_start_matches('/'),\n        ))\n    }\n}\n\nimpl From<Asset> for String {\n    fn from(value: Asset) -> Self {\n        value.to_string()\n    }\n}\nimpl From<Asset> for Option<String> {\n    fn from(value: Asset) -> Self {\n        Some(value.to_string())\n    }\n}\n\nimpl std::fmt::Display for Asset {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(f, \"{}\", self.resolve().display())\n    }\n}\n\n#[cfg(feature = \"dioxus\")]\nimpl dioxus_core_types::DioxusFormattable for Asset {\n    fn format(&self) -> std::borrow::Cow<'static, str> {\n        std::borrow::Cow::Owned(self.to_string())\n    }\n}\n"
  },
  {
    "path": "packages/manganis/manganis-core/src/css.rs",
    "content": "use crate::{AssetOptions, AssetOptionsBuilder, AssetVariant};\nuse const_serialize_07 as const_serialize;\nuse const_serialize_08::SerializeConst;\n\n/// Options for a css asset\n#[derive(\n    Debug,\n    Eq,\n    PartialEq,\n    PartialOrd,\n    Clone,\n    Copy,\n    Hash,\n    SerializeConst,\n    const_serialize::SerializeConst,\n    serde::Serialize,\n    serde::Deserialize,\n)]\n#[const_serialize(crate = const_serialize_08)]\npub struct CssAssetOptions {\n    minify: bool,\n    preload: bool,\n    static_head: bool,\n}\n\nimpl Default for CssAssetOptions {\n    fn default() -> Self {\n        Self::default()\n    }\n}\n\nimpl CssAssetOptions {\n    /// Create a new css asset using the builder\n    pub const fn new() -> AssetOptionsBuilder<CssAssetOptions> {\n        AssetOptions::css()\n    }\n\n    /// Create a default css asset options\n    pub const fn default() -> Self {\n        Self {\n            preload: false,\n            minify: true,\n            static_head: false,\n        }\n    }\n\n    /// Check if the asset is preloaded\n    pub const fn preloaded(&self) -> bool {\n        self.preload\n    }\n\n    /// Check if the asset is statically created\n    pub const fn static_head(&self) -> bool {\n        self.static_head\n    }\n\n    /// Check if the asset is minified\n    pub const fn minified(&self) -> bool {\n        self.minify\n    }\n}\n\nimpl AssetOptions {\n    /// Create a new css asset builder\n    ///\n    /// ```rust\n    /// # use manganis::{asset, Asset, AssetOptions};\n    /// const _: Asset = asset!(\"/assets/style.css\", AssetOptions::css());\n    /// ```\n    pub const fn css() -> AssetOptionsBuilder<CssAssetOptions> {\n        AssetOptionsBuilder::variant(CssAssetOptions::default())\n    }\n}\n\nimpl AssetOptionsBuilder<CssAssetOptions> {\n    /// Sets whether the css should be minified (default: true)\n    ///\n    /// Minifying the css can make your site load faster by loading less data\n    ///\n    /// ```rust\n    /// # use manganis::{asset, Asset, AssetOptions};\n    /// const _: Asset = asset!(\"/assets/style.css\", AssetOptions::css().with_minify(false));\n    /// ```\n    pub const fn with_minify(mut self, minify: bool) -> Self {\n        self.variant.minify = minify;\n        self\n    }\n\n    /// Make the asset statically inserted (default: false)\n    ///\n    /// Statically insert the file at compile time.\n    ///\n    /// ```rust\n    /// # use manganis::{asset, Asset, AssetOptions};\n    /// const _: Asset = asset!(\"/assets/style.css\", AssetOptions::css().with_static_head(true));\n    /// ```\n    #[allow(unused)]\n    pub const fn with_static_head(mut self, static_head: bool) -> Self {\n        self.variant.static_head = static_head;\n        self\n    }\n\n    /// Make the asset preloaded\n    ///\n    /// Preloading css will make the file start to load as soon as possible. This is useful for css that is used soon after the page loads or css that may not be used immediately, but should start loading sooner\n    ///\n    /// ```rust\n    /// # use manganis::{asset, Asset, AssetOptions};\n    /// const _: Asset = asset!(\"/assets/style.css\", AssetOptions::css().with_preload(true));\n    /// ```\n    pub const fn with_preload(mut self, preload: bool) -> Self {\n        self.variant.preload = preload;\n        self\n    }\n\n    /// Convert the options into options for a generic asset\n    pub const fn into_asset_options(self) -> AssetOptions {\n        AssetOptions {\n            add_hash: true,\n            variant: AssetVariant::Css(self.variant),\n        }\n    }\n}\n"
  },
  {
    "path": "packages/manganis/manganis-core/src/css_module.rs",
    "content": "use std::{\n    collections::HashSet,\n    hash::{DefaultHasher, Hash, Hasher},\n    path::Path,\n};\n\nuse crate::{AssetOptions, AssetOptionsBuilder, AssetVariant};\nuse const_serialize_07 as const_serialize;\nuse const_serialize_08::SerializeConst;\n\n/// Options for a css module asset\n#[derive(\n    Debug,\n    Eq,\n    PartialEq,\n    PartialOrd,\n    Clone,\n    Copy,\n    Hash,\n    SerializeConst,\n    const_serialize::SerializeConst,\n    serde::Serialize,\n    serde::Deserialize,\n)]\n#[const_serialize(crate = const_serialize_08)]\n#[non_exhaustive]\n#[doc(hidden)]\npub struct CssModuleAssetOptions {\n    minify: bool,\n    preload: bool,\n}\n\nimpl Default for CssModuleAssetOptions {\n    fn default() -> Self {\n        Self::default()\n    }\n}\n\nimpl CssModuleAssetOptions {\n    /// Create a new css asset using the builder\n    pub const fn new() -> AssetOptionsBuilder<CssModuleAssetOptions> {\n        AssetOptions::css_module()\n    }\n\n    /// Create a default css module asset options\n    pub const fn default() -> Self {\n        Self {\n            preload: false,\n            minify: true,\n        }\n    }\n\n    /// Check if the asset is minified\n    pub const fn minified(&self) -> bool {\n        self.minify\n    }\n\n    /// Check if the asset is preloaded\n    pub const fn preloaded(&self) -> bool {\n        self.preload\n    }\n}\n\nimpl AssetOptions {\n    /// Create a new css module asset builder\n    ///\n    /// ```rust\n    /// # use manganis::{asset, Asset, AssetOptions};\n    /// const _: Asset = asset!(\"/assets/style.css\", AssetOptions::css_module());\n    /// ```\n    pub const fn css_module() -> AssetOptionsBuilder<CssModuleAssetOptions> {\n        AssetOptionsBuilder::variant(CssModuleAssetOptions::default())\n    }\n}\n\nimpl AssetOptionsBuilder<CssModuleAssetOptions> {\n    /// Sets whether the css should be minified (default: true)\n    ///\n    /// Minifying the css can make your site load faster by loading less data\n    pub const fn with_minify(mut self, minify: bool) -> Self {\n        self.variant.minify = minify;\n        self\n    }\n\n    /// Make the asset preloaded\n    ///\n    /// Preloading css will make the image start to load as soon as possible. This is useful for css that is used soon after the page loads or css that may not be used immediately, but should start loading sooner\n    pub const fn with_preload(mut self, preload: bool) -> Self {\n        self.variant.preload = preload;\n        self\n    }\n\n    /// Convert the options into options for a generic asset\n    pub const fn into_asset_options(self) -> AssetOptions {\n        AssetOptions {\n            add_hash: self.add_hash,\n            variant: AssetVariant::CssModule(self.variant),\n        }\n    }\n}\n\n/// Create a hash for a css module based on the file path\npub fn create_module_hash(css_path: &Path) -> String {\n    let path_string = css_path.to_string_lossy();\n    let mut hasher = DefaultHasher::new();\n    path_string.hash(&mut hasher);\n    let hash = hasher.finish();\n    format!(\"{:016x}\", hash)[..8].to_string()\n}\n\n/// Collect CSS classes & ids.\n///\n/// This is a rudementary css classes & ids collector.\n/// Idents used only in media queries will not be collected. (not support yet)\n///\n/// There are likely a number of edge cases that will show up.\n///\n/// Returns `(HashSet<Classes>, HashSet<Ids>)`\n#[deprecated(\n    since = \"0.7.3\",\n    note = \"This function is no longer used by the css module system and will be removed in a future release.\"\n)]\npub fn collect_css_idents(css: &str) -> (HashSet<String>, HashSet<String>) {\n    const ALLOWED: &str = \"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-\";\n\n    let mut classes = HashSet::new();\n    let mut ids = HashSet::new();\n\n    // Collected ident name and true for ids.\n    let mut start: Option<(String, bool)> = None;\n\n    // True if we have the first comment start delimiter `/`\n    let mut comment_start = false;\n    // True if we have the first comment end delimiter '*'\n    let mut comment_end = false;\n    // True if we're in a comment scope.\n    let mut in_comment_scope = false;\n\n    // True if we're in a block scope: `#hi { this is block scope }`\n    let mut in_block_scope = false;\n\n    // If we are currently collecting an ident:\n    // - Check if the char is allowed, put it into the ident string.\n    // - If not allowed, finalize the ident string and reset start.\n    // Otherwise:\n    // Check if character is a `.` or `#` representing a class or string, and start collecting.\n    for (_byte_index, c) in css.char_indices() {\n        if let Some(ident) = start.as_mut() {\n            if ALLOWED.find(c).is_some() {\n                // CSS ignore idents that start with a number.\n                // 1. Difficult to process\n                // 2. Avoid false positives (transition: 0.5s)\n                if ident.0.is_empty() && c.is_numeric() {\n                    start = None;\n                    continue;\n                }\n\n                ident.0.push(c);\n            } else {\n                match ident.1 {\n                    true => ids.insert(ident.0.clone()),\n                    false => classes.insert(ident.0.clone()),\n                };\n\n                start = None;\n            }\n        } else {\n            // Handle entering an exiting scopede.\n            match c {\n                // Mark as comment scope if we have comment start: /*\n                '*' if comment_start => {\n                    comment_start = false;\n                    in_comment_scope = true;\n                }\n                // Mark start of comment end if in comment scope: */\n                '*' if in_comment_scope => comment_end = true,\n                // Mark as comment start if not in comment scope and no comment start, mark comment_start\n                '/' if !in_comment_scope => {\n                    comment_start = true;\n                }\n                // If we get the closing delimiter, mark as non-comment scope.\n                '/' if comment_end => {\n                    in_comment_scope = false;\n                    comment_start = false;\n                    comment_end = false;\n                }\n                // Entering & Exiting block scope.\n                '{' => in_block_scope = true,\n                '}' => in_block_scope = false,\n                // Any other character, reset comment start and end if not in scope.\n                _ => {\n                    comment_start = false;\n                    comment_end = false;\n                }\n            }\n\n            // No need to process this char if in bad scope.\n            if in_comment_scope || in_block_scope {\n                continue;\n            }\n\n            match c {\n                '.' => start = Some((String::new(), false)),\n                '#' => start = Some((String::new(), true)),\n                _ => {}\n            }\n        }\n    }\n\n    (classes, ids)\n}\n"
  },
  {
    "path": "packages/manganis/manganis-core/src/css_module_parser.rs",
    "content": "use std::borrow::Cow;\n\nuse winnow::{\n    combinator::{alt, cut_err, delimited, opt, peek, preceded, repeat, terminated},\n    error::{ContextError, ErrMode, ParseError},\n    prelude::*,\n    stream::{AsChar, ContainsToken, Range},\n    token::{none_of, one_of, take_till, take_until, take_while},\n};\n\n/// ```text\n///         v----v inner span\n/// :global(.class)\n/// ^-------------^ outer span\n/// ```\n#[derive(Debug, PartialEq)]\npub struct Global<'s> {\n    pub inner: &'s str,\n    pub outer: &'s str,\n}\n\n#[derive(Debug, PartialEq)]\npub enum CssFragment<'s> {\n    Class(&'s str),\n    Global(Global<'s>),\n}\n\n//************************************************************************//\n\n/// Parses and rewrites CSS class selectors with the hash applied.\n/// Does not modify `:global(...)` selectors.\npub fn transform_css<'a>(\n    css: &'a str,\n    hash: &str,\n) -> Result<String, ParseError<&'a str, ContextError>> {\n    let fragments = parse_css(css)?;\n\n    let mut new_css = String::with_capacity(css.len() * 2);\n    let mut cursor = css;\n\n    for fragment in fragments {\n        let (span, replace) = match fragment {\n            CssFragment::Class(class) => (class, Cow::Owned(apply_hash(class, hash))),\n            CssFragment::Global(Global { inner, outer }) => (outer, Cow::Borrowed(inner)),\n        };\n\n        let (before, after) = cursor.split_at(span.as_ptr() as usize - cursor.as_ptr() as usize);\n        cursor = &after[span.len()..];\n        new_css.push_str(before);\n        new_css.push_str(&replace);\n    }\n\n    new_css.push_str(cursor);\n    Ok(new_css)\n}\n\n/// Gets all the classes in the css files and their rewritten names.\n/// Includes `:global(...)` classes where the name is not changed.\n#[allow(clippy::type_complexity)]\npub fn get_class_mappings<'a>(\n    css: &'a str,\n    hash: &str,\n) -> Result<Vec<(&'a str, Cow<'a, str>)>, ParseError<&'a str, ContextError>> {\n    let fragments = parse_css(css)?;\n    let mut result = Vec::new();\n\n    for c in fragments {\n        match c {\n            CssFragment::Class(class) => {\n                result.push((class, Cow::Owned(apply_hash(class, hash))));\n            }\n            CssFragment::Global(global) => {\n                let global_classes = resolve_global_inner_classes(global)?;\n                result.extend(\n                    global_classes\n                        .into_iter()\n                        .map(|class| (class, Cow::Borrowed(class))),\n                );\n            }\n        }\n    }\n    result.sort_by_key(|e| e.0);\n    result.dedup_by_key(|e| e.0);\n    Ok(result)\n}\n\nfn resolve_global_inner_classes<'a>(\n    global: Global<'a>,\n) -> Result<Vec<&'a str>, ParseError<&'a str, ContextError>> {\n    let input = global.inner;\n    let fragments = selector.parse(input)?;\n    let mut result = Vec::new();\n    for c in fragments {\n        match c {\n            CssFragment::Class(class) => result.push(class),\n            CssFragment::Global(_) => {\n                unreachable!(\"Top level parser should have already errored if globals are nested\")\n            }\n        }\n    }\n    Ok(result)\n}\n\nfn apply_hash(class: &str, hash: &str) -> String {\n    format!(\"{}-{}\", class, hash)\n}\n\n//************************************************************************//\n\npub fn parse_css(input: &str) -> Result<Vec<CssFragment<'_>>, ParseError<&str, ContextError>> {\n    style_rule_block_contents.parse(input)\n}\n\nfn recognize_repeat<'s, O>(\n    range: impl Into<Range>,\n    f: impl Parser<&'s str, O, ErrMode<ContextError>>,\n) -> impl Parser<&'s str, &'s str, ErrMode<ContextError>> {\n    repeat(range, f).fold(|| (), |_, _| ()).take()\n}\n\nfn ws<'s>(input: &mut &'s str) -> ModalResult<&'s str> {\n    recognize_repeat(\n        0..,\n        alt((\n            line_comment,\n            block_comment,\n            take_while(1.., (AsChar::is_space, '\\n', '\\r')),\n        )),\n    )\n    .parse_next(input)\n}\n\nfn line_comment<'s>(input: &mut &'s str) -> ModalResult<&'s str> {\n    (\"//\", take_while(0.., |c| c != '\\n'))\n        .take()\n        .parse_next(input)\n}\n\nfn block_comment<'s>(input: &mut &'s str) -> ModalResult<&'s str> {\n    (\"/*\", cut_err(terminated(take_until(0.., \"*/\"), \"*/\")))\n        .take()\n        .parse_next(input)\n}\n\n// matches a sass interpolation of the form #{...}\nfn sass_interpolation<'s>(input: &mut &'s str) -> ModalResult<&'s str> {\n    (\n        \"#{\",\n        cut_err(terminated(take_till(1.., ('{', '}', '\\n')), '}')),\n    )\n        .take()\n        .parse_next(input)\n}\n\nfn identifier<'s>(input: &mut &'s str) -> ModalResult<&'s str> {\n    (\n        one_of(('_', '-', AsChar::is_alpha)),\n        take_while(0.., ('_', '-', AsChar::is_alphanum)),\n    )\n        .take()\n        .parse_next(input)\n}\n\nfn class<'s>(input: &mut &'s str) -> ModalResult<&'s str> {\n    preceded('.', identifier).parse_next(input)\n}\n\nfn global<'s>(input: &mut &'s str) -> ModalResult<Global<'s>> {\n    let (inner, outer) = preceded(\n        \":global(\",\n        cut_err(terminated(\n            stuff_till(0.., (')', '(', '{')), // inner\n            ')',\n        )),\n    )\n    .with_taken() // outer\n    .parse_next(input)?;\n    Ok(Global { inner, outer })\n}\n\nfn string_dq<'s>(input: &mut &'s str) -> ModalResult<&'s str> {\n    let str_char = alt((none_of(['\"']).void(), \"\\\\\\\"\".void()));\n    let str_chars = recognize_repeat(0.., str_char);\n\n    preceded('\"', cut_err(terminated(str_chars, '\"'))).parse_next(input)\n}\n\nfn string_sq<'s>(input: &mut &'s str) -> ModalResult<&'s str> {\n    let str_char = alt((none_of(['\\'']).void(), \"\\\\'\".void()));\n    let str_chars = recognize_repeat(0.., str_char);\n\n    preceded('\\'', cut_err(terminated(str_chars, '\\''))).parse_next(input)\n}\n\nfn string<'s>(input: &mut &'s str) -> ModalResult<&'s str> {\n    alt((string_dq, string_sq)).parse_next(input)\n}\n\n/// Behaves like take_till except it finds and parses strings and\n/// comments (allowing those to contain the end condition characters).\nfn stuff_till<'s>(\n    range: impl Into<Range>,\n    list: impl ContainsToken<char>,\n) -> impl Parser<&'s str, &'s str, ErrMode<ContextError>> {\n    recognize_repeat(\n        range,\n        alt((\n            string.void(),\n            block_comment.void(),\n            line_comment.void(),\n            sass_interpolation.void(),\n            '/'.void(),\n            '#'.void(),\n            take_till(1.., ('\\'', '\"', '/', '#', list)).void(),\n        )),\n    )\n}\n\nfn selector<'s>(input: &mut &'s str) -> ModalResult<Vec<CssFragment<'s>>> {\n    repeat(\n        1..,\n        alt((\n            class.map(|c| Some(CssFragment::Class(c))),\n            global.map(|g| Some(CssFragment::Global(g))),\n            ':'.map(|_| None),\n            stuff_till(1.., ('.', ';', '{', '}', ':')).map(|_| None),\n        )),\n    )\n    .fold(Vec::new, |mut acc, item| {\n        if let Some(item) = item {\n            acc.push(item);\n        }\n        acc\n    })\n    .parse_next(input)\n}\n\nfn declaration<'s>(input: &mut &'s str) -> ModalResult<&'s str> {\n    (\n        (opt('$'), identifier),\n        ws,\n        ':',\n        terminated(\n            stuff_till(1.., (';', '{', '}')),\n            alt((';', peek('}'))), // semicolon is optional if it's the last element in a rule block\n        ),\n    )\n        .take()\n        .parse_next(input)\n}\n\nfn style_rule_block_statement<'s>(input: &mut &'s str) -> ModalResult<Vec<CssFragment<'s>>> {\n    let content = alt((\n        declaration.map(|_| Vec::new()), //\n        at_rule,\n        style_rule,\n    ));\n    delimited(ws, content, ws).parse_next(input)\n}\n\nfn style_rule_block_contents<'s>(input: &mut &'s str) -> ModalResult<Vec<CssFragment<'s>>> {\n    repeat(0.., style_rule_block_statement)\n        .fold(Vec::new, |mut acc, mut item| {\n            acc.append(&mut item);\n            acc\n        })\n        .parse_next(input)\n}\n\nfn style_rule_block<'s>(input: &mut &'s str) -> ModalResult<Vec<CssFragment<'s>>> {\n    preceded(\n        '{',\n        cut_err(terminated(style_rule_block_contents, (ws, '}'))),\n    )\n    .parse_next(input)\n}\n\nfn style_rule<'s>(input: &mut &'s str) -> ModalResult<Vec<CssFragment<'s>>> {\n    let (mut classes, mut nested_classes) = (selector, style_rule_block).parse_next(input)?;\n    classes.append(&mut nested_classes);\n    Ok(classes)\n}\n\nfn at_rule<'s>(input: &mut &'s str) -> ModalResult<Vec<CssFragment<'s>>> {\n    let (identifier, char) = preceded(\n        '@',\n        cut_err((\n            terminated(identifier, stuff_till(0.., ('{', '}', ';'))),\n            alt(('{', ';', peek('}'))),\n        )),\n    )\n    .parse_next(input)?;\n\n    if char != '{' {\n        return Ok(vec![]);\n    }\n\n    match identifier {\n        \"media\" | \"layer\" | \"container\" | \"include\" => {\n            cut_err(terminated(style_rule_block_contents, '}')).parse_next(input)\n        }\n        _ => {\n            cut_err(terminated(unknown_block_contents, '}')).parse_next(input)?;\n            Ok(vec![])\n        }\n    }\n}\n\nfn unknown_block_contents<'s>(input: &mut &'s str) -> ModalResult<&'s str> {\n    recognize_repeat(\n        0..,\n        alt((\n            stuff_till(1.., ('{', '}')).void(),\n            ('{', cut_err((unknown_block_contents, '}'))).void(),\n        )),\n    )\n    .parse_next(input)\n}\n\n//************************************************************************//\n\n#[test]\nfn test_class() {\n    let mut input = \"._x1a2b Hello\";\n\n    let r = class.parse_next(&mut input);\n    assert_eq!(r, Ok(\"_x1a2b\"));\n}\n\n#[test]\nfn test_selector() {\n    let mut input = \".foo.bar [value=\\\"fa.sdasd\\\"] /* .banana */ // .apple \\n \\t .cry {\";\n\n    let r = selector.parse_next(&mut input);\n    assert_eq!(\n        r,\n        Ok(vec![\n            CssFragment::Class(\"foo\"),\n            CssFragment::Class(\"bar\"),\n            CssFragment::Class(\"cry\")\n        ])\n    );\n\n    let mut input = \"{\";\n\n    let r = selector.take().parse_next(&mut input);\n    assert!(r.is_err());\n}\n\n#[test]\nfn test_declaration() {\n    let mut input = \"background-color \\t : red;\";\n\n    let r = declaration.parse_next(&mut input);\n    assert_eq!(r, Ok(\"background-color \\t : red;\"));\n\n    let r = declaration.parse_next(&mut input);\n    assert!(r.is_err());\n}\n\n#[test]\nfn test_style_rule() {\n    let mut input = \".foo.bar {\n        background-color: red;\n        .baz {\n            color: blue;\n        }\n        $some-scss-var: 10px;\n        @some-at-rule blah blah;\n        @media blah .blah {\n            .moo {\n                color: red;\n            }\n        }\n        @container (width > 700px) {\n            .zoo {\n                color: blue;\n            }\n        }\n    }END\";\n\n    let r = style_rule.parse_next(&mut input);\n    assert_eq!(\n        r,\n        Ok(vec![\n            CssFragment::Class(\"foo\"),\n            CssFragment::Class(\"bar\"),\n            CssFragment::Class(\"baz\"),\n            CssFragment::Class(\"moo\"),\n            CssFragment::Class(\"zoo\")\n        ])\n    );\n\n    assert_eq!(input, \"END\");\n}\n\n#[test]\nfn test_at_rule_simple() {\n    let mut input = \"@simple-rule blah \\\"asd;asd\\\" blah;\";\n\n    let r = at_rule.parse_next(&mut input);\n    assert_eq!(r, Ok(vec![]));\n\n    assert!(input.is_empty());\n}\n\n#[test]\nfn test_at_rule_unknown() {\n    let mut input = \"@unknown blah \\\"asdasd\\\" blah {\n        bunch of stuff {\n            // things inside {\n            blah\n            ' { '\n        }\n\n        .bar {\n            color: blue;\n\n            .baz {\n                color: green;\n            }\n        }\n    }\";\n\n    let r = at_rule.parse_next(&mut input);\n    assert_eq!(r, Ok(vec![]));\n\n    assert!(input.is_empty());\n}\n\n#[test]\nfn test_at_rule_media() {\n    let mut input = \"@media blah \\\"asdasd\\\" blah {\n        .foo {\n            background-color: red;\n        }\n\n        .bar {\n            color: blue;\n\n            .baz {\n                color: green;\n            }\n        }\n    }\";\n\n    let r = at_rule.parse_next(&mut input);\n    assert_eq!(\n        r,\n        Ok(vec![\n            CssFragment::Class(\"foo\"),\n            CssFragment::Class(\"bar\"),\n            CssFragment::Class(\"baz\")\n        ])\n    );\n\n    assert!(input.is_empty());\n}\n\n#[test]\nfn test_at_rule_layer() {\n    let mut input = \"@layer test {\n        .foo {\n            background-color: red;\n        }\n\n        .bar {\n            color: blue;\n\n            .baz {\n                color: green;\n            }\n        }\n    }\";\n\n    let r = at_rule.parse_next(&mut input);\n    assert_eq!(\n        r,\n        Ok(vec![\n            CssFragment::Class(\"foo\"),\n            CssFragment::Class(\"bar\"),\n            CssFragment::Class(\"baz\")\n        ])\n    );\n\n    assert!(input.is_empty());\n}\n\n#[test]\nfn test_top_level() {\n    let mut input = \"// tool.module.scss\n\n        .default_border {\n          border-color: lch(100% 10 10);\n          border-style: dashed double;\n          border-radius: 30px;\n\n        }\n\n        @media testing {\n            .media-foo {\n                color: red;\n            }\n        }\n\n        @layer {\n            .layer-foo {\n                color: blue;\n            }\n        }\n\n        @include mixin {\n            border: none;\n\n            .include-foo {\n                color: green;\n            }\n        }\n\n        @layer foo;\n\n        @debug 1+2 * 3==1+(2 * 3); // true\n\n        .container {\n          padding: 1em;\n          border: 2px solid;\n          border-color: lch(100% 10 10);\n          border-style: dashed double;\n          border-radius: 30px;\n          margin: 1em;\n          background-color: lch(45% 9.5 140.4);\n\n          .bar {\n            color: red;\n          }\n        }\n\n        @debug 1+2 * 3==1+(2 * 3); // true\n        \";\n\n    let r = style_rule_block_contents.parse_next(&mut input);\n    assert_eq!(\n        r,\n        Ok(vec![\n            CssFragment::Class(\"default_border\"),\n            CssFragment::Class(\"media-foo\"),\n            CssFragment::Class(\"layer-foo\"),\n            CssFragment::Class(\"include-foo\"),\n            CssFragment::Class(\"container\"),\n            CssFragment::Class(\"bar\"),\n        ])\n    );\n\n    println!(\"{input}\");\n    assert!(input.is_empty());\n}\n\n#[test]\nfn test_sass_interpolation() {\n    let mut input = \"#{$test-test}END\";\n\n    let r = sass_interpolation.parse_next(&mut input);\n    assert_eq!(r, Ok(\"#{$test-test}\"));\n\n    assert_eq!(input, \"END\");\n\n    let mut input = \"#{$test-test\n        }END\";\n    let r = sass_interpolation.parse_next(&mut input);\n    assert!(r.is_err());\n\n    let mut input = \"#{$test-test\";\n    let r = sass_interpolation.parse_next(&mut input);\n    assert!(r.is_err());\n\n    let mut input = \"#{$test-te{st}\";\n    let r = sass_interpolation.parse_next(&mut input);\n    assert!(r.is_err());\n}\n\n#[test]\nfn test_get_class_mappings() {\n    let css = r#\".foo.bar {\n        background-color: red;\n        :global(.baz) {\n            color: blue;\n        }\n        :global(.bag .biz) {\n            color: blue;\n        }\n        .zig {\n\n        }\n        .bong {}\n        .zig {\n            color: blue;\n        }\n    }\"#;\n    let hash = \"abc1234\";\n    let mappings = get_class_mappings(css, hash).unwrap();\n    let expected = [\n        (\"bag\", \"bag\"),\n        (\"bar\", \"bar-abc1234\"),\n        (\"baz\", \"baz\"),\n        (\"biz\", \"biz\"),\n        (\"bong\", \"bong-abc1234\"),\n        (\"foo\", \"foo-abc1234\"),\n        (\"zig\", \"zig-abc1234\"),\n    ];\n    if mappings.len() != expected.len() {\n        panic!(\n            \"Expected {} mappings, got {}\",\n            expected.len(),\n            mappings.len()\n        );\n    }\n    for (i, (original, hashed)) in mappings.iter().enumerate() {\n        assert_eq!(expected[i].0, *original);\n        assert_eq!(expected[i].1, *hashed);\n    }\n}\n\n#[test]\nfn test_parser_error_on_nested_globals() {\n    let css = r#\".foo :global(.bar .baz) {\n        color: blue;\n    }\"#;\n    let result = parse_css(css);\n    assert!(result.is_ok());\n    let css = r#\".foo :global(.bar :global(.baz)) {\n        color: blue;\n    }\"#;\n    let result = parse_css(css);\n    assert!(result.is_err());\n}\n\n#[test]\n#[should_panic]\nfn test_resolve_global_inner_classes_nested() {\n    let global = Global {\n        inner: \".foo :global(.bar)\",\n        outer: \":global(.foo :global(.bar))\",\n    };\n    let _ = resolve_global_inner_classes(global);\n}\n"
  },
  {
    "path": "packages/manganis/manganis-core/src/ffi.rs",
    "content": "use crate::BundledAsset;\nuse const_serialize::{ConstStr, SerializeConst};\nuse const_serialize_08 as const_serialize;\nuse std::hash::Hash;\n\n/// Unified symbol data that can represent both assets and permissions\n///\n/// This enum is used to serialize different types of metadata into the binary\n/// using the same `__ASSETS__` symbol prefix. The CBOR format allows for\n/// self-describing data, making it easy to add new variants in the future.\n///\n/// Variant order does NOT matter for CBOR enum serialization - variants are\n/// matched by name (string), not by position or tag value.\n#[derive(Debug, Clone, Copy, PartialEq, Eq, SerializeConst)]\n#[repr(C, u8)]\n#[allow(clippy::large_enum_variant)]\n#[non_exhaustive]\npub enum SymbolData {\n    /// An asset that should be bundled with the application\n    Asset(BundledAsset),\n\n    /// Android plugin metadata (prebuilt artifacts + Gradle deps)\n    AndroidArtifact(AndroidArtifactMetadata),\n\n    /// Swift package metadata (SPM location + product)\n    SwiftPackage(SwiftPackageMetadata),\n}\n\n/// Platform categories for permission mapping\n#[repr(u8)]\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, SerializeConst)]\npub enum Platform {\n    Android,\n    Ios,\n    Macos,\n}\n\n/// Bit flags for supported platforms\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, SerializeConst)]\npub struct PlatformFlags(u8);\n\nimpl PlatformFlags {\n    pub const fn new() -> Self {\n        Self(0)\n    }\n}\n\nimpl Default for PlatformFlags {\n    fn default() -> Self {\n        Self::new()\n    }\n}\n\nimpl PlatformFlags {\n    pub const fn with_platform(mut self, platform: Platform) -> Self {\n        self.0 |= 1 << platform as u8;\n        self\n    }\n\n    pub const fn supports(&self, platform: Platform) -> bool {\n        (self.0 & (1 << platform as u8)) != 0\n    }\n\n    pub const fn all() -> Self {\n        Self(0b000111) // Android + iOS + macOS\n    }\n\n    pub const fn mobile() -> Self {\n        Self(0b000011) // Android + iOS\n    }\n}\n\n/// Platform-specific permission identifiers\n#[derive(Debug, Clone, PartialEq, Eq, Hash)]\npub struct PlatformIdentifiers {\n    pub android: Option<ConstStr>,\n    pub ios: Option<ConstStr>,\n    pub macos: Option<ConstStr>,\n}\n\n/// Metadata describing an Android plugin artifact (.aar) that must be copied into the host Gradle project.\n#[derive(Debug, Clone, Copy, PartialEq, Eq, SerializeConst)]\npub struct AndroidArtifactMetadata {\n    pub plugin_name: ConstStr,\n    pub artifact_path: ConstStr,\n    pub gradle_dependencies: ConstStr,\n}\n\nimpl AndroidArtifactMetadata {\n    pub const fn new(\n        plugin_name: &'static str,\n        artifact_path: &'static str,\n        gradle_dependencies: &'static str,\n    ) -> Self {\n        Self {\n            plugin_name: ConstStr::new(plugin_name),\n            artifact_path: ConstStr::new(artifact_path),\n            gradle_dependencies: ConstStr::new(gradle_dependencies),\n        }\n    }\n}\n\n/// Metadata for a Swift package that needs to be linked into the app (iOS/macOS).\n#[derive(Debug, Clone, Copy, PartialEq, Eq, SerializeConst)]\npub struct SwiftPackageMetadata {\n    pub plugin_name: ConstStr,\n    pub package_path: ConstStr,\n    pub product: ConstStr,\n}\n\nimpl SwiftPackageMetadata {\n    pub const fn new(\n        plugin_name: &'static str,\n        package_path: &'static str,\n        product: &'static str,\n    ) -> Self {\n        Self {\n            plugin_name: ConstStr::new(plugin_name),\n            package_path: ConstStr::new(package_path),\n            product: ConstStr::new(product),\n        }\n    }\n}\n"
  },
  {
    "path": "packages/manganis/manganis-core/src/folder.rs",
    "content": "use const_serialize_07 as const_serialize;\nuse const_serialize_08::SerializeConst;\n\nuse crate::{AssetOptions, AssetOptionsBuilder};\n\n/// The builder for a folder asset.\n#[derive(\n    Debug,\n    Eq,\n    PartialEq,\n    PartialOrd,\n    Clone,\n    Copy,\n    Hash,\n    SerializeConst,\n    const_serialize::SerializeConst,\n    serde::Serialize,\n    serde::Deserialize,\n)]\n#[const_serialize(crate = const_serialize_08)]\npub struct FolderAssetOptions {}\n\nimpl Default for FolderAssetOptions {\n    fn default() -> Self {\n        Self::default()\n    }\n}\n\nimpl FolderAssetOptions {\n    /// Create a new folder asset builder\n    pub const fn new() -> AssetOptionsBuilder<FolderAssetOptions> {\n        AssetOptions::folder()\n    }\n\n    /// Create a default folder asset options\n    pub const fn default() -> Self {\n        Self {}\n    }\n}\n\nimpl AssetOptions {\n    /// Create a new folder asset builder\n    ///\n    /// ```rust\n    /// # use manganis::{asset, Asset, AssetOptions};\n    /// const _: Asset = asset!(\"/assets\", AssetOptions::folder());\n    /// ```\n    pub const fn folder() -> AssetOptionsBuilder<FolderAssetOptions> {\n        AssetOptionsBuilder::variant(FolderAssetOptions::default())\n    }\n}\n\nimpl AssetOptionsBuilder<FolderAssetOptions> {\n    /// Convert the options into options for a generic asset\n    pub const fn into_asset_options(self) -> AssetOptions {\n        AssetOptions {\n            add_hash: false,\n            variant: crate::AssetVariant::Folder(self.variant),\n        }\n    }\n}\n"
  },
  {
    "path": "packages/manganis/manganis-core/src/images.rs",
    "content": "use const_serialize_07 as const_serialize;\nuse const_serialize_08::SerializeConst;\n\nuse crate::{AssetOptions, AssetOptionsBuilder, AssetVariant};\n\n/// The type of an image. You can read more about the tradeoffs between image formats [here](https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Image_types)\n#[derive(\n    Debug,\n    Eq,\n    PartialEq,\n    PartialOrd,\n    Clone,\n    Copy,\n    Hash,\n    SerializeConst,\n    const_serialize::SerializeConst,\n    serde::Serialize,\n    serde::Deserialize,\n)]\n#[const_serialize(crate = const_serialize_08)]\n#[repr(u8)]\npub enum ImageFormat {\n    /// A png image. Png images cannot contain transparency and tend to compress worse than other formats\n    Png,\n    /// A jpg image. Jpg images can contain transparency and tend to compress better than png images\n    Jpg,\n    /// A webp image. Webp images can contain transparency and tend to compress better than jpg images\n    Webp,\n    /// An avif image. Avif images can compress slightly better than webp images but are not supported by all browsers\n    Avif,\n    /// An unknown image type\n    Unknown,\n}\n\n/// The size of an image asset\n#[derive(\n    Debug,\n    Eq,\n    PartialEq,\n    PartialOrd,\n    Clone,\n    Copy,\n    Hash,\n    SerializeConst,\n    const_serialize::SerializeConst,\n    serde::Serialize,\n    serde::Deserialize,\n)]\n#[const_serialize(crate = const_serialize_08)]\n#[repr(C, u8)]\npub enum ImageSize {\n    /// A manual size in pixels\n    Manual {\n        /// The width of the image in pixels\n        width: u32,\n        /// The height of the image in pixels\n        height: u32,\n    },\n    /// The size will be automatically determined from the image source\n    Automatic,\n}\n\n/// Options for an image asset\n#[derive(\n    Debug,\n    Eq,\n    PartialEq,\n    PartialOrd,\n    Clone,\n    Copy,\n    Hash,\n    SerializeConst,\n    const_serialize::SerializeConst,\n    serde::Serialize,\n    serde::Deserialize,\n)]\n#[const_serialize(crate = const_serialize_08)]\npub struct ImageAssetOptions {\n    ty: ImageFormat,\n    low_quality_preview: bool,\n    size: ImageSize,\n    preload: bool,\n}\n\nimpl Default for ImageAssetOptions {\n    fn default() -> Self {\n        Self::default()\n    }\n}\n\nimpl ImageAssetOptions {\n    /// Create a new builder for image asset options\n    pub const fn new() -> AssetOptionsBuilder<ImageAssetOptions> {\n        AssetOptions::image()\n    }\n\n    /// Create a default image asset options\n    pub const fn default() -> Self {\n        Self {\n            ty: ImageFormat::Unknown,\n            low_quality_preview: false,\n            size: ImageSize::Automatic,\n            preload: false,\n        }\n    }\n\n    /// Check if the asset is preloaded\n    pub const fn preloaded(&self) -> bool {\n        self.preload\n    }\n\n    /// Get the format of the image\n    pub const fn format(&self) -> ImageFormat {\n        self.ty\n    }\n\n    /// Get the size of the image\n    pub const fn size(&self) -> ImageSize {\n        self.size\n    }\n\n    pub(crate) const fn extension(&self) -> Option<&'static str> {\n        match self.ty {\n            ImageFormat::Png => Some(\"png\"),\n            ImageFormat::Jpg => Some(\"jpg\"),\n            ImageFormat::Webp => Some(\"webp\"),\n            ImageFormat::Avif => Some(\"avif\"),\n            ImageFormat::Unknown => None,\n        }\n    }\n}\n\nimpl AssetOptions {\n    /// Create a new image asset builder\n    ///\n    /// ```rust\n    /// # use manganis::{asset, Asset, AssetOptions};\n    /// const _: Asset = asset!(\"/assets/image.png\", AssetOptions::image());\n    /// ```\n    pub const fn image() -> AssetOptionsBuilder<ImageAssetOptions> {\n        AssetOptionsBuilder::variant(ImageAssetOptions::default())\n    }\n}\n\nimpl AssetOptionsBuilder<ImageAssetOptions> {\n    /// Make the asset preloaded\n    ///\n    /// Preloading an image will make the image start to load as soon as possible. This is useful for images that will be displayed soon after the page loads or images that may not be visible immediately, but should start loading sooner\n    ///\n    /// ```rust\n    /// # use manganis::{asset, Asset, AssetOptions};\n    /// const _: Asset = asset!(\"/assets/image.png\", AssetOptions::image().with_preload(true));\n    /// ```\n    pub const fn with_preload(mut self, preload: bool) -> Self {\n        self.variant.preload = preload;\n        self\n    }\n\n    /// Sets the format of the image\n    ///\n    /// Choosing the right format can make your site load much faster. Webp and avif images tend to be a good default for most images\n    ///\n    /// ```rust\n    /// # use manganis::{asset, Asset, AssetOptions, ImageFormat};\n    /// const _: Asset = asset!(\"/assets/image.png\", AssetOptions::image().with_format(ImageFormat::Webp));\n    /// ```\n    pub const fn with_format(mut self, format: ImageFormat) -> Self {\n        self.variant.ty = format;\n        self\n    }\n\n    /// Sets the format of the image to [`ImageFormat::Avif`]\n    ///\n    /// Avif images tend to be a good default for most images rendered in browser because\n    /// they compress images well\n    ///\n    /// ```rust\n    /// # use manganis::{asset, Asset, AssetOptions, ImageFormat};\n    /// const _: Asset = asset!(\"/assets/image.png\", AssetOptions::image().with_avif());\n    /// ```\n    pub const fn with_avif(self) -> Self {\n        self.with_format(ImageFormat::Avif)\n    }\n\n    /// Sets the format of the image to [`ImageFormat::Webp`]\n    ///\n    /// Webp images tend to be a good default for most images rendered in browser because\n    /// they compress images well\n    ///\n    /// ```rust\n    /// # use manganis::{asset, Asset, AssetOptions, ImageFormat};\n    /// const _: Asset = asset!(\"/assets/image.png\", AssetOptions::image().with_webp());\n    /// ```\n    pub const fn with_webp(self) -> Self {\n        self.with_format(ImageFormat::Webp)\n    }\n\n    /// Sets the format of the image to [`ImageFormat::Jpg`]\n    ///\n    /// Jpeg images compress much better than [`ImageFormat::Png`], but worse than [`ImageFormat::Webp`] or [`ImageFormat::Avif`]\n    ///\n    /// ```rust\n    /// # use manganis::{asset, Asset, AssetOptions, ImageFormat};\n    /// const _: Asset = asset!(\"/assets/image.png\", AssetOptions::image().with_jpg());\n    /// ```\n    pub const fn with_jpg(self) -> Self {\n        self.with_format(ImageFormat::Jpg)\n    }\n\n    /// Sets the format of the image to [`ImageFormat::Png`]\n    ///\n    /// Png images don't compress very well, so they are not recommended for large images\n    ///\n    /// ```rust\n    /// # use manganis::{asset, Asset, AssetOptions, ImageFormat};\n    /// const _: Asset = asset!(\"/assets/image.png\", AssetOptions::image().with_png());\n    /// ```\n    pub const fn with_png(self) -> Self {\n        self.with_format(ImageFormat::Png)\n    }\n\n    /// Sets the size of the image\n    ///\n    /// If you only use the image in one place, you can set the size of the image to the size it will be displayed at. This will make the image load faster\n    ///\n    /// ```rust\n    /// # use manganis::{asset, Asset, AssetOptions, ImageSize};\n    /// const _: Asset = asset!(\"/assets/image.png\", AssetOptions::image().with_size(ImageSize::Manual { width: 512, height: 512 }));\n    /// ```\n    pub const fn with_size(mut self, size: ImageSize) -> Self {\n        self.variant.size = size;\n        self\n    }\n\n    /// Convert the options into options for a generic asset\n    pub const fn into_asset_options(self) -> AssetOptions {\n        AssetOptions {\n            add_hash: self.add_hash,\n            variant: AssetVariant::Image(self.variant),\n        }\n    }\n}\n"
  },
  {
    "path": "packages/manganis/manganis-core/src/js.rs",
    "content": "use const_serialize_07 as const_serialize;\nuse const_serialize_08::SerializeConst;\n\nuse crate::{AssetOptions, AssetOptionsBuilder, AssetVariant};\n\n/// Options for a javascript asset\n#[derive(\n    Debug,\n    Eq,\n    PartialEq,\n    PartialOrd,\n    Clone,\n    Copy,\n    Hash,\n    SerializeConst,\n    const_serialize::SerializeConst,\n    serde::Serialize,\n    serde::Deserialize,\n)]\n#[const_serialize(crate = const_serialize_08)]\npub struct JsAssetOptions {\n    minify: bool,\n    preload: bool,\n    static_head: bool,\n}\n\nimpl Default for JsAssetOptions {\n    fn default() -> Self {\n        Self::default()\n    }\n}\n\nimpl JsAssetOptions {\n    /// Create a new js asset options builder\n    pub const fn new() -> AssetOptionsBuilder<JsAssetOptions> {\n        AssetOptions::js()\n    }\n\n    /// Create a default js asset options\n    pub const fn default() -> Self {\n        Self {\n            preload: false,\n            minify: true,\n            static_head: false,\n        }\n    }\n\n    /// Check if the asset is preloaded\n    pub const fn preloaded(&self) -> bool {\n        self.preload\n    }\n\n    /// Check if the asset is statically created\n    pub const fn static_head(&self) -> bool {\n        self.static_head\n    }\n\n    /// Check if the asset is minified\n    pub const fn minified(&self) -> bool {\n        self.minify\n    }\n}\n\nimpl AssetOptions {\n    /// Create a new js asset builder\n    ///\n    /// ```rust\n    /// # use manganis::{asset, Asset, AssetOptions};\n    /// const _: Asset = asset!(\"/assets/script.js\", AssetOptions::js());\n    /// ```\n    pub const fn js() -> AssetOptionsBuilder<JsAssetOptions> {\n        AssetOptionsBuilder::variant(JsAssetOptions::default())\n    }\n}\n\nimpl AssetOptionsBuilder<JsAssetOptions> {\n    /// Sets whether the js should be minified (default: true)\n    ///\n    /// Minifying the js can make your site load faster by loading less data\n    ///\n    /// ```rust\n    /// # use manganis::{asset, Asset, AssetOptions};\n    /// const _: Asset = asset!(\"/assets/script.js\", AssetOptions::js().with_minify(false));\n    /// ```\n    #[allow(unused)]\n    pub const fn with_minify(mut self, minify: bool) -> Self {\n        self.variant.minify = minify;\n        self\n    }\n\n    /// Make the asset statically inserted (default: false)\n    ///\n    /// Statically insert the file at compile time.\n    ///\n    /// ```rust\n    /// # use manganis::{asset, Asset, AssetOptions};\n    /// const _: Asset = asset!(\"/assets/script.js\", AssetOptions::js().with_static_head(true));\n    /// ```\n    #[allow(unused)]\n    pub const fn with_static_head(mut self, static_head: bool) -> Self {\n        self.variant.static_head = static_head;\n        self\n    }\n\n    /// Make the asset preloaded\n    ///\n    /// Preloading the javascript will make the javascript start to load as soon as possible. This is useful for javascript that will be used soon after the page loads or javascript that may not be used immediately, but should start loading sooner\n    ///\n    /// ```rust\n    /// # use manganis::{asset, Asset, AssetOptions};\n    /// const _: Asset = asset!(\"/assets/script.js\", AssetOptions::js().with_preload(true));\n    /// ```\n    #[allow(unused)]\n    pub const fn with_preload(mut self, preload: bool) -> Self {\n        self.variant.preload = preload;\n        self\n    }\n\n    /// Convert the builder into asset options with the given variant\n    pub const fn into_asset_options(self) -> AssetOptions {\n        AssetOptions {\n            add_hash: self.add_hash,\n            variant: AssetVariant::Js(self.variant),\n        }\n    }\n}\n"
  },
  {
    "path": "packages/manganis/manganis-core/src/lib.rs",
    "content": "mod folder;\npub use folder::*;\n\nmod images;\npub use images::*;\n\nmod options;\npub use options::*;\n\nmod css;\npub use css::*;\n\nmod js;\npub use js::*;\n\nmod asset;\npub use asset::*;\n\nmod css_module;\npub use css_module::*;\n\nmod css_module_parser;\npub use css_module_parser::*;\n\nmod ffi;\npub use ffi::*;\n"
  },
  {
    "path": "packages/manganis/manganis-core/src/options.rs",
    "content": "use const_serialize_07 as const_serialize;\nuse const_serialize_08::SerializeConst;\n\nuse crate::{\n    CssAssetOptions, CssModuleAssetOptions, FolderAssetOptions, ImageAssetOptions, JsAssetOptions,\n};\n\n/// Settings for a generic asset\n#[derive(\n    Debug,\n    Eq,\n    PartialEq,\n    PartialOrd,\n    Clone,\n    Copy,\n    Hash,\n    SerializeConst,\n    const_serialize::SerializeConst,\n    serde::Serialize,\n    serde::Deserialize,\n)]\n#[const_serialize(crate = const_serialize_08)]\n#[non_exhaustive]\npub struct AssetOptions {\n    /// If a hash should be added to the asset path\n    pub(crate) add_hash: bool,\n    /// The variant of the asset\n    pub(crate) variant: AssetVariant,\n}\n\nimpl AssetOptions {\n    /// Create a new asset options builder\n    pub const fn builder() -> AssetOptionsBuilder<()> {\n        AssetOptionsBuilder::new()\n    }\n\n    /// Get the variant of the asset\n    pub const fn variant(&self) -> &AssetVariant {\n        &self.variant\n    }\n\n    /// Check if a hash should be added to the asset path\n    pub const fn hash_suffix(&self) -> bool {\n        self.add_hash\n    }\n\n    /// Try to get the extension for the asset. If the asset options don't define an extension, this will return None\n    pub const fn extension(&self) -> Option<&'static str> {\n        match self.variant {\n            AssetVariant::Image(image) => image.extension(),\n            AssetVariant::Css(_) => Some(\"css\"),\n            AssetVariant::CssModule(_) => Some(\"css\"),\n            AssetVariant::Js(_) => Some(\"js\"),\n            AssetVariant::Folder(_) => None,\n            AssetVariant::Unknown => None,\n        }\n    }\n\n    /// Convert the options into options for a generic asset\n    pub const fn into_asset_options(self) -> AssetOptions {\n        self\n    }\n}\n\n/// A builder for [`AssetOptions`]\n///\n/// ```rust\n/// # use manganis::{AssetOptions, Asset, asset};\n/// static ASSET: Asset = asset!(\n///     \"/assets/style.css\",\n///     AssetOptions::builder()\n///     .with_hash_suffix(false)\n/// );\n/// ```\npub struct AssetOptionsBuilder<T> {\n    /// If a hash should be added to the asset path\n    pub(crate) add_hash: bool,\n    /// The variant of the asset\n    pub(crate) variant: T,\n}\n\nimpl Default for AssetOptionsBuilder<()> {\n    fn default() -> Self {\n        Self::default()\n    }\n}\n\nimpl AssetOptionsBuilder<()> {\n    /// Create a new asset options builder with an unknown variant\n    pub const fn new() -> Self {\n        Self {\n            add_hash: true,\n            variant: (),\n        }\n    }\n\n    /// Create a default asset options builder\n    pub const fn default() -> Self {\n        Self::new()\n    }\n\n    /// Convert the builder into asset options with the given variant\n    pub const fn into_asset_options(self) -> AssetOptions {\n        AssetOptions {\n            add_hash: self.add_hash,\n            variant: AssetVariant::Unknown,\n        }\n    }\n}\n\nimpl<T> AssetOptionsBuilder<T> {\n    /// Create a new asset options builder with the given variant\n    pub const fn variant(variant: T) -> Self {\n        Self {\n            add_hash: true,\n            variant,\n        }\n    }\n\n    /// Set whether a hash should be added to the asset path. Manganis adds hashes to asset paths by default\n    /// for [cache busting](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Caching#cache_busting).\n    /// With hashed assets, you can serve the asset with a long expiration time, and when the asset changes,\n    /// the hash in the path will change, causing the browser to fetch the new version.\n    ///\n    /// This method will only effect if the hash is added to the bundled asset path. If you are using the asset\n    /// macro, the asset struct still needs to be used in your rust code to ensure the asset is included in the binary.\n    ///\n    /// <div class=\"warning\">\n    ///\n    /// If you are using an asset outside of rust code where you know what the asset hash will be, you must use the\n    /// `#[used]` attribute to ensure the asset is included in the binary even if it is not referenced in the code.\n    ///\n    /// ```rust\n    /// #[used]\n    /// static ASSET: manganis::Asset = manganis::asset!(\n    ///     \"/assets/style.css\",\n    ///     manganis::AssetOptions::builder()\n    ///         .with_hash_suffix(false)\n    /// );\n    /// ```\n    ///\n    /// </div>\n    pub const fn with_hash_suffix(mut self, add_hash: bool) -> Self {\n        self.add_hash = add_hash;\n        self\n    }\n}\n\n/// Settings for a specific type of asset\n#[derive(\n    Debug,\n    Eq,\n    PartialEq,\n    PartialOrd,\n    Clone,\n    Copy,\n    Hash,\n    SerializeConst,\n    const_serialize::SerializeConst,\n    serde::Serialize,\n    serde::Deserialize,\n)]\n#[const_serialize(crate = const_serialize_08)]\n#[repr(C, u8)]\n#[non_exhaustive]\npub enum AssetVariant {\n    /// An image asset\n    Image(ImageAssetOptions),\n    /// A folder asset\n    Folder(FolderAssetOptions),\n    /// A css asset\n    Css(CssAssetOptions),\n    /// A css module asset\n    CssModule(CssModuleAssetOptions),\n    /// A javascript asset\n    Js(JsAssetOptions),\n    /// An unknown asset\n    Unknown,\n}\n"
  },
  {
    "path": "packages/manganis/manganis-macro/Cargo.toml",
    "content": "[package]\nname = \"manganis-macro\"\nversion.workspace = true\nedition = \"2021\"\nauthors = [\"Evan Almloff\"]\ndescription = \"Ergonomic, automatic, cross crate asset collection and optimization\"\nlicense = \"MIT OR Apache-2.0\"\nrepository = \"https://github.com/DioxusLabs/dioxus/tree/main/packages/manganis/manganis-macro\"\nhomepage = \"https://dioxuslabs.com\"\nkeywords = [\"assets\"]\n\n[lib]\nproc-macro = true\n\n# be careful with dependencies you add here - these need to get compiled for the proc macro and therefore\n# prevent the main code from compiling!\n[dependencies]\nproc-macro2 = { workspace = true, features = [\"span-locations\"] }\nquote = { workspace = true }\nsyn = { workspace = true, features = [\"full\", \"extra-traits\"] }\nmanganis-core = { workspace = true }\ndunce = { workspace = true }\nmacro-string = { workspace = true }\n\n[features]\ndefault = []\n\n[dev-dependencies]\nmanganis = { workspace = true }\n"
  },
  {
    "path": "packages/manganis/manganis-macro/README.md",
    "content": "# Manganis Macro\n\nThis crate contains the macro used to interact with the Manganis asset system. \n"
  },
  {
    "path": "packages/manganis/manganis-macro/assets/asset.txt",
    "content": ""
  },
  {
    "path": "packages/manganis/manganis-macro/assets/script.js",
    "content": ""
  },
  {
    "path": "packages/manganis/manganis-macro/assets/style.css",
    "content": ""
  },
  {
    "path": "packages/manganis/manganis-macro/src/asset.rs",
    "content": "use crate::{linker::generate_link_section, resolve_path, AssetParseError};\nuse macro_string::MacroString;\nuse proc_macro2::TokenStream as TokenStream2;\nuse quote::{quote, ToTokens};\nuse std::{\n    hash::{DefaultHasher, Hash, Hasher},\n    path::{Path, PathBuf},\n};\nuse syn::{\n    parse::{Parse, ParseStream},\n    spanned::Spanned as _,\n    Token,\n};\n\npub struct AssetParser {\n    /// The token(s) of the source string, for error reporting\n    pub(crate) path_expr: proc_macro2::TokenStream,\n\n    /// The asset itself\n    pub(crate) asset: Result<PathBuf, AssetParseError>,\n\n    /// The source of the trailing options\n    pub(crate) options: TokenStream2,\n}\n\nimpl Parse for AssetParser {\n    // we can take\n    //\n    // This gives you the Asset type - it's generic and basically unrefined\n    // ```\n    // asset!(\"/assets/myfile.png\")\n    // ```\n    //\n    // To narrow the type, use a method call to get the refined type\n    // ```\n    // asset!(\n    //     \"/assets/myfile.png\",\n    //      AssetOptions::image()\n    //        .format(ImageFormat::Jpg)\n    //        .size(512, 512)\n    // )\n    // ```\n    //\n    // But we need to decide the hint first before parsing the options\n    fn parse(input: ParseStream) -> syn::Result<Self> {\n        // And then parse the options\n        let (MacroString(src), path_expr) = input.call(crate::parse_with_tokens)?;\n        let asset = resolve_path(&src, path_expr.span());\n        let _comma = input.parse::<Token![,]>();\n        let options = input.parse()?;\n\n        Ok(Self {\n            path_expr,\n            asset,\n            options,\n        })\n    }\n}\n\nimpl ToTokens for AssetParser {\n    // The manganis macro outputs info to two different places:\n    // 1) The crate the macro was invoked in\n    //   - It needs the hashed contents of the file, the file path, and the file options\n    //   - Most of this is just forwarding the input, the only thing that the macro needs to do is hash the file contents\n    // 2) A bundler that supports manganis (currently just dioxus-cli)\n    //   - The macro needs to output the absolute path to the asset for the bundler to find later\n    //   - It also needs to serialize the bundled asset along with the asset options for the bundler to use later\n    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {\n        match self.asset.as_ref() {\n            Ok(asset) => tokens.extend(self.expand_asset_tokens(asset)),\n            Err(err) => tokens.extend(self.error_tokens(err)),\n        }\n    }\n}\n\nimpl AssetParser {\n    pub(crate) fn expand_asset_tokens(&self, asset: &Path) -> proc_macro2::TokenStream {\n        let asset_string = asset.to_string_lossy();\n        let mut asset_str = proc_macro2::Literal::string(&asset_string);\n        asset_str.set_span(self.path_expr.span());\n\n        let mut hash = DefaultHasher::new();\n        format!(\"{:?}\", self.options.span()).hash(&mut hash);\n        format!(\"{:?}\", self.options.to_string()).hash(&mut hash);\n        asset_string.hash(&mut hash);\n        let asset_hash = format!(\"{:016x}\", hash.finish());\n\n        // Generate the link section for the asset. The link section includes the source path and the\n        // output path of the asset. We force the asset to be included in the binary even if it is unused\n        // if the asset is unhashed\n        let link_section = generate_link_section(quote!(__ASSET), &asset_hash);\n\n        // generate the asset::new method to deprecate the `./assets/blah.css` syntax\n        let constructor = if asset.is_relative() {\n            quote! { create_bundled_asset_relative }\n        } else {\n            quote! { create_bundled_asset }\n        };\n\n        let options = if self.options.is_empty() {\n            quote! { manganis::AssetOptions::builder() }\n        } else {\n            self.options.clone()\n        };\n\n        quote! {\n            {\n                // The source is used by the CLI to copy the asset\n                const __ASSET_SOURCE_PATH: &'static str = #asset_str;\n                // The options give the CLI info about how to process the asset\n                // Note: into_asset_options is not a trait, so we cannot accept the options directly\n                // in the constructor. Stable rust doesn't have support for constant functions in traits\n                const __ASSET_OPTIONS: manganis::AssetOptions = #options.into_asset_options();\n                // The input token hash is used to uniquely identify the link section for this asset\n                const __ASSET_HASH: &'static str = #asset_hash;\n                // Create the asset that the crate will use. This is used both in the return value and\n                // added to the linker for the bundler to copy later\n                const __ASSET: manganis::BundledAsset = manganis::macro_helpers::#constructor(__ASSET_SOURCE_PATH, __ASSET_OPTIONS);\n\n                #link_section\n\n                manganis::Asset::new(\n                    || unsafe { std::ptr::read_volatile(&__LINK_SECTION) },\n                    || unsafe { std::ptr::read_volatile(&__LEGACY_LINK_SECTION) }\n                )\n            }\n        }\n    }\n\n    pub(crate) fn expand_option_tokens(&self) -> proc_macro2::TokenStream {\n        match self.asset.as_ref() {\n            Ok(asset) => {\n                let asset_tokens = self.expand_asset_tokens(asset);\n                quote! { ::core::option::Option::Some(#asset_tokens) }\n            }\n            Err(AssetParseError::AssetDoesntExist { .. }) => {\n                quote! { ::core::option::Option::<manganis::Asset>::None }\n            }\n            Err(err) => self.error_tokens(err),\n        }\n    }\n\n    fn error_tokens(&self, err: &AssetParseError) -> proc_macro2::TokenStream {\n        let err = err.to_string();\n        quote! { compile_error!(#err) }\n    }\n}\n"
  },
  {
    "path": "packages/manganis/manganis-macro/src/css_module.rs",
    "content": "use crate::{asset::AssetParser, resolve_path};\nuse macro_string::MacroString;\nuse manganis_core::{create_module_hash, get_class_mappings};\nuse proc_macro2::{Span, TokenStream};\nuse quote::{format_ident, quote, ToTokens, TokenStreamExt};\nuse syn::{\n    parse::{Parse, ParseStream},\n    spanned::Spanned,\n    token::Comma,\n    Ident, ItemStruct,\n};\n\npub(crate) struct CssModuleAttribute {\n    asset_parser: AssetParser,\n}\n\nimpl Parse for CssModuleAttribute {\n    fn parse(input: ParseStream) -> syn::Result<Self> {\n        // Asset path \"/path.css\"\n        let (MacroString(src), path_expr) = input.call(crate::parse_with_tokens)?;\n        let asset = resolve_path(&src, path_expr.span());\n\n        let _comma = input.parse::<Comma>();\n\n        // Optional options\n        let mut options = input.parse::<TokenStream>()?;\n        if options.is_empty() {\n            options = quote! { manganis::AssetOptions::css_module() }\n        }\n\n        let asset_parser = AssetParser {\n            path_expr,\n            asset,\n            options,\n        };\n\n        Ok(Self { asset_parser })\n    }\n}\n\npub(crate) fn expand_css_module_struct(\n    tokens: &mut proc_macro2::TokenStream,\n    attribute: &CssModuleAttribute,\n    item_struct: &ItemStruct,\n) {\n    if !item_struct.fields.is_empty() {\n        let err = syn::Error::new(\n            item_struct.fields.span(),\n            \"css_module can only be applied to unit structs\",\n        )\n        .into_compile_error();\n        tokens.append_all(err);\n        return;\n    }\n    if !item_struct.generics.params.is_empty() {\n        let err = syn::Error::new(\n            item_struct.generics.span(),\n            \"css_module cannot be applied to generic structs\",\n        )\n        .into_compile_error();\n        tokens.append_all(err);\n        return;\n    }\n    let struct_vis = &item_struct.vis;\n    let struct_name = &item_struct.ident;\n    let struct_name_private = format_ident!(\"__{}\", struct_name);\n\n    // Use the regular asset parser to generate the linker bridge.\n    let mut linker_tokens = quote! {\n        /// Auto-generated Manganis asset for css modules.\n        #[allow(missing_docs)]\n        const ASSET: manganis::Asset =\n    };\n    attribute.asset_parser.to_tokens(&mut linker_tokens);\n\n    let asset = match attribute.asset_parser.asset.as_ref() {\n        Ok(path) => path,\n        Err(err) => {\n            let err = err.to_string();\n            tokens.append_all(quote! { compile_error!(#err) });\n            return;\n        }\n    };\n\n    let css = std::fs::read_to_string(asset).expect(\"Unable to read css module file\");\n\n    let mut values = Vec::new();\n\n    let hash = create_module_hash(asset);\n    let class_mappings = get_class_mappings(css.as_str(), hash.as_str()).expect(\"Invalid css\");\n\n    // Generate class struct field tokens.\n    for (old_class, new_class) in class_mappings.iter() {\n        let as_snake = to_snake_case(old_class);\n\n        let ident = Ident::new(&as_snake, Span::call_site());\n        values.push(quote! {\n            pub const #ident: #struct_name_private::__CssIdent = #struct_name_private::__CssIdent { inner: #new_class };\n        });\n    }\n\n    // We use a PhantomData to prevent Rust from complaining about an unused lifetime if a css module without any idents is used.\n    tokens.extend(quote! {\n        #[doc(hidden)]\n        #[allow(missing_docs, non_snake_case)]\n        mod #struct_name_private {\n            use dioxus::prelude::*;\n\n            #linker_tokens;\n\n            // Css ident class for deref stylesheet inclusion.\n            pub struct __CssIdent { pub inner: &'static str }\n\n            use std::ops::Deref;\n            use std::sync::OnceLock;\n            use dioxus::document::{document, LinkProps};\n\n            impl Deref for __CssIdent {\n                type Target = str;\n\n                fn deref(&self) -> &Self::Target {\n                    static CELL: OnceLock<()> = OnceLock::new();\n                    CELL.get_or_init(move || {\n                        let doc = document();\n                        doc.create_link(\n                            LinkProps::builder()\n                                .rel(Some(\"stylesheet\".to_string()))\n                                .href(Some(ASSET.to_string()))\n                                .build(),\n                        );\n                    });\n\n                    self.inner\n                }\n            }\n\n            impl std::fmt::Display for __CssIdent {\n                fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {\n                    self.deref().fmt(f)\n                }\n            }\n        }\n\n        /// Auto-generated idents struct for CSS modules.\n        #[allow(missing_docs, non_snake_case)]\n        #struct_vis struct #struct_name {}\n\n        impl #struct_name {\n            #( #values )*\n        }\n\n            impl dioxus::core::IntoAttributeValue for #struct_name_private::__CssIdent {\n                fn into_value(self) -> dioxus::core::AttributeValue {\n                    dioxus::core::AttributeValue::Text(self.to_string())\n                }\n            }\n    })\n}\n\n/// Convert camel and kebab case to snake case.\n///\n/// This can fail sometimes, for example `myCss-Class`` is `my_css__class`\nfn to_snake_case(input: &str) -> String {\n    let mut new = String::new();\n\n    for (i, c) in input.chars().enumerate() {\n        if c.is_uppercase() && i != 0 {\n            new.push('_');\n        }\n\n        new.push(c.to_ascii_lowercase());\n    }\n\n    new.replace('-', \"_\")\n}\n"
  },
  {
    "path": "packages/manganis/manganis-macro/src/ffi.rs",
    "content": "//! FFI bridge macro for native plugin interop.\n//!\n//! This module implements the `#[ffi]` attribute macro that generates direct FFI bindings\n//! between Rust and native platforms (Swift/Kotlin).\n\nuse proc_macro2::TokenStream as TokenStream2;\nuse quote::{format_ident, quote};\nuse std::hash::{DefaultHasher, Hash, Hasher};\nuse syn::{\n    parse::{Parse, ParseStream},\n    spanned::Spanned,\n    ForeignItem, ForeignItemFn, Ident, ItemForeignMod, LitStr, Pat, ReturnType, Type,\n};\n\n/// The foreign ABI being targeted\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]\npub enum ForeignAbi {\n    /// Swift (iOS/macOS)\n    Swift,\n    /// Kotlin (Android)\n    Kotlin,\n}\n\n/// A foreign type declaration (`type Foo;`)\n#[derive(Debug, Clone)]\npub struct ForeignTypeDecl {\n    pub name: Ident,\n}\n\n/// A parsed foreign type in function signatures\n#[derive(Debug, Clone)]\npub enum ForeignType {\n    Bool,\n    I8,\n    I16,\n    I32,\n    I64,\n    U8,\n    U16,\n    U32,\n    U64,\n    F32,\n    F64,\n    String,\n    StrRef,\n    Option(Box<ForeignType>),\n    Result(Box<ForeignType>, Box<ForeignType>),\n    OpaqueRef(Ident),\n    Unit,\n}\n\nimpl ForeignType {\n    /// Parse a Rust type into a ForeignType\n    fn from_type(ty: &Type) -> syn::Result<Self> {\n        match ty {\n            Type::Path(type_path) => {\n                let path = &type_path.path;\n                if path.segments.len() == 1 {\n                    let segment = &path.segments[0];\n                    let ident = segment.ident.to_string();\n                    match ident.as_str() {\n                        \"bool\" => return Ok(ForeignType::Bool),\n                        \"i8\" => return Ok(ForeignType::I8),\n                        \"i16\" => return Ok(ForeignType::I16),\n                        \"i32\" => return Ok(ForeignType::I32),\n                        \"i64\" => return Ok(ForeignType::I64),\n                        \"u8\" => return Ok(ForeignType::U8),\n                        \"u16\" => return Ok(ForeignType::U16),\n                        \"u32\" => return Ok(ForeignType::U32),\n                        \"u64\" => return Ok(ForeignType::U64),\n                        \"f32\" => return Ok(ForeignType::F32),\n                        \"f64\" => return Ok(ForeignType::F64),\n                        \"String\" => return Ok(ForeignType::String),\n                        \"Option\" => {\n                            if let syn::PathArguments::AngleBracketed(args) = &segment.arguments {\n                                if let Some(syn::GenericArgument::Type(inner)) = args.args.first() {\n                                    let inner_type = Self::from_type(inner)?;\n                                    return Ok(ForeignType::Option(Box::new(inner_type)));\n                                }\n                            }\n                            return Err(syn::Error::new(ty.span(), \"Invalid Option type\"));\n                        }\n                        \"Result\" => {\n                            if let syn::PathArguments::AngleBracketed(args) = &segment.arguments {\n                                let mut iter = args.args.iter();\n                                if let (\n                                    Some(syn::GenericArgument::Type(ok_ty)),\n                                    Some(syn::GenericArgument::Type(err_ty)),\n                                ) = (iter.next(), iter.next())\n                                {\n                                    let ok_type = Self::from_type(ok_ty)?;\n                                    let err_type = Self::from_type(err_ty)?;\n                                    return Ok(ForeignType::Result(\n                                        Box::new(ok_type),\n                                        Box::new(err_type),\n                                    ));\n                                }\n                            }\n                            return Err(syn::Error::new(ty.span(), \"Invalid Result type\"));\n                        }\n                        _ => {\n                            // Assume it's an opaque type reference\n                            return Ok(ForeignType::OpaqueRef(segment.ident.clone()));\n                        }\n                    }\n                }\n                Err(syn::Error::new(ty.span(), \"Unsupported type path\"))\n            }\n            Type::Reference(type_ref) => {\n                if let Type::Path(path) = &*type_ref.elem {\n                    if path.path.is_ident(\"str\") {\n                        return Ok(ForeignType::StrRef);\n                    }\n                    // Check for &OpaqueType\n                    if path.path.segments.len() == 1 {\n                        return Ok(ForeignType::OpaqueRef(path.path.segments[0].ident.clone()));\n                    }\n                }\n                Err(syn::Error::new(ty.span(), \"Unsupported reference type\"))\n            }\n            Type::Tuple(tuple) if tuple.elems.is_empty() => Ok(ForeignType::Unit),\n            _ => Err(syn::Error::new(ty.span(), \"Unsupported type\")),\n        }\n    }\n\n    /// Get the JNI signature for this type\n    fn jni_signature(&self) -> String {\n        match self {\n            ForeignType::Bool => \"Z\".into(),\n            ForeignType::I8 => \"B\".into(),\n            ForeignType::I16 => \"S\".into(),\n            ForeignType::I32 => \"I\".into(),\n            ForeignType::I64 => \"J\".into(),\n            ForeignType::U8 => \"B\".into(), // JNI doesn't have unsigned, use signed\n            ForeignType::U16 => \"S\".into(),\n            ForeignType::U32 => \"I\".into(),\n            ForeignType::U64 => \"J\".into(),\n            ForeignType::F32 => \"F\".into(),\n            ForeignType::F64 => \"D\".into(),\n            ForeignType::String | ForeignType::StrRef => \"Ljava/lang/String;\".into(),\n            ForeignType::Option(inner) => inner.jni_signature(),\n            ForeignType::Result(ok, _) => ok.jni_signature(),\n            ForeignType::OpaqueRef(name) => format!(\"L{};\", name),\n            ForeignType::Unit => \"V\".into(),\n        }\n    }\n\n    /// Generate Rust type tokens\n    fn to_rust_type(&self) -> TokenStream2 {\n        match self {\n            ForeignType::Bool => quote! { bool },\n            ForeignType::I8 => quote! { i8 },\n            ForeignType::I16 => quote! { i16 },\n            ForeignType::I32 => quote! { i32 },\n            ForeignType::I64 => quote! { i64 },\n            ForeignType::U8 => quote! { u8 },\n            ForeignType::U16 => quote! { u16 },\n            ForeignType::U32 => quote! { u32 },\n            ForeignType::U64 => quote! { u64 },\n            ForeignType::F32 => quote! { f32 },\n            ForeignType::F64 => quote! { f64 },\n            ForeignType::String => quote! { String },\n            ForeignType::StrRef => quote! { &str },\n            ForeignType::Option(inner) => {\n                let inner_ty = inner.to_rust_type();\n                quote! { Option<#inner_ty> }\n            }\n            ForeignType::Result(ok, err) => {\n                let ok_ty = ok.to_rust_type();\n                let err_ty = err.to_rust_type();\n                quote! { Result<#ok_ty, #err_ty> }\n            }\n            ForeignType::OpaqueRef(name) => quote! { #name },\n            ForeignType::Unit => quote! { () },\n        }\n    }\n}\n\n/// A foreign function argument\n#[derive(Debug, Clone)]\npub struct ForeignArg {\n    pub name: Ident,\n    pub ty: ForeignType,\n}\n\n/// A foreign function declaration\n#[derive(Debug, Clone)]\npub struct ForeignFunctionDecl {\n    pub name: Ident,\n    pub receiver: Option<Ident>, // The type name if first arg is `this: &TypeName`\n    pub args: Vec<ForeignArg>,\n    pub return_type: ForeignType,\n}\n\nimpl ForeignFunctionDecl {\n    fn from_foreign_fn(func: &ForeignItemFn) -> syn::Result<Self> {\n        let name = func.sig.ident.clone();\n        let mut receiver = None;\n        let mut args = Vec::new();\n\n        for (i, input) in func.sig.inputs.iter().enumerate() {\n            match input {\n                syn::FnArg::Typed(pat_type) => {\n                    let arg_name = match &*pat_type.pat {\n                        Pat::Ident(pat_ident) => pat_ident.ident.clone(),\n                        _ => {\n                            return Err(syn::Error::new(\n                                pat_type.pat.span(),\n                                \"Expected identifier pattern\",\n                            ))\n                        }\n                    };\n\n                    let arg_ty = ForeignType::from_type(&pat_type.ty)?;\n\n                    // Check if first arg is `this: &SomeType`\n                    if i == 0 && arg_name == \"this\" {\n                        if let ForeignType::OpaqueRef(type_name) = &arg_ty {\n                            receiver = Some(type_name.clone());\n                            continue; // Don't add to args\n                        }\n                    }\n\n                    args.push(ForeignArg {\n                        name: arg_name,\n                        ty: arg_ty,\n                    });\n                }\n                syn::FnArg::Receiver(_) => {\n                    return Err(syn::Error::new(\n                        input.span(),\n                        \"Use `this: &Self` instead of `self`\",\n                    ));\n                }\n            }\n        }\n\n        let return_type = match &func.sig.output {\n            ReturnType::Default => ForeignType::Unit,\n            ReturnType::Type(_, ty) => ForeignType::from_type(ty)?,\n        };\n\n        Ok(Self {\n            name,\n            receiver,\n            args,\n            return_type,\n        })\n    }\n}\n\n/// The main parser for the `#[ffi]` attribute macro\npub struct FfiBridgeParser {\n    /// Source folder path (relative to CARGO_MANIFEST_DIR)\n    pub source_path: String,\n    /// The foreign ABI (Swift or Kotlin)\n    pub abi: ForeignAbi,\n    /// Type declarations\n    pub types: Vec<ForeignTypeDecl>,\n    /// Function declarations\n    pub functions: Vec<ForeignFunctionDecl>,\n}\n\n/// Parser for the attribute: `#[ffi(\"/src/ios\")]`\npub struct FfiAttribute {\n    pub source_path: String,\n}\n\nimpl Parse for FfiAttribute {\n    fn parse(input: ParseStream) -> syn::Result<Self> {\n        let lit: LitStr = input.parse()?;\n        Ok(FfiAttribute {\n            source_path: lit.value(),\n        })\n    }\n}\n\nimpl FfiBridgeParser {\n    /// Parse the attribute and item together\n    pub fn parse_with_attr(attr: FfiAttribute, item: ItemForeignMod) -> syn::Result<Self> {\n        let source_path = attr.source_path;\n\n        // Determine the ABI\n        let abi = match &item.abi.name {\n            Some(name) => match name.value().as_str() {\n                \"Swift\" => ForeignAbi::Swift,\n                \"Kotlin\" => ForeignAbi::Kotlin,\n                other => {\n                    return Err(syn::Error::new(\n                        name.span(),\n                        format!(\"Unsupported ABI: {}. Expected 'Swift' or 'Kotlin'\", other),\n                    ))\n                }\n            },\n            None => {\n                return Err(syn::Error::new(\n                    item.abi.extern_token.span,\n                    \"Expected ABI string (e.g., extern \\\"Swift\\\")\",\n                ))\n            }\n        };\n\n        let mut types = Vec::new();\n        let mut functions = Vec::new();\n\n        for item in &item.items {\n            match item {\n                ForeignItem::Type(ty) => {\n                    types.push(ForeignTypeDecl {\n                        name: ty.ident.clone(),\n                    });\n                }\n                ForeignItem::Fn(func) => {\n                    functions.push(ForeignFunctionDecl::from_foreign_fn(func)?);\n                }\n                _ => {\n                    return Err(syn::Error::new(\n                        item.span(),\n                        \"Only type and function declarations are supported in FFI blocks\",\n                    ));\n                }\n            }\n        }\n\n        Ok(Self {\n            source_path,\n            abi,\n            types,\n            functions,\n        })\n    }\n\n    /// Generate all the code\n    pub fn generate(&self) -> TokenStream2 {\n        match self.abi {\n            ForeignAbi::Kotlin => self.generate_android(),\n            ForeignAbi::Swift => self.generate_ios(),\n        }\n    }\n\n    /// Extract Android namespace from build.gradle.kts\n    /// Looks for `namespace = \"com.example.foo\"` and converts to JNI format `com/example/foo`\n    fn extract_android_namespace(&self) -> Option<String> {\n        // Get the manifest dir from environment (set by cargo during compilation)\n        let manifest_dir = std::env::var(\"CARGO_MANIFEST_DIR\").ok()?;\n        let source_path = std::path::Path::new(&manifest_dir).join(&self.source_path);\n        let build_gradle = source_path.join(\"build.gradle.kts\");\n\n        if !build_gradle.exists() {\n            return None;\n        }\n\n        let contents = std::fs::read_to_string(&build_gradle).ok()?;\n\n        // Look for namespace = \"com.example.foo\" pattern\n        for line in contents.lines() {\n            let trimmed = line.trim();\n            if trimmed.starts_with(\"namespace\") {\n                // Extract the quoted string\n                if let Some(start) = trimmed.find('\"') {\n                    if let Some(end) = trimmed[start + 1..].find('\"') {\n                        let namespace = &trimmed[start + 1..start + 1 + end];\n                        // Convert dots to slashes for JNI format\n                        return Some(namespace.replace('.', \"/\"));\n                    }\n                }\n            }\n        }\n\n        None\n    }\n\n    /// Generate Android JNI code\n    fn generate_android(&self) -> TokenStream2 {\n        let mut output = TokenStream2::new();\n\n        // Try to extract namespace from build.gradle.kts\n        let namespace = self\n            .extract_android_namespace()\n            .unwrap_or_else(|| \"com/example\".to_string());\n\n        // Generate opaque type wrappers\n        for ty in &self.types {\n            let name = &ty.name;\n            // Use namespace from build.gradle.kts or default\n            let class_name = format!(\"{}/{}\", namespace, name);\n            let class_name_lit = syn::LitStr::new(&class_name, proc_macro2::Span::call_site());\n\n            let type_def = quote! {\n                /// Opaque wrapper around a JNI GlobalRef\n                pub struct #name {\n                    inner: manganis::jni::objects::GlobalRef,\n                }\n\n                impl #name {\n                    /// Create a new instance by looking up the Java class at runtime\n                    pub fn new() -> Result<Self, String> {\n                        // Use with_activity which returns Option<R>, wrapping our Result in the Option\n                        let inner_result: Option<Result<Self, String>> = manganis::android::with_activity(|mut env, activity| {\n                            // Find the class\n                            let class_result = env.find_class(#class_name_lit);\n                            let class = match class_result {\n                                Ok(c) => c,\n                                Err(e) => return Some(Err(format!(\"Failed to find class {}: {:?}\", #class_name_lit, e))),\n                            };\n\n                            // Create a new instance with Activity parameter\n                            // The Kotlin plugin constructor takes (Activity) as parameter\n                            let instance = match env.new_object(\n                                &class,\n                                \"(Landroid/app/Activity;)V\",\n                                &[manganis::jni::objects::JValue::Object(&activity)],\n                            ) {\n                                Ok(i) => i,\n                                Err(e) => return Some(Err(format!(\"Failed to create instance of {}: {:?}\", #class_name_lit, e))),\n                            };\n\n                            // Convert to global ref\n                            let global = match env.new_global_ref(&instance) {\n                                Ok(g) => g,\n                                Err(e) => return Some(Err(format!(\"Failed to create global ref: {:?}\", e))),\n                            };\n\n                            Some(Ok(Self { inner: global }))\n                        });\n\n                        // Convert Option<Result<T, E>> to Result<T, E>\n                        match inner_result {\n                            Some(result) => result,\n                            None => Err(\"Failed to get JNI environment\".to_string()),\n                        }\n                    }\n\n                    /// Create from an existing GlobalRef\n                    pub fn from_global_ref(global: manganis::jni::objects::GlobalRef) -> Self {\n                        Self { inner: global }\n                    }\n\n                    /// Get the underlying JObject\n                    pub fn as_obj(&self) -> &manganis::jni::objects::JObject<'_> {\n                        self.inner.as_obj()\n                    }\n                }\n            };\n            output.extend(type_def);\n        }\n\n        // Generate function implementations\n        for func in &self.functions {\n            let func_code = self.generate_android_function(func);\n            output.extend(func_code);\n        }\n\n        // Generate linker metadata\n        let metadata = self.generate_android_metadata();\n        output.extend(metadata);\n\n        // Wrap in cfg\n        quote! {\n            #[cfg(target_os = \"android\")]\n            mod __ffi_android {\n                #output\n            }\n\n            #[cfg(target_os = \"android\")]\n            pub use __ffi_android::*;\n        }\n    }\n\n    fn generate_android_function(&self, func: &ForeignFunctionDecl) -> TokenStream2 {\n        let fn_name = &func.name;\n        // Convert snake_case to camelCase for Kotlin/Java method name\n        let method_name = to_camel_case(&func.name.to_string());\n\n        // Build argument list for Rust function\n        let mut rust_args = Vec::new();\n        if let Some(receiver_type) = &func.receiver {\n            rust_args.push(quote! { this: &#receiver_type });\n        }\n        for arg in &func.args {\n            let name = &arg.name;\n            let ty = arg.ty.to_rust_type();\n            rust_args.push(quote! { #name: #ty });\n        }\n\n        // Build return type\n        let return_type = func.return_type.to_rust_type();\n\n        // Build JNI signature\n        let jni_args: String = func.args.iter().map(|a| a.ty.jni_signature()).collect();\n        let jni_ret = func.return_type.jni_signature();\n        let jni_sig = format!(\"({}){}\", jni_args, jni_ret);\n        let jni_sig_lit = syn::LitStr::new(&jni_sig, proc_macro2::Span::call_site());\n\n        // Build JNI call arguments - each arg needs separate binding before the call\n        let mut arg_bindings = Vec::new();\n        let mut jni_call_args = Vec::new();\n        for (i, arg) in func.args.iter().enumerate() {\n            let name = &arg.name;\n            let binding_name = format_ident!(\"__jni_arg_{}\", i);\n\n            let (binding, arg_expr) = match &arg.ty {\n                ForeignType::Bool => (\n                    quote! { let #binding_name = manganis::jni::objects::JValue::Bool(if #name { 1 } else { 0 }); },\n                    quote! { #binding_name.borrow() },\n                ),\n                ForeignType::I8 | ForeignType::U8 => (\n                    quote! { let #binding_name = manganis::jni::objects::JValue::Byte(#name as i8); },\n                    quote! { #binding_name.borrow() },\n                ),\n                ForeignType::I16 | ForeignType::U16 => (\n                    quote! { let #binding_name = manganis::jni::objects::JValue::Short(#name as i16); },\n                    quote! { #binding_name.borrow() },\n                ),\n                ForeignType::I32 | ForeignType::U32 => (\n                    quote! { let #binding_name = manganis::jni::objects::JValue::Int(#name as i32); },\n                    quote! { #binding_name.borrow() },\n                ),\n                ForeignType::I64 | ForeignType::U64 => (\n                    quote! { let #binding_name = manganis::jni::objects::JValue::Long(#name as i64); },\n                    quote! { #binding_name.borrow() },\n                ),\n                ForeignType::F32 => (\n                    quote! { let #binding_name = manganis::jni::objects::JValue::Float(#name); },\n                    quote! { #binding_name.borrow() },\n                ),\n                ForeignType::F64 => (\n                    quote! { let #binding_name = manganis::jni::objects::JValue::Double(#name); },\n                    quote! { #binding_name.borrow() },\n                ),\n                ForeignType::String | ForeignType::StrRef => (\n                    quote! {\n                        let #binding_name = match env.new_string(#name) {\n                            Ok(s) => s,\n                            Err(e) => return Some(Err(format!(\"Failed to create JNI string: {:?}\", e))),\n                        };\n                    },\n                    quote! { (&&#binding_name).into() },\n                ),\n                _ => (\n                    quote! { let #binding_name = #name.inner.as_obj(); },\n                    quote! { (&#binding_name).into() },\n                ),\n            };\n            arg_bindings.push(binding);\n            jni_call_args.push(arg_expr);\n        }\n\n        // Build the call expression\n        let call_target = if func.receiver.is_some() {\n            quote! { this.inner.as_obj() }\n        } else {\n            quote! { &class }\n        };\n\n        let method_name_lit = syn::LitStr::new(&method_name, proc_macro2::Span::call_site());\n\n        // Generate result conversion that takes env as a parameter\n        // Note: call_method returns JValueGen<JObject<'_>> (owned), not a reference\n        let result_conversion_fn = match &func.return_type {\n            ForeignType::Unit => quote! {\n                fn convert_result<'a>(_env: &mut manganis::jni::JNIEnv<'a>, _result: manganis::jni::objects::JValueGen<manganis::jni::objects::JObject<'a>>) -> Result<(), String> {\n                    Ok(())\n                }\n            },\n            ForeignType::Bool => quote! {\n                fn convert_result<'a>(_env: &mut manganis::jni::JNIEnv<'a>, result: manganis::jni::objects::JValueGen<manganis::jni::objects::JObject<'a>>) -> Result<bool, String> {\n                    result.z()\n                        .map(|v| v != 0)\n                        .map_err(|e| format!(\"Failed to get boolean result: {:?}\", e))\n                }\n            },\n            ForeignType::I32 | ForeignType::U32 => quote! {\n                fn convert_result<'a>(_env: &mut manganis::jni::JNIEnv<'a>, result: manganis::jni::objects::JValueGen<manganis::jni::objects::JObject<'a>>) -> Result<i32, String> {\n                    result.i()\n                        .map_err(|e| format!(\"Failed to get int result: {:?}\", e))\n                }\n            },\n            ForeignType::I64 | ForeignType::U64 => quote! {\n                fn convert_result<'a>(_env: &mut manganis::jni::JNIEnv<'a>, result: manganis::jni::objects::JValueGen<manganis::jni::objects::JObject<'a>>) -> Result<i64, String> {\n                    result.j()\n                        .map_err(|e| format!(\"Failed to get long result: {:?}\", e))\n                }\n            },\n            ForeignType::F32 => quote! {\n                fn convert_result<'a>(_env: &mut manganis::jni::JNIEnv<'a>, result: manganis::jni::objects::JValueGen<manganis::jni::objects::JObject<'a>>) -> Result<f32, String> {\n                    result.f()\n                        .map_err(|e| format!(\"Failed to get float result: {:?}\", e))\n                }\n            },\n            ForeignType::F64 => quote! {\n                fn convert_result<'a>(_env: &mut manganis::jni::JNIEnv<'a>, result: manganis::jni::objects::JValueGen<manganis::jni::objects::JObject<'a>>) -> Result<f64, String> {\n                    result.d()\n                        .map_err(|e| format!(\"Failed to get double result: {:?}\", e))\n                }\n            },\n            ForeignType::String => quote! {\n                fn convert_result<'a>(env: &mut manganis::jni::JNIEnv<'a>, result: manganis::jni::objects::JValueGen<manganis::jni::objects::JObject<'a>>) -> Result<String, String> {\n                    let obj = result.l()\n                        .map_err(|e| format!(\"Failed to get object result: {:?}\", e))?;\n                    if obj.is_null() {\n                        return Ok(String::new());\n                    }\n                    let jstr: manganis::jni::objects::JString = obj.into();\n                    let rust_str: String = env.get_string(&jstr)\n                        .map_err(|e| format!(\"Failed to get string: {:?}\", e))?\n                        .into();\n                    Ok(rust_str)\n                }\n            },\n            ForeignType::Option(inner) => match inner.as_ref() {\n                ForeignType::String => quote! {\n                    fn convert_result<'a>(env: &mut manganis::jni::JNIEnv<'a>, result: manganis::jni::objects::JValueGen<manganis::jni::objects::JObject<'a>>) -> Result<Option<String>, String> {\n                        let obj = result.l()\n                            .map_err(|e| format!(\"Failed to get object result: {:?}\", e))?;\n                        if obj.is_null() {\n                            Ok(None)\n                        } else {\n                            let jstr: manganis::jni::objects::JString = obj.into();\n                            let rust_str: String = env.get_string(&jstr)\n                                .map_err(|e| format!(\"Failed to get string: {:?}\", e))?\n                                .into();\n                            Ok(Some(rust_str))\n                        }\n                    }\n                },\n                _ => quote! {\n                    fn convert_result<'a>(_env: &mut manganis::jni::JNIEnv<'a>, result: manganis::jni::objects::JValueGen<manganis::jni::objects::JObject<'a>>) -> Result<Option<()>, String> {\n                        let obj = result.l()\n                            .map_err(|e| format!(\"Failed to get object result: {:?}\", e))?;\n                        if obj.is_null() {\n                            Ok(None)\n                        } else {\n                            Ok(Some(()))\n                        }\n                    }\n                },\n            },\n            _ => quote! {\n                fn convert_result<'a>(_env: &mut manganis::jni::JNIEnv<'a>, _result: manganis::jni::objects::JValueGen<manganis::jni::objects::JObject<'a>>) -> Result<(), String> {\n                    Ok(())\n                }\n            },\n        };\n\n        quote! {\n            pub fn #fn_name(#(#rust_args),*) -> Result<#return_type, String> {\n                // Use with_activity which returns Option<R>, wrapping our Result in the Option\n                let inner_result: Option<Result<#return_type, String>> = manganis::android::with_activity(|mut env, _activity| {\n                    // Define result conversion as a local function to avoid closure capture issues\n                    #result_conversion_fn\n\n                    // Perform the JNI call directly in the closure\n                    #(#arg_bindings)*\n\n                    let call_result = env.call_method(\n                        #call_target,\n                        #method_name_lit,\n                        #jni_sig_lit,\n                        &[#(#jni_call_args),*],\n                    );\n\n                    match call_result {\n                        Ok(result) => Some(convert_result(&mut env, result)),\n                        Err(e) => Some(Err(format!(\"JNI call failed: {:?}\", e))),\n                    }\n                });\n\n                // Convert Option<Result<T, E>> to Result<T, E>\n                match inner_result {\n                    Some(result) => result,\n                    None => Err(\"Failed to get JNI environment\".to_string()),\n                }\n            }\n        }\n    }\n\n    fn generate_android_metadata(&self) -> TokenStream2 {\n        // Get the first type name or use \"plugin\" as default\n        let plugin_name = self\n            .types\n            .first()\n            .map(|t| t.name.to_string().to_lowercase())\n            .unwrap_or_else(|| \"plugin\".to_string());\n\n        let source_path_lit = syn::LitStr::new(&self.source_path, proc_macro2::Span::call_site());\n        let plugin_name_lit = syn::LitStr::new(&plugin_name, proc_macro2::Span::call_site());\n\n        let mut hash = DefaultHasher::new();\n        self.source_path.hash(&mut hash);\n        plugin_name.hash(&mut hash);\n        let plugin_hash = format!(\"{:016x}\", hash.finish());\n\n        let link_section = crate::linker::generate_link_section_inner(\n            quote! { __METADATA },\n            &plugin_hash,\n            \"__ASSETS__\",\n            quote! { manganis::android::metadata::serialize_android_metadata },\n            quote! { manganis::android::macro_helpers::copy_bytes },\n            quote! { manganis::android::metadata::AndroidMetadataBuffer },\n        );\n\n        quote! {\n            const _: () = {\n                const __METADATA: manganis::android::AndroidArtifactMetadata =\n                    manganis::android::AndroidArtifactMetadata::new(\n                        #plugin_name_lit,\n                        concat!(env!(\"CARGO_MANIFEST_DIR\"), \"/\", #source_path_lit),\n                        \"\", // No extra dependencies by default\n                    );\n\n                #link_section\n            };\n        }\n    }\n\n    /// Generate iOS Objective-C code\n    fn generate_ios(&self) -> TokenStream2 {\n        let mut output = TokenStream2::new();\n\n        // Generate opaque type wrappers\n        for ty in &self.types {\n            let name = &ty.name;\n            let class_name_bytes = format!(\"{}\\0\", name);\n\n            let type_def = quote! {\n                /// Opaque wrapper around an Objective-C object pointer\n                /// The actual Swift class is looked up dynamically at runtime after dx links everything\n                pub struct #name {\n                    inner: *mut manganis::objc2::runtime::AnyObject,\n                }\n\n                unsafe impl Send for #name {}\n                unsafe impl Sync for #name {}\n\n                impl #name {\n                    /// Load the Swift framework bundle to make classes available.\n                    ///\n                    /// We use dlopen rather than build-time linking because Swift packages are compiled\n                    /// after the Rust binary (we extract plugin metadata from the linker args).\n                    ///\n                    /// This is App Store compliant because:\n                    /// - The framework is bundled inside the .app bundle (not downloaded)\n                    /// - The framework is code-signed as part of the app\n                    /// - No external code is loaded - only bundled, reviewed code\n                    fn load_swift_framework() -> Result<(), &'static str> {\n                        use std::sync::Once;\n                        static LOAD_ONCE: Once = Once::new();\n                        static mut LOAD_RESULT: Result<(), &'static str> = Ok(());\n\n                        #[link(name = \"System\")]\n                        extern \"C\" {\n                            fn dlopen(filename: *const std::ffi::c_char, flags: std::ffi::c_int) -> *mut std::ffi::c_void;\n                            fn dlerror() -> *const std::ffi::c_char;\n                        }\n                        const RTLD_NOW: std::ffi::c_int = 0x2;\n                        const RTLD_GLOBAL: std::ffi::c_int = 0x8;\n\n                        LOAD_ONCE.call_once(|| {\n                            unsafe {\n                                // Get the path to the executable\n                                let exe_path = std::env::current_exe()\n                                    .map_err(|_| \"Failed to get executable path\")\n                                    .ok();\n\n                                let framework_path = if let Some(exe) = exe_path {\n                                    // For macOS: App.app/Contents/MacOS/binary -> App.app/Contents/Frameworks/\n                                    // For iOS: App.app/binary -> App.app/Frameworks/\n                                    let parent = exe.parent().unwrap_or(&exe);\n\n                                    #[cfg(target_os = \"macos\")]\n                                    let frameworks_dir = parent.parent().unwrap_or(parent).join(\"Frameworks\");\n                                    #[cfg(target_os = \"ios\")]\n                                    let frameworks_dir = parent.join(\"Frameworks\");\n\n                                    let path = frameworks_dir.join(\"DioxusSwiftPlugins.framework/DioxusSwiftPlugins\");\n                                    if path.exists() {\n                                        Some(path)\n                                    } else {\n                                        // Try versioned path for macOS\n                                        let versioned = frameworks_dir.join(\"DioxusSwiftPlugins.framework/Versions/Current/DioxusSwiftPlugins\");\n                                        if versioned.exists() {\n                                            Some(versioned)\n                                        } else {\n                                            None\n                                        }\n                                    }\n                                } else {\n                                    None\n                                };\n\n                                if let Some(path) = framework_path {\n                                    let path_cstr = std::ffi::CString::new(path.to_string_lossy().as_bytes())\n                                        .expect(\"Invalid framework path\");\n\n                                    // Use dlopen to load the framework\n                                    let handle = dlopen(path_cstr.as_ptr(), RTLD_NOW | RTLD_GLOBAL);\n                                    if handle.is_null() {\n                                        let err = dlerror();\n                                        if !err.is_null() {\n                                            let msg = std::ffi::CStr::from_ptr(err).to_string_lossy();\n                                            eprintln!(\"Failed to load Swift framework: {}\", msg);\n                                        }\n                                        LOAD_RESULT = Err(\"Failed to load Swift framework with dlopen\");\n                                    }\n                                } else {\n                                    LOAD_RESULT = Err(\"Swift framework not found at expected path\");\n                                }\n                            }\n                        });\n\n                        unsafe { LOAD_RESULT }\n                    }\n\n                    /// Create a new instance by looking up the ObjC class dynamically at runtime\n                    pub fn new() -> Result<Self, &'static str> {\n                        // First ensure the framework is loaded\n                        Self::load_swift_framework()?;\n\n                        unsafe {\n                            // Dynamic runtime lookup - the class will be available after the framework is loaded\n                            let class_name = ::std::ffi::CStr::from_bytes_with_nul(#class_name_bytes.as_bytes())\n                                .expect(\"Invalid class name\");\n                            let class = manganis::objc2::runtime::AnyClass::get(class_name)\n                                .ok_or(\"Class not found - ensure Swift sources are compiled and linked\")?;\n\n                            let instance: *mut manganis::objc2::runtime::AnyObject = manganis::objc2::msg_send![class, alloc];\n                            let instance: *mut manganis::objc2::runtime::AnyObject = manganis::objc2::msg_send![instance, init];\n\n                            if instance.is_null() {\n                                return Err(\"Failed to initialize instance\");\n                            }\n\n                            Ok(Self { inner: instance })\n                        }\n                    }\n\n                    /// Create from an existing object pointer\n                    pub unsafe fn from_raw(ptr: *mut manganis::objc2::runtime::AnyObject) -> Self {\n                        Self { inner: ptr }\n                    }\n                }\n            };\n            output.extend(type_def);\n        }\n\n        // Generate function implementations\n        for func in &self.functions {\n            let func_code = self.generate_ios_function(func);\n            output.extend(func_code);\n        }\n\n        // Generate linker metadata\n        let metadata = self.generate_ios_metadata();\n        output.extend(metadata);\n\n        // Wrap in cfg\n        quote! {\n            #[cfg(any(target_os = \"ios\", target_os = \"macos\"))]\n            mod __ffi_darwin {\n                #output\n            }\n\n            #[cfg(any(target_os = \"ios\", target_os = \"macos\"))]\n            pub use __ffi_darwin::*;\n        }\n    }\n\n    fn generate_ios_function(&self, func: &ForeignFunctionDecl) -> TokenStream2 {\n        let fn_name = &func.name;\n\n        // Build Objective-C selector\n        let selector = self.rust_to_objc_selector(&func.name.to_string(), &func.args);\n\n        // Build argument list for Rust function\n        let mut rust_args = Vec::new();\n        if let Some(receiver_type) = &func.receiver {\n            rust_args.push(quote! { this: &#receiver_type });\n        }\n        for arg in &func.args {\n            let name = &arg.name;\n            let ty = arg.ty.to_rust_type();\n            rust_args.push(quote! { #name: #ty });\n        }\n\n        // Build return type\n        let return_type = func.return_type.to_rust_type();\n\n        // Build argument conversions (variable bindings before msg_send)\n        let mut arg_conversions = Vec::new();\n        let mut arg_names = Vec::new();\n        for (i, arg) in func.args.iter().enumerate() {\n            let name = &arg.name;\n            let converted_name = format_ident!(\"__arg_{}\", i);\n            let conversion = match &arg.ty {\n                ForeignType::Bool => quote! {\n                    let #converted_name = manganis::objc2::runtime::Bool::new(#name);\n                },\n                ForeignType::String | ForeignType::StrRef => {\n                    quote! {\n                        let __cstr = ::std::ffi::CString::new(#name.as_bytes()).unwrap();\n                        let __nsstring_class = manganis::objc2::runtime::AnyClass::get(\n                            ::std::ffi::CStr::from_bytes_with_nul(b\"NSString\\0\").unwrap()\n                        ).unwrap();\n                        let #converted_name: *mut manganis::objc2::runtime::AnyObject = manganis::objc2::msg_send![\n                            __nsstring_class,\n                            stringWithUTF8String: __cstr.as_ptr()\n                        ];\n                    }\n                }\n                _ => quote! { let #converted_name = #name; },\n            };\n            arg_conversions.push(conversion);\n            arg_names.push(converted_name);\n        }\n\n        // Build result conversion\n        let result_conversion = match &func.return_type {\n            ForeignType::Unit => quote! { Ok(()) },\n            ForeignType::Bool => quote! {\n                Ok(result.as_bool())\n            },\n            ForeignType::String => quote! {\n                {\n                    if result.is_null() {\n                        Ok(String::new())\n                    } else {\n                        let cstr: *const ::std::os::raw::c_char = manganis::objc2::msg_send![result, UTF8String];\n                        let rust_str = ::std::ffi::CStr::from_ptr(cstr)\n                            .to_str()\n                            .map_err(|_| \"Invalid UTF-8\")?;\n                        Ok(rust_str.to_owned())\n                    }\n                }\n            },\n            ForeignType::Option(inner) => match inner.as_ref() {\n                ForeignType::String => quote! {\n                    {\n                        if result.is_null() {\n                            Ok(None)\n                        } else {\n                            let cstr: *const ::std::os::raw::c_char = manganis::objc2::msg_send![result, UTF8String];\n                            let rust_str = ::std::ffi::CStr::from_ptr(cstr)\n                                .to_str()\n                                .map_err(|_| \"Invalid UTF-8\")?;\n                            Ok(Some(rust_str.to_owned()))\n                        }\n                    }\n                },\n                _ => quote! {\n                    if result.is_null() {\n                        Ok(None)\n                    } else {\n                        Ok(Some(Default::default()))\n                    }\n                },\n            },\n            _ => quote! { Ok(Default::default()) },\n        };\n\n        // Build the msg_send call\n        let this_expr = if func.receiver.is_some() {\n            quote! { this.inner }\n        } else {\n            // For static methods, we'd need the class\n            quote! { class }\n        };\n\n        // Build msg_send expression with proper selector syntax\n        // For Swift methods with `_` external labels, the selector is just `methodName:`\n        // and we call it as: msg_send![obj, methodName: arg0]\n        let msg_send_call = if func.args.is_empty() {\n            // No arguments - use the simple selector (no colons)\n            let selector_ident = format_ident!(\"{}\", selector);\n            quote! {\n                manganis::objc2::msg_send![#this_expr, #selector_ident]\n            }\n        } else {\n            // With arguments - the selector is `methodName:` for one arg, `methodName::` for two, etc.\n            // The msg_send syntax is: msg_send![obj, methodName: arg0, _: arg1, _: arg2]\n            // where `_` is used for subsequent unlabeled parameters\n\n            let method_name = to_camel_case(&func.name.to_string());\n            let method_ident = format_ident!(\"{}\", method_name);\n\n            // Build the msg_send call tokens\n            let mut tokens = quote! { #this_expr, };\n\n            // First argument uses the method name\n            if !arg_names.is_empty() {\n                let first_arg = &arg_names[0];\n                tokens.extend(quote! { #method_ident: #first_arg });\n            }\n\n            // Subsequent arguments use `_` as the label (for Swift's unlabeled parameters)\n            for arg in arg_names.iter().skip(1) {\n                let underscore = format_ident!(\"_\");\n                tokens.extend(quote! { , #underscore: #arg });\n            }\n\n            quote! {\n                manganis::objc2::msg_send![#tokens]\n            }\n        };\n\n        quote! {\n            pub fn #fn_name(#(#rust_args),*) -> Result<#return_type, &'static str> {\n                unsafe {\n                    #(#arg_conversions)*\n                    let result: *mut manganis::objc2::runtime::AnyObject = #msg_send_call;\n\n                    #result_conversion\n                }\n            }\n        }\n    }\n\n    fn generate_ios_metadata(&self) -> TokenStream2 {\n        // Get the first type name or use \"plugin\" as default\n        let plugin_name = self\n            .types\n            .first()\n            .map(|t| t.name.to_string())\n            .unwrap_or_else(|| \"Plugin\".to_string());\n\n        let source_path_lit = syn::LitStr::new(&self.source_path, proc_macro2::Span::call_site());\n        let plugin_name_lit =\n            syn::LitStr::new(&plugin_name.to_lowercase(), proc_macro2::Span::call_site());\n        let product_lit = syn::LitStr::new(&plugin_name, proc_macro2::Span::call_site());\n\n        let mut hash = DefaultHasher::new();\n        self.source_path.hash(&mut hash);\n        plugin_name.hash(&mut hash);\n        let plugin_hash = format!(\"{:016x}\", hash.finish());\n\n        let link_section = crate::linker::generate_link_section_inner(\n            quote! { __METADATA },\n            &plugin_hash,\n            \"__ASSETS__\",\n            quote! { manganis::darwin::metadata::serialize_swift_metadata },\n            quote! { manganis::darwin::macro_helpers::copy_bytes },\n            quote! { manganis::darwin::metadata::SwiftMetadataBuffer },\n        );\n\n        quote! {\n            const _: () = {\n                const __METADATA: manganis::darwin::SwiftPackageMetadata =\n                    manganis::darwin::SwiftPackageMetadata::new(\n                        #plugin_name_lit,\n                        concat!(env!(\"CARGO_MANIFEST_DIR\"), \"/\", #source_path_lit),\n                        #product_lit,\n                    );\n\n                #link_section\n            };\n        }\n    }\n\n    /// Convert a Rust function name to an Objective-C selector\n    ///\n    /// For Swift methods that use `_` as the external parameter label (like most FFI methods),\n    /// the selector is just the method name followed by colons for each parameter.\n    /// e.g., `func getCurrentPositionJson(_ optionsJson: String)` -> `getCurrentPositionJson:`\n    fn rust_to_objc_selector(&self, fn_name: &str, args: &[ForeignArg]) -> String {\n        let mut selector = to_camel_case(fn_name);\n\n        // For each argument, just add a colon (assuming Swift uses _ for external labels)\n        for _ in args {\n            selector.push(':');\n        }\n\n        selector\n    }\n}\n\n/// Convert snake_case to camelCase\nfn to_camel_case(s: &str) -> String {\n    let mut result = String::new();\n    let mut capitalize_next = false;\n\n    for (i, c) in s.chars().enumerate() {\n        if c == '_' {\n            capitalize_next = true;\n        } else if capitalize_next {\n            result.push(c.to_ascii_uppercase());\n            capitalize_next = false;\n        } else if i == 0 {\n            result.push(c.to_ascii_lowercase());\n        } else {\n            result.push(c);\n        }\n    }\n\n    result\n}\n"
  },
  {
    "path": "packages/manganis/manganis-macro/src/lib.rs",
    "content": "#![doc = include_str!(\"../README.md\")]\n#![deny(missing_docs)]\n\nuse std::path::PathBuf;\n\nuse proc_macro::TokenStream;\nuse proc_macro2::Span;\nuse quote::{quote, ToTokens};\nuse syn::{\n    parse::{Parse, ParseStream},\n    parse_macro_input, ItemStruct,\n};\n\npub(crate) mod asset;\npub(crate) mod css_module;\npub(crate) mod ffi;\npub(crate) mod linker;\n\nuse crate::css_module::{expand_css_module_struct, CssModuleAttribute};\n\n/// The asset macro collects assets that will be included in the final binary\n///\n/// # Files\n///\n/// The file builder collects an arbitrary file. Relative paths are resolved relative to the package root\n/// ```rust\n/// # use manganis::{asset, Asset};\n/// const _: Asset = asset!(\"/assets/asset.txt\");\n/// ```\n/// Macros like `concat!` and `env!` are supported in the asset path.\n/// ```rust\n/// # use manganis::{asset, Asset};\n/// const _: Asset = asset!(concat!(\"/assets/\", env!(\"CARGO_CRATE_NAME\"), \".dat\"));\n/// ```\n///\n/// # Images\n///\n/// You can collect images which will be automatically optimized with the image builder:\n/// ```rust\n/// # use manganis::{asset, Asset};\n/// const _: Asset = asset!(\"/assets/image.png\");\n/// ```\n/// Resize the image at compile time to make the assets file size smaller:\n/// ```rust\n/// # use manganis::{asset, Asset, AssetOptions, ImageSize};\n/// const _: Asset = asset!(\"/assets/image.png\", AssetOptions::image().with_size(ImageSize::Manual { width: 52, height: 52 }));\n/// ```\n/// Or convert the image at compile time to a web friendly format:\n/// ```rust\n/// # use manganis::{asset, Asset, AssetOptions, ImageSize, ImageFormat};\n/// const _: Asset = asset!(\"/assets/image.png\", AssetOptions::image().with_format(ImageFormat::Avif));\n/// ```\n/// You can mark images as preloaded to make them load faster in your app\n/// ```rust\n/// # use manganis::{asset, Asset, AssetOptions};\n/// const _: Asset = asset!(\"/assets/image.png\", AssetOptions::image().with_preload(true));\n/// ```\n#[proc_macro]\npub fn asset(input: TokenStream) -> TokenStream {\n    let asset = parse_macro_input!(input as asset::AssetParser);\n\n    quote! { #asset }.into_token_stream().into()\n}\n\n/// Resolve an asset at compile time, returning `None` if the asset does not exist.\n///\n/// This behaves like the `asset!` macro when the asset can be resolved, but mirrors\n/// [`option_env!`](core::option_env) by returning an `Option` instead of emitting a compile error\n/// when the asset is missing.\n///\n/// ```rust\n/// # use manganis::{asset, option_asset, Asset};\n/// const REQUIRED: Asset = asset!(\"/assets/style.css\");\n/// const OPTIONAL: Option<Asset> = option_asset!(\"/assets/maybe.css\");\n/// ```\n#[proc_macro]\npub fn option_asset(input: TokenStream) -> TokenStream {\n    let asset = parse_macro_input!(input as asset::AssetParser);\n\n    asset.expand_option_tokens().into()\n}\n\n/// Generate type-safe styles with scoped CSS class names.\n///\n/// The `css_module` attribute macro creates scoped CSS modules that prevent class name collisions\n/// by making each class globally unique. It expands the annotated struct to provide type-safe\n/// identifiers for your CSS classes, allowing you to reference styles in your Rust code with\n/// compile-time guarantees.\n///\n/// # Syntax\n///\n/// The `css_module` attribute takes:\n/// - The asset string path - the absolute path (from the crate root) to your CSS file.\n/// - Optional `AssetOptions` to configure the processing of your CSS module.\n///\n/// It must be applied to a unit struct:\n/// ```rust, ignore\n/// #[css_module(\"/assets/my-styles.css\")]\n/// struct Styles;\n///\n/// #[css_module(\"/assets/my-styles.css\", AssetOptions::css_module().with_minify(true))]\n/// struct Styles;\n/// ```\n///\n/// # Generation\n///\n/// The `css_module` attribute macro does two things:\n/// - It generates an asset and automatically inserts it as a stylesheet link in the document.\n/// - It expands the annotated struct with snake-case associated constants for your CSS class names.\n///\n/// ```rust, ignore\n/// // This macro usage:\n/// #[css_module(\"/assets/mycss.css\")]\n/// struct Styles;\n///\n/// // Will expand the struct to (simplified):\n/// struct Styles {}\n///\n/// impl Styles {\n///     // Snake-cased class names can be accessed like this:\n///     pub const your_class: &str = \"your_class-a1b2c3\";\n/// }\n/// ```\n///\n/// # CSS Class Name Scoping\n///\n/// **The macro only processes CSS class selectors (`.class-name`).** Other selectors like IDs (`#id`),\n/// element selectors (`div`, `p`), attribute selectors, etc. are left unchanged and not exposed as\n/// Rust constants.\n///\n/// The macro collects all class selectors in your CSS file and transforms them to be globally unique\n/// by appending a hash. For example, `.myClass` becomes `.myClass-a1b2c3` where `a1b2c3` is a hash\n/// of the file path.\n///\n/// Class names are converted to snake_case for the Rust constants. For example:\n/// - `.fooBar` becomes `Styles::foo_bar`\n/// - `.my-class` becomes `Styles::my_class`\n///\n/// To prevent a class from being scoped, wrap it in `:global()`:\n/// ```css\n/// /* This class will be scoped */\n/// .my-class { color: blue; }\n///\n/// /* This class will NOT be scoped (no hash added) */\n/// :global(.global-class) { color: red; }\n///\n/// /* Element selectors and other CSS remain unchanged */\n/// div { margin: 0; }\n/// #my-id { padding: 10px; }\n/// ```\n///\n/// # Using Multiple CSS Modules\n///\n/// Multiple `css_module` attributes can be used in the same scope by applying them to different structs:\n/// ```rust, ignore\n/// // First CSS module\n/// #[css_module(\"/assets/styles1.css\")]\n/// struct Styles;\n///\n/// // Second CSS module with a different struct name\n/// #[css_module(\"/assets/styles2.css\")]\n/// struct OtherStyles;\n///\n/// // Access classes from both:\n/// rsx! {\n///     div { class: Styles::container }\n///     div { class: OtherStyles::button }\n/// }\n/// ```\n///\n/// # Asset Options\n///\n/// Similar to the `asset!()` macro, you can pass optional `AssetOptions` to configure processing:\n/// ```rust, ignore\n/// #[css_module(\n///     \"/assets/mycss.css\",\n///     AssetOptions::css_module()\n///         .with_minify(true)\n///         .with_preload(false)\n/// )]\n/// struct Styles;\n/// ```\n///\n/// # Example\n///\n/// First create a CSS file:\n/// ```css\n/// /* assets/styles.css */\n///\n/// .container {\n///     padding: 20px;\n/// }\n///\n/// .button {\n///     background-color: #373737;\n/// }\n///\n/// :global(.global-text) {\n///     font-weight: bold;\n/// }\n/// ```\n///\n/// Then use the `css_module` attribute:\n/// ```rust, ignore\n/// use dioxus::prelude::*;\n///\n/// fn app() -> Element {\n///     #[css_module(\"/assets/styles.css\")]\n///     struct Styles;\n///\n///     rsx! {\n///         div { class: Styles::container,\n///             button { class: Styles::button, \"Click me\" }\n///             span { class: Styles::global_text, \"This uses global class\" }\n///         }\n///     }\n/// }\n/// ```\n#[proc_macro_attribute]\npub fn css_module(input: TokenStream, item: TokenStream) -> TokenStream {\n    let attribute = parse_macro_input!(input as CssModuleAttribute);\n    let item_struct = parse_macro_input!(item as ItemStruct);\n    let mut tokens = proc_macro2::TokenStream::new();\n    expand_css_module_struct(&mut tokens, &attribute, &item_struct);\n    tokens.into()\n}\n\n/// Generate FFI bindings between Rust and native platforms (Swift/Kotlin)\n///\n/// This attribute macro parses an `extern \"Swift\"` or `extern \"Kotlin\"` block and generates:\n/// 1. Opaque type wrappers for foreign types\n/// 2. Function implementations with direct JNI/ObjC bindings\n/// 3. Linker metadata for the CLI to compile the native source\n///\n/// # Syntax\n///\n/// ```rust,ignore\n/// #[manganis::ffi(\"/src/ios\")]\n/// extern \"Swift\" {\n///     pub type GeolocationPlugin;\n///     pub fn get_position(this: &GeolocationPlugin, high_accuracy: bool) -> Option<String>;\n/// }\n///\n/// #[manganis::ffi(\"/src/android\")]\n/// extern \"Kotlin\" {\n///     pub type GeolocationPlugin;\n///     pub fn get_position(this: &GeolocationPlugin, high_accuracy: bool) -> Option<String>;\n/// }\n/// ```\n///\n/// # Path Parameter\n///\n/// The path in the attribute specifies the native source folder relative to `CARGO_MANIFEST_DIR`:\n/// - For Swift: A SwiftPM package folder containing `Package.swift`\n/// - For Kotlin: A Gradle project folder containing `build.gradle.kts`\n///\n/// # Type Declarations\n///\n/// Use `type Name;` to declare opaque foreign types. These become Rust structs wrapping\n/// the native object handle (GlobalRef for JNI, raw pointer for ObjC).\n///\n/// # Function Declarations\n///\n/// Functions can be:\n/// - **Instance methods**: First argument is `this: &TypeName`\n/// - **Static methods**: No `this` argument\n///\n/// # Supported Types\n///\n/// - Primitives: `bool`, `i8`-`i64`, `u8`-`u64`, `f32`, `f64`\n/// - Strings: `String`, `&str`\n/// - Options: `Option<T>` where T is supported\n/// - Opaque refs: `&TypeName` for foreign type references\n#[proc_macro_attribute]\npub fn ffi(attr: TokenStream, item: TokenStream) -> TokenStream {\n    use ffi::{FfiAttribute, FfiBridgeParser};\n\n    let attr = parse_macro_input!(attr as FfiAttribute);\n    let item = parse_macro_input!(item as syn::ItemForeignMod);\n\n    match FfiBridgeParser::parse_with_attr(attr, item) {\n        Ok(parser) => parser.generate().into(),\n        Err(err) => err.to_compile_error().into(),\n    }\n}\n\nfn resolve_path(raw: &str, span: Span) -> Result<PathBuf, AssetParseError> {\n    // Get the location of the root of the crate which is where all assets are relative to\n    //\n    // IE\n    // /users/dioxus/dev/app/\n    // is the root of\n    // /users/dioxus/dev/app/assets/blah.css\n    let manifest_dir = dunce::canonicalize(\n        std::env::var(\"CARGO_MANIFEST_DIR\")\n            .map(PathBuf::from)\n            .unwrap(),\n    )\n    .unwrap();\n\n    // 1. the input file should be a pathbuf\n    let input = PathBuf::from(raw);\n\n    let path = if raw.starts_with('.') {\n        if let Some(local_folder) = span.local_file().as_ref().and_then(|f| f.parent()) {\n            local_folder.join(raw)\n        } else {\n            // If we are running in rust analyzer, just assume the path is valid and return an error when\n            // we compile if it doesn't exist\n            if looks_like_rust_analyzer(&span) {\n                return Ok(\n                    \"The asset macro was expanded under Rust Analyzer which doesn't support paths or local assets yet\"\n                        .into(),\n                );\n            }\n\n            // Otherwise, return an error about the version of rust required for relative assets\n            return Err(AssetParseError::RelativeAssetPath);\n        }\n    } else {\n        manifest_dir.join(raw.trim_start_matches('/'))\n    };\n\n    // 2. absolute path to the asset\n    let Ok(path) = std::path::absolute(path) else {\n        return Err(AssetParseError::InvalidPath {\n            path: input.clone(),\n        });\n    };\n\n    // 3. Ensure the path exists\n    let Ok(path) = dunce::canonicalize(path) else {\n        return Err(AssetParseError::AssetDoesntExist {\n            path: input.clone(),\n        });\n    };\n\n    // 4. Ensure the path doesn't escape the crate dir\n    //\n    // - Note: since we called canonicalize on both paths, we can safely compare the parent dirs.\n    //   On windows, we can only compare the prefix if both paths are canonicalized (not just absolute)\n    //   https://github.com/rust-lang/rust/issues/42869\n    if path == manifest_dir || !path.starts_with(manifest_dir) {\n        return Err(AssetParseError::InvalidPath { path });\n    }\n\n    Ok(path)\n}\n\n/// Parse `T`, while also collecting the tokens it was parsed from.\nfn parse_with_tokens<T: Parse>(input: ParseStream) -> syn::Result<(T, proc_macro2::TokenStream)> {\n    let begin = input.cursor();\n    let t: T = input.parse()?;\n    let end = input.cursor();\n\n    let mut cursor = begin;\n    let mut tokens = proc_macro2::TokenStream::new();\n    while cursor != end {\n        let (tt, next) = cursor.token_tree().unwrap();\n        tokens.extend(std::iter::once(tt));\n        cursor = next;\n    }\n\n    Ok((t, tokens))\n}\n\n#[derive(Debug)]\nenum AssetParseError {\n    AssetDoesntExist { path: PathBuf },\n    InvalidPath { path: PathBuf },\n    RelativeAssetPath,\n}\n\nimpl std::fmt::Display for AssetParseError {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        match self {\n            AssetParseError::AssetDoesntExist { path } => {\n                write!(f, \"Asset at {} doesn't exist\", path.display())\n            }\n            AssetParseError::InvalidPath { path } => {\n                write!(\n                    f,\n                    \"Asset path {} is invalid. Make sure the asset exists within this crate.\",\n                    path.display()\n                )\n            }\n            AssetParseError::RelativeAssetPath => write!(f, \"Failed to resolve relative asset path. Relative assets are only supported in rust 1.88+.\"),\n        }\n    }\n}\n\n/// Rust analyzer doesn't provide a stable way to detect if macros are running under it.\n/// This function uses heuristics to determine if we are running under rust analyzer for better error\n/// messages.\nfn looks_like_rust_analyzer(span: &Span) -> bool {\n    // Rust analyzer spans have a struct debug impl compared to rustcs custom debug impl\n    // RA Example: SpanData { range: 45..58, anchor: SpanAnchor(EditionedFileId(0, Edition2024), ErasedFileAstId { kind: Fn, index: 0, hash: 9CD8 }), ctx: SyntaxContext(4294967036) }\n    // Rustc Example: #0 bytes(70..83)\n    let looks_like_rust_analyzer_span = format!(\"{:?}\", span).contains(\"ctx:\");\n    // The rust analyzer macro expander runs under RUST_ANALYZER_INTERNALS_DO_NOT_USE\n    let looks_like_rust_analyzer_env = std::env::var(\"RUST_ANALYZER_INTERNALS_DO_NOT_USE\").is_ok();\n    // The rust analyzer executable is named rust-analyzer-proc-macro-srv\n    let looks_like_rust_analyzer_exe = std::env::current_exe().ok().is_some_and(|p| {\n        p.file_stem()\n            .and_then(|s| s.to_str())\n            .is_some_and(|s| s.contains(\"rust-analyzer\"))\n    });\n    looks_like_rust_analyzer_span || looks_like_rust_analyzer_env || looks_like_rust_analyzer_exe\n}\n"
  },
  {
    "path": "packages/manganis/manganis-macro/src/linker.rs",
    "content": "use proc_macro2::TokenStream as TokenStream2;\nuse quote::quote;\nuse quote::ToTokens;\n\n/// Generate a linker section for embedding arbitrary data in the binary\n///\n/// This is a generic version that allows customizing the serialization function,\n/// buffer type, and copy bytes function. Used by both asset and FFI metadata embedding.\npub fn generate_link_section_inner(\n    item: TokenStream2,\n    hash: &str,\n    prefix: &str,\n    serialize_fn: TokenStream2,\n    copy_bytes_fn: TokenStream2,\n    buffer_type: TokenStream2,\n) -> TokenStream2 {\n    let position = proc_macro2::Span::call_site();\n    let export_name = syn::LitStr::new(&format!(\"{}{}\", prefix, hash), position);\n\n    quote! {\n        #[used]\n        static __LINK_SECTION: &'static [u8] = {\n            const __BUFFER: #buffer_type = #serialize_fn(&#item);\n            const __BYTES: &[u8] = __BUFFER.as_ref();\n            const __LEN: usize = __BYTES.len();\n\n            #[unsafe(export_name = #export_name)]\n            #[used]\n            static __LINK_SECTION: [u8; __LEN] = #copy_bytes_fn(__BYTES);\n            &__LINK_SECTION\n        };\n    }\n}\n\n/// Generate a linker section for embedding asset data in the binary\n///\n/// This function creates a static array containing the serialized asset data\n/// and exports it with the __ASSETS__ prefix for unified symbol collection.\n/// Uses the generic linker helper from dx-macro-helpers for consistency.\npub fn generate_link_section(asset: impl ToTokens, asset_hash: &str) -> TokenStream2 {\n    let item = asset;\n    let hash: &str = asset_hash;\n    let prefix_current: &str = \"__ASSETS__\";\n    let prefix_legacy: &str = \"__MANGANIS__\";\n    let serialize_fn_current = quote! { manganis::macro_helpers::serialize_asset };\n    let serialize_fn_legacy = quote! { manganis::macro_helpers::serialize_asset_07 };\n    let copy_bytes_fn = quote! { manganis::macro_helpers::copy_bytes };\n    let buffer_type_current = quote! { manganis::macro_helpers::ConstVec<u8, 4096> };\n    let buffer_type_legacy = quote! { manganis::macro_helpers::const_serialize_07::ConstVec<u8> };\n    let position = proc_macro2::Span::call_site();\n    let export_name = syn::LitStr::new(&format!(\"{}{}\", prefix_current, hash), position);\n    let legacy_export_name = syn::LitStr::new(&format!(\"{}{}\", prefix_legacy, hash), position);\n\n    let used_attr = if false {\n        quote! { #[used] }\n    } else {\n        quote! {}\n    };\n\n    quote! {\n        // We bundle both the legacy and new link sections for compatibility.\n        static __LEGACY_LINK_SECTION: &'static [u8] = {\n            const __BUFFER: #buffer_type_legacy = #serialize_fn_legacy(&#item);\n            const __BYTES: &[u8] = __BUFFER.as_ref();\n            const __LEN: usize = __BYTES.len();\n\n            #used_attr\n            #[unsafe(export_name = #legacy_export_name)]\n            static __LINK_SECTION: [u8; __LEN] = #copy_bytes_fn(__BYTES);\n            &__LINK_SECTION\n        };\n\n        static __LINK_SECTION: &'static [u8] = {\n            const __BUFFER: #buffer_type_current = #serialize_fn_current(&#item);\n            const __BYTES: &[u8] = __BUFFER.as_ref();\n            const __LEN: usize = __BYTES.len();\n\n            #used_attr\n            #[unsafe(export_name = #export_name)]\n            static __LINK_SECTION: [u8; __LEN] = #copy_bytes_fn(__BYTES);\n            &__LINK_SECTION\n        };\n    }\n}\n"
  },
  {
    "path": "packages/manganis/manganis-macro/tests/option_asset.rs",
    "content": "use manganis::{asset, option_asset, Asset};\n\n#[test]\nfn resolves_existing_asset() {\n    const REQUIRED: Asset = asset!(\"/assets/asset.txt\");\n    const OPTIONAL: Option<Asset> = option_asset!(\"/assets/asset.txt\");\n\n    let optional = OPTIONAL.expect(\"option_asset! should return Some for existing assets\");\n    assert_eq!(optional.to_string(), REQUIRED.to_string());\n}\n\n#[test]\nfn missing_asset_returns_none() {\n    const OPTIONAL: Option<Asset> = option_asset!(\"/assets/does_not_exist.txt\");\n\n    assert!(OPTIONAL.is_none());\n}\n"
  },
  {
    "path": "packages/native/Cargo.toml",
    "content": "[package]\nname = \"dioxus-native\"\nversion = { workspace = true }\nauthors = [\"Jonathan Kelley\", \"Nico Burns\"]\nedition = \"2021\"\ndescription = \"Native renderer for Dioxus based on blitz\"\nlicense = \"MIT OR Apache-2.0\"\nrepository = \"https://github.com/DioxusLabs/dioxus/\"\nhomepage = \"https://dioxuslabs.com/learn/0.7/getting_started\"\nkeywords = [\"dom\", \"ui\", \"gui\", \"react\"]\n\n[features]\ndefault = [\"accessibility\", \"hot-reload\", \"net\", \"html\", \"svg\", \"system-fonts\", \"clipboard\", \"file_dialog\"]\nprelude = [\"dep:dioxus-core-macro\", \"dep:dioxus-hooks\", \"dep:dioxus-signals\", \"dep:dioxus-stores\", \"dep:manganis\"]\n\n# DOM features\nsvg = [\"blitz-dom/svg\", \"blitz-paint/svg\"]\nincremental = [\"blitz-dom/incremental\"]\nautofocus = [\"blitz-dom/autofocus\"]\nsystem-fonts = [\"blitz-dom/system_fonts\"]\n\n# Shell Features\nclipboard = [\"blitz-shell/clipboard\"]\nfile_dialog = [\"file-dialog\"] # Backwards compat. Remove in next breaking version\nfile-dialog = [\"blitz-shell/file_dialog\"]\naccessibility = [\"blitz-shell/accessibility\", \"blitz-dom/accessibility\"]\n\n# Other features\nnet = [\"dep:tokio\", \"dep:blitz-net\"]\nhtml = [\"dep:blitz-html\"]\n\n# Dev / Debug\nhot-reload = [\"dep:dioxus-cli-config\", \"dep:dioxus-devtools\"]\ntracing = [\"dep:tracing\", \"dioxus-native-dom/tracing\", \"blitz-shell/tracing\", \"blitz-dom/tracing\"]\n\n[dependencies]\n# Blitz dependencies\nblitz-dom = { workspace = true }\nblitz-html = { workspace = true, optional = true }\nblitz-net = { workspace = true, optional = true }\nblitz-paint = { workspace = true, optional = true }\nblitz-traits = { workspace = true }\nblitz-shell = { workspace = true }\n\n# AnyRender dependencies\nanyrender = { workspace = true }\nanyrender_vello = { workspace = true }\n\n# DioxusLabs dependencies\ndioxus-core = { workspace = true }\ndioxus-html = { workspace = true }\ndioxus-native-dom = { workspace = true }\ndioxus-asset-resolver = { workspace = true, features = [\"native\"] }\ndioxus-history = { workspace = true }\ndioxus-document = { workspace = true }\n\n# Dioxus hot reload\ndioxus-cli-config = { workspace = true, optional = true }\ndioxus-devtools = { workspace = true, optional = true }\n\n# Dioxus prelude\ndioxus-hooks = { workspace = true, optional = true }\ndioxus-signals = { workspace = true, optional = true }\ndioxus-stores = { workspace = true, optional = true }\ndioxus-core-macro = { workspace = true, optional = true }\nmanganis = { workspace = true, features = [\"dioxus\"], optional = true }\n\n# Windowing & Input\nwinit = { workspace = true }\nkeyboard-types = { workspace = true }\n\n# IO & Networking\ntokio = { workspace = true, features = [\"rt-multi-thread\"], optional = true }\nwebbrowser = { workspace = true }\n\n# Other dependencies\ntracing = { workspace = true, optional = true }\nrustc-hash = { workspace = true }\nfutures-util = { workspace = true }\n\n[target.'cfg(all(target_os = \"ios\", target_abi = \"sim\"))'.dependencies]\nanyrender_vello_cpu = { workspace = true, features = [\"pixels_window_renderer\"]}\n\n[package.metadata.docs.rs]\nall-features = true\n"
  },
  {
    "path": "packages/native/src/assets.rs",
    "content": "use blitz_shell::BlitzShellNetCallback;\nuse std::sync::Arc;\n\nuse blitz_dom::net::Resource;\nuse blitz_shell::BlitzShellEvent;\nuse blitz_traits::net::{NetCallback, NetProvider};\nuse winit::event_loop::EventLoopProxy;\n\npub struct DioxusNativeNetProvider {\n    callback: Arc<dyn NetCallback<Resource> + 'static>,\n    #[cfg(feature = \"net\")]\n    inner_net_provider: Arc<dyn NetProvider<Resource> + 'static>,\n}\nimpl DioxusNativeNetProvider {\n    pub fn shared(proxy: EventLoopProxy<BlitzShellEvent>) -> Arc<dyn NetProvider<Resource>> {\n        Arc::new(Self::new(proxy)) as Arc<dyn NetProvider<Resource>>\n    }\n\n    pub fn new(proxy: EventLoopProxy<BlitzShellEvent>) -> Self {\n        let net_callback = BlitzShellNetCallback::shared(proxy);\n\n        #[cfg(feature = \"net\")]\n        let net_provider = blitz_net::Provider::shared(net_callback.clone());\n\n        Self {\n            callback: net_callback,\n            #[cfg(feature = \"net\")]\n            inner_net_provider: net_provider,\n        }\n    }\n}\n\nimpl NetProvider<Resource> for DioxusNativeNetProvider {\n    fn fetch(\n        &self,\n        doc_id: usize,\n        request: blitz_traits::net::Request,\n        handler: blitz_traits::net::BoxedHandler<Resource>,\n    ) {\n        if request.url.scheme() == \"dioxus\" {\n            #[allow(clippy::single_match)]\n            // cfg'd code has multiple branches in some configurations\n            match dioxus_asset_resolver::native::serve_asset(request.url.path()) {\n                Ok(res) => {\n                    #[cfg(feature = \"tracing\")]\n                    tracing::trace!(\"fetching asset from file system success {request:#?}\");\n                    handler.bytes(doc_id, res.into_body().into(), self.callback.clone())\n                }\n                Err(_) => {\n                    #[cfg(feature = \"tracing\")]\n                    tracing::warn!(\"fetching asset from file system error {request:#?}\");\n                }\n            }\n        } else {\n            #[cfg(feature = \"net\")]\n            self.inner_net_provider.fetch(doc_id, request, handler);\n\n            #[cfg(all(not(feature = \"net\"), feature = \"tracing\"))]\n            tracing::warn!(\"net feature not enabled, cannot fetch {request:#?}\");\n        }\n    }\n}\n"
  },
  {
    "path": "packages/native/src/config.rs",
    "content": "use dioxus_core::LaunchConfig;\nuse winit::window::WindowAttributes;\n\n/// The configuration for the desktop application.\npub struct Config {\n    pub(crate) window_attributes: WindowAttributes,\n}\n\nimpl LaunchConfig for Config {}\n\nimpl Default for Config {\n    fn default() -> Self {\n        Self {\n            window_attributes: WindowAttributes::default().with_title(\n                dioxus_cli_config::app_title().unwrap_or_else(|| \"Dioxus App\".to_string()),\n            ),\n        }\n    }\n}\n\nimpl Config {\n    pub fn new() -> Self {\n        Self::default()\n    }\n\n    /// Set the configuration for the window.\n    pub fn with_window_attributes(mut self, attrs: WindowAttributes) -> Self {\n        // We need to do a swap because the window builder only takes itself as muy self\n        self.window_attributes = attrs;\n        self\n    }\n}\n"
  },
  {
    "path": "packages/native/src/contexts.rs",
    "content": "use blitz_shell::BlitzShellEvent;\nuse dioxus_document::{Document, NoOpDocument};\nuse winit::{event_loop::EventLoopProxy, window::WindowId};\n\nuse crate::DioxusNativeEvent;\n\npub struct DioxusNativeDocument {\n    pub(crate) proxy: EventLoopProxy<BlitzShellEvent>,\n    pub(crate) window: WindowId,\n}\n\nimpl DioxusNativeDocument {\n    pub(crate) fn new(proxy: EventLoopProxy<BlitzShellEvent>, window: WindowId) -> Self {\n        Self { proxy, window }\n    }\n}\n\nimpl Document for DioxusNativeDocument {\n    fn eval(&self, _js: String) -> dioxus_document::Eval {\n        NoOpDocument.eval(_js)\n    }\n\n    fn create_head_element(\n        &self,\n        name: &str,\n        attributes: &[(&str, String)],\n        contents: Option<String>,\n    ) {\n        let window = self.window;\n        _ = self.proxy.send_event(BlitzShellEvent::embedder_event(\n            DioxusNativeEvent::CreateHeadElement {\n                name: name.to_string(),\n                attributes: attributes\n                    .iter()\n                    .map(|(name, value)| (name.to_string(), value.clone()))\n                    .collect(),\n                contents,\n                window,\n            },\n        ));\n    }\n\n    fn set_title(&self, title: String) {\n        self.create_head_element(\"title\", &[], Some(title));\n    }\n\n    fn create_meta(&self, props: dioxus_document::MetaProps) {\n        let attributes = props.attributes();\n        self.create_head_element(\"meta\", &attributes, None);\n    }\n\n    fn create_script(&self, props: dioxus_document::ScriptProps) {\n        let attributes = props.attributes();\n        self.create_head_element(\"script\", &attributes, props.script_contents().ok());\n    }\n\n    fn create_style(&self, props: dioxus_document::StyleProps) {\n        let attributes = props.attributes();\n        self.create_head_element(\"style\", &attributes, props.style_contents().ok());\n    }\n\n    fn create_link(&self, props: dioxus_document::LinkProps) {\n        let attributes = props.attributes();\n        self.create_head_element(\"link\", &attributes, None);\n    }\n\n    fn create_head_component(&self) -> bool {\n        true\n    }\n}\n"
  },
  {
    "path": "packages/native/src/dioxus_application.rs",
    "content": "use blitz_shell::{BlitzApplication, View};\nuse dioxus_core::{provide_context, ScopeId};\nuse dioxus_history::{History, MemoryHistory};\nuse std::rc::Rc;\nuse winit::application::ApplicationHandler;\nuse winit::event::{StartCause, WindowEvent};\nuse winit::event_loop::{ActiveEventLoop, EventLoopProxy};\nuse winit::window::WindowId;\n\nuse crate::DioxusNativeWindowRenderer;\nuse crate::{contexts::DioxusNativeDocument, BlitzShellEvent, DioxusDocument, WindowConfig};\n\n/// Dioxus-native specific event type\npub enum DioxusNativeEvent {\n    /// A hotreload event, basically telling us to update our templates.\n    #[cfg(all(\n        feature = \"hot-reload\",\n        debug_assertions,\n        not(target_os = \"android\"),\n        not(target_os = \"ios\")\n    ))]\n    DevserverEvent(dioxus_devtools::DevserverMsg),\n\n    /// Create a new head element from the Link and Title elements\n    ///\n    /// todo(jon): these should probabkly be synchronous somehow\n    CreateHeadElement {\n        window: WindowId,\n        name: String,\n        attributes: Vec<(String, String)>,\n        contents: Option<String>,\n    },\n}\n\npub struct DioxusNativeApplication {\n    pending_window: Option<WindowConfig<DioxusNativeWindowRenderer>>,\n    inner: BlitzApplication<DioxusNativeWindowRenderer>,\n    proxy: EventLoopProxy<BlitzShellEvent>,\n}\n\nimpl DioxusNativeApplication {\n    pub fn new(\n        proxy: EventLoopProxy<BlitzShellEvent>,\n        config: WindowConfig<DioxusNativeWindowRenderer>,\n    ) -> Self {\n        Self {\n            pending_window: Some(config),\n            inner: BlitzApplication::new(proxy.clone()),\n            proxy,\n        }\n    }\n\n    pub fn add_window(&mut self, window_config: WindowConfig<DioxusNativeWindowRenderer>) {\n        self.inner.add_window(window_config);\n    }\n\n    fn handle_blitz_shell_event(\n        &mut self,\n        event_loop: &ActiveEventLoop,\n        event: &DioxusNativeEvent,\n    ) {\n        match event {\n            #[cfg(all(\n                feature = \"hot-reload\",\n                debug_assertions,\n                not(target_os = \"android\"),\n                not(target_os = \"ios\")\n            ))]\n            DioxusNativeEvent::DevserverEvent(event) => match event {\n                dioxus_devtools::DevserverMsg::HotReload(hotreload_message) => {\n                    for window in self.inner.windows.values_mut() {\n                        let doc = window.downcast_doc_mut::<DioxusDocument>();\n\n                        // Apply changes to vdom\n                        dioxus_devtools::apply_changes(&doc.vdom, hotreload_message);\n\n                        // Reload changed assets\n                        for asset_path in &hotreload_message.assets {\n                            if let Some(url) = asset_path.to_str() {\n                                doc.reload_resource_by_href(url);\n                            }\n                        }\n\n                        window.poll();\n                    }\n                }\n                dioxus_devtools::DevserverMsg::Shutdown => event_loop.exit(),\n                dioxus_devtools::DevserverMsg::FullReloadStart => {}\n                dioxus_devtools::DevserverMsg::FullReloadFailed => {}\n                dioxus_devtools::DevserverMsg::FullReloadCommand => {}\n                _ => {}\n            },\n\n            DioxusNativeEvent::CreateHeadElement {\n                name,\n                attributes,\n                contents,\n                window,\n            } => {\n                if let Some(window) = self.inner.windows.get_mut(window) {\n                    let doc = window.downcast_doc_mut::<DioxusDocument>();\n                    doc.create_head_element(name, attributes, contents);\n                    window.poll();\n                }\n            }\n\n            // Suppress unused variable warning\n            #[cfg(not(all(\n                feature = \"hot-reload\",\n                debug_assertions,\n                not(target_os = \"android\"),\n                not(target_os = \"ios\")\n            )))]\n            #[allow(unreachable_patterns)]\n            _ => {\n                let _ = event_loop;\n                let _ = event;\n            }\n        }\n    }\n}\n\nimpl ApplicationHandler<BlitzShellEvent> for DioxusNativeApplication {\n    fn resumed(&mut self, event_loop: &ActiveEventLoop) {\n        #[cfg(feature = \"tracing\")]\n        tracing::debug!(\"Injecting document provider into all windows\");\n\n        if let Some(config) = self.pending_window.take() {\n            let mut window = View::init(config, event_loop, &self.proxy);\n            let renderer = window.renderer.clone();\n            let window_id = window.window_id();\n            let doc = window.downcast_doc_mut::<DioxusDocument>();\n\n            doc.vdom.in_scope(ScopeId::ROOT, || {\n                let shared: Rc<dyn dioxus_document::Document> =\n                    Rc::new(DioxusNativeDocument::new(self.proxy.clone(), window_id));\n                provide_context(shared);\n            });\n\n            // Add history\n            let history_provider: Rc<dyn History> = Rc::new(MemoryHistory::default());\n            doc.vdom\n                .in_scope(ScopeId::ROOT, move || provide_context(history_provider));\n\n            // Add renderer\n            doc.vdom\n                .in_scope(ScopeId::ROOT, move || provide_context(renderer));\n\n            // Queue rebuild\n            doc.initial_build();\n\n            // And then request redraw\n            window.request_redraw();\n\n            // todo(jon): we should actually mess with the pending windows instead of passing along the contexts\n            self.inner.windows.insert(window_id, window);\n        }\n\n        self.inner.resumed(event_loop);\n    }\n\n    fn suspended(&mut self, event_loop: &ActiveEventLoop) {\n        self.inner.suspended(event_loop);\n    }\n\n    fn new_events(&mut self, event_loop: &ActiveEventLoop, cause: StartCause) {\n        self.inner.new_events(event_loop, cause);\n    }\n\n    fn window_event(\n        &mut self,\n        event_loop: &ActiveEventLoop,\n        window_id: WindowId,\n        event: WindowEvent,\n    ) {\n        self.inner.window_event(event_loop, window_id, event);\n    }\n\n    fn user_event(&mut self, event_loop: &ActiveEventLoop, event: BlitzShellEvent) {\n        match event {\n            BlitzShellEvent::Embedder(event) => {\n                if let Some(event) = event.downcast_ref::<DioxusNativeEvent>() {\n                    self.handle_blitz_shell_event(event_loop, event);\n                }\n            }\n            event => self.inner.user_event(event_loop, event),\n        }\n    }\n}\n"
  },
  {
    "path": "packages/native/src/dioxus_renderer.rs",
    "content": "use std::cell::RefCell;\nuse std::rc::Rc;\nuse std::sync::Arc;\n\nuse anyrender::WindowRenderer;\n\npub use anyrender_vello::{\n    wgpu::{Features, Limits},\n    CustomPaintSource, VelloRendererOptions,\n};\n\n#[cfg(not(all(target_os = \"ios\", target_abi = \"sim\")))]\npub use anyrender_vello::VelloWindowRenderer as InnerRenderer;\n\n#[cfg(all(target_os = \"ios\", target_abi = \"sim\"))]\npub use anyrender_vello_cpu::VelloCpuWindowRenderer as InnerRenderer;\n\n#[cfg(not(all(target_os = \"ios\", target_abi = \"sim\")))]\npub fn use_wgpu<T: CustomPaintSource>(create_source: impl FnOnce() -> T) -> u64 {\n    use dioxus_core::{consume_context, use_hook_with_cleanup};\n\n    let (_renderer, id) = use_hook_with_cleanup(\n        || {\n            let renderer = consume_context::<DioxusNativeWindowRenderer>();\n            let source = Box::new(create_source());\n            let id = renderer.register_custom_paint_source(source);\n            (renderer, id)\n        },\n        |(renderer, id)| {\n            renderer.unregister_custom_paint_source(id);\n        },\n    );\n\n    id\n}\n\n#[derive(Clone)]\npub struct DioxusNativeWindowRenderer {\n    inner: Rc<RefCell<InnerRenderer>>,\n}\n\nimpl Default for DioxusNativeWindowRenderer {\n    fn default() -> Self {\n        Self::new()\n    }\n}\n\nimpl DioxusNativeWindowRenderer {\n    pub fn new() -> Self {\n        let vello_renderer = InnerRenderer::new();\n        Self::with_inner_renderer(vello_renderer)\n    }\n\n    #[cfg(not(all(target_os = \"ios\", target_abi = \"sim\")))]\n    pub fn with_features_and_limits(features: Option<Features>, limits: Option<Limits>) -> Self {\n        let vello_renderer = InnerRenderer::with_options(VelloRendererOptions {\n            features,\n            limits,\n            ..Default::default()\n        });\n        Self::with_inner_renderer(vello_renderer)\n    }\n\n    fn with_inner_renderer(vello_renderer: InnerRenderer) -> Self {\n        Self {\n            inner: Rc::new(RefCell::new(vello_renderer)),\n        }\n    }\n}\n\n#[cfg(not(all(target_os = \"ios\", target_abi = \"sim\")))]\nimpl DioxusNativeWindowRenderer {\n    pub fn register_custom_paint_source(&self, source: Box<dyn CustomPaintSource>) -> u64 {\n        self.inner.borrow_mut().register_custom_paint_source(source)\n    }\n\n    pub fn unregister_custom_paint_source(&self, id: u64) {\n        self.inner.borrow_mut().unregister_custom_paint_source(id)\n    }\n}\n\nimpl WindowRenderer for DioxusNativeWindowRenderer {\n    type ScenePainter<'a>\n        = <InnerRenderer as WindowRenderer>::ScenePainter<'a>\n    where\n        Self: 'a;\n\n    fn resume(&mut self, window: Arc<dyn anyrender::WindowHandle>, width: u32, height: u32) {\n        self.inner.borrow_mut().resume(window, width, height)\n    }\n\n    fn suspend(&mut self) {\n        self.inner.borrow_mut().suspend()\n    }\n\n    fn is_active(&self) -> bool {\n        self.inner.borrow().is_active()\n    }\n\n    fn set_size(&mut self, width: u32, height: u32) {\n        self.inner.borrow_mut().set_size(width, height)\n    }\n\n    fn render<F: FnOnce(&mut Self::ScenePainter<'_>)>(&mut self, draw_fn: F) {\n        self.inner.borrow_mut().render(draw_fn)\n    }\n}\n"
  },
  {
    "path": "packages/native/src/lib.rs",
    "content": "#![cfg_attr(docsrs, feature(doc_cfg))]\n\n//! A native renderer for Dioxus.\n//!\n//! ## Feature flags\n//!  - `default`: Enables the features listed below.\n//!  - `accessibility`: Enables [`accesskit`](https://docs.rs/accesskit/latest/accesskit/) accessibility support.\n//!  - `hot-reload`: Enables hot-reloading of Dioxus RSX.\n//!  - `menu`: Enables the [`muda`](https://docs.rs/muda/latest/muda/) menubar.\n//!  - `tracing`: Enables tracing support.\n\nmod assets;\nmod config;\nmod contexts;\nmod dioxus_application;\nmod dioxus_renderer;\nmod link_handler;\n\n#[cfg(feature = \"prelude\")]\npub mod prelude;\n\n#[doc(inline)]\npub use dioxus_native_dom::*;\n\npub use anyrender_vello::{CustomPaintCtx, CustomPaintSource, DeviceHandle, TextureHandle};\nuse assets::DioxusNativeNetProvider;\npub use dioxus_application::{DioxusNativeApplication, DioxusNativeEvent};\npub use dioxus_renderer::{DioxusNativeWindowRenderer, Features, Limits};\n\n#[cfg(not(all(target_os = \"ios\", target_abi = \"sim\")))]\npub use dioxus_renderer::use_wgpu;\n\npub use config::Config;\npub use winit::dpi::{LogicalSize, PhysicalSize};\npub use winit::window::WindowAttributes;\n\nuse blitz_shell::{create_default_event_loop, BlitzShellEvent, WindowConfig};\nuse dioxus_core::{ComponentFunction, Element, VirtualDom};\nuse link_handler::DioxusNativeNavigationProvider;\nuse std::any::Any;\nuse std::sync::Arc;\n\n/// Launch an interactive HTML/CSS renderer driven by the Dioxus virtualdom\npub fn launch(app: fn() -> Element) {\n    launch_cfg(app, vec![], vec![])\n}\n\npub fn launch_cfg(\n    app: fn() -> Element,\n    contexts: Vec<Box<dyn Fn() -> Box<dyn Any> + Send + Sync>>,\n    cfg: Vec<Box<dyn Any>>,\n) {\n    launch_cfg_with_props(app, (), contexts, cfg)\n}\n\n// todo: props shouldn't have the clone bound - should try and match dioxus-desktop behavior\npub fn launch_cfg_with_props<P: Clone + 'static, M: 'static>(\n    app: impl ComponentFunction<P, M>,\n    props: P,\n    contexts: Vec<Box<dyn Fn() -> Box<dyn Any> + Send + Sync>>,\n    configs: Vec<Box<dyn Any>>,\n) {\n    // Macro to attempt to downcast a type out of a Box<dyn Any>\n    macro_rules! try_read_config {\n        ($input:ident, $store:ident, $kind:ty) => {\n            // Try to downcast the Box<dyn Any> to type $kind\n            match $input.downcast::<$kind>() {\n                // If the type matches then write downcast value to variable $store\n                Ok(value) => {\n                    $store = Some(*value);\n                    continue;\n                }\n                // Else extract the original Box<dyn Any> value out of the error type\n                // and return it so that we can try again with a different type.\n                Err(cfg) => cfg,\n            }\n        };\n    }\n\n    // Read config values\n    let mut features = None;\n    let mut limits = None;\n    let mut window_attributes = None;\n    let mut config = None;\n    for mut cfg in configs {\n        cfg = try_read_config!(cfg, features, Features);\n        cfg = try_read_config!(cfg, limits, Limits);\n        cfg = try_read_config!(cfg, window_attributes, WindowAttributes);\n        cfg = try_read_config!(cfg, config, Config);\n        let _ = cfg;\n    }\n\n    let mut config = config.unwrap_or_default();\n    if let Some(window_attributes) = window_attributes {\n        config.window_attributes = window_attributes;\n    }\n    let event_loop = create_default_event_loop::<BlitzShellEvent>();\n\n    // Turn on the runtime and enter it\n    #[cfg(feature = \"net\")]\n    let rt = tokio::runtime::Builder::new_multi_thread()\n        .enable_all()\n        .build()\n        .unwrap();\n    #[cfg(feature = \"net\")]\n    let _guard = rt.enter();\n\n    // Setup hot-reloading if enabled.\n    #[cfg(all(\n        feature = \"hot-reload\",\n        debug_assertions,\n        not(target_os = \"android\"),\n        not(target_os = \"ios\")\n    ))]\n    {\n        let proxy = event_loop.create_proxy();\n        dioxus_devtools::connect(move |event| {\n            let dxn_event = DioxusNativeEvent::DevserverEvent(event);\n            let _ = proxy.send_event(BlitzShellEvent::embedder_event(dxn_event));\n        })\n    }\n\n    // Spin up the virtualdom\n    // We're going to need to hit it with a special waker\n    // Note that we are delaying the initialization of window-specific contexts (net provider, document, etc)\n    let mut vdom = VirtualDom::new_with_props(app, props);\n\n    // Add contexts\n    for context in contexts {\n        vdom.insert_any_root_context(context());\n    }\n\n    let net_provider = Some(DioxusNativeNetProvider::shared(event_loop.create_proxy()));\n\n    #[cfg(feature = \"html\")]\n    let html_parser_provider = Some(Arc::new(blitz_html::HtmlProvider) as _);\n    #[cfg(not(feature = \"html\"))]\n    let html_parser_provider = None;\n\n    let navigation_provider = Some(Arc::new(DioxusNativeNavigationProvider) as _);\n\n    // Create document + window from the baked virtualdom\n    let doc = DioxusDocument::new(\n        vdom,\n        DocumentConfig {\n            net_provider,\n            html_parser_provider,\n            navigation_provider,\n            ..Default::default()\n        },\n    );\n    #[cfg(not(all(target_os = \"ios\", target_abi = \"sim\")))]\n    let renderer = DioxusNativeWindowRenderer::with_features_and_limits(features, limits);\n    #[cfg(all(target_os = \"ios\", target_abi = \"sim\"))]\n    let renderer = DioxusNativeWindowRenderer::new();\n    let config = WindowConfig::with_attributes(\n        Box::new(doc) as _,\n        renderer.clone(),\n        config.window_attributes,\n    );\n\n    // Create application\n    let mut application = DioxusNativeApplication::new(event_loop.create_proxy(), config);\n\n    // Run event loop\n    event_loop.run_app(&mut application).unwrap();\n}\n"
  },
  {
    "path": "packages/native/src/link_handler.rs",
    "content": "use blitz_traits::{\n    navigation::{NavigationOptions, NavigationProvider},\n    net::Method,\n};\n\npub(crate) struct DioxusNativeNavigationProvider;\n\nimpl NavigationProvider for DioxusNativeNavigationProvider {\n    fn navigate_to(&self, options: NavigationOptions) {\n        if options.method == Method::GET\n            && matches!(options.url.scheme(), \"http\" | \"https\" | \"mailto\")\n        {\n            if let Err(_err) = webbrowser::open(options.url.as_str()) {\n                #[cfg(feature = \"tracing\")]\n                tracing::error!(\"Failed to open URL: {}\", _err);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "packages/native/src/prelude.rs",
    "content": "// RSX and component definition\npub use dioxus_core;\npub use dioxus_core::{\n    consume_context, provide_context, spawn, suspend, try_consume_context, use_drop, use_hook,\n    AnyhowContext, Attribute, Callback, Component, Element, ErrorBoundary, ErrorContext, Event,\n    EventHandler, Fragment, HasAttributes, IntoDynNode, RenderError, ScopeId, SuspenseBoundary,\n    SuspenseContext, VNode, VirtualDom,\n};\n#[allow(deprecated)]\npub use dioxus_core_macro::{component, rsx, Props};\npub use dioxus_html as dioxus_elements;\npub use dioxus_html::{\n    events::*, extensions::*, global_attributes, keyboard_types, svg_attributes, traits::*,\n    GlobalAttributesExtension, SvgAttributesExtension,\n};\npub use dioxus_html::{Code, Key, Location, Modifiers};\n\n// Assets\npub use manganis::{self, *};\n\n// Hooks, signals, stores\npub use dioxus_hooks::*;\npub use dioxus_signals::{self, *};\npub use dioxus_stores::{self, store, use_store, GlobalStore, ReadStore, Store, WriteStore};\n\n// Document and History\npub use dioxus_document::{self as document, Meta, Stylesheet, Title};\npub use dioxus_history::{history, History};\n"
  },
  {
    "path": "packages/native-dom/Cargo.toml",
    "content": "[package]\nname = \"dioxus-native-dom\"\nversion = { workspace = true }\nauthors = [\"Jonathan Kelley\", \"Nico Burns\"]\nedition = \"2021\"\ndescription = \"Core headless native renderer for Dioxus based on blitz\"\nlicense = \"MIT OR Apache-2.0\"\nrepository = \"https://github.com/DioxusLabs/dioxus/\"\nhomepage = \"https://dioxuslabs.com/learn/0.7/getting_started\"\nkeywords = [\"dom\", \"ui\", \"gui\", \"react\"]\n\n[features]\ndefault = [\"accessibility\", \"svg\", \"system-fonts\"]\nsvg = [\"blitz-dom/svg\"]\naccessibility = [\"blitz-dom/accessibility\"]\ntracing = [\"dep:tracing\", \"blitz-dom/tracing\"]\nsystem-fonts = [\"blitz-dom/system_fonts\"]\nautofocus = []\n\n[dependencies]\n# Blitz dependencies\nblitz-dom = { workspace = true, default-features = false }\nblitz-traits = { workspace = true }\n\n# DioxusLabs dependencies\ndioxus-core = { workspace = true }\ndioxus-html = { workspace = true }\n\n# Windowing & Input\nkeyboard-types = { workspace = true }\n\n\n# Other dependencies\ntracing = { workspace = true, optional = true }\nrustc-hash = { workspace = true }\nfutures-util = { workspace = true }\n\n[dev-dependencies]\ndioxus = { workspace = true }\n\n[package.metadata.docs.rs]\nall-features = true\n"
  },
  {
    "path": "packages/native-dom/src/dioxus_document.rs",
    "content": "//! Integration between Dioxus and Blitz\nuse crate::events::{BlitzKeyboardData, NativeClickData, NativeConverter, NativeFormData};\nuse crate::mutation_writer::{DioxusState, MutationWriter};\nuse crate::qual_name;\nuse crate::NodeId;\nuse blitz_dom::{\n    Attribute, BaseDocument, Document, DocumentConfig, EventDriver, EventHandler, Node, DEFAULT_CSS,\n};\nuse blitz_traits::events::{DomEvent, DomEventData, EventState, UiEvent};\nuse dioxus_core::{ElementId, Event, VirtualDom};\nuse dioxus_html::{set_event_converter, PlatformEventData};\nuse futures_util::task::noop_waker;\nuse futures_util::{pin_mut, FutureExt};\nuse std::ops::{Deref, DerefMut};\nuse std::sync::LazyLock;\nuse std::task::{Context as TaskContext, Waker};\nuse std::{any::Any, rc::Rc};\n\nfn wrap_event_data<T: Any>(value: T) -> Rc<dyn Any> {\n    Rc::new(PlatformEventData::new(Box::new(value)))\n}\n\n/// Get the value of the \"dioxus-data-id\" attribute parsed aa usize\nfn get_dioxus_id(node: &Node) -> Option<ElementId> {\n    node.element_data()?\n        .attrs\n        .iter()\n        .find(|attr| *attr.name.local == *\"data-dioxus-id\")\n        .and_then(|attr| attr.value.parse::<usize>().ok())\n        .map(ElementId)\n}\n\n/// Integrates [`BaseDocument`] from  [`blitz-dom`](blitz_dom)  with [`VirtualDom`] from [`dioxus-core`](dioxus_core)\n///\n/// ### Example\n///\n/// ```rust\n/// use blitz_traits::shell::{Viewport, ColorScheme};\n/// use dioxus_native_dom::{DioxusDocument, DocumentConfig};\n/// use dioxus::prelude::*;\n///\n/// // Example Dioxus app\n/// fn app() -> Element {\n///     rsx! {\n///         div { \"Hello, world!\" }\n///     }\n/// }\n///\n/// fn main() {\n///    let vdom = VirtualDom::new(app);\n///    let mut doc = DioxusDocument::new(vdom, DocumentConfig {\n///         viewport: Some(Viewport::new(800, 600, 1.0, ColorScheme::Light)),\n///         ..Default::default()\n///    });\n///    doc.initial_build();\n/// }\n/// ```\n///\n/// You can just push events into the [`DioxusDocument`] with [`doc.handle_ui_event(..)`](Self::handle_ui_event)\n/// and then flush the changes with [`doc.poll(..)`](Self::poll)\npub struct DioxusDocument {\n    pub inner: BaseDocument,\n    pub vdom: VirtualDom,\n    pub vdom_state: DioxusState,\n\n    #[allow(unused)]\n    pub(crate) html_element_id: NodeId,\n    #[allow(unused)]\n    pub(crate) head_element_id: NodeId,\n    #[allow(unused)]\n    pub(crate) body_element_id: NodeId,\n    #[allow(unused)]\n    pub(crate) main_element_id: NodeId,\n}\n\nimpl DioxusDocument {\n    /// Create a new [`DioxusDocument`] from a [`VirtualDom`].\n    pub fn new(vdom: VirtualDom, mut config: DocumentConfig) -> Self {\n        // Only really needs to happen once\n        set_event_converter(Box::new(NativeConverter {}));\n\n        config.base_url = Some(\n            config\n                .base_url\n                .unwrap_or_else(|| String::from(\"dioxus://index.html\")),\n        );\n        let mut doc = BaseDocument::new(config);\n\n        // Include default stylesheet\n        doc.add_user_agent_stylesheet(DEFAULT_CSS);\n\n        // Create some minimal HTML to render the app into.\n        // HTML is equivalent to:\n        //\n        // <html>\n        // <head></head>\n        // <body>\n        //    <div id=\"main\"></div>\n        // </body>\n        // </html>\n        //\n        // TODO: Support arbitrary \"index.html\" templates\n\n        // Create the html element\n        let mut mutr = doc.mutate();\n        let html_element_id = mutr.create_element(qual_name(\"html\", None), vec![]);\n        mutr.append_children(mutr.doc.root_node().id, &[html_element_id]);\n\n        // Create the body element\n        let head_element_id = mutr.create_element(qual_name(\"head\", None), vec![]);\n        mutr.append_children(html_element_id, &[head_element_id]);\n\n        // Create the body element\n        let body_element_id = mutr.create_element(qual_name(\"body\", None), vec![]);\n        mutr.append_children(html_element_id, &[body_element_id]);\n\n        // Create another virtual element to hold the root <div id=\"main\"></div> under the html element\n        let main_attr = blitz_dom::Attribute {\n            name: qual_name(\"id\", None),\n            value: \"main\".to_string(),\n        };\n        let main_element_id = mutr.create_element(qual_name(\"main\", None), vec![main_attr]);\n        mutr.append_children(body_element_id, &[main_element_id]);\n\n        drop(mutr);\n\n        let vdom_state = DioxusState::create(main_element_id);\n        Self {\n            vdom,\n            vdom_state,\n            inner: doc,\n            html_element_id,\n            head_element_id,\n            body_element_id,\n            main_element_id,\n        }\n    }\n\n    /// Run an initial build of the Dioxus vdom\n    pub fn initial_build(&mut self) {\n        let mut writer = MutationWriter::new(&mut self.inner, &mut self.vdom_state);\n        self.vdom.rebuild(&mut writer);\n    }\n\n    /// Used to respond to a `CreateHeadElement` event generated by Dioxus. These\n    /// events allow Dioxus to create elements in the `<head>` of the document.\n    #[doc(hidden)]\n    pub fn create_head_element(\n        &mut self,\n        name: &str,\n        attributes: &[(String, String)],\n        contents: &Option<String>,\n    ) {\n        let mut mutr = self.inner.mutate();\n\n        let attributes = attributes\n            .iter()\n            .map(|(name, value)| Attribute {\n                name: qual_name(name, None),\n                value: value.clone(),\n            })\n            .collect();\n\n        let new_elem_id = mutr.create_element(qual_name(name, None), attributes);\n        mutr.append_children(self.head_element_id, &[new_elem_id]);\n        if let Some(contents) = contents {\n            let text_node_id = mutr.create_text_node(contents);\n            mutr.append_children(new_elem_id, &[text_node_id]);\n        }\n    }\n}\n\n// Implement DocumentLike and required traits for DioxusDocument\nimpl Document for DioxusDocument {\n    fn id(&self) -> usize {\n        self.inner.id()\n    }\n\n    fn as_any_mut(&mut self) -> &mut dyn Any {\n        self\n    }\n\n    fn poll(&mut self, cx: Option<TaskContext>) -> bool {\n        {\n            let fut = self.vdom.wait_for_work();\n            pin_mut!(fut);\n\n            static NOOP_WAKER: LazyLock<Waker> = LazyLock::new(noop_waker);\n            let mut cx = cx.unwrap_or_else(|| TaskContext::from_waker(&NOOP_WAKER));\n            match fut.poll_unpin(&mut cx) {\n                std::task::Poll::Ready(_) => {}\n                std::task::Poll::Pending => return false,\n            }\n        }\n\n        let mut writer = MutationWriter::new(&mut self.inner, &mut self.vdom_state);\n        self.vdom.render_immediate(&mut writer);\n\n        true\n    }\n\n    fn handle_ui_event(&mut self, event: UiEvent) {\n        let handler = DioxusEventHandler {\n            vdom: &mut self.vdom,\n            vdom_state: &mut self.vdom_state,\n        };\n        let mut driver = EventDriver::new(self.inner.mutate(), handler);\n        driver.handle_ui_event(event);\n    }\n}\n\nimpl Deref for DioxusDocument {\n    type Target = BaseDocument;\n    fn deref(&self) -> &BaseDocument {\n        &self.inner\n    }\n}\nimpl DerefMut for DioxusDocument {\n    fn deref_mut(&mut self) -> &mut Self::Target {\n        &mut self.inner\n    }\n}\nimpl From<DioxusDocument> for BaseDocument {\n    fn from(doc: DioxusDocument) -> BaseDocument {\n        doc.inner\n    }\n}\n\npub struct DioxusEventHandler<'v> {\n    vdom: &'v mut VirtualDom,\n    #[allow(dead_code, reason = \"WIP\")]\n    vdom_state: &'v mut DioxusState,\n}\n\nimpl EventHandler for DioxusEventHandler<'_> {\n    fn handle_event(\n        &mut self,\n        chain: &[usize],\n        event: &mut DomEvent,\n        mutr: &mut blitz_dom::DocumentMutator<'_>,\n        event_state: &mut EventState,\n    ) {\n        // As an optimisation we maintain a count of the total number event handlers of a given type\n        // If this count is zero then we can skip handling that kind of event entirely.\n        let event_kind_idx = event.data.discriminant() as usize;\n        let event_kind_count = self.vdom_state.event_handler_counts[event_kind_idx];\n        if event_kind_count == 0 {\n            return;\n        }\n\n        let event_data = match &event.data {\n            DomEventData::MouseMove(mevent)\n            | DomEventData::MouseDown(mevent)\n            | DomEventData::MouseUp(mevent)\n            | DomEventData::Click(mevent) => Some(wrap_event_data(NativeClickData(mevent.clone()))),\n\n            DomEventData::KeyDown(kevent)\n            | DomEventData::KeyUp(kevent)\n            | DomEventData::KeyPress(kevent) => {\n                Some(wrap_event_data(BlitzKeyboardData(kevent.clone())))\n            }\n\n            DomEventData::Input(data) => Some(wrap_event_data(NativeFormData {\n                value: data.value.clone(),\n                values: vec![],\n            })),\n\n            // TODO: Implement IME handling\n            DomEventData::Ime(_) => None,\n        };\n\n        let Some(event_data) = event_data else {\n            return;\n        };\n\n        for &node_id in chain {\n            // Get dioxus vdom id for node\n            let dioxus_id = mutr.doc.get_node(node_id).and_then(get_dioxus_id);\n            let Some(id) = dioxus_id else {\n                continue;\n            };\n\n            // Handle event in vdom\n            let dx_event = Event::new(event_data.clone(), event.bubbles);\n            self.vdom\n                .runtime()\n                .handle_event(event.name(), dx_event.clone(), id);\n\n            // Update event state\n            if !dx_event.default_action_enabled() {\n                event_state.prevent_default();\n            }\n            if !dx_event.propagates() {\n                event_state.stop_propagation();\n                break;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "packages/native-dom/src/events.rs",
    "content": "use blitz_traits::events::{BlitzKeyEvent, BlitzMouseButtonEvent, MouseEventButton};\nuse dioxus_html::{\n    geometry::{ClientPoint, ElementPoint, PagePoint, ScreenPoint},\n    input_data::{MouseButton, MouseButtonSet},\n    point_interaction::{\n        InteractionElementOffset, InteractionLocation, ModifiersInteraction, PointerInteraction,\n    },\n    AnimationData, CancelData, ClipboardData, CompositionData, DragData, FocusData, FormData,\n    FormValue, HasFileData, HasFocusData, HasFormData, HasKeyboardData, HasMouseData,\n    HtmlEventConverter, ImageData, KeyboardData, MediaData, MountedData, MouseData,\n    PlatformEventData, PointerData, ResizeData, ScrollData, SelectionData, ToggleData, TouchData,\n    TransitionData, VisibleData, WheelData,\n};\nuse keyboard_types::{Code, Key, Location, Modifiers};\nuse std::any::Any;\n\npub struct NativeConverter {}\n\nimpl HtmlEventConverter for NativeConverter {\n    fn convert_cancel_data(&self, _event: &PlatformEventData) -> CancelData {\n        unimplemented!(\"todo: convert_cancel_data in dioxus-native. requires support in blitz\")\n    }\n\n    fn convert_form_data(&self, event: &PlatformEventData) -> FormData {\n        event.downcast::<NativeFormData>().unwrap().clone().into()\n    }\n\n    fn convert_mouse_data(&self, event: &PlatformEventData) -> MouseData {\n        event.downcast::<NativeClickData>().unwrap().clone().into()\n    }\n\n    fn convert_keyboard_data(&self, event: &PlatformEventData) -> KeyboardData {\n        event\n            .downcast::<BlitzKeyboardData>()\n            .unwrap()\n            .clone()\n            .into()\n    }\n\n    fn convert_focus_data(&self, _event: &PlatformEventData) -> FocusData {\n        NativeFocusData {}.into()\n    }\n\n    fn convert_animation_data(&self, _event: &PlatformEventData) -> AnimationData {\n        unimplemented!(\"todo: convert_animation_data in dioxus-native. requires support in blitz\")\n    }\n\n    fn convert_clipboard_data(&self, _event: &PlatformEventData) -> ClipboardData {\n        unimplemented!(\"todo: convert_clipboard_data in dioxus-native. requires support in blitz\")\n    }\n\n    fn convert_composition_data(&self, _event: &PlatformEventData) -> CompositionData {\n        unimplemented!(\"todo: convert_composition_data in dioxus-native. requires support in blitz\")\n    }\n\n    fn convert_drag_data(&self, _event: &PlatformEventData) -> DragData {\n        unimplemented!(\"todo: convert_drag_data in dioxus-native. requires support in blitz\")\n    }\n\n    fn convert_image_data(&self, _event: &PlatformEventData) -> ImageData {\n        unimplemented!(\"todo: convert_image_data in dioxus-native. requires support in blitz\")\n    }\n\n    fn convert_media_data(&self, _event: &PlatformEventData) -> MediaData {\n        unimplemented!(\"todo: convert_media_data in dioxus-native. requires support in blitz\")\n    }\n\n    fn convert_mounted_data(&self, _event: &PlatformEventData) -> MountedData {\n        unimplemented!(\"todo: convert_mounted_data in dioxus-native. requires support in blitz\")\n    }\n\n    fn convert_pointer_data(&self, _event: &PlatformEventData) -> PointerData {\n        unimplemented!(\"todo: convert_pointer_data in dioxus-native. requires support in blitz\")\n    }\n\n    fn convert_scroll_data(&self, _event: &PlatformEventData) -> ScrollData {\n        unimplemented!(\"todo: convert_scroll_data in dioxus-native. requires support in blitz\")\n    }\n\n    fn convert_selection_data(&self, _event: &PlatformEventData) -> SelectionData {\n        unimplemented!(\"todo: convert_selection_data in dioxus-native. requires support in blitz\")\n    }\n\n    fn convert_toggle_data(&self, _event: &PlatformEventData) -> ToggleData {\n        unimplemented!(\"todo: convert_toggle_data in dioxus-native. requires support in blitz\")\n    }\n\n    fn convert_touch_data(&self, _event: &PlatformEventData) -> TouchData {\n        unimplemented!(\"todo: convert_touch_data in dioxus-native. requires support in blitz\")\n    }\n\n    fn convert_transition_data(&self, _event: &PlatformEventData) -> TransitionData {\n        unimplemented!(\"todo: convert_transition_data in dioxus-native. requires support in blitz\")\n    }\n\n    fn convert_wheel_data(&self, _event: &PlatformEventData) -> WheelData {\n        unimplemented!(\"todo: convert_wheel_data in dioxus-native. requires support in blitz\")\n    }\n\n    fn convert_resize_data(&self, _event: &PlatformEventData) -> ResizeData {\n        unimplemented!(\"todo: convert_resize_data in dioxus-native. requires support in blitz\")\n    }\n\n    fn convert_visible_data(&self, _event: &PlatformEventData) -> VisibleData {\n        unimplemented!(\"todo: convert_visible_data in dioxus-native. requires support in blitz\")\n    }\n}\n\n#[derive(Clone, Debug)]\npub struct NativeFormData {\n    pub value: String,\n    pub values: Vec<(String, FormValue)>,\n}\n\nimpl HasFormData for NativeFormData {\n    fn as_any(&self) -> &dyn std::any::Any {\n        self as &dyn std::any::Any\n    }\n\n    fn value(&self) -> String {\n        self.value.clone()\n    }\n\n    fn values(&self) -> Vec<(String, FormValue)> {\n        self.values.clone()\n    }\n    fn valid(&self) -> bool {\n        // todo: actually implement validation here.\n        true\n    }\n}\n\nimpl HasFileData for NativeFormData {\n    fn files(&self) -> Vec<dioxus_html::FileData> {\n        vec![]\n    }\n}\n\n#[derive(Clone, Debug)]\npub(crate) struct BlitzKeyboardData(pub(crate) BlitzKeyEvent);\n\nimpl ModifiersInteraction for BlitzKeyboardData {\n    fn modifiers(&self) -> Modifiers {\n        self.0.modifiers\n    }\n}\n\nimpl HasKeyboardData for BlitzKeyboardData {\n    fn key(&self) -> Key {\n        self.0.key.clone()\n    }\n\n    fn code(&self) -> Code {\n        self.0.code\n    }\n\n    fn location(&self) -> Location {\n        self.0.location\n    }\n\n    fn is_auto_repeating(&self) -> bool {\n        self.0.is_auto_repeating\n    }\n\n    fn is_composing(&self) -> bool {\n        self.0.is_composing\n    }\n\n    fn as_any(&self) -> &dyn std::any::Any {\n        self as &dyn Any\n    }\n}\n\n#[derive(Clone)]\npub struct NativeClickData(pub(crate) BlitzMouseButtonEvent);\n\nimpl InteractionLocation for NativeClickData {\n    fn client_coordinates(&self) -> ClientPoint {\n        ClientPoint::new(self.0.x as _, self.0.y as _)\n    }\n\n    // these require blitz to pass them along, or a dom rect\n    fn screen_coordinates(&self) -> ScreenPoint {\n        unimplemented!()\n    }\n\n    fn page_coordinates(&self) -> PagePoint {\n        unimplemented!()\n    }\n}\n\nimpl InteractionElementOffset for NativeClickData {\n    fn element_coordinates(&self) -> ElementPoint {\n        unimplemented!()\n    }\n}\n\nimpl ModifiersInteraction for NativeClickData {\n    fn modifiers(&self) -> Modifiers {\n        self.0.mods\n    }\n}\n\nimpl PointerInteraction for NativeClickData {\n    fn trigger_button(&self) -> Option<MouseButton> {\n        Some(match self.0.button {\n            MouseEventButton::Main => MouseButton::Primary,\n            MouseEventButton::Auxiliary => MouseButton::Auxiliary,\n            MouseEventButton::Secondary => MouseButton::Secondary,\n            MouseEventButton::Fourth => MouseButton::Fourth,\n            MouseEventButton::Fifth => MouseButton::Fifth,\n        })\n    }\n\n    fn held_buttons(&self) -> MouseButtonSet {\n        dioxus_html::input_data::decode_mouse_button_set(self.0.buttons.bits() as u16)\n    }\n}\nimpl HasMouseData for NativeClickData {\n    fn as_any(&self) -> &dyn std::any::Any {\n        self as &dyn std::any::Any\n    }\n}\n\n#[derive(Clone)]\npub struct NativeFocusData {}\nimpl HasFocusData for NativeFocusData {\n    fn as_any(&self) -> &dyn std::any::Any {\n        self as &dyn std::any::Any\n    }\n}\n"
  },
  {
    "path": "packages/native-dom/src/lib.rs",
    "content": "#![cfg_attr(docsrs, feature(doc_cfg))]\n\n//! Core headless native renderer for Dioxus.\n//!\n//! ## Feature flags\n//!  - `default`: Enables the features listed below.\n//!  - `accessibility`: Enables [`accesskit`](https://docs.rs/accesskit/latest/accesskit/) accessibility support.\n//!  - `hot-reload`: Enables hot-reloading of Dioxus RSX.\n//!  - `menu`: Enables the [`muda`](https://docs.rs/muda/latest/muda/) menubar.\n//!  - `tracing`: Enables tracing support.\n\nmod dioxus_document;\nmod events;\nmod mutation_writer;\npub use blitz_dom::DocumentConfig;\npub use dioxus_document::DioxusDocument;\n\nuse blitz_dom::{ns, LocalName, Namespace, QualName};\ntype NodeId = usize;\n\npub(crate) fn qual_name(local_name: &str, namespace: Option<&str>) -> QualName {\n    QualName {\n        prefix: None,\n        ns: namespace.map(Namespace::from).unwrap_or(ns!(html)),\n        local: LocalName::from(local_name),\n    }\n}\n\n// Syntax sugar to make tracing calls less noisy in function below\nmacro_rules! trace {\n    ($pattern:literal) => {{\n        #[cfg(feature = \"tracing\")]\n        tracing::debug!($pattern);\n    }};\n    ($pattern:literal, $item1:expr) => {{\n        #[cfg(feature = \"tracing\")]\n        tracing::debug!($pattern, $item1);\n    }};\n    ($pattern:literal, $item1:expr, $item2:expr) => {{\n        #[cfg(feature = \"tracing\")]\n        tracing::debug!($pattern, $item1, $item2);\n    }};\n    ($pattern:literal, $item1:expr, $item2:expr, $item3:expr) => {{\n        #[cfg(feature = \"tracing\")]\n        tracing::debug!($pattern, $item1, $item2);\n    }};\n    ($pattern:literal, $item1:expr, $item2:expr, $item3:expr, $item4:expr) => {{\n        #[cfg(feature = \"tracing\")]\n        tracing::debug!($pattern, $item1, $item2, $item3, $item4);\n    }};\n}\npub(crate) use trace;\n"
  },
  {
    "path": "packages/native-dom/src/mutation_writer.rs",
    "content": "//! Integration between Dioxus and Blitz\nuse crate::{qual_name, trace, NodeId};\nuse blitz_dom::{BaseDocument, DocumentMutator};\nuse blitz_traits::events::DomEventKind;\nuse dioxus_core::{\n    AttributeValue, ElementId, Template, TemplateAttribute, TemplateNode, WriteMutations,\n};\nuse rustc_hash::FxHashMap;\nuse std::str::FromStr as _;\n\n/// The state of the Dioxus integration with the RealDom\n#[derive(Debug)]\npub struct DioxusState {\n    /// Store of templates keyed by unique name\n    pub(crate) templates: FxHashMap<Template, Vec<NodeId>>,\n    /// Stack machine state for applying dioxus mutations\n    pub(crate) stack: Vec<NodeId>,\n    /// Mapping from vdom ElementId -> rdom NodeId\n    pub(crate) node_id_mapping: Vec<Option<NodeId>>,\n    /// Count of each handler type\n    pub(crate) event_handler_counts: [u32; 32],\n}\n\nimpl DioxusState {\n    /// Initialize the DioxusState in the RealDom\n    pub fn create(root_id: usize) -> Self {\n        Self {\n            templates: FxHashMap::default(),\n            stack: vec![root_id],\n            node_id_mapping: vec![Some(root_id)],\n            event_handler_counts: [0; 32],\n        }\n    }\n\n    /// Convert an ElementId to a NodeId\n    pub fn element_to_node_id(&self, element_id: ElementId) -> NodeId {\n        self.try_element_to_node_id(element_id).unwrap()\n    }\n\n    /// Attempt to convert an ElementId to a NodeId. This will return None if the ElementId is not in the RealDom.\n    pub fn try_element_to_node_id(&self, element_id: ElementId) -> Option<NodeId> {\n        self.node_id_mapping.get(element_id.0).copied().flatten()\n    }\n\n    pub(crate) fn anchor_and_nodes(&mut self, id: ElementId, m: usize) -> (usize, Vec<usize>) {\n        let anchor_node_id = self.element_to_node_id(id);\n        let new_nodes = self.m_stack_nodes(m);\n        (anchor_node_id, new_nodes)\n    }\n\n    pub(crate) fn m_stack_nodes(&mut self, m: usize) -> Vec<usize> {\n        self.stack.split_off(self.stack.len() - m)\n    }\n}\n\n/// A writer for mutations that can be used with the RealDom.\npub struct MutationWriter<'a> {\n    /// The realdom associated with this writer\n    pub docm: DocumentMutator<'a>,\n    /// The state associated with this writer\n    pub state: &'a mut DioxusState,\n}\n\nimpl<'a> MutationWriter<'a> {\n    pub fn new(doc: &'a mut BaseDocument, state: &'a mut DioxusState) -> Self {\n        MutationWriter {\n            docm: doc.mutate(),\n            state,\n        }\n    }\n}\n\nimpl MutationWriter<'_> {\n    /// Update an ElementId -> NodeId mapping\n    fn set_id_mapping(&mut self, node_id: NodeId, element_id: ElementId) {\n        let element_id: usize = element_id.0;\n\n        // Ensure node_id_mapping is large enough to contain element_id\n        if self.state.node_id_mapping.len() <= element_id {\n            self.state.node_id_mapping.resize(element_id + 1, None);\n        }\n\n        // Set the new mapping\n        self.state.node_id_mapping[element_id] = Some(node_id);\n    }\n\n    /// Create a ElementId -> NodeId mapping and push the node to the stack\n    fn map_new_node(&mut self, node_id: NodeId, element_id: ElementId) {\n        self.set_id_mapping(node_id, element_id);\n        self.state.stack.push(node_id);\n    }\n\n    /// Find a child in the document by child index path\n    fn load_child(&self, path: &[u8]) -> NodeId {\n        let top_of_stack_node_id = *self.state.stack.last().unwrap();\n        self.docm.node_at_path(top_of_stack_node_id, path)\n    }\n}\n\nimpl WriteMutations for MutationWriter<'_> {\n    fn assign_node_id(&mut self, path: &'static [u8], id: ElementId) {\n        trace!(\"assign_node_id path:{:?} id:{}\", path, id.0);\n\n        // If there is an existing node already mapped to that ID and it has no parent, then drop it\n        // TODO: more automated GC/ref-counted semantics for node lifetimes\n        if let Some(node_id) = self.state.try_element_to_node_id(id) {\n            self.docm.remove_node_if_unparented(node_id);\n        }\n\n        // Map the node at specified path\n        self.set_id_mapping(self.load_child(path), id);\n    }\n\n    fn create_placeholder(&mut self, id: ElementId) {\n        trace!(\"create_placeholder id:{}\", id.0);\n        let node_id = self.docm.create_comment_node();\n        self.map_new_node(node_id, id);\n    }\n\n    fn create_text_node(&mut self, value: &str, id: ElementId) {\n        trace!(\"create_text_node id:{} text:{}\", id.0, value);\n        let node_id = self.docm.create_text_node(value);\n        self.map_new_node(node_id, id);\n    }\n\n    fn append_children(&mut self, id: ElementId, m: usize) {\n        trace!(\"append_children id:{} m:{}\", id.0, m);\n        let (parent_id, child_node_ids) = self.state.anchor_and_nodes(id, m);\n        self.docm.append_children(parent_id, &child_node_ids);\n    }\n\n    fn insert_nodes_after(&mut self, id: ElementId, m: usize) {\n        trace!(\"insert_nodes_after id:{} m:{}\", id.0, m);\n        let (anchor_node_id, new_node_ids) = self.state.anchor_and_nodes(id, m);\n        self.docm.insert_nodes_after(anchor_node_id, &new_node_ids);\n    }\n\n    fn insert_nodes_before(&mut self, id: ElementId, m: usize) {\n        trace!(\"insert_nodes_before id:{} m:{}\", id.0, m);\n        let (anchor_node_id, new_node_ids) = self.state.anchor_and_nodes(id, m);\n        self.docm.insert_nodes_before(anchor_node_id, &new_node_ids);\n    }\n\n    fn replace_node_with(&mut self, id: ElementId, m: usize) {\n        trace!(\"replace_node_with id:{} m:{}\", id.0, m);\n        let (anchor_node_id, new_node_ids) = self.state.anchor_and_nodes(id, m);\n        self.docm.replace_node_with(anchor_node_id, &new_node_ids);\n    }\n\n    fn replace_placeholder_with_nodes(&mut self, path: &'static [u8], m: usize) {\n        trace!(\"replace_placeholder_with_nodes path:{:?} m:{}\", path, m);\n        // WARNING: DO NOT REORDER\n        // The order of the following two lines is very important as \"m_stack_nodes\" mutates\n        // the stack and then \"load_child\" reads from the top of the stack.\n        let new_node_ids = self.state.m_stack_nodes(m);\n        let anchor_node_id = self.load_child(path);\n        self.docm.replace_node_with(anchor_node_id, &new_node_ids);\n    }\n\n    fn remove_node(&mut self, id: ElementId) {\n        trace!(\"remove_node id:{}\", id.0);\n        let node_id = self.state.element_to_node_id(id);\n        self.docm.remove_node(node_id);\n    }\n\n    fn push_root(&mut self, id: ElementId) {\n        trace!(\"push_root id:{}\", id.0);\n        let node_id = self.state.element_to_node_id(id);\n        self.state.stack.push(node_id);\n    }\n\n    fn set_node_text(&mut self, value: &str, id: ElementId) {\n        trace!(\"set_node_text id:{} value:{}\", id.0, value);\n        let node_id = self.state.element_to_node_id(id);\n        self.docm.set_node_text(node_id, value);\n    }\n\n    fn set_attribute(\n        &mut self,\n        local_name: &'static str,\n        ns: Option<&'static str>,\n        value: &AttributeValue,\n        id: ElementId,\n    ) {\n        let node_id = self.state.element_to_node_id(id);\n        fn is_falsy(val: &AttributeValue) -> bool {\n            match val {\n                AttributeValue::None => true,\n                AttributeValue::Text(val) => val == \"false\",\n                AttributeValue::Bool(val) => !val,\n                AttributeValue::Int(val) => *val == 0,\n                AttributeValue::Float(val) => *val == 0.0,\n                _ => false,\n            }\n        }\n\n        let falsy = is_falsy(value);\n        match value {\n            AttributeValue::None => {\n                set_attribute_inner(&mut self.docm, local_name, ns, None, falsy, node_id)\n            }\n            AttributeValue::Text(value) => {\n                set_attribute_inner(&mut self.docm, local_name, ns, Some(value), falsy, node_id)\n            }\n            AttributeValue::Float(value) => {\n                let value = value.to_string();\n                set_attribute_inner(&mut self.docm, local_name, ns, Some(&value), falsy, node_id);\n            }\n            AttributeValue::Int(value) => {\n                let value = value.to_string();\n                set_attribute_inner(&mut self.docm, local_name, ns, Some(&value), falsy, node_id);\n            }\n            AttributeValue::Bool(value) => {\n                let value = value.to_string();\n                set_attribute_inner(&mut self.docm, local_name, ns, Some(&value), falsy, node_id);\n            }\n            _ => {\n                // FIXME: support all attribute types\n            }\n        };\n    }\n\n    fn load_template(&mut self, template: Template, index: usize, id: ElementId) {\n        // TODO: proper template node support\n        let template_entry = self.state.templates.entry(template).or_insert_with(|| {\n            let template_root_ids: Vec<NodeId> = template\n                .roots\n                .iter()\n                .map(|root| create_template_node(&mut self.docm, root))\n                .collect();\n\n            template_root_ids\n        });\n\n        let template_node_id = template_entry[index];\n        let clone_id = self.docm.deep_clone_node(template_node_id);\n\n        trace!(\"load_template template_node_id:{template_node_id} clone_id:{clone_id}\");\n        self.map_new_node(clone_id, id);\n    }\n\n    fn create_event_listener(&mut self, name: &'static str, id: ElementId) {\n        // We're going to actually set the listener here as a placeholder - in JS this would also be a placeholder\n        // we might actually just want to attach the attribute to the root element (delegation)\n        let value = AttributeValue::Text(\"<rust func>\".into());\n        self.set_attribute(name, None, &value, id);\n\n        // Also set the data-dioxus-id attribute so we can find the element later\n        let value = AttributeValue::Text(id.0.to_string());\n        self.set_attribute(\"data-dioxus-id\", None, &value, id);\n\n        // node.add_event_listener(name);\n\n        if let Ok(kind) = DomEventKind::from_str(name) {\n            let idx = kind.discriminant() as usize;\n            self.state.event_handler_counts[idx] += 1;\n        }\n    }\n\n    fn remove_event_listener(&mut self, name: &'static str, _id: ElementId) {\n        if let Ok(kind) = DomEventKind::from_str(name) {\n            let idx = kind.discriminant() as usize;\n            self.state.event_handler_counts[idx] -= 1;\n        }\n    }\n}\n\nfn create_template_node(docm: &mut DocumentMutator<'_>, node: &TemplateNode) -> NodeId {\n    match node {\n        TemplateNode::Element {\n            tag,\n            namespace,\n            attrs,\n            children,\n        } => {\n            let name = qual_name(tag, *namespace);\n            // let attrs = attrs.iter().filter_map(map_template_attr).collect();\n            let node_id = docm.create_element(name, Vec::new());\n\n            for attr in attrs.iter() {\n                let TemplateAttribute::Static {\n                    name,\n                    value,\n                    namespace,\n                } = attr\n                else {\n                    continue;\n                };\n                let falsy = *value == \"false\";\n                set_attribute_inner(docm, name, *namespace, Some(value), falsy, node_id);\n            }\n\n            let child_ids: Vec<NodeId> = children\n                .iter()\n                .map(|child| create_template_node(docm, child))\n                .collect();\n\n            docm.append_children(node_id, &child_ids);\n\n            node_id\n        }\n        TemplateNode::Text { text } => docm.create_text_node(text),\n        TemplateNode::Dynamic { .. } => docm.create_comment_node(),\n    }\n}\n\nfn set_attribute_inner(\n    docm: &mut DocumentMutator<'_>,\n    local_name: &'static str,\n    ns: Option<&'static str>,\n    value: Option<&str>,\n    is_falsy: bool,\n    node_id: usize,\n) {\n    trace!(\"set_attribute node_id:{node_id} ns: {ns:?} name:{local_name}, value:{value:?}\");\n\n    // Dioxus has overloaded the style namespace to accumulate style attributes without a `style` block\n    // TODO: accumulate style attributes into a single style element.\n    if ns == Some(\"style\") {\n        match value {\n            Some(value) => docm.set_style_property(node_id, local_name, value),\n            None => docm.remove_style_property(node_id, local_name),\n        }\n        return;\n    }\n\n    let name = qual_name(local_name, ns);\n\n    // FIXME: more principled handling of special case attributes\n    match value {\n        None => docm.clear_attribute(node_id, name),\n        Some(value) => {\n            if local_name == \"checked\" && is_falsy {\n                docm.clear_attribute(node_id, name);\n            } else if local_name == \"dangerous_inner_html\" {\n                docm.set_inner_html(node_id, value);\n            } else {\n                docm.set_attribute(node_id, name, value);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "packages/playwright-tests/.gitignore",
    "content": "playwright-report\n/web/dist/\n"
  },
  {
    "path": "packages/playwright-tests/barebones-template/.gitignore",
    "content": "# Generated by Cargo\n# will have compiled files and executables\n/target\n.DS_Store\n\n# These are backup files generated by rustfmt\n**/*.rs.bk\n"
  },
  {
    "path": "packages/playwright-tests/barebones-template/Cargo.toml",
    "content": "[package]\nname = \"barebones-template-test\"\nversion = \"0.1.0\"\nauthors = [\"Jonathan Kelley <jkelleyrtp@gmail.com>\"]\nedition = \"2021\"\npublish = false\n\n# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html\n\n[dependencies]\ndioxus = { workspace = true, features = [] }\n\n[features]\ndefault = [\"web\"]\nweb = [\"dioxus/web\"]\ndesktop = [\"dioxus/desktop\"]\nmobile = [\"dioxus/mobile\"]\n"
  },
  {
    "path": "packages/playwright-tests/barebones-template/Dioxus.toml",
    "content": "[application]\n\n[web.app]\n\n# HTML title tag content\ntitle = \"myapp\"\n\n# include `assets` in web platform\n[web.resource]\n\n# Additional CSS style files\nstyle = []\n\n# Additional JavaScript files\nscript = []\n\n[web.resource.dev]\n\n# Javascript code file\n# serve: [dev-server] only\nscript = []\n"
  },
  {
    "path": "packages/playwright-tests/barebones-template/README.md",
    "content": "# Development\n\nYour new bare-bones project includes minimal organization with a single `main.rs` file and a few assets.\n\n```\nproject/\n├─ assets/ # Any assets that are used by the app should be placed here\n├─ src/\n│  ├─ main.rs # main.rs is the entry point to your application and currently contains all components for the app\n├─ Cargo.toml # The Cargo.toml file defines the dependencies and feature flags for your project\n```\n\n### Serving Your App\n\nRun the following command in the root of your project to start developing with the default platform:\n\n```bash\ndx serve\n```\n\nTo run for a different platform, use the `--webview`, `--native`, or `--web` flags. E.g.\n\n```bash\ndx serve --webview\n```\n"
  },
  {
    "path": "packages/playwright-tests/barebones-template/assets/main.css",
    "content": "/* App-wide styling */\nbody {\n    background-color: #0f1116;\n    color: #ffffff;\n    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;\n    margin: 20px;\n}\n\n#hero {\n    margin: 0;\n    display: flex;\n    flex-direction: column;\n    justify-content: center;\n    align-items: center;\n}\n\n#links {\n    width: 400px;\n    text-align: left;\n    font-size: x-large;\n    color: white;\n    display: flex;\n    flex-direction: column;\n}\n\n#links a {\n    color: white;\n    text-decoration: none;\n    margin-top: 20px;\n    margin: 10px 0px;\n    border: white 1px solid;\n    border-radius: 5px;\n    padding: 10px;\n}\n\n#links a:hover {\n    background-color: #1f1f1f;\n    cursor: pointer;\n}\n\n#header {\n    max-width: 1200px;\n}\n\n\n\n"
  },
  {
    "path": "packages/playwright-tests/barebones-template/src/main.rs",
    "content": "use dioxus::prelude::*;\n\nconst FAVICON: Asset = asset!(\"/assets/favicon.ico\");\nconst MAIN_CSS: Asset = asset!(\"/assets/main.css\");\nconst HEADER_SVG: Asset = asset!(\"/assets/header.svg\");\n\nfn main() {\n    dioxus::launch(App);\n}\n\n#[component]\nfn App() -> Element {\n    rsx! {\n        document::Link { rel: \"icon\", href: FAVICON }\n        document::Link { rel: \"stylesheet\", href: MAIN_CSS }\n        Hero {}\n\n    }\n}\n\n#[component]\npub fn Hero() -> Element {\n    rsx! {\n        div {\n            id: \"hero\",\n            img { src: HEADER_SVG, id: \"header\" }\n            div { id: \"links\",\n                a { href: \"https://dioxuslabs.com/learn/0.7/\", \"📚 Learn Dioxus\" }\n                a { href: \"https://dioxuslabs.com/awesome\", \"🚀 Awesome Dioxus\" }\n                a { href: \"https://github.com/dioxus-community/\", \"📡 Community Libraries\" }\n                a { href: \"https://github.com/DioxusLabs/sdk\", \"⚙️ Dioxus Development Kit\" }\n                a { href: \"https://marketplace.visualstudio.com/items?itemName=DioxusLabs.dioxus\", \"💫 VSCode Extension\" }\n                a { href: \"https://discord.gg/XgGxMSkvUM\", \"👋 Community Discord\" }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "packages/playwright-tests/cli-optimization/.gitignore",
    "content": "dist\ntarget\nmonaco-editor\npartial-monaco-editor\ndx-07"
  },
  {
    "path": "packages/playwright-tests/cli-optimization/Cargo.toml",
    "content": "[package]\nname = \"dioxus-cli-optimization-test\"\nversion = \"0.0.1\"\nedition = \"2021\"\ndescription = \"Playwright test for Dioxus CLI optimization\"\nlicense = \"MIT OR Apache-2.0\"\npublish = false\n\n[dependencies]\ndioxus = { workspace = true, features = [\"web\"] }\nserde = { workspace = true, features = [\"derive\"] }\nserde_json.workspace = true\n\n# [build-dependencies]\n# reqwest = { workspace = true, features = [\"blocking\"] }\n# flate2 = \"1.1.2\"\n# tar = \"0.4.44\"\n\n# We need a separate bin for the old CLI build to avoid conflicting caches\n[[bin]]\nname = \"cli-optimization-old-cli\"\npath = \"src/old_cli.rs\"\n"
  },
  {
    "path": "packages/playwright-tests/cli-optimization/assets/data.json",
    "content": "{\n  \"list\": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]\n}\n"
  },
  {
    "path": "packages/playwright-tests/cli-optimization/build.rs",
    "content": "fn main() {\n    // use std::path::PathBuf;\n\n    // // If the monaco editor folder doesn't exist, download it\n    // let monaco_path = PathBuf::from(\"monaco-editor\");\n    // if monaco_path.exists() {\n    //     return;\n    // }\n\n    // let url = \"https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.52.2.tgz\";\n    // let bytes = reqwest::blocking::get(url).unwrap().bytes().unwrap();\n    // let mut archive = tar::Archive::new(flate2::read::GzDecoder::new(bytes.as_ref()));\n    // let monaco_path_partial = PathBuf::from(\"partial-monaco-editor\");\n    // archive.unpack(&monaco_path_partial).unwrap();\n    // std::fs::rename(monaco_path_partial, monaco_path).unwrap();\n}\n"
  },
  {
    "path": "packages/playwright-tests/cli-optimization/src/lib.rs",
    "content": "// This test checks the CLI optimizes assets correctly without breaking them\n\nuse dioxus::prelude::*;\n\nconst SOME_IMAGE: Asset = asset!(\"/images/toasts.png\", AssetOptions::image().with_avif());\nconst SOME_IMAGE_WITH_THE_SAME_URL: Asset =\n    asset!(\"/images/toasts.png\", AssetOptions::image().with_jpg());\n#[used]\nstatic SOME_IMAGE_WITHOUT_HASH: Asset = asset!(\n    \"/images/toasts.png\",\n    AssetOptions::image().with_avif().with_hash_suffix(false)\n);\n// This asset is unused, but it should still be bundled because it is an external asset\n#[used]\nstatic _ASSET: Asset = asset!(\n    \"/images/toasts.png\",\n    AssetOptions::builder().with_hash_suffix(false)\n);\n\npub fn main() {\n    dioxus::launch(App);\n}\n\n#[component]\nfn App() -> Element {\n    // todo: test monaco more....\n    // const MONACO_FOLDER: Asset = asset!(\"/monaco-editor/package/min/vs\");\n    //     let script = format!(\"(() => {{\n    //     require.config({{ paths: {{ vs: '{MONACO_FOLDER}' }} }});\n\n    //     require(['vs/editor/editor.main'], () => {{\n    //         var model = monaco.editor.createModel('fn main() {{\\\\n\\\\tprintln!(\\\\\\\"hi\\\\\\\")\\\\n}}', 'rust');\n    //         var editor = monaco.editor.create(document.getElementById('editor'));\n    //         editor.setModel(model);\n    //     }})\n    // }})()\");\n\n    rsx! {\n        div {\n            id: \"editor\",\n            width: \"100vw\",\n            height: \"100vw\",\n        }\n        // // Monaco script\n        // script {\n        //     src: \"{MONACO_FOLDER}/loader.js\",\n        //     \"onload\": script\n        // }\n        img {\n            id: \"some_image\",\n            src: \"{SOME_IMAGE}\"\n        }\n        img {\n            id: \"some_image_with_the_same_url\",\n            src: \"{SOME_IMAGE_WITH_THE_SAME_URL}\"\n        }\n        img {\n            id: \"some_image_without_hash\",\n            src: \"{SOME_IMAGE_WITHOUT_HASH}\"\n        }\n        LoadsAsset {}\n    }\n}\n\nconst JSON: Asset = asset!(\"/assets/data.json\");\n\n#[derive(Debug, Clone, serde::Deserialize)]\nstruct Data {\n    list: Vec<i32>,\n}\n\n#[component]\nfn LoadsAsset() -> Element {\n    let data = use_resource(|| async {\n        let bytes = dioxus::asset_resolver::read_asset_bytes(&JSON)\n            .await\n            .unwrap();\n        serde_json::from_slice::<Data>(&bytes).unwrap()\n    });\n    match data() {\n        Some(data) => rsx! {\n            div {\n                id: \"resolved-data\",\n                \"List: {data.list:?}\"\n            }\n        },\n        None => rsx! {\n            div {\n                \"Loading...\"\n            }\n        },\n    }\n}\n"
  },
  {
    "path": "packages/playwright-tests/cli-optimization/src/main.rs",
    "content": "fn main() {\n    dioxus_cli_optimization_test::main()\n}\n"
  },
  {
    "path": "packages/playwright-tests/cli-optimization/src/old_cli.rs",
    "content": "fn main() {\n    dioxus_cli_optimization_test::main()\n}\n"
  },
  {
    "path": "packages/playwright-tests/cli-optimization.spec.js",
    "content": "// @ts-check\nconst { test, expect } = require(\"@playwright/test\");\n\nconst test_variants = [\n  { port: 8989, name: \"current version\" },\n];\n\nfor (let { port, name } of test_variants) {\n  test(`optimized scripts run in ${name}`, async ({ page }) => {\n    await page.goto(`http://localhost:${port}`);\n\n    // // Expect the page to load the script after optimizations have been applied. The script\n    // // should add an editor to the page that shows a main function\n    // const main = page.locator(\"#main\");\n    // await expect(main).toContainText(\"hi\");\n\n    // Expect the page to contain an image with the id \"some_image\"\n    const image = page.locator(\"#some_image\");\n    await expect(image).toBeVisible();\n    // Get the image src\n    const src = await image.getAttribute(\"src\");\n\n    // Expect the page to contain an image with the id \"some_image_with_the_same_url\"\n    const image2 = page.locator(\"#some_image_with_the_same_url\");\n    await expect(image2).toBeVisible();\n    // Get the image src\n    const src2 = await image2.getAttribute(\"src\");\n\n    // Expect the urls to be different\n    expect(src).not.toEqual(src2);\n\n    // Expect the page to contain an image with the id \"some_image_without_hash\"\n    const image3 = page.locator(\"#some_image_without_hash\");\n    await expect(image3).toBeVisible();\n    // Get the image src\n    const src3 = await image3.getAttribute(\"src\");\n    // Expect the src to be without a hash\n    expect(src3).toEqual(\"/assets/toasts.avif\");\n  });\n\n  test(`unused external assets are bundled in ${name}`, async ({ page }) => {\n    await page.goto(`http://localhost:${port}`);\n\n    // Assert http://localhost:{port}/assets/toasts.png is found even though it is not used in the page\n    const response = await page.request.get(\n      `http://localhost:${port}/assets/toasts.png`\n    );\n    // Expect the response to be ok\n    expect(response.status()).toBe(200);\n    // make sure the response is an image\n    expect(response.headers()[\"content-type\"]).toBe(\"image/png\");\n  });\n\n  test(`assets are resolved in ${name}`, async ({ page }) => {\n    await page.goto(`http://localhost:${port}`);\n\n    // Expect the page to contain an element with the id \"resolved-data\"\n    const resolvedData = page.locator(\"#resolved-data\");\n    await expect(resolvedData).toBeVisible();\n    // Expect the element to contain the text \"List: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]\"\n    await expect(resolvedData).toContainText(\n      \"List: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]\"\n    );\n  });\n\n}\n"
  },
  {
    "path": "packages/playwright-tests/default-features-disabled/.gitignore",
    "content": ".dioxus\ndist\ntarget"
  },
  {
    "path": "packages/playwright-tests/default-features-disabled/Cargo.toml",
    "content": "[package]\nname = \"dioxus-playwright-default-features-disabled-test\"\nversion = \"0.1.0\"\nedition = \"2021\"\npublish = false\n\n# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html\n\n[dependencies]\ndioxus = { workspace = true, features = [\"fullstack\"] }\n\n[features]\n# Web is a default feature, but it shouldn't actually be enabled in server builds\ndefault = [\"web\"]\nserver = [\"dioxus/server\"]\nweb = [\"dioxus/web\"]\n"
  },
  {
    "path": "packages/playwright-tests/default-features-disabled/src/main.rs",
    "content": "// This test asserts that the client feature is disable on the server build by the cli\n// even if it is set as a default feature\n\n#![allow(non_snake_case)]\nuse dioxus::prelude::*;\n\nfn main() {\n    launch(app);\n}\n\nfn app() -> Element {\n    let server_features = use_server_future(get_server_features)?.unwrap().unwrap();\n    let mut client_features = use_signal(Vec::new);\n\n    use_effect(move || {\n        client_features.set(current_platform_features());\n    });\n\n    let mut count = use_signal(|| 0);\n\n    rsx! {\n        div {\n            \"server features: {server_features:?}\"\n        }\n        div {\n            \"client features: {client_features:?}\"\n        }\n        button {\n            onclick: move |_| count += 1,\n            \"{count}\"\n        }\n    }\n}\n\nfn current_platform_features() -> Vec<String> {\n    let mut features = Vec::new();\n    if cfg!(feature = \"web\") {\n        features.push(\"web\".to_string());\n    }\n    if cfg!(feature = \"server\") {\n        features.push(\"server\".to_string());\n    }\n    features\n}\n\n#[server]\nasync fn get_server_features() -> ServerFnResult<Vec<String>> {\n    Ok(current_platform_features())\n}\n"
  },
  {
    "path": "packages/playwright-tests/default-features-disabled.spec.js",
    "content": "// @ts-check\nconst { test, expect } = require(\"@playwright/test\");\n\ntest(\"loads with correct features\", async ({ page }) => {\n  await page.goto(\"http://localhost:8002\");\n\n  // Expect the page to contain the pending text.\n  const main = page.locator(\"#main\");\n  await expect(main).toContainText('server features: [\"server\"]');\n  await expect(main).toContainText('client features: [\"web\"]');\n});\n"
  },
  {
    "path": "packages/playwright-tests/fullstack/.gitignore",
    "content": ".dioxus\ndist\ntarget"
  },
  {
    "path": "packages/playwright-tests/fullstack/Cargo.toml",
    "content": "[package]\nname = \"dioxus-playwright-fullstack-test\"\nversion = \"0.1.0\"\nedition = \"2021\"\npublish = false\n\n# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html\n\n[dependencies]\ndioxus = { workspace = true, features = [\"fullstack\"] }\nfutures.workspace = true\nserde = \"1.0.219\"\ntokio = { workspace = true, features = [\"full\"], optional = true }\n\n[features]\ndefault = []\nserver = [\"dioxus/server\", \"dep:tokio\"]\nweb = [\"dioxus/web\"]\n"
  },
  {
    "path": "packages/playwright-tests/fullstack/Dioxus.toml",
    "content": "[application]\n# resource (public) file folder\nasset_dir = \"assets\"\n"
  },
  {
    "path": "packages/playwright-tests/fullstack/src/main.rs",
    "content": "// This test is used by playwright configured in the root of the repo\n// Tests:\n// - Server functions\n// - SSR\n// - Hydration\n\n#![allow(non_snake_case)]\nuse dioxus::fullstack::{commit_initial_chunk, Websocket};\nuse dioxus::{fullstack::WebSocketOptions, prelude::*};\n\nfn main() {\n    #[cfg(feature = \"server\")]\n    dioxus::serve(|| async move {\n        use dioxus::server::axum::{self, Extension};\n\n        let cfg = dioxus::server::ServeConfig::builder().enable_out_of_order_streaming();\n        let router = axum::Router::new()\n            .serve_dioxus_application(cfg, app)\n            .layer(Extension(1234u32));\n\n        Ok(router)\n    });\n    #[cfg(not(feature = \"server\"))]\n    launch(app);\n}\n\nfn app() -> Element {\n    let mut count = use_signal(|| 12345);\n    let mut text = use_signal(|| \"...\".to_string());\n\n    rsx! {\n        Title { \"hello axum! {count}\" }\n        h1 { \"hello axum! {count}\" }\n        button { class: \"increment-button\", onclick: move |_| count += 1, \"Increment\" }\n        button {\n            class: \"server-button\",\n            onclick: move |_| async move {\n                if let Ok(data) = get_server_data().await {\n                    println!(\"Client received: {}\", data);\n                    text.set(data.clone());\n                    post_server_data(data).await.unwrap();\n                }\n            },\n            \"Run a server function!\"\n        }\n        \"Server said: {text}\"\n        div {\n            id: \"errors\",\n            Errors {}\n        }\n        OnMounted {}\n        DefaultServerFnCodec {}\n        DocumentElements {}\n        Assets {}\n        WebSockets {}\n    }\n}\n\n#[component]\nfn OnMounted() -> Element {\n    let mut mounted_triggered_count = use_signal(|| 0);\n    rsx! {\n        div {\n            class: \"onmounted-div\",\n            onmounted: move |_| {\n                mounted_triggered_count += 1;\n            },\n            \"onmounted was called {mounted_triggered_count} times\"\n        }\n    }\n}\n\n#[component]\nfn DefaultServerFnCodec() -> Element {\n    let resource = use_server_future(|| get_server_data_empty_vec(Vec::new()))?;\n    let empty_vec = resource.unwrap().unwrap();\n    assert!(empty_vec.is_empty());\n\n    rsx! {}\n}\n\n#[cfg(feature = \"server\")]\nasync fn assert_server_context_provided() {\n    use dioxus::{fullstack::FullstackContext, server::axum::Extension};\n    // Just make sure the server context is provided\n    let Extension(id): Extension<u32> = FullstackContext::extract().await.unwrap();\n    assert_eq!(id, 1234u32);\n}\n\n#[server]\nasync fn post_server_data(data: String) -> ServerFnResult {\n    assert_server_context_provided().await;\n    println!(\"Server received: {}\", data);\n\n    Ok(())\n}\n\n#[server]\nasync fn get_server_data() -> ServerFnResult<String> {\n    assert_server_context_provided().await;\n    Ok(\"Hello from the server!\".to_string())\n}\n\n// Make sure the default codec work with empty data structures\n// Regression test for https://github.com/DioxusLabs/dioxus/issues/2628\n#[server]\nasync fn get_server_data_empty_vec(empty_vec: Vec<String>) -> ServerFnResult<Vec<String>> {\n    assert_server_context_provided().await;\n    assert!(empty_vec.is_empty());\n    Ok(Vec::new())\n}\n\n#[server]\nasync fn server_error() -> ServerFnResult<String> {\n    use dioxus_core::AnyhowContext;\n    assert_server_context_provided().await;\n    tokio::time::sleep(tokio::time::Duration::from_millis(1000)).await;\n    Err(None.context(\"Server error occurred\")?)\n}\n\n#[component]\nfn Errors() -> Element {\n    // Make the suspense boundary below happen during streaming\n    use_hook(commit_initial_chunk);\n\n    rsx! {\n        // This is a tricky case for suspense https://github.com/DioxusLabs/dioxus/issues/2570\n        // Root suspense boundary is already resolved when the inner suspense boundary throws an error.\n        // We need to throw the error from the inner suspense boundary on the server to the hydrated\n        // suspense boundary on the client\n        ErrorBoundary {\n            handle_error: |_| rsx! {\n                \"Hmm, something went wrong.\"\n            },\n            SuspenseBoundary {\n                fallback: |_: SuspenseContext| rsx! {\n                    div {\n                        \"Loading...\"\n                    }\n                },\n                ThrowsError {}\n            }\n        }\n    }\n}\n\n#[component]\npub fn ThrowsError() -> Element {\n    use_server_future(server_error)?.unwrap()?;\n    rsx! {\n        \"success\"\n    }\n}\n\n/// This component tests the document::* elements pre-rendered on the server\n#[component]\nfn DocumentElements() -> Element {\n    rsx! {\n        document::Meta { id: \"meta-head\", name: \"testing\", data: \"dioxus-meta-element\" }\n        document::Link {\n            id: \"link-head\",\n            rel: \"stylesheet\",\n            href: \"https://fonts.googleapis.com/css?family=Roboto+Mono\"\n        }\n        document::Stylesheet { id: \"stylesheet-head\", href: \"https://fonts.googleapis.com/css?family=Roboto:300,300italic,700,700italic\" }\n        document::Script { id: \"script-head\", async: true, \"console.log('hello world');\" }\n        document::Style { id: \"style-head\", \"body {{ font-family: 'Roboto'; }}\" }\n    }\n}\n\n/// Make sure assets in the assets folder are served correctly and hashed assets are cached forever\n#[component]\nfn Assets() -> Element {\n    #[used]\n    static _ASSET: Asset = asset!(\"/assets/image.png\");\n\n    #[used]\n    static _STATIC_NO_HASH: Asset = asset!(\n        \"/assets/image.png\",\n        AssetOptions::image().with_hash_suffix(false)\n    );\n\n    #[used]\n    static _UNHASHED_FOLDER: Asset = asset!(\n        \"/assets/nested/\",\n        AssetOptions::folder().with_hash_suffix(false)\n    );\n\n    #[used]\n    static _EMBEDDED_FOLDER: Asset = asset!(\"/assets/nested\");\n\n    rsx! {\n        img {\n            src: asset!(\"/assets/image.png\"),\n        }\n        img {\n            src: \"{_EMBEDDED_FOLDER}/image.png\",\n        }\n        img {\n            src: \"{_UNHASHED_FOLDER}/image.png\",\n        }\n        img {\n            src: \"/assets/image.png\",\n        }\n    }\n}\n\n/// This component tests websocket server functions\n#[component]\nfn WebSockets() -> Element {\n    let mut received = use_signal(String::new);\n\n    use_future(move || async move {\n        let socket = echo_ws(WebSocketOptions::default()).await.unwrap();\n\n        socket.send(\"hello world\".to_string()).await.unwrap();\n\n        while let Ok(msg) = socket.recv().await {\n            received.write().push_str(&msg);\n        }\n    });\n\n    rsx! {\n        div {\n            id: \"websocket-div\",\n            \"Received: {received}\"\n        }\n    }\n}\n\n#[get(\"/api/echo_ws\")]\nasync fn echo_ws(options: WebSocketOptions) -> Result<Websocket> {\n    info!(\"Upgrading to websocket\");\n\n    Ok(options.on_upgrade(\n        |mut tx: dioxus::fullstack::TypedWebsocket<String, String>| async move {\n            while let Ok(msg) = tx.recv().await {\n                let _ = tx.send(msg.to_ascii_uppercase()).await;\n            }\n        },\n    ))\n}\n"
  },
  {
    "path": "packages/playwright-tests/fullstack-error-codes/.gitignore",
    "content": ".dioxus\ndist\ntarget"
  },
  {
    "path": "packages/playwright-tests/fullstack-error-codes/Cargo.toml",
    "content": "[package]\nname = \"dioxus-playwright-fullstack-error-codes-test\"\nversion = \"0.1.0\"\nedition = \"2021\"\npublish = false\n\n# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html\n\n[dependencies]\ndioxus = { workspace = true, features = [\"fullstack\", \"router\"] }\n\n[features]\ndefault = [\"server\", \"web\"]\nserver = [\"dioxus/server\"]\nweb = [\"dioxus/web\"]\n"
  },
  {
    "path": "packages/playwright-tests/fullstack-error-codes/src/main.rs",
    "content": "//! To render custom error pages, you can create a layout component that captures errors from routes\n//! with an `ErrorBoundary` and display different content based on the error type.\n//!\n//! While capturing the error, we set the appropriate HTTP status code using `FullstackContext::commit_error_status`.\n//! The router will then use this status code when doing server-side rendering (SSR).\n//!\n//! Any errors not captured by an error boundary will be handled by dioxus-ssr itself, which will render\n//! a generic error page instead.\n\nuse dioxus::prelude::*;\nuse dioxus_fullstack::{FullstackContext, StatusCode};\n\nfn main() {\n    dioxus::launch(|| {\n        rsx! {\n            Router::<Route> {}\n        }\n    });\n}\n\n#[derive(Routable, PartialEq, Clone, Debug)]\nenum Route {\n    #[layout(ErrorLayout)]\n    #[route(\"/\")]\n    Home,\n\n    #[route(\"/blog/:id\")]\n    Blog { id: u32 },\n}\n\n#[component]\nfn Home() -> Element {\n    rsx! {\n        div { \"Welcome to the home page!\" }\n        div { display: \"flex\", flex_direction: \"column\",\n            Link { to: Route::Blog { id: 1 }, \"Go to blog post 1\" }\n            Link { to: Route::Blog { id: 2 }, \"Go to blog post 2 (201)\" }\n            Link { to: Route::Blog { id: 3 }, \"Go to blog post 3 (error)\" }\n            Link { to: Route::Blog { id: 4 }, \"Go to blog post 4 (not found)\" }\n        }\n    }\n}\n\n#[component]\nfn Blog(id: u32) -> Element {\n    match id {\n        1 => rsx! { div { \"Blog post 1\" } },\n        2 => {\n            FullstackContext::commit_http_status(StatusCode::CREATED, None);\n            rsx! { div { \"Blog post 2\" } }\n        }\n        3 => dioxus_core::bail!(\"An error occurred while loading the blog post!\"),\n        _ => HttpError::not_found(\"Blog post not found\")?,\n    }\n}\n\n/// In our `ErrorLayout` component, we wrap the `Outlet` in an `ErrorBoundary`. This lets us attempt\n/// to downcast the error to an `HttpError` and set the appropriate status code.\n///\n/// The `commit_error_status` function will attempt to downcast the error to an `HttpError` and\n/// set the status code accordingly. Note that you can commit any status code you want with `commit_http_status`.\n///\n/// The router will automatically set the HTTP status code when doing SSR.\n#[component]\nfn ErrorLayout() -> Element {\n    rsx! {\n        ErrorBoundary {\n            handle_error: move |err: ErrorContext| {\n                let http_error = FullstackContext::commit_error_status(err.error().unwrap());\n                match http_error.status {\n                    StatusCode::NOT_FOUND => rsx! { div { \"404 - Page not found\" } },\n                    StatusCode::UNAUTHORIZED => rsx! { div { \"401 - Unauthorized\" } },\n                    StatusCode::INTERNAL_SERVER_ERROR => rsx! { div { \"500 - Internal Server Error\" } },\n                    _ => rsx! { div { \"An unknown error occurred\" } },\n                }\n            },\n            Outlet::<Route> {}\n        }\n    }\n}\n"
  },
  {
    "path": "packages/playwright-tests/fullstack-error-codes.spec.js",
    "content": "// @ts-check\nconst { test, expect } = require(\"@playwright/test\");\n\ntest(\"errors\", async ({ page }) => {\n  // Make sure going to home returns a 200\n  const res = await page.goto(\"http://localhost:8124\");\n  if (!res) {\n    throw new Error(\"Failed to navigate to http://localhost:8124\");\n  }\n\n  expect(res.status()).toBe(200);\n\n  // Go to /blog/1 which should also be okay\n  const blogRes = await page.goto(\"http://localhost:8124/blog/1\");\n  if (!blogRes) {\n    throw new Error(\"Failed to navigate to http://localhost:8124/blog/1\");\n  }\n  expect(blogRes.status()).toBe(200);\n\n  // Go to /blog/2 which should be a 201\n  const blogErrorRes = await page.goto(\"http://localhost:8124/blog/2\");\n  if (!blogErrorRes) {\n    throw new Error(\"Failed to navigate to http://localhost:8124/blog/2\");\n  }\n  expect(blogErrorRes.status()).toBe(201);\n\n  // Go to /blog/3 which should be a 500\n  const blogPanicRes = await page.goto(\"http://localhost:8124/blog/3\");\n  if (!blogPanicRes) {\n    throw new Error(\"Failed to navigate to http://localhost:8124/blog/3\");\n  }\n  expect(blogPanicRes.status()).toBe(500);\n\n  // Go to /blog/4 which should be a 404\n  const blogNotFoundRes = await page.goto(\"http://localhost:8124/blog/4\");\n  if (!blogNotFoundRes) {\n    throw new Error(\"Failed to navigate to http://localhost:8124/blog/4\");\n  }\n  expect(blogNotFoundRes.status()).toBe(404);\n});\n"
  },
  {
    "path": "packages/playwright-tests/fullstack-errors/.gitignore",
    "content": ".dioxus\ndist\ntarget"
  },
  {
    "path": "packages/playwright-tests/fullstack-errors/Cargo.toml",
    "content": "[package]\nname = \"dioxus-playwright-fullstack-errors-test\"\nversion = \"0.1.0\"\nedition = \"2021\"\npublish = false\n\n# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html\n\n[dependencies]\ndioxus = { workspace = true, features = [\"fullstack\"] }\nserde = \"1.0.219\"\ntokio = { workspace = true, features = [\"full\"], optional = true }\n\n[features]\ndefault = []\nserver = [\"dioxus/server\", \"dep:tokio\"]\nweb = [\"dioxus/web\"]\n"
  },
  {
    "path": "packages/playwright-tests/fullstack-errors/src/main.rs",
    "content": "// This test is used by playwright configured in the root of the repo\n// Tests:\n// - Errors that originate in the initial render\n\n#![allow(non_snake_case)]\nuse dioxus::prelude::*;\n\nfn main() {\n    dioxus::launch(app);\n}\n\nfn app() -> Element {\n    rsx! {\n        Errors {}\n    }\n}\n\n#[server]\nasync fn server_error() -> ServerFnResult<String> {\n    tokio::time::sleep(tokio::time::Duration::from_millis(1000)).await;\n    Err(ServerFnError::new(\"the server threw an error!\"))\n}\n\n#[component]\nfn Errors() -> Element {\n    rsx! {\n        // This is a tricky case for suspense https://github.com/DioxusLabs/dioxus/issues/2570\n        // Root suspense boundary is already resolved when the inner suspense boundary throws an error.\n        // We need to throw the error from the inner suspense boundary on the server to the hydrated\n        // suspense boundary on the client\n        ErrorBoundary {\n            handle_error: |_| rsx! {\n                ErrorFallbackButton {}\n            },\n            SuspenseBoundary {\n                fallback: |_: SuspenseContext| rsx! {\n                    div {\n                        \"Loading...\"\n                    }\n                },\n                ThrowsError {}\n            }\n        }\n    }\n}\n\n#[component]\npub fn ErrorFallbackButton() -> Element {\n    let mut count = use_signal(|| 0);\n\n    rsx! {\n        // Make sure the error fallback is interactive after hydration\n        button {\n            id: \"error-fallback-button\",\n            onclick: move |_| count += 1,\n            \"Error fallback button clicked {count} times\"\n        }\n    }\n}\n\n#[component]\npub fn ThrowsError() -> Element {\n    use_server_future(server_error)?.unwrap()?;\n    rsx! {\n        \"success\"\n    }\n}\n"
  },
  {
    "path": "packages/playwright-tests/fullstack-errors.spec.js",
    "content": "// @ts-check\nconst { test, expect } = require(\"@playwright/test\");\n\ntest(\"errors\", async ({ page }) => {\n  await page.goto(\"http://localhost:3232\");\n\n  // give the page time to finish loading resources\n  await page.waitForLoadState(\"networkidle\");\n  await page.waitForTimeout(2000);\n\n  // Make sure the error that was thrown on the server is shown in the error boundary on the client\n  const errors = page.locator(\"#error-fallback-button\");\n  await expect(errors).toContainText(\"Error fallback button clicked 0 times\");\n  // Make sure the fallback is interactive\n  await errors.click();\n  await expect(errors).toContainText(\"Error fallback button clicked 1 times\");\n});\n"
  },
  {
    "path": "packages/playwright-tests/fullstack-hydration-order/Cargo.toml",
    "content": "[package]\nname = \"dioxus-playwright-fullstack-hydration-order-test\"\nversion = \"0.1.0\"\nedition = \"2021\"\npublish = false\n\n# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html\n\n[dependencies]\ndioxus = { workspace = true, features = [\"fullstack\"] }\nserde = \"1.0.219\"\ntokio = { workspace = true, features = [\"full\"], optional = true }\n\n[features]\ndefault = []\nserver = [\"dioxus/server\", \"dep:tokio\"]\nweb = [\"dioxus/web\"]\n"
  },
  {
    "path": "packages/playwright-tests/fullstack-hydration-order/src/main.rs",
    "content": "// Adjacent server components that both use server functions shouldn't cause\n// hydration issues\n// https://github.com/DioxusLabs/dioxus/issues/4595\n\nuse dioxus::prelude::*;\n\nfn main() {\n    dioxus::launch(|| rsx! { Home {} });\n}\n\n#[component]\npub fn Home() -> Element {\n    let mut count = use_signal(|| 0);\n    rsx! {\n        div {\n            MyStrings {}\n            MyFloats {}\n        }\n        button {\n            id: \"counter\",\n            onclick: move |_| count += 1,\n            \"Count {count}\"\n        }\n    }\n}\n\n#[component]\nfn MyStrings() -> Element {\n    let strings = use_server_future(get_strings)?;\n    let data = match &*strings.read() {\n        Some(Ok(data)) => data.clone(),\n        _ => vec![],\n    };\n\n    rsx! {\n        div {\n            for string in data.iter() {\n                p { \"{string}\" }\n            }\n        }\n    }\n}\n#[get(\"/api/get_strings\")]\npub async fn get_strings() -> Result<Vec<String>, ServerFnError> {\n    let data: Vec<String> = vec![\"Hello\".to_string(), \"World\".to_string()];\n    Ok(data)\n}\n\n#[component]\nfn MyFloats() -> Element {\n    let floats = use_server_future(get_floats)?;\n    let data = match &*floats.read() {\n        Some(Ok(data)) => data.clone(),\n        _ => vec![],\n    };\n\n    rsx! {\n        div {\n            for float in data.iter() {\n                p { \"{float}\" }\n            }\n        }\n    }\n}\n\n#[get(\"/api/get_floats\")]\npub async fn get_floats() -> Result<Vec<f32>, ServerFnError> {\n    let data: Vec<f32> = vec![1.0, 2.0, 3.0];\n    Ok(data)\n}\n"
  },
  {
    "path": "packages/playwright-tests/fullstack-hydration-order.spec.js",
    "content": "// @ts-check\nconst { test, expect } = require(\"@playwright/test\");\n\ntest(\"hydration\", async ({ page }) => {\n  await page.goto(\"http://localhost:7979\");\n\n  // give time for the page to load and hydrate\n  await page.waitForTimeout(2000);\n\n  // Expect the page to contain a button\n  const button = page.locator(\"#counter\");\n  await expect(button).toContainText(\"Count 0\");\n\n  // Hydration should succeed and clicking the button should increase the count\n  await button.click();\n  await expect(button).toContainText(\"Count 1\");\n});\n"
  },
  {
    "path": "packages/playwright-tests/fullstack-mounted/Cargo.toml",
    "content": "[package]\nname = \"dioxus-playwright-fullstack-mounted-test\"\nversion = \"0.1.0\"\nedition = \"2021\"\npublish = false\n\n# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html\n\n[dependencies]\ndioxus = { workspace = true, features = [\"fullstack\"] }\nserde = \"1.0.219\"\ntokio = { workspace = true, features = [\"full\"], optional = true }\n\n[features]\ndefault = []\nserver = [\"dioxus/server\", \"dep:tokio\"]\nweb = [\"dioxus/web\"]\n"
  },
  {
    "path": "packages/playwright-tests/fullstack-mounted/src/main.rs",
    "content": "// Regression test for https://github.com/DioxusLabs/dioxus/pull/3480\n\nuse dioxus::prelude::*;\n\nfn main() {\n    dioxus::launch(App);\n}\n\n#[component]\nfn App() -> Element {\n    let mut mounted = use_signal(|| false);\n\n    rsx! {\n        div {\n            onmounted: move |_| {\n                mounted.set(true);\n            },\n            if mounted() {\n                \"The mounted event was triggered.\"\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "packages/playwright-tests/fullstack-mounted.spec.js",
    "content": "// @ts-check\nconst { test, expect } = require(\"@playwright/test\");\n\ntest(\"hydration\", async ({ page }) => {\n  await page.goto(\"http://localhost:7777\");\n\n  // Expect the page to contain the pending text.\n  const main = page.locator(\"#main\");\n  await expect(main).toContainText(\"The mounted event was triggered.\");\n});\n"
  },
  {
    "path": "packages/playwright-tests/fullstack-routing/.gitignore",
    "content": ".dioxus\ndist\ntarget"
  },
  {
    "path": "packages/playwright-tests/fullstack-routing/Cargo.toml",
    "content": "[package]\nname = \"dioxus-playwright-fullstack-routing-test\"\nversion = \"0.1.0\"\nedition = \"2021\"\npublish = false\n\n# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html\n\n[dependencies]\ndioxus = { workspace = true, features = [\"fullstack\", \"router\"] }\nserde = \"1.0.219\"\ntokio = { workspace = true, features = [\"full\"], optional = true }\n\n[features]\ndefault = []\nserver = [\"dioxus/server\", \"dep:tokio\"]\nweb = [\"dioxus/web\"]\n"
  },
  {
    "path": "packages/playwright-tests/fullstack-routing/src/main.rs",
    "content": "// This test is used by playwright configured in the root of the repo\n// Tests:\n// - 200 Routes\n// - 404 Routes\n// - 500 Routes\n\n#![allow(non_snake_case)]\nuse dioxus::prelude::*;\n\nfn main() {\n    dioxus::LaunchBuilder::new()\n        .with_cfg(server_only! {\n            dioxus::server::ServeConfig::builder().enable_out_of_order_streaming()\n        })\n        .launch(app);\n}\n\nfn app() -> Element {\n    rsx! { Router::<Route> {} }\n}\n\n#[derive(Clone, Routable, Debug, PartialEq, serde::Serialize, serde::Deserialize)]\nenum Route {\n    #[route(\"/\")]\n    Home,\n\n    #[route(\"/blog/:id/\")]\n    Blog { id: i32 },\n\n    #[route(\"/error\")]\n    ThrowsError,\n\n    #[route(\"/async-error\")]\n    ThrowsAsyncError,\n\n    #[route(\"/can-go-back\")]\n    HydrateCanGoBack,\n}\n\n#[component]\nfn Blog(id: i32) -> Element {\n    let route: Route = use_route();\n    assert_eq!(route, Route::Blog { id });\n\n    rsx! {\n        Link { to: Route::Home {}, \"Go home\" }\n        \"id: {id}\"\n    }\n}\n\n#[component]\nfn ThrowsError() -> Element {\n    dioxus::core::bail!(\"This route tests uncaught errors in the server\",)\n}\n\n#[component]\nfn ThrowsAsyncError() -> Element {\n    #[server]\n    async fn error_after_delay() -> ServerFnResult<()> {\n        tokio::time::sleep(std::time::Duration::from_millis(100)).await;\n        Err(ServerFnError::new(\"Async error from a server function\"))\n    }\n\n    use_server_future(error_after_delay)?().unwrap()?;\n    rsx! {\n        \"Hello, world!\"\n    }\n}\n\n#[component]\nfn Home() -> Element {\n    let route: Route = use_route();\n    assert_eq!(route, Route::Home);\n\n    rsx! {\n        \"Home\"\n        Link { to: Route::Blog { id: 1 }, \"Go to blog 1\" }\n    }\n}\n\n#[component]\npub fn HydrateCanGoBack() -> Element {\n    let navigator = use_navigator();\n    let mut count = use_signal(|| 0);\n    rsx! {\n        header {\n            class:\"flex justify-start items-center app-bg-color-primary px-5 py-2 space-x-4\",\n            if navigator.can_go_back() {\n                button  {\n                    class: \"app-button-circle item-navbar\",\n                    onclick: move |_| {\n                        count += 1;\n                    },\n                    \"{count}\"\n                },\n            }\n            else {\n                div {\n                    Link  {\n                        class: \"app-button-circle item-navbar\",\n                        to: Route::Home,\n                        \"Go to home\"\n                    },\n                    button  {\n                        class: \"app-button-circle item-navbar\",\n                        onclick: move |_| {\n                            count += 1;\n                        },\n                        \"{count}\"\n                    },\n                }\n            }\n        },\n    }\n}\n"
  },
  {
    "path": "packages/playwright-tests/fullstack-routing.spec.js",
    "content": "// @ts-check\nconst { test, expect } = require(\"@playwright/test\");\n\n// Wait for the build to finish\nasync function waitForBuild(request) {\n  for (let i = 0; i < 10; i++) {\n    const build = await request.get(\"http://localhost:8888\");\n    let text = await build.text();\n    if (!text.includes(\"Backend connection failed\")) {\n      return;\n    }\n    await new Promise((r) => setTimeout(r, 1000));\n  }\n}\n\n// The home and id routes should return 200\ntest(\"home route\", async ({ request }) => {\n  await waitForBuild(request);\n  const response = await request.get(\"http://localhost:8888\");\n\n  expect(response.status()).toBe(200);\n\n  const text = await response.text();\n  expect(text).toContain(\"Home\");\n});\n\ntest(\"blog route\", async ({ request }) => {\n  await waitForBuild(request);\n  const response = await request.get(\"http://localhost:8888/blog/123\");\n\n  expect(response.status()).toBe(200);\n\n  const text = await response.text();\n  expect(text).toContain(\"id: 123\");\n});\n\n// The error route should return 500\ntest(\"error route\", async ({ request }) => {\n  await waitForBuild(request);\n  const response = await request.get(\"http://localhost:8888/error\");\n\n  expect(response.status()).toBe(500);\n});\n\n// The async error route should return 500\ntest(\"async error route\", async ({ request }) => {\n  await waitForBuild(request);\n  const response = await request.get(\"http://localhost:8888/async-error\");\n\n  expect(response.status()).toBe(500);\n\n  // Expect the response to contain the error message\n  const errorMessage = \"Async error from a server function\";\n  const text = await response.text();\n  expect(text).toContain(errorMessage);\n});\n\n// An unknown route should return 404\ntest(\"unknown route\", async ({ request }) => {\n  await waitForBuild(request);\n  const response = await request.get(\n    \"http://localhost:8888/this-route-does-not-exist\"\n  );\n\n  expect(response.status()).toBe(404);\n});\n\n// Clicking the link on the home page should navigate to the blog route\ntest(\"click blog link\", async ({ page }) => {\n  await page.goto(\"http://localhost:8888\");\n\n  // Click the link to the blog route\n  await page.locator('a').click();\n\n  // Wait for navigation to complete\n  await page.waitForURL(\"http://localhost:8888/blog/1/\");\n\n  // Check that the blog page is displayed\n  const text = await page.textContent(\"body\");\n  expect(text).toContain(\"id: 1\");\n});\n\n// Clicking the link on the blog page should navigate back to the home route\ntest(\"click home link from blog\", async ({ page }) => {\n  await page.goto(\"http://localhost:8888/blog/1\");\n\n  // Click the link to the home route\n  await page.locator('a').click();\n\n  // Wait for navigation to complete\n  await page.waitForURL(\"http://localhost:8888\");\n\n  // Check that the home page is displayed\n  const text = await page.textContent(\"body\");\n  expect(text).toContain(\"Home\");\n});\n"
  },
  {
    "path": "packages/playwright-tests/fullstack-spread/Cargo.toml",
    "content": "[package]\nname = \"dioxus-playwright-fullstack-spread-test\"\nversion = \"0.1.0\"\nedition = \"2021\"\npublish = false\n\n# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html\n\n[dependencies]\ndioxus = { workspace = true, features = [\"fullstack\"] }\n\n[features]\ndefault = []\nserver = [\"dioxus/server\"]\nweb = [\"dioxus/web\"]\n"
  },
  {
    "path": "packages/playwright-tests/fullstack-spread/src/main.rs",
    "content": "//! Regression test for <https://github.com/DioxusLabs/dioxus/issues/4646>\n\nuse dioxus::prelude::*;\n\nfn main() {\n    // we split these two to ensure `dioxus::serve` works properly.\n    #[cfg(feature = \"server\")]\n    dioxus::serve(|| async move { Ok(dioxus::server::router(app)) });\n\n    #[cfg(not(feature = \"server\"))]\n    dioxus::launch(app);\n}\n\nfn app() -> Element {\n    rsx! {\n        Comp {}\n        Comp {}\n        Button {}\n    }\n}\n\n#[component]\nfn Button() -> Element {\n    let mut count = use_signal(|| 0);\n\n    rsx! {\n        button {\n            id: \"counter\",\n            onclick: move |_| {\n                count += 1;\n            },\n            \"Count: {count}\"\n        }\n    }\n}\n\n#[component]\nfn Comp(#[props(extends = GlobalAttributes)] attributes: Vec<Attribute>) -> Element {\n    rsx! {\n        div {\n            width: 100,\n            div {\n                ..attributes,\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "packages/playwright-tests/fullstack-spread.spec.js",
    "content": "// @ts-check\nconst { test, expect } = require(\"@playwright/test\");\n\ntest(\"spread attributes hydrate\", async ({ page }) => {\n  await page.goto(\"http://localhost:7980\");\n\n  await page.waitForTimeout(2000); // wait for hydration\n\n  // Expect the page to contain the button\n  const counter = page.locator(\"#counter\");\n  await expect(counter).toHaveText(\"Count: 0\");\n\n  // Clicking on the button should increment the count\n  await counter.click();\n  await expect(counter).toHaveText(\"Count: 1\");\n});\n"
  },
  {
    "path": "packages/playwright-tests/fullstack.spec.js",
    "content": "// @ts-check\nconst { test, expect } = require(\"@playwright/test\");\n\ntest(\"hydration\", async ({ page }) => {\n  await page.goto(\"http://localhost:3333\");\n\n  // Then wait for the page to start loading\n  await page.goto(\"http://localhost:3333\", { waitUntil: \"networkidle\" });\n\n  // Expect the page to contain the pending text.\n  const main = page.locator(\"#main\");\n  await expect(main).toContainText(\"Server said: ...\");\n\n  // Expect the page to contain the counter text.\n  await expect(main).toContainText(\"hello axum! 12345\");\n  // Expect the title to contain the counter text.\n  await expect(page).toHaveTitle(\"hello axum! 12345\");\n\n  // Click the increment button.\n  let button = page.locator(\"button.increment-button\");\n  await button.click();\n\n  // Click the server button.\n  let serverButton = page.locator(\"button.server-button\");\n  await serverButton.click();\n\n  // Expect the page to contain the updated counter text.\n  await expect(main).toContainText(\"hello axum! 12346\");\n  // Expect the title to contain the updated counter text.\n  await expect(page).toHaveTitle(\"hello axum! 12346\");\n\n  // Expect the page to contain the updated counter text.\n  await expect(main).toContainText(\"Server said: Hello from the server!\");\n\n  // Make sure the error that was thrown on the server is shown in the error boundary on the client\n  const errors = page.locator(\"#errors\");\n  await expect(errors).toContainText(\"Hmm, something went wrong.\");\n\n  // Expect the onmounted event to be called exactly once.\n  const mountedDiv = page.locator(\"div.onmounted-div\");\n  await expect(mountedDiv).toHaveText(\"onmounted was called 1 times\");\n});\n\ntest(\"document elements\", async ({ page }) => {\n  await page.goto(\"http://localhost:3333\");\n  // wait until the meta element is mounted\n  const meta = page.locator(\"meta#meta-head[name='testing']\");\n  await meta.waitFor({ state: \"attached\" });\n  await expect(meta).toHaveAttribute(\"data\", \"dioxus-meta-element\");\n\n  const link = page.locator(\"link#link-head[rel='stylesheet']\");\n  await link.waitFor({ state: \"attached\" });\n  await expect(link).toHaveAttribute(\n    \"href\",\n    \"https://fonts.googleapis.com/css?family=Roboto+Mono\"\n  );\n\n  const stylesheet = page.locator(\"link#stylesheet-head[rel='stylesheet']\");\n  await stylesheet.waitFor({ state: \"attached\" });\n  await expect(stylesheet).toHaveAttribute(\n    \"href\",\n    \"https://fonts.googleapis.com/css?family=Roboto:300,300italic,700,700italic\"\n  );\n\n  const script = page.locator(\"script#script-head\");\n  await script.waitFor({ state: \"attached\" });\n  await expect(script).toHaveAttribute(\"async\", \"true\");\n\n  const style = page.locator(\"style#style-head\");\n  await style.waitFor({ state: \"attached\" });\n  const main = page.locator(\"#main\");\n  await expect(main).toHaveCSS(\"font-family\", \"Roboto\");\n});\n\ntest(\"assets cache correctly\", async ({ page }) => {\n  // Wait for the hashed image to be loaded\n  const hashedImageFuture = page.waitForResponse((resp) => {\n    console.log(\"Response URL:\", resp.url());\n    return resp.url().includes(\"/assets/image-\") && resp.status() === 200;\n  });\n  const assetImageFuture = page.waitForResponse(\n    (resp) => resp.url().includes(\"/assets/image.png\") && resp.status() === 200\n  );\n  const nestedAssetImageFuture = page.waitForResponse(\n    (resp) =>\n      resp.url().includes(\"/assets/nested/image.png\") && resp.status() === 200\n  );\n\n  // Navigate to the page that includes the image.\n  await page.goto(\"http://localhost:3333\");\n\n  const hashedImageResponse = await hashedImageFuture;\n\n  // Make sure the hashed image cache control header is set to immutable\n  const cacheControl = hashedImageResponse.headers()[\"cache-control\"];\n  console.log(\"Cache-Control header:\", cacheControl);\n  expect(cacheControl).toContain(\"immutable\");\n\n  // Wait for the asset image to be loaded\n  const assetImageResponse = await assetImageFuture;\n  // console.log(\"Asset Image Response:\", assetImageResponse);\n  // Make sure the asset image cache control header does not contain immutable\n  const assetCacheControl = assetImageResponse.headers()[\"cache-control\"];\n  console.log(\"Cache-Control header:\", assetCacheControl);\n  // Expect there to be no cache control header\n  expect(assetCacheControl).toBeFalsy();\n\n  // Wait for the nested asset image to be loaded\n  const nestedAssetImageResponse = await nestedAssetImageFuture;\n  // console.log(\n  //   \"Nested Asset Image Response:\",\n  //   nestedAssetImageResponse\n  // );\n  // Make sure the nested asset image cache control header does not contain immutable\n  const nestedAssetCacheControl =\n    nestedAssetImageResponse.headers()[\"cache-control\"];\n  console.log(\"Cache-Control header:\", nestedAssetCacheControl);\n  // Expect there to be no cache control header\n  expect(nestedAssetCacheControl).toBeFalsy();\n});\n\ntest(\"websockets\", async ({ page }) => {\n  await page.goto(\"http://localhost:3333\");\n  // wait until the websocket div is mounted\n  const wsDiv = page.locator(\"div#websocket-div\");\n  await expect(wsDiv).toHaveText(\"Received: HELLO WORLD\");\n});\n\n"
  },
  {
    "path": "packages/playwright-tests/liveview/Cargo.toml",
    "content": "[package]\nname = \"dioxus-playwright-liveview-test\"\nversion = \"0.0.1\"\nedition = \"2021\"\ndescription = \"Playwright test for Dioxus Liveview\"\nlicense = \"MIT OR Apache-2.0\"\npublish = false\n\n[dependencies]\ndioxus = { workspace = true }\ndioxus-liveview = { workspace = true, features = [\"axum\"] }\ntokio = { workspace = true, features = [\"full\"] }\naxum = { workspace = true, default-features = true, features = [\"ws\"] }\n"
  },
  {
    "path": "packages/playwright-tests/liveview/src/main.rs",
    "content": "// This test is used by playwright configured in the root of the repo\n\nuse axum::{extract::ws::WebSocketUpgrade, response::Html, routing::get, Router};\nuse dioxus::{logger::tracing::Level, prelude::*};\n\nfn app() -> Element {\n    let mut num = use_signal(|| 0);\n\n    rsx! {\n        div {\n            \"hello axum! {num}\"\n            button { onclick: move |_| num += 1, \"Increment\" }\n        }\n        svg { circle { cx: 50, cy: 50, r: 40, stroke: \"green\", fill: \"yellow\" } }\n        div { class: \"raw-attribute-div\", \"raw-attribute\": \"raw-attribute-value\" }\n        div { class: \"hidden-attribute-div\", hidden: true }\n        div {\n            class: \"dangerous-inner-html-div\",\n            dangerous_inner_html: \"<p>hello dangerous inner html</p>\"\n        }\n        input { value: \"hello input\" }\n        div { class: \"style-div\", color: \"red\", \"colored text\" }\n        OnMounted {}\n    }\n}\n\n#[component]\nfn OnMounted() -> Element {\n    let mut mounted_triggered_count = use_signal(|| 0);\n    rsx! {\n        div {\n            class: \"onmounted-div\",\n            onmounted: move |_| {\n                mounted_triggered_count += 1;\n            },\n            \"onmounted was called {mounted_triggered_count} times\"\n        }\n    }\n}\n\n#[tokio::main]\nasync fn main() {\n    _ = dioxus::logger::init(Level::DEBUG);\n\n    let addr: std::net::SocketAddr = ([127, 0, 0, 1], 3030).into();\n\n    let view = dioxus_liveview::LiveViewPool::new();\n\n    let app = Router::new()\n        .route(\n            \"/\",\n            get(move || async move {\n                Html(format!(\n                    r#\"\n            <!DOCTYPE html>\n            <html>\n                <head> <title>Dioxus LiveView with axum</title>  </head>\n                <body> <div id=\"main\"></div> </body>\n                {glue}\n            </html>\n            \"#,\n                    glue = dioxus_liveview::interpreter_glue(&format!(\"ws://{addr}/ws\"))\n                ))\n            }),\n        )\n        .route(\n            \"/ws\",\n            get(move |ws: WebSocketUpgrade| async move {\n                ws.on_upgrade(move |socket| async move {\n                    _ = view.launch(dioxus_liveview::axum_socket(socket), app).await;\n                })\n            }),\n        );\n\n    println!(\"Listening on http://{addr}\");\n\n    let listener = tokio::net::TcpListener::bind(&addr).await.unwrap();\n    axum::serve(listener, app.into_make_service())\n        .await\n        .unwrap();\n}\n"
  },
  {
    "path": "packages/playwright-tests/liveview.spec.js",
    "content": "// @ts-check\nconst { test, expect } = require(\"@playwright/test\");\n\ntest(\"button click\", async ({ page }) => {\n  await page.goto(\"http://127.0.0.1:3030\");\n\n  // Expect the page to contain the counter text.\n  const main = page.locator(\"#main\");\n  await expect(main).toContainText(\"hello axum! 0\");\n\n  // Click the increment button.\n  await page.getByRole(\"button\", { name: \"Increment\" }).click();\n\n  // Expect the page to contain the updated counter text.\n  await expect(main).toContainText(\"hello axum! 1\");\n});\n\ntest(\"svg\", async ({ page }) => {\n  await page.goto(\"http://127.0.0.1:3030\");\n\n  // Expect the page to contain the svg.\n  const svg = page.locator(\"svg\");\n\n  // Expect the svg to contain the circle.\n  const circle = svg.locator(\"circle\");\n  await expect(circle).toHaveAttribute(\"cx\", \"50\");\n  await expect(circle).toHaveAttribute(\"cy\", \"50\");\n  await expect(circle).toHaveAttribute(\"r\", \"40\");\n  await expect(circle).toHaveAttribute(\"stroke\", \"green\");\n  await expect(circle).toHaveAttribute(\"fill\", \"yellow\");\n});\n\ntest(\"raw attribute\", async ({ page }) => {\n  await page.goto(\"http://127.0.0.1:3030\");\n\n  // Expect the page to contain the div with the raw attribute.\n  const div = page.locator(\"div.raw-attribute-div\");\n  await expect(div).toHaveAttribute(\"raw-attribute\", \"raw-attribute-value\");\n});\n\ntest(\"hidden attribute\", async ({ page }) => {\n  await page.goto(\"http://127.0.0.1:3030\");\n\n  // Expect the page to contain the div with the hidden attribute.\n  const div = page.locator(\"div.hidden-attribute-div\");\n  await expect(div).toHaveAttribute(\"hidden\", \"true\");\n});\n\ntest(\"dangerous inner html\", async ({ page }) => {\n  await page.goto(\"http://127.0.0.1:3030\");\n\n  // Expect the page to contain the div with the dangerous inner html.\n  const div = page.locator(\"div.dangerous-inner-html-div\");\n  await expect(div).toContainText(\"hello dangerous inner html\");\n});\n\ntest(\"input value\", async ({ page }) => {\n  await page.goto(\"http://127.0.0.1:3030\");\n\n  // Expect the page to contain the input with the value.\n  const input = page.locator(\"input\");\n  await expect(input).toHaveValue(\"hello input\");\n});\n\ntest(\"style\", async ({ page }) => {\n  await page.goto(\"http://127.0.0.1:3030\");\n\n  // Expect the page to contain the div with the style.\n  const div = page.locator(\"div.style-div\");\n  await expect(div).toHaveText(\"colored text\");\n  await expect(div).toHaveCSS(\"color\", \"rgb(255, 0, 0)\");\n});\n\ntest(\"onmounted\", async ({ page }) => {\n  await page.goto(\"http://127.0.0.1:3030\");\n\n  // Expect the onmounted event to be called exactly once.\n  const mountedDiv = page.locator(\"div.onmounted-div\");\n  await expect(mountedDiv).toHaveText(\"onmounted was called 1 times\");\n});\n"
  },
  {
    "path": "packages/playwright-tests/nested-suspense/.gitignore",
    "content": ".dioxus\ndist\ntarget"
  },
  {
    "path": "packages/playwright-tests/nested-suspense/Cargo.toml",
    "content": "[package]\nname = \"nested-suspense\"\nversion = \"0.1.0\"\nedition = \"2021\"\npublish = false\n\n# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html\n\n[dependencies]\ndioxus = { workspace = true, features = [\"fullstack\"] }\nserde = \"1.0.219\"\ntokio = { workspace = true, features = [\"full\"], optional = true }\n\n[features]\ndefault = []\nserver = [\"dioxus/server\", \"dep:tokio\"]\nweb = [\"dioxus/web\"]\n\n# We need a separate bin for the SSG build to avoid conflicting server caches\n[[bin]]\nname = \"nested-suspense-ssg\"\npath = \"src/ssg.rs\"\n\n[[bin]]\nname = \"nested-suspense\"\npath = \"src/main.rs\"\n"
  },
  {
    "path": "packages/playwright-tests/nested-suspense/assets/style.css",
    "content": ""
  },
  {
    "path": "packages/playwright-tests/nested-suspense/src/lib.rs",
    "content": "// This test is used by playwright configured in the root of the repo\n// Tests:\n// - SEO without JS\n// - Streaming hydration\n// - Suspense\n// - Server functions\n//\n// Without Javascript, content may not load into the right location, but it should still be somewhere in the html even if it is invisible\n\nuse dioxus::prelude::*;\nuse serde::{Deserialize, Serialize};\n\npub fn app() -> Element {\n    // Start streaming immediately\n    use_hook(dioxus::fullstack::commit_initial_chunk);\n\n    rsx! {\n        SuspenseBoundary {\n            fallback: move |_| rsx! {},\n            document::Style {\n                href: asset!(\"/assets/style.css\")\n            }\n            LoadTitle {}\n        }\n        MessageWithLoader { id: 0 }\n    }\n}\n\n#[component]\nfn MessageWithLoader(id: usize) -> Element {\n    rsx! {\n        SuspenseBoundary {\n            fallback: move |_| rsx! {\n                \"Loading {id}...\"\n            },\n            Message { id }\n        }\n    }\n}\n\n#[component]\nfn LoadTitle() -> Element {\n    let title = use_server_future(move || server_content(0))?()\n        .unwrap()\n        .unwrap();\n\n    rsx! {\n        \"title loaded\"\n        document::Title { \"{title.title}\" }\n    }\n}\n\n#[component]\nfn Message(id: usize) -> Element {\n    let message = use_server_future(move || server_content(id))?()\n        .unwrap()\n        .unwrap();\n\n    rsx! {\n        h2 {\n            id: \"title-{id}\",\n            \"{message.title}\"\n        }\n        p {\n            id: \"body-{id}\",\n            \"{message.body}\"\n        }\n        div {\n            id: \"children-{id}\",\n            padding: \"10px\",\n            for child in message.children {\n                MessageWithLoader { id: child }\n            }\n        }\n    }\n}\n\n#[derive(Clone, Serialize, Deserialize)]\npub struct Content {\n    title: String,\n    body: String,\n    children: Vec<usize>,\n}\n\n#[server]\nasync fn server_content(id: usize) -> ServerFnResult<Content> {\n    let content_tree = [\n        Content {\n            title: \"The robot says hello world\".to_string(),\n            body: \"The robot becomes sentient and says hello world\".to_string(),\n            children: vec![1, 2, 3],\n        },\n        Content {\n            title: \"The world says hello back\".to_string(),\n            body: \"In a stunning turn of events, the world collectively unites and says hello back\"\n                .to_string(),\n            children: vec![4],\n        },\n        Content {\n            title: \"Goodbye Robot\".to_string(),\n            body: \"The robot says goodbye\".to_string(),\n            children: vec![],\n        },\n        Content {\n            title: \"Goodbye World\".to_string(),\n            body: \"The world says goodbye\".to_string(),\n            children: vec![],\n        },\n        Content {\n            title: \"Hello World\".to_string(),\n            body: \"The world says hello again\".to_string(),\n            children: vec![],\n        },\n    ];\n    tokio::time::sleep(std::time::Duration::from_millis(1000)).await;\n    Ok(content_tree[id].clone())\n}\n"
  },
  {
    "path": "packages/playwright-tests/nested-suspense/src/main.rs",
    "content": "#![allow(non_snake_case)]\nuse dioxus::prelude::*;\nuse nested_suspense::app;\n\nfn main() {\n    LaunchBuilder::new()\n        .with_cfg(server_only! {\n            ServeConfig::builder()\n                .enable_out_of_order_streaming()\n        })\n        .launch(app);\n}\n"
  },
  {
    "path": "packages/playwright-tests/nested-suspense/src/ssg.rs",
    "content": "#![allow(non_snake_case)]\nuse dioxus::prelude::*;\nuse nested_suspense::app;\n\nfn main() {\n    dioxus::logger::init(dioxus::logger::tracing::Level::TRACE).expect(\"logger failed to init\");\n\n    dioxus::LaunchBuilder::new()\n        .with_cfg(server_only! {\n            ServeConfig::builder()\n                .incremental(\n                    dioxus::server::IncrementalRendererConfig::new()\n                        .static_dir(\n                            std::env::current_exe()\n                                .unwrap()\n                                .parent()\n                                .unwrap()\n                                .join(\"public\")\n                        )\n                        .clear_cache(false)\n                )\n                .enable_out_of_order_streaming()\n        })\n        .launch(app);\n}\n\n#[server(endpoint = \"static_routes\")]\nasync fn static_routes() -> ServerFnResult<Vec<String>> {\n    Ok(vec![\"/\".to_string()])\n}\n"
  },
  {
    "path": "packages/playwright-tests/nested-suspense-no-js.spec.js",
    "content": "// @ts-check\nconst { test, expect } = require(\"@playwright/test\");\n\ntest.use({ javaScriptEnabled: false });\n\ntest(\"text appears in the body without javascript\", async ({ page }) => {\n  await page.goto(\"http://localhost:5050\", { waitUntil: \"commit\" });\n  // Wait for the page to finish building. Reload until it's ready\n  for (let i = 0; i < 50; i++) {\n    // If the page doesn't contain #building or \"Backend connection failed\", we're ready\n    let building_count = await page.locator(\"#building\").count();\n    building_count += await page\n      .locator(\"body\", { hasText: \"backend connection failed\" })\n      .count();\n    if (building_count === 0) {\n      break;\n    }\n    await page.waitForTimeout(1000);\n    await page.goto(\"http://localhost:5050\", { waitUntil: \"commit\" });\n  }\n  // If we wait until the whole page loads, the content of the site should still be in the body even if javascript is disabled\n  // It will not be visible, and may not be in the right order/location, but SEO should still work\n  await page.waitForLoadState(\"load\");\n\n  const body = page.locator(\"body\");\n  const textExpected = [\n    \"The robot becomes sentient and says hello world\",\n    \"The world says hello back\",\n    \"In a stunning turn of events, the world collectively unites and says hello back\",\n    \"Goodbye Robot\",\n    \"The robot says goodbye\",\n    \"Goodbye World\",\n    \"The world says goodbye\",\n    \"Hello World\",\n    \"The world says hello again\",\n  ];\n  for (let i = 0; i < textExpected.length; i++) {\n    await expect(body).toContainText(textExpected[i]);\n  }\n});\n"
  },
  {
    "path": "packages/playwright-tests/nested-suspense-ssg.spec.js",
    "content": "// @ts-check\nconst { test, expect } = require(\"@playwright/test\");\n\ntest(\"nested suspense resolves\", async ({ page }) => {\n  // Wait for the dev server to reload\n  await page.goto(\"http://localhost:6060\");\n  // Then wait for the page to start loading\n  await page.goto(\"http://localhost:6060\", { waitUntil: \"commit\" });\n\n  // Expect the page to contain the suspense result from the server\n  const mainMessageTitle = page.locator(\"#title-0\");\n  await expect(mainMessageTitle).toContainText(\"The robot says hello world\");\n  const mainMessageBody = page.locator(\"#body-0\");\n  await expect(mainMessageBody).toContainText(\n    \"The robot becomes sentient and says hello world\"\n  );\n\n  // And expect the title to have resolved on the client\n  await expect(page).toHaveTitle(\"The robot says hello world\");\n\n  // Nested suspense should be resolved\n  const nestedMessageTitle1 = page.locator(\"#title-1\");\n  await expect(nestedMessageTitle1).toContainText(\"The world says hello back\");\n  const nestedMessageBody1 = page.locator(\"#body-1\");\n  await expect(nestedMessageBody1).toContainText(\n    \"In a stunning turn of events, the world collectively unites and says hello back\"\n  );\n\n  const nestedMessageDiv2 = page.locator(\"#children-2\");\n  await expect(nestedMessageDiv2).toBeEmpty();\n  const nestedMessageTitle2 = page.locator(\"#title-2\");\n  await expect(nestedMessageTitle2).toContainText(\"Goodbye Robot\");\n  const nestedMessageBody2 = page.locator(\"#body-2\");\n  await expect(nestedMessageBody2).toContainText(\"The robot says goodbye\");\n\n  const nestedMessageDiv3 = page.locator(\"#children-3\");\n  await expect(nestedMessageDiv3).toBeEmpty();\n  const nestedMessageTitle3 = page.locator(\"#title-3\");\n  await expect(nestedMessageTitle3).toContainText(\"Goodbye World\");\n  const nestedMessageBody3 = page.locator(\"#body-3\");\n  await expect(nestedMessageBody3).toContainText(\"The world says goodbye\");\n\n  // Deeply nested suspense should be resolved\n  const nestedMessageDiv4 = page.locator(\"#children-4\");\n  await expect(nestedMessageDiv4).toBeEmpty();\n  const nestedMessageTitle4 = page.locator(\"#title-4\");\n  await expect(nestedMessageTitle4).toContainText(\"Hello World\");\n  const nestedMessageBody4 = page.locator(\"#body-4\");\n  await expect(nestedMessageBody4).toContainText(\"The world says hello again\");\n});\n"
  },
  {
    "path": "packages/playwright-tests/nested-suspense.spec.js",
    "content": "// @ts-check\nconst { test, expect } = require(\"@playwright/test\");\nconst { timeout } = require(\"./playwright.config\");\n\ntest(\"nested suspense resolves\", async ({ page }) => {\n  // Wait for the dev server to reload\n  await page.goto(\"http://localhost:5050\");\n  // Then wait for the page to start loading\n  await page.goto(\"http://localhost:5050\", { waitUntil: \"commit\" });\n\n  // On the client, we should see some loading text\n  const main = page.locator(\"#main\");\n  await expect(main).toContainText(\"Loading 0...\");\n\n  // Expect the page to contain the suspense result from the server\n  const mainMessageDiv = page.locator(\"#children-0\");\n  const mainMessageTitle = page.locator(\"#title-0\");\n  await expect(mainMessageTitle).toContainText(\"The robot says hello world\");\n  const mainMessageBody = page.locator(\"#body-0\");\n  await expect(mainMessageBody).toContainText(\n    \"The robot becomes sentient and says hello world\"\n  );\n\n  // And expect the title to have resolved on the client\n  await expect(page).toHaveTitle(\"The robot says hello world\");\n\n  // And more loading text for the nested suspense\n  await expect(mainMessageDiv).toContainText(\"Loading 1...\");\n  await expect(mainMessageDiv).toContainText(\"Loading 2...\");\n  await expect(mainMessageDiv).toContainText(\"Loading 3...\");\n\n  const nestedMessageDiv1 = page.locator(\"#children-1\");\n  const nestedMessageTitle1 = page.locator(\"#title-1\");\n  await expect(nestedMessageTitle1).toContainText(\"The world says hello back\");\n  const nestedMessageBody1 = page.locator(\"#body-1\");\n  await expect(nestedMessageBody1).toContainText(\n    \"In a stunning turn of events, the world collectively unites and says hello back\"\n  );\n\n  const nestedMessageDiv2 = page.locator(\"#children-2\");\n  await expect(nestedMessageDiv2).toBeEmpty();\n  const nestedMessageTitle2 = page.locator(\"#title-2\");\n  await expect(nestedMessageTitle2).toContainText(\"Goodbye Robot\");\n  const nestedMessageBody2 = page.locator(\"#body-2\");\n  await expect(nestedMessageBody2).toContainText(\"The robot says goodbye\");\n\n  const nestedMessageDiv3 = page.locator(\"#children-3\");\n  await expect(nestedMessageDiv3).toBeEmpty();\n  const nestedMessageTitle3 = page.locator(\"#title-3\");\n  await expect(nestedMessageTitle3).toContainText(\"Goodbye World\");\n  const nestedMessageBody3 = page.locator(\"#body-3\");\n  await expect(nestedMessageBody3).toContainText(\"The world says goodbye\");\n\n  // Even more loading text for the deeply nested suspense\n  await expect(nestedMessageDiv1).toContainText(\"Loading 4...\");\n\n  const nestedMessageDiv4 = page.locator(\"#children-4\");\n  await expect(nestedMessageDiv4).toBeEmpty();\n  const nestedMessageTitle4 = page.locator(\"#title-4\");\n  await expect(nestedMessageTitle4).toContainText(\"Hello World\");\n  const nestedMessageBody4 = page.locator(\"#body-4\");\n  await expect(nestedMessageBody4).toContainText(\"The world says hello again\");\n});\n"
  },
  {
    "path": "packages/playwright-tests/package.json",
    "content": "{\n  \"name\": \"dioxus\",\n  \"version\": \"1.0.0\",\n  \"description\": \"<p align=\\\"center\\\">   <img src=\\\"./notes/header.svg\\\"> </p>\",\n  \"main\": \"index.js\",\n  \"directories\": {\n    \"doc\": \"docs\",\n    \"example\": \"examples\"\n  },\n  \"scripts\": {},\n  \"keywords\": [],\n  \"author\": \"\",\n  \"license\": \"ISC\",\n  \"devDependencies\": {\n    \"@playwright/test\": \"^1.56.1\"\n  }\n}\n"
  },
  {
    "path": "packages/playwright-tests/playwright.config.js",
    "content": "// @ts-check\nconst { defineConfig, devices } = require(\"@playwright/test\");\nconst path = require(\"path\");\n\n/**\n * Read environment variables from file.\n * https://github.com/motdotla/dotenv\n */\n// require('dotenv').config();\n\nlet webServer = [];\nlet grep = undefined;\nlet grepInvert = undefined;\nif (process.platform === \"win32\") {\n  webServer = [\n    {\n      cwd: path.join(process.cwd(), \"windows-headless\"),\n      command:\n        \"cargo run --package dioxus-cli --release -- run --force-sequential\",\n      port: 8787,\n      timeout: 50 * 60 * 1000,\n      reuseExistingServer: !process.env.CI,\n      stdout: \"pipe\",\n    },\n  ];\n  grep = /windows/;\n} else {\n  webServer = [\n    {\n      command:\n        \"cargo run --package dioxus-playwright-liveview-test --bin dioxus-playwright-liveview-test\",\n      port: 3030,\n      timeout: 50 * 60 * 1000,\n      reuseExistingServer: !process.env.CI,\n      stdout: \"pipe\",\n    },\n    {\n      cwd: path.join(process.cwd(), \"web\"),\n      command:\n        'cargo run --package dioxus-cli --release -- run --force-sequential --web --addr \"127.0.0.1\" --port 9990',\n      port: 9990,\n      timeout: 50 * 60 * 1000,\n      reuseExistingServer: !process.env.CI,\n      stdout: \"pipe\",\n    },\n    {\n      cwd: path.join(process.cwd(), \"web-routing\"),\n      command:\n        'cargo run --package dioxus-cli --release -- run --force-sequential --web --addr \"127.0.0.1\" --port 2020',\n      port: 2020,\n      timeout: 50 * 60 * 1000,\n      reuseExistingServer: !process.env.CI,\n      stdout: \"pipe\",\n    },\n    {\n      cwd: path.join(process.cwd(), \"web-hash-routing\"),\n      command:\n        'cargo run --package dioxus-cli --release -- run --force-sequential --web --addr \"127.0.0.1\" --port 2021',\n      port: 2021,\n      timeout: 50 * 60 * 1000,\n      reuseExistingServer: !process.env.CI,\n      stdout: \"pipe\",\n    },\n    {\n      cwd: path.join(process.cwd(), \"fullstack\"),\n      command:\n        'cargo run --package dioxus-cli --release -- run --force-sequential --web --addr \"127.0.0.1\" --port 3333 --release',\n      port: 3333,\n      timeout: 50 * 60 * 1000,\n      reuseExistingServer: !process.env.CI,\n      stdout: \"pipe\",\n    },\n    {\n      cwd: path.join(process.cwd(), \"fullstack-errors\"),\n      command:\n        'cargo run --package dioxus-cli --release -- run --force-sequential --web --addr \"127.0.0.1\" --port 3232',\n      port: 3232,\n      timeout: 50 * 60 * 1000,\n      reuseExistingServer: !process.env.CI,\n      stdout: \"pipe\",\n    },\n    {\n      cwd: path.join(process.cwd(), \"fullstack-mounted\"),\n      command:\n        'cargo run --package dioxus-cli --release -- run --force-sequential --web --addr \"127.0.0.1\" --port 7777',\n      port: 7777,\n      timeout: 50 * 60 * 1000,\n      reuseExistingServer: !process.env.CI,\n      stdout: \"pipe\",\n    },\n    {\n      cwd: path.join(process.cwd(), \"fullstack-spread\"),\n      command:\n        'cargo run --package dioxus-cli --release -- run --verbose --force-sequential --web --addr \"127.0.0.1\" --port 7980',\n      port: 7980,\n      timeout: 50 * 60 * 1000,\n      reuseExistingServer: !process.env.CI,\n      stdout: \"pipe\",\n    },\n    {\n      cwd: path.join(process.cwd(), \"fullstack-routing\"),\n      command:\n        'cargo run --package dioxus-cli --release -- run --force-sequential --web --addr \"127.0.0.1\" --port 8888',\n      port: 8888,\n      timeout: 50 * 60 * 1000,\n      reuseExistingServer: !process.env.CI,\n      stdout: \"pipe\",\n    },\n    {\n      cwd: path.join(process.cwd(), \"suspense-carousel\"),\n      command:\n        'cargo run --package dioxus-cli --release -- run --force-sequential --web --addr \"127.0.0.1\" --port 4040',\n      port: 4040,\n      timeout: 50 * 60 * 1000,\n      reuseExistingServer: !process.env.CI,\n      stdout: \"pipe\",\n    },\n    {\n      cwd: path.join(process.cwd(), \"nested-suspense\"),\n      command:\n        'cargo run --package dioxus-cli --release -- run --force-sequential --web --addr \"127.0.0.1\" --port 5050',\n      port: 5050,\n      timeout: 50 * 60 * 1000,\n      reuseExistingServer: !process.env.CI,\n      stdout: \"pipe\",\n    },\n    {\n      cwd: path.join(process.cwd(), \"nested-suspense\"),\n      command:\n        'cargo run --package dioxus-cli --release -- run --bin nested-suspense-ssg --force-sequential --web --ssg --addr \"127.0.0.1\" --port 6060',\n      port: 6060,\n      timeout: 50 * 60 * 1000,\n      reuseExistingServer: !process.env.CI,\n      stdout: \"pipe\",\n    },\n    {\n      cwd: path.join(process.cwd(), \"fullstack-hydration-order\"),\n      command:\n        'cargo run --package dioxus-cli --release -- run --force-sequential --web --addr \"127.0.0.1\" --port 7979',\n      port: 7979,\n      timeout: 50 * 60 * 1000,\n      reuseExistingServer: !process.env.CI,\n      stdout: \"pipe\",\n    },\n    {\n      cwd: path.join(process.cwd(), \"cli-optimization\"),\n      command:\n        'cargo run --package dioxus-cli --release -- run --addr \"127.0.0.1\" --port 8989',\n      port: 8989,\n      timeout: 50 * 60 * 1000,\n      reuseExistingServer: !process.env.CI,\n      stdout: \"pipe\",\n    },\n    {\n      cwd: path.join(process.cwd(), \"wasm-split-harness\"),\n      command:\n        'cargo run --package dioxus-cli --release -- run --bin wasm-split-harness --web --addr \"127.0.0.1\" --port 8001 --wasm-split --profile wasm-split-release',\n      port: 8001,\n      timeout: 50 * 60 * 1000,\n      reuseExistingServer: !process.env.CI,\n      stdout: \"pipe\",\n    },\n    {\n      cwd: path.join(process.cwd(), \"default-features-disabled\"),\n      command:\n        'cargo run --package dioxus-cli --release -- run --force-sequential --addr \"127.0.0.1\" --port 8002',\n      port: 8002,\n      timeout: 50 * 60 * 1000,\n      reuseExistingServer: !process.env.CI,\n      stdout: \"pipe\",\n    },\n    {\n      cwd: path.join(process.cwd(), \"barebones-template\"),\n      command:\n        'cargo run --package dioxus-cli --release -- run --force-sequential --addr \"127.0.0.1\" --port 8123',\n      port: 8123,\n      timeout: 50 * 60 * 1000,\n      reuseExistingServer: !process.env.CI,\n      stdout: \"pipe\",\n    },\n    {\n      command:\n        'rm -rf ./web-hot-patch-temp && cp -r ./web-hot-patch ./web-hot-patch-temp && cd web-hot-patch-temp && cargo run --manifest-path ../../cli/Cargo.toml --release -- serve --verbose --force-sequential --web --addr \"127.0.0.1\" --port 9980 --hot-patch --exit-on-error',\n      port: 9980,\n      timeout: 50 * 60 * 1000,\n      reuseExistingServer: !process.env.CI,\n      stdout: \"pipe\",\n    },\n    {\n      command:\n        'rm -rf ./web-hot-patch-fullstack-temp && cp -r ./web-hot-patch-fullstack ./web-hot-patch-fullstack-temp && cd web-hot-patch-fullstack-temp && cargo run --manifest-path ../../cli/Cargo.toml --release -- serve --verbose --force-sequential --web --addr \"127.0.0.1\" --port 9981 --hot-patch --exit-on-error',\n      port: 9981,\n      timeout: 50 * 60 * 1000,\n      reuseExistingServer: !process.env.CI,\n      stdout: \"pipe\",\n    },\n    {\n      cwd: path.join(process.cwd(), \"fullstack-error-codes\"),\n      command:\n        'cargo run --package dioxus-cli --release -- run --force-sequential --addr \"127.0.0.1\" --port 8124',\n      port: 8124,\n      timeout: 50 * 60 * 1000,\n      reuseExistingServer: !process.env.CI,\n      stdout: \"pipe\",\n    },\n  ];\n\n  grepInvert = /windows/;\n}\n\n/**\n * @see https://playwright.dev/docs/test-configuration\n */\nmodule.exports = defineConfig({\n  testDir: \".\",\n  /* Run tests in files in parallel */\n  fullyParallel: true,\n  /* Fail the build on CI if you accidentally left test.only in the source code. */\n  forbidOnly: !!process.env.CI,\n  /* Retry on CI only */\n  retries: process.env.CI ? 2 : 0,\n  /* Opt out of parallel tests on CI. */\n  workers: process.env.CI ? 1 : undefined,\n  /* Reporter to use. See https://playwright.dev/docs/test-reporters */\n  reporter: \"html\",\n  /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */\n  use: {\n    /* Base URL to use in actions like `await page.goto('/')`. */\n    // baseURL: 'http://127.0.0.1:3000',\n\n    /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */\n    trace: \"retain-on-failure\",\n    // Increase the timeout for navigations to give dx time to build the project\n    navigationTimeout: 50 * 60 * 1000,\n  },\n\n  timeout: 50 * 60 * 1000,\n\n  /* Configure projects for major browsers */\n  projects: [\n    {\n      name: \"chromium\",\n      grep,\n      grepInvert,\n      use: { ...devices[\"Desktop Chrome\"] },\n    },\n  ],\n\n  /* Run your local dev server before starting the tests */\n  webServer,\n});\n"
  },
  {
    "path": "packages/playwright-tests/suspense-carousel/.gitignore",
    "content": ".dioxus\ndist\ntarget"
  },
  {
    "path": "packages/playwright-tests/suspense-carousel/Cargo.toml",
    "content": "[package]\nname = \"suspense-carousel\"\nedition = \"2021\"\nversion.workspace = true\npublish = false\n\n[dependencies]\ndioxus = { workspace = true, features = [\"fullstack\"] }\nasync-std = \"1.13.1\"\nserde = \"1.0.219\"\n\n[features]\ndefault = []\nserver = [\"dioxus/server\"]\nweb = [\"dioxus/web\"]\n"
  },
  {
    "path": "packages/playwright-tests/suspense-carousel/src/main.rs",
    "content": "// This test is used by playwright configured in the root of the repo\n// Tests:\n// - Streaming hydration\n// - Suspense\n// - Server futures\n\n#![allow(non_snake_case, unused)]\nuse dioxus::{fullstack::commit_initial_chunk, prelude::*};\nuse serde::{Deserialize, Serialize};\n\nfn app() -> Element {\n    // Start streaming immediately\n    use_hook(commit_initial_chunk);\n    let mut count = use_signal(|| 0);\n\n    rsx! {\n        button {\n            id: \"increment-carousel-button\",\n            onclick: move |_| count += 1,\n            \"Increment\"\n        }\n        button {\n            id: \"decrement-carousel-button\",\n            onclick: move |_| count -= 1,\n            \"Decrement\"\n        }\n        div {\n            \"Hello world\"\n        }\n        div {\n            for i in count()..count() + 3 {\n                SuspenseBoundary {\n                    key: \"{i}\",\n                    fallback: |_| rsx! {\n                        \"Loading...\"\n                    },\n                    SuspendedComponent {\n                        id: i\n                    }\n                }\n            }\n        }\n        div { \"footer 123\" }\n    }\n}\n\n#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]\nenum ResolvedOn {\n    Server,\n    Client,\n}\n\nimpl ResolvedOn {\n    #[cfg(feature = \"web\")]\n    const CURRENT: Self = Self::Client;\n    #[cfg(not(feature = \"web\"))]\n    const CURRENT: Self = Self::Server;\n}\n\n#[component]\nfn SuspendedComponent(id: i32) -> Element {\n    let resolved_on = use_server_future(move || async move {\n        async_std::task::sleep(std::time::Duration::from_secs(1)).await;\n        ResolvedOn::CURRENT\n    })?()\n    .unwrap();\n\n    let mut count = use_signal(|| 0);\n\n    rsx! {\n        div {\n            id: \"outer-{id}\",\n            \"outer suspense result: {resolved_on:?}\"\n            button {\n                id: \"outer-button-{id}\",\n                onclick: move |_| count += 1,\n                \"{count}\"\n            }\n            SuspenseBoundary {\n                fallback: |_| rsx! {\n                    \"Loading... more\"\n                },\n                NestedSuspendedComponent {\n                    id\n                }\n            }\n        }\n    }\n}\n\n#[component]\nfn NestedSuspendedComponent(id: i32) -> Element {\n    let resolved_on = use_server_future(move || async move {\n        async_std::task::sleep(std::time::Duration::from_secs(1)).await;\n        ResolvedOn::CURRENT\n    })?()\n    .unwrap();\n    let mut count = use_signal(|| 0);\n    rsx! {\n        div {\n            \"nested suspense result: {resolved_on:?}\"\n            button {\n                id: \"nested-button-{id}\",\n                onclick: move |_| count += 1,\n                \"{count}\"\n            }\n        }\n    }\n}\n\nfn main() {\n    LaunchBuilder::new()\n        .with_cfg(server_only! {\n            dioxus::server::ServeConfig::builder().enable_out_of_order_streaming()\n        })\n        .launch(app);\n}\n"
  },
  {
    "path": "packages/playwright-tests/suspense-carousel.spec.js",
    "content": "// @ts-check\nconst { test, expect } = require(\"@playwright/test\");\n\ntest(\"suspense resolves on server\", async ({ page }) => {\n  // Wait for the dev server to reload\n  await page.goto(\"http://localhost:4040\");\n  // Then wait for the page to start loading\n  await page.goto(\"http://localhost:4040\", { waitUntil: \"commit\" });\n\n  // On the client, we should see some loading text\n  const main = page.locator(\"#main\");\n  await expect(main).toContainText(\"Loading...\");\n\n  await page.waitForTimeout(1000);\n\n  // Expect the page to contain the suspense result from the server\n  await expect(main).toContainText(\"outer suspense result: Server\");\n\n  // And more loading text for the nested suspense\n  await expect(main).toContainText(\"Loading... more\");\n\n  await page.waitForTimeout(1000);\n\n  // And the nested suspense result\n  await expect(main).toContainText(\"nested suspense result: Server\");\n\n  // Click the outer button\n  let button = page.locator(\"button#outer-button-0\");\n  await button.click();\n  // The button should have incremented\n  await expect(button).toContainText(\"1\");\n\n  // Click the nested button\n  button = page.locator(\"button#nested-button-0\");\n  await button.click();\n  // The button should have incremented\n  await expect(button).toContainText(\"1\");\n\n  // Now incrementing the carousel should create a new suspense boundary\n  let incrementCarouselButton = page.locator(\n    \"button#increment-carousel-button\"\n  );\n  await incrementCarouselButton.click();\n\n  // A new pending suspense should be created on the client\n  await expect(main).toContainText(\"Loading...\");\n\n  // The suspense should resolve on the client\n  let newSuspense = page.locator(\"#outer-3\");\n  await expect(newSuspense).toContainText(\"outer suspense result: Client\");\n\n  // It should be loading more\n  await expect(newSuspense).toContainText(\"Loading... more\");\n\n  // And the nested suspense result\n  await expect(newSuspense).toContainText(\"nested suspense result: Client\");\n\n  // Click the outer button\n  button = page.locator(\"button#outer-button-3\");\n  await button.click();\n  // The button should have incremented\n  await expect(button).toContainText(\"1\");\n\n  // Click the nested button\n  button = page.locator(\"button#nested-button-3\");\n  await button.click();\n  // The button should have incremented\n  await expect(button).toContainText(\"1\");\n});\n"
  },
  {
    "path": "packages/playwright-tests/wasm-split-harness/.cargo/config.toml",
    "content": "# It's recommended to set the flag on a per-target basis:\n[target.wasm32-unknown-unknown]\nrustflags = ['--cfg', 'getrandom_backend=\"wasm_js\"']\n"
  },
  {
    "path": "packages/playwright-tests/wasm-split-harness/Cargo.toml",
    "content": "[package]\nname = \"wasm-split-harness\"\nversion = \"0.1.0\"\nedition = \"2021\"\npublish = false\nauthors = [\"Jonathan Kelley\"]\n\n[dependencies]\ndioxus = { workspace = true, features = [\"web\", \"router\", \"wasm-split\"] }\ndioxus-router = { workspace = true, features = [\"wasm-split\"] }\nanyhow = { workspace = true }\nasync-compression = { workspace = true, features = [\n    \"futures-io\",\n    \"gzip\",\n    \"brotli\",\n] }\nfutures = { workspace = true }\njs-sys = { workspace = true }\nwasm-bindgen = { workspace = true }\nwasm-bindgen-futures = { workspace = true }\nweb-sys = { workspace = true, features = [\n    \"Document\",\n    \"Window\",\n    \"HtmlElement\",\n    \"Text\",\n    \"DomRectReadOnly\",\n    \"console\",\n] }\ngetrandom = { workspace = true, features = [\"wasm_js\"] }\nreqwest = { workspace = true, features = [\"json\"] }\n"
  },
  {
    "path": "packages/playwright-tests/wasm-split-harness/data/.gitignore",
    "content": "harness/\n"
  },
  {
    "path": "packages/playwright-tests/wasm-split-harness/data/index.html",
    "content": "<html>\n    <head>\n        <meta charset=\"utf-8\">\n        <title>Hello wasm-bindgen</title>\n    </head>\n    <body>\n        <div id=\"main\"></div>\n    </body>\n    <script type=\"module\">\n        import init from \"/harness/split/main.js\";\n        init();\n    </script>\n</html>\n"
  },
  {
    "path": "packages/playwright-tests/wasm-split-harness/docsite.sh",
    "content": "cargo build --bin wasm-split-cli\nCLI=./target/debug/wasm-split-cli\n\nrm -rf docsite/chunks\n\n# Run the wasm-split-cli on the with_body.wasm file\n${CLI} split docsite/input.wasm docsite/bindgen/main_bg.wasm docsite/chunks\n\n# copy the contents of the wasm_bindgen folder to the docsite folder\nmv docsite/chunks/main.wasm docsite/chunks/main_bg.wasm # rename the main wasm file\ncp -r docsite/bindgen/snippets docsite/chunks/snippets\ncp docsite/bindgen/main.js docsite/chunks/main.js\n\npython3 -m http.server 8080 --directory docsite\n"
  },
  {
    "path": "packages/playwright-tests/wasm-split-harness/run.sh",
    "content": "# This file is a simple shell script that runs the bundle split process manually without the CLI involved\n# it's not necessarily meant to work on your machine (sorry!)\n#\n# To hack on harness you need the `wasm-tools` CLI installed\n# `cargo binstall wasm-tools`\n#\n# This script is also sensitive to where it's run from, so you *need* to be in the harness folder (running as `./run.sh`)\n\nTARGET_DIR=../../../target\n\n# build the harness\ncargo rustc --package wasm-split-harness --target wasm32-unknown-unknown --profile wasm-split-release -- -Clink-args=--emit-relocs\n\n# for a much smaller compile, you can crank up the flags. However, dioxus relies heavily on location detail, so we can't disable that\n#\n# -Zlocation-detail=none - we could compile with location detail off but if breaks our signals...\n#\n# cargo +nightly rustc \\\n#   -Z build-std=std,panic_abort \\\n#   -Z build-std-features=\"optimize_for_size\" \\\n#   -Z build-std-features=panic_immediate_abort \\\n#   --target wasm32-unknown-unknown \\\n#   --no-default-features \\\n#   --profile wasm-split-release \\\n#   -- -Clink-args=--emit-relocs\n\n# Build the wasm-split-cli. We are going to call it directly since it's so noisy to build it multiple times\ncargo build --package wasm-split-cli --bin wasm-split-cli\nCLI=$TARGET_DIR/debug/wasm-split-cli\n\n# clear the workdir and assemble the new structure\nrm -rf data/harness\nmkdir -p data/harness/split\nmkdir -p data/harness/split_not\n\n# copy the output wasm file to the harness dir\ncp $TARGET_DIR/wasm32-unknown-unknown/wasm-split-release/wasm-split-harness.wasm data/harness/input.wasm\n\n# Run wasm-bindgen on this module, without splitting it\nwasm-bindgen data/harness/input.wasm --out-dir data/harness/split_not --target web --out-name main --no-demangle --no-typescript --keep-lld-exports --keep-debug\n\n# Run the wasm-split-cli on the with_body.wasm file\n${CLI} split data/harness/input.wasm data/harness/split_not/main_bg.wasm data/harness/chunks\n\n# copy over the chunks\npaths=$(ls data/harness/chunks/ | grep \"\\.wasm\")\nfor path in $paths\ndo\n\n    path_without_ext=${path%.*}\n    wasm-opt -Oz data/harness/chunks/$path -o data/harness/split/$path --enable-reference-types --memory-packing --debuginfo\n\n    # remove stuff like manganis, etc\n    wasm-tools strip data/harness/split/$path -o data/harness/split/$path\n\n    # if you don't want names (making it harder to debug the outputs) use `--all`\n    # wasm-tools strip data/harness/split/$path -o data/harness/split/$path --all\ndone\n\n\n# rename the main chunk\nmv data/harness/split/main.wasm data/harness/split/main_bg.wasm\ncp data/harness/split_not/main.js data/harness/split/main.js\ncp -r data/harness/split_not/snippets data/harness/split/snippets\ncp data/harness/chunks/__wasm_split.js data/harness/split/__wasm_split.js\n\nwasm-opt -Oz data/harness/split_not/main_bg.wasm -o data/harness/split_not/main_bg_opt.wasm --enable-reference-types --memory-packing --debuginfo\n\n# Run wasm-strip to strip out the debug symbols\nwasm-tools strip data/harness/split_not/main_bg_opt.wasm -o data/harness/split_not/main_bg_opt.wasm\n\n# if you don't want names (making it harder to debug the outputs) use `--all`\n# wasm-tools strip data/harness/split/$path -o strip data/harness/split_not/main_bg_opt.wasm --all\n\necho \"===========================================================================\\n\"\nls -l data/harness/split_not/main_bg_opt.wasm | awk '{ printf(\"%07d -> \", $5);print $9}'\necho \"\"\nls -l data/harness/split | grep \"\\.wasm\" | awk '{ printf(\"%07d -> \", $5);print $9}'\necho \"\\n===========================================================================\"\n\n# hope you have python3 installed :)\npython3 -m http.server 9876 --directory data\n"
  },
  {
    "path": "packages/playwright-tests/wasm-split-harness/src/main.rs",
    "content": "//! This file is a fuzz-test for the wasm-split engine to ensure that it works as expected.\n//! The docsite is a better target for this, but we try to boil down the complexity into this small\n//! test file.\n\n#![allow(non_snake_case)]\n\nuse dioxus::prelude::*;\nuse futures::AsyncReadExt;\nuse js_sys::Date;\nuse std::pin::Pin;\nuse wasm_bindgen::prelude::*;\nuse wasm_split::lazy_loader;\n\nfn main() {\n    dioxus::launch(|| {\n        rsx! {\n            Router::<Route> {}\n        }\n    });\n}\n\n#[derive(Routable, PartialEq, Eq, Debug, Clone)]\nenum Route {\n    #[layout(Nav)]\n    #[route(\"/\")]\n    Home,\n    #[route(\"/child\")]\n    ChildSplit,\n}\n\nfn Nav() -> Element {\n    rsx! {\n        div {\n            Link { id: \"link-home\", to: Route::Home, \"Home\" }\n            Link { id: \"link-child\", to: Route::ChildSplit, \"Child\" }\n            Outlet::<Route> {}\n        }\n    }\n}\n\npub(crate) static GLOBAL_COUNTER: GlobalSignal<usize> = Signal::global(|| 0);\n\nfn Home() -> Element {\n    let mut count = use_signal(|| 1);\n    let mut res = use_signal(|| \"hello\".to_string());\n\n    rsx! {\n        h1 { \"Hello bundle split 456\" }\n        h3 { id: \"counter-display\", \"Count: {count}\" }\n        h3 { id: \"global-counter\", \"Global Counter: {GLOBAL_COUNTER}\" }\n        button {\n            id: \"increment-counter\",\n            onclick: move |_| count += 1,\n            \"Click me\"\n        }\n        button {\n            id: \"increment-counter-global\",\n            onclick: move |_| *GLOBAL_COUNTER.write() += 1,\n            \"Click me\"\n        }\n        button {\n            id: \"add-body-text\",\n            onclick: move |_| add_body_text(),\n            \"Add body text\"\n        }\n        button {\n            id: \"add-body-element\",\n            onclick: move |_| async move {\n                add_body_element().await;\n                count += 1;\n            },\n            \"Add body element\"\n        }\n        button {\n            id: \"gzip-it\",\n            onclick: move |_| async move {\n                gzip_it().await;\n            },\n            \"GZIP it\"\n        }\n        button {\n            id: \"brotli-it\",\n            onclick: move |_| async move {\n                brotli_it(&[0u8; 10]).await;\n            },\n            \"Brotli It\"\n        }\n        button {\n            id: \"make-request\",\n            onclick: move |_| async move {\n                let res_ = make_request().await.unwrap();\n                res.set(res_);\n            },\n            \"Make Request!\"\n        }\n        button {\n            id: \"make-local-request\",\n            onclick: move |_| async move {\n                let client = reqwest::Client::new();\n                let response = client\n                    .get(\"https://dog.ceo/api/breeds/image/random\")\n                    .send()\n                    .await?;\n                let body = response.text().await?;\n                *res.write() = body;\n                Ok(())\n            },\n            \"local request\"\n        }\n        LazyComponent {\n            abc: 0\n        }\n        div { \"Response: {res}\" }\n        div { id: \"output-box\" }\n    }\n}\n\n#[wasm_split::wasm_split(one)]\nasync fn add_body_text() {\n    let window = web_sys::window().unwrap_throw();\n    let document = window.document().unwrap_throw();\n    let output = document.create_text_node(\"Rendered!\");\n    let output_box = document.get_element_by_id(\"output-box\").unwrap_throw();\n    output_box.append_child(&output).unwrap_throw();\n    *GLOBAL_COUNTER.write() += 1;\n}\n\n#[wasm_split::wasm_split(two)]\nasync fn add_body_element() {\n    let window = web_sys::window().unwrap_throw();\n    let document = window.document().unwrap_throw();\n    let output = document.create_element(\"div\").unwrap_throw();\n    output.set_text_content(Some(\"Some inner div\"));\n    let output_box = document.get_element_by_id(\"output-box\").unwrap_throw();\n    output_box.append_child(&output).unwrap_throw();\n\n    dioxus::core::queue_effect(move || {\n        web_sys::console::log_1(&\"add body async internal!\".into());\n        *GLOBAL_COUNTER.write() += 2;\n    });\n}\n\n#[wasm_split::wasm_split(four)]\nasync fn gzip_it() {\n    static DATA: &[u8] = &[0u8; 10];\n    let reader = Box::pin(futures::io::BufReader::new(DATA));\n    let reader: Pin<Box<dyn futures::io::AsyncBufRead>> = reader;\n\n    dioxus::core::spawn(async move {\n        let mut fut = Box::pin(async_compression::futures::bufread::GzipDecoder::new(\n            reader,\n        ));\n        if fut.read_to_end(&mut Vec::new()).await.is_err() {\n            web_sys::console::log_1(&\"error reading gzip\".into());\n        }\n        *GLOBAL_COUNTER.write() += 3;\n\n        let res: Result<String, anyhow::Error> = Box::pin(async move {\n            let client = reqwest::Client::new();\n            let response = client\n                .get(\"https://dog.ceo/api/breeds/image/random\")\n                .send()\n                .await?;\n            let body = response.text().await?;\n            Ok(body)\n        })\n        .await;\n\n        if res.is_err() {\n            web_sys::console::log_1(&\"error making request\".into());\n        }\n    });\n}\n\n#[wasm_split::wasm_split(three)]\nasync fn brotli_it(data: &'static [u8]) {\n    let reader = Box::pin(futures::io::BufReader::new(data));\n    let reader: Pin<Box<dyn futures::io::AsyncBufRead>> = reader;\n\n    dioxus::core::spawn(async move {\n        let mut fut = Box::pin(async_compression::futures::bufread::BrotliDecoder::new(\n            reader,\n        ));\n        if fut.read_to_end(&mut Vec::new()).await.is_err() {\n            web_sys::console::log_1(&\"error reading brotli\".into());\n        }\n        *GLOBAL_COUNTER.write() += 4;\n    });\n}\n\n#[wasm_split::wasm_split(eleven)]\nasync fn make_request() -> Result<String, anyhow::Error> {\n    let client = reqwest::Client::new();\n    let response = client\n        .get(\"https://dog.ceo/api/breeds/image/random\")\n        .send()\n        .await?;\n    let body = response.text().await?;\n    Ok(body)\n}\n\n#[component(lazy)]\nfn LazyComponent(abc: i32) -> Element {\n    rsx! {\n        div {\n            \"This is a lazy component! {abc}\"\n        }\n    }\n}\n\nfn ChildSplit() -> Element {\n    pub(crate) static DATE: GlobalSignal<Date> = Signal::global(Date::new_0);\n\n    static LOADER: wasm_split::LazyLoader<(), Element> =\n        lazy_loader!(extern \"five\" fn InnerChild(props: ()) -> Element);\n\n    fn InnerChild(_props: ()) -> Element {\n        static LOADER2: wasm_split::LazyLoader<Signal<String>, Element> =\n            lazy_loader!(extern \"fortytwo\" fn InnerChild3(props: Signal<String>) -> Element);\n\n        fn InnerChild3(count: Signal<String>) -> Element {\n            pub(crate) static ICONCHECK: Component<()> = |_| {\n                rsx! {\n                    svg {\n                        class: \"octicon octicon-check js-clipboard-check-icon d-inline-block d-none\",\n                        fill: \"rgb(26, 127, 55)\",\n                        height: \"24\",\n                        version: \"1.1\",\n                        \"aria_hidden\": \"true\",\n                        width: \"24\",\n                        view_box: \"0 0 16 16\",\n                        \"data_view_component\": \"true\",\n                        path {\n                            d: \"M13.78 4.22a.75.75 0 010 1.06l-7.25 7.25a.75.75 0 01-1.06 0L2.22 9.28a.75.75 0 011.06-1.06L6 10.94l6.72-6.72a.75.75 0 011.06 0z\",\n                            fill_rule: \"evenodd\",\n                        }\n                    }\n                    button {\n                        onclick: move |_| {\n                            *DATE.write() = Date::new_0();\n                        },\n                        \"Update Date\"\n                    }\n                }\n            };\n\n            let now = DATE.read().clone();\n\n            rsx! {\n                h1 { \"Some other child\" }\n                h3 { \"Global Counter: {GLOBAL_COUNTER}\" }\n                h3 { \"Date: {now.to_date_string()}\" }\n                h3 { \"count: {count}\" }\n                ICONCHECK {}\n            }\n        }\n\n        #[wasm_bindgen(module = \"/src/stars.js\")]\n        extern \"C\" {\n            pub(crate) fn get_stars(name: String) -> Option<usize>;\n            pub(crate) fn set_stars(name: String, stars: usize);\n        }\n\n        let num = get_stars(\"stars\".to_string()).unwrap_or(0);\n\n        use_resource(|| async move { LOADER2.load().await }).suspend()?;\n        let mut count = use_signal(|| \"hello\".to_string());\n\n        let fp = LOADER2.call(count).unwrap();\n\n        rsx! {\n            h1 { \"Some huge child?\" }\n            p { \"Stars: {num}\" }\n            button {\n                onclick: move |_| {\n                    set_stars(\"stars\".to_string(), num + 1);\n                    dioxus::core::needs_update();\n                },\n                \"Add Star\"\n            }\n            {fp}\n            h3 { id: \"nested-child-count\", \"Count: {count}\" }\n            button {\n                id: \"nested-child-add-world\",\n                onclick: move |_| {\n                    *count.write() += \" world\";\n                },\n                \"Add World\"\n            }\n        }\n    }\n\n    use_resource(|| async move { LOADER.load().await }).suspend()?;\n\n    LOADER.call(()).unwrap()\n}\n"
  },
  {
    "path": "packages/playwright-tests/wasm-split-harness/src/stars.js",
    "content": "// Handle caching of github stars\n// This file is part of the fuzz test to prove we can handle linked js files\n\n// Two days\nconst STAR_EXPIRE_TIME = 172800000;\n\nexport function get_stars(name) {\n  let item = localStorage.getItem(name);\n  let data = JSON.parse(item);\n\n  if (!data) {\n    return null;\n  }\n\n  if (data.expires <= Date.now()) {\n    localStorage.removeItem(name);\n    return null;\n  }\n\n  return data.stars;\n}\n\nexport function set_stars(name, value) {\n  let expires = Date.now() + STAR_EXPIRE_TIME;\n  let data = { stars: value, expires };\n\n  let converted = JSON.stringify(data);\n  localStorage.setItem(name, converted);\n}\n"
  },
  {
    "path": "packages/playwright-tests/wasm-split.spec.js",
    "content": "// @ts-check\nconst { test, expect } = require(\"@playwright/test\");\n\ntest(\"wasm-split page is functional\", async ({ page }) => {\n  // Wait for the dev server to load\n  await page.goto(\"http://localhost:8001\");\n\n  // Make sure the local button works - no broken wasm\n  const counter = page.locator(\"#counter-display\");\n  await expect(counter).toContainText(\"Count: 1\");\n  await page.locator(\"#increment-counter\").click();\n  await expect(counter).toContainText(\"Count: 2\");\n\n  // Make sure the global button works - no broken wasm\n  const counterGlobal = page.locator(\"#global-counter\");\n  await expect(counterGlobal).toContainText(\"Global Counter: 0\");\n  await page.locator(\"#increment-counter-global\").click();\n  await expect(counterGlobal).toContainText(\"Global Counter: 1\");\n\n  // Fire one of the wasm modules to load. Should update the counter and add some text\n  const addBodyTextButton = page.locator(\"#add-body-text\");\n  await addBodyTextButton.click();\n  await expect(counterGlobal).toContainText(\"Global Counter: 2\");\n  const outputBox = page.locator(\"#output-box\");\n  await expect(outputBox).toContainText(\"Rendered!\");\n\n  // The other wasm module\n  const addBodyElementButton = page.locator(\"#add-body-element\");\n  await addBodyElementButton.click();\n  await expect(counterGlobal).toContainText(\"Global Counter: 4\");\n  await expect(outputBox).toContainText(\"Some inner div\");\n\n  // Load the gzip and brotli modules\n  const gzipButton = page.locator(\"#gzip-it\");\n  await gzipButton.click();\n  await expect(counterGlobal).toContainText(\"Global Counter: 7\");\n  const brotliButton = page.locator(\"#brotli-it\");\n  await brotliButton.click();\n  await expect(counterGlobal).toContainText(\"Global Counter: 11\");\n\n  // Ignore the requests in CI\n  // Load the other router module\n  const childRouteButton = page.locator(\"#link-child\");\n  await childRouteButton.click();\n  const nestedChildCounter = page.locator(\"#nested-child-count\");\n  await expect(nestedChildCounter).toContainText(\"Count: hello\");\n  await page.locator(\"#nested-child-add-world\").click();\n  await expect(nestedChildCounter).toContainText(\"Count: hello world\");\n});\n"
  },
  {
    "path": "packages/playwright-tests/web/.gitignore",
    "content": "dist\ntarget\n"
  },
  {
    "path": "packages/playwright-tests/web/Cargo.toml",
    "content": "[package]\nname = \"dioxus-playwright-web-test\"\nversion = \"0.0.1\"\nedition = \"2021\"\ndescription = \"Playwright test for Dioxus Web\"\nlicense = \"MIT OR Apache-2.0\"\npublish = false\n\n[dependencies]\ndioxus = { workspace = true, features = [\"web\"]}\nserde_json = { workspace = true }\ntracing = { workspace = true }\ntracing-wasm = { workspace = true }\nwasm-bindgen = { workspace = true }\nweb-sys = { workspace = true, features = [\"Element\", \"Window\"] }\n"
  },
  {
    "path": "packages/playwright-tests/web/src/main.rs",
    "content": "// This test is used by playwright configured in the root of the repo\n\nuse dioxus::prelude::*;\nuse wasm_bindgen::prelude::*;\n\nfn app() -> Element {\n    let mut num = use_signal(|| 0);\n    let mut eval_result = use_signal(String::new);\n\n    rsx! {\n        div {\n            \"hello axum! {num}\"\n            document::Title { \"hello axum! {num}\" }\n            button { class: \"increment-button\", onclick: move |_| num += 1, \"Increment\" }\n        }\n        svg { circle { cx: 50, cy: 50, r: 40, stroke: \"green\", fill: \"yellow\" } }\n        div { class: \"raw-attribute-div\", \"raw-attribute\": \"raw-attribute-value\" }\n        div { class: \"hidden-attribute-div\", hidden: true }\n        div {\n            class: \"dangerous-inner-html-div\",\n            dangerous_inner_html: \"<p>hello dangerous inner html</p>\"\n        }\n        input { value: \"hello input\" }\n        div { class: \"style-div\", color: \"red\", \"colored text\" }\n        button {\n            class: \"eval-button\",\n            onclick: move |_| async move {\n                // Make sure normal return values work. Regression test for https://github.com/DioxusLabs/dioxus/issues/3655\n                let eval = document::eval(r#\"return \"hello world\";\"#);\n\n                let result = eval.await.unwrap();\n                assert_eq!(result, \"hello world\");\n\n                // Make sure dioxus.send/dioxus.recv works\n                let mut eval = document::eval(\n                    r#\"\n                        window.document.title = 'Hello from Dioxus Eval!';\n                        // Receive and multiply 10 numbers\n                        for (let i = 0; i < 10; i++) {\n                            let value = await dioxus.recv();\n                            dioxus.send(value*2);\n                        }\n                        dioxus.send(\"returned eval value\");\n                    \"#,\n                );\n\n                // Send 10 numbers\n                for i in 0..10 {\n                    eval.send(i).unwrap();\n                    let value: i32 = eval.recv().await.unwrap();\n                    assert_eq!(value, i * 2);\n                }\n\n                let result = eval.recv().await;\n                if let Ok(serde_json::Value::String(string)) = result {\n                    eval_result.set(string);\n                }\n            },\n            \"Eval\"\n        }\n        div { class: \"eval-result\", \"{eval_result}\" }\n        PreventDefault {}\n        OnMounted {}\n        WebSysClosure {}\n        DocumentElements {}\n        MergeStyles {}\n        SelectMultiple {}\n    }\n}\n\n#[component]\nfn PreventDefault() -> Element {\n    let mut text = use_signal(|| \"View source\".to_string());\n    rsx! {\n        a {\n            class: \"prevent-default\",\n            href: \"https://github.com/DioxusLabs/dioxus/tree/main/packages/playwright-tests/web\",\n            onclick: move |evt| {\n                evt.prevent_default();\n                text.set(\"Psych!\".to_string());\n            },\n            \"{text}\"\n        }\n    }\n}\n\n#[component]\nfn OnMounted() -> Element {\n    let mut mounted_triggered_count = use_signal(|| 0);\n    rsx! {\n        div {\n            class: \"onmounted-div\",\n            onmounted: move |_| {\n                mounted_triggered_count += 1;\n            },\n            \"onmounted was called {mounted_triggered_count} times\"\n        }\n    }\n}\n\n// This component tests attaching an event listener to the document with a web-sys closure\n// and effect\n#[component]\nfn WebSysClosure() -> Element {\n    static TRIGGERED: GlobalSignal<bool> = GlobalSignal::new(|| false);\n    use_effect(|| {\n        let window = web_sys::window().expect(\"window not available\");\n\n        // Assert the component contents have been mounted\n        window\n            .document()\n            .unwrap()\n            .get_element_by_id(\"web-sys-closure-div\")\n            .expect(\"Effects should only be run after all contents have bene mounted to the dom\");\n\n        // Make sure passing the runtime into the closure works\n        let callback = Callback::new(|_| {\n            assert!(!dioxus::dioxus_core::Runtime::current().vdom_is_rendering());\n            *TRIGGERED.write() = true;\n        });\n        let closure: Closure<dyn Fn()> = Closure::new({\n            move || {\n                callback(());\n            }\n        });\n\n        window\n            .add_event_listener_with_callback(\"keydown\", closure.as_ref().unchecked_ref())\n            .expect(\"Failed to add keydown event listener\");\n\n        closure.forget();\n    });\n\n    rsx! {\n        div {\n            id: \"web-sys-closure-div\",\n            if TRIGGERED() {\n                \"the keydown event was triggered\"\n            }\n        }\n    }\n}\n\n/// This component tests the document::* elements\n#[component]\nfn DocumentElements() -> Element {\n    rsx! {\n        document::Meta { id: \"meta-head\", name: \"testing\", data: \"dioxus-meta-element\" }\n        document::Link {\n            id: \"link-head\",\n            rel: \"stylesheet\",\n            href: \"https://fonts.googleapis.com/css?family=Roboto+Mono\"\n        }\n        document::Stylesheet { id: \"stylesheet-head\", href: \"https://fonts.googleapis.com/css?family=Roboto:300,300italic,700,700italic\" }\n        document::Script { id: \"script-head\", async: true, \"console.log('hello world');\" }\n        document::Style { id: \"style-head\", \"body {{ font-family: 'Roboto'; }}\" }\n\n        // Test that links with same href but different rel are NOT deduplicated\n        // See https://github.com/DioxusLabs/dioxus/issues/5070\n        document::Link {\n            id: \"dedup-preload\",\n            rel: \"preload\",\n            href: \"dedup-test.css\",\n            r#as: \"style\",\n        }\n        document::Link {\n            id: \"dedup-stylesheet\",\n            rel: \"stylesheet\",\n            href: \"dedup-test.css\",\n        }\n\n        // Test that links with same href AND same rel ARE deduplicated\n        document::Link {\n            id: \"dedup-first\",\n            rel: \"stylesheet\",\n            href: \"dedup-same.css\",\n        }\n        document::Link {\n            id: \"dedup-second\",\n            rel: \"stylesheet\",\n            href: \"dedup-same.css\",\n        }\n    }\n}\n\n// Regression test for https://github.com/DioxusLabs/dioxus/issues/3887\n#[component]\nfn MergeStyles() -> Element {\n    let px = 100;\n\n    rsx! {\n        div {\n            id: \"merge-styles-div\",\n            style: \"width: {px}px; height: {px}px\",\n            background_color: \"red\",\n        }\n    }\n}\n\n// Select elements have odd default behavior when you set the multiple attribute after mounting the element\n// Regression test for https://github.com/DioxusLabs/dioxus/issues/3185\n#[component]\nfn SelectMultiple() -> Element {\n    rsx! {\n        select {\n            id: \"static-multiple-select\",\n            // This is static and will be set in the template\n            multiple: \"true\",\n            option { label: \"Value1\", value: \"1\" }\n            option { label: \"Value2\", value: \"2\" }\n        }\n\n        select {\n            id: \"dynamic-multiple-select\",\n            // This is dynamic and will be set after it is mounted\n            multiple: true,\n            option { label: \"Value1\", value: \"1\" }\n            option { label: \"Value2\", value: \"2\" }\n        }\n    }\n}\n\nfn main() {\n    tracing_wasm::set_as_global_default_with_config(\n        tracing_wasm::WASMLayerConfigBuilder::default()\n            .set_max_level(tracing::Level::TRACE)\n            .build(),\n    );\n    dioxus::launch(app);\n}\n"
  },
  {
    "path": "packages/playwright-tests/web-hash-routing/Cargo.toml",
    "content": "[package]\nname = \"dioxus-playwright-web-hash-routing-test\"\nversion = \"0.0.1\"\nedition = \"2021\"\ndescription = \"Playwright test for Dioxus Web Routing\"\nlicense = \"MIT OR Apache-2.0\"\npublish = false\n\n[dependencies]\ndioxus = { workspace = true, features = [\"web\", \"router\"]}\n"
  },
  {
    "path": "packages/playwright-tests/web-hash-routing/src/main.rs",
    "content": "use std::rc::Rc;\n\nuse dioxus::{prelude::*, web::HashHistory};\n\nfn main() {\n    dioxus::LaunchBuilder::new()\n        .with_cfg(dioxus::web::Config::new().history(Rc::new(HashHistory::new(false))))\n        .launch(|| {\n            rsx! {\n                Router::<Route> {}\n            }\n        })\n}\n\n#[derive(Routable, Clone, PartialEq)]\n#[rustfmt::skip]\nenum Route {\n    #[redirect(\"/\",|| Route::Other)]\n    #[route(\"/other\")]\n    Other,\n    #[route(\"/other/:id\")]\n    OtherId { id: String },\n    #[route(\"/:..segments\")]\n    NotFound { segments: Vec<String> },\n}\n\n#[component]\nfn Other() -> Element {\n    rsx! {\n        div {\n            id: \"other\",\n            \"Other\"\n        }\n\n        Link {\n            id: \"other-id-link\",\n            to: Route::OtherId { id: \"123\".to_string() },\n            \"go to OtherId\"\n        }\n    }\n}\n\n#[component]\nfn OtherId(id: String) -> Element {\n    rsx! {\n        div {\n            id: \"other-id\",\n            \"OtherId {id}\"\n        }\n    }\n}\n\n#[component]\nfn NotFound(segments: Vec<String>) -> Element {\n    rsx! {\n        div {\n            id: \"not-found\",\n            \"NotFound {segments:?}\"\n        }\n    }\n}\n"
  },
  {
    "path": "packages/playwright-tests/web-hash-routing.spec.js",
    "content": "// @ts-check\nconst { test, expect } = require(\"@playwright/test\");\n\ntest(\"redirect\", async ({ page }) => {\n  // Going to the root url should redirect to /other.\n  await page.goto(\"http://localhost:2021\");\n\n  // Expect the page to the text Other\n  const main = page.locator(\"#other\");\n  await expect(main).toContainText(\"Other\");\n\n  // Expect the url to be /#/other\n  await expect(page).toHaveURL(\"http://localhost:2021/#/other\");\n});\n\ntest(\"links\", async ({ page }) => {\n  await page.goto(\"http://localhost:2021/#/other\");\n\n  // Expect clicking the link to /other/123 to navigate to /other/123\n  const link = page.locator(\"a[href='/#/other/123']\");\n  await link.click();\n  await expect(page).toHaveURL(\"http://localhost:2021/#/other/123\");\n});\n\ntest(\"fallback\", async ({ page }) => {\n  await page.goto(\"http://localhost:2021/#/my/404/route\");\n\n  // Expect the page to contain the text Fallback\n  const main = page.locator(\"#not-found\");\n  await expect(main).toContainText(\"NotFound\");\n});\n"
  },
  {
    "path": "packages/playwright-tests/web-hot-patch/.gitignore",
    "content": "dist\ntarget\n"
  },
  {
    "path": "packages/playwright-tests/web-hot-patch/Cargo.toml",
    "content": "[package]\nname = \"dioxus-playwright-web-patch-test\"\nversion = \"0.0.1\"\nedition = \"2021\"\ndescription = \"Playwright test for Dioxus Web\"\nlicense = \"MIT OR Apache-2.0\"\npublish = false\n\n[dependencies]\ndioxus = { path = \"../../dioxus\", features = [\"web\"] }\nwasm-bindgen = { version = \"0.2.100\" }\nweb-sys = { version = \"*\", features = [\"MouseEvent\"] }\n\n[workspace]\n"
  },
  {
    "path": "packages/playwright-tests/web-hot-patch/assets/alternative-style.css",
    "content": "body {\n  background-color: rgb(100, 100, 100);\n}\n"
  },
  {
    "path": "packages/playwright-tests/web-hot-patch/assets/style.css",
    "content": "#main {\n  background-color: red;\n}\n"
  },
  {
    "path": "packages/playwright-tests/web-hot-patch/src/main.rs",
    "content": "use dioxus::prelude::*;\n\nconst CSS: Asset = asset!(\"/assets/style.css\");\nconst IMAGE: Asset = asset!(\"/assets/toasts.png\");\n\nfn app() -> Element {\n    let mut num = use_signal(|| 0);\n\n    // make sure to emit funky closure code in this module to test wasm-bindgen handling\n    let _closures = wasm_bindgen::closure::Closure::<dyn FnMut(web_sys::MouseEvent)>::new(\n        move |event: web_sys::MouseEvent| {},\n    );\n\n    rsx! {\n        document::Link {\n            href: CSS,\n            rel: \"stylesheet\",\n        }\n        img {\n            id: \"toasts\",\n            src: IMAGE,\n        }\n        button {\n            id: \"increment-button\",\n            onclick: move |_| {\n                num += 1;\n            },\n            \"Click me! Count: {num}\"\n        }\n    }\n}\n\nfn main() {\n    dioxus::launch(app);\n}\n"
  },
  {
    "path": "packages/playwright-tests/web-hot-patch-fullstack/.gitignore",
    "content": "dist\ntarget\n"
  },
  {
    "path": "packages/playwright-tests/web-hot-patch-fullstack/Cargo.toml",
    "content": "[package]\nname = \"dioxus-playwright-fullstack-patch-test\"\nversion = \"0.0.1\"\nedition = \"2021\"\ndescription = \"Playwright test for Dioxus Web\"\nlicense = \"MIT OR Apache-2.0\"\npublish = false\n\n[dependencies]\ndioxus = { path = \"../../dioxus\", features = [\"fullstack\"] }\n\n[features]\nweb = [\"dioxus/web\"]\nserver = [\"dioxus/server\"]\n\n[workspace]\n"
  },
  {
    "path": "packages/playwright-tests/web-hot-patch-fullstack/assets/alternative-style.css",
    "content": "body {\n  background-color: rgb(100, 100, 100);\n}\n"
  },
  {
    "path": "packages/playwright-tests/web-hot-patch-fullstack/assets/style.css",
    "content": "#main {\n  background-color: red;\n}\n"
  },
  {
    "path": "packages/playwright-tests/web-hot-patch-fullstack/src/main.rs",
    "content": "use dioxus::prelude::*;\n\nconst CSS: Asset = asset!(\"/assets/style.css\");\nconst IMAGE: Asset = asset!(\"/assets/toasts.png\");\n\nfn app() -> Element {\n    let mut num = use_signal(|| 0);\n\n    rsx! {\n        document::Link {\n            href: CSS,\n            rel: \"stylesheet\",\n        }\n        img {\n            id: \"toasts\",\n            src: IMAGE,\n        }\n        button {\n            id: \"increment-button\",\n            onclick: move |_| async move {\n                let increment_amount = get_count().await.unwrap();\n                *num.write() += increment_amount;\n            },\n            \"Click me! Count: {num}\"\n        }\n    }\n}\n\n#[post(\"/api/get_count\")]\nasync fn get_count() -> Result<i32> {\n    Ok(1)\n}\n\nfn main() {\n    dioxus::launch(app);\n}\n"
  },
  {
    "path": "packages/playwright-tests/web-patch-fullstack.spec.js",
    "content": "// @ts-check\nconst { test, expect } = require(\"@playwright/test\");\n\nconst hotPatchTimeout = {\n  timeout: 1000 * 60 * 2, // 2 minute\n};\n\ntest(\"button click\", async ({ page }) => {\n  const fs = require(\"fs\");\n  const mainPath = \"web-hot-patch-fullstack-temp/src/main.rs\";\n  var mainContent = fs.readFileSync(mainPath, \"utf8\");\n  const stylePath = \"web-hot-patch-fullstack-temp/assets/style.css\";\n  var styleContent = fs.readFileSync(stylePath, \"utf8\");\n\n  // Reset any changes made to the main.rs and style.css files.\n  mainContent = mainContent.replace(/Ok\\(\\s*2\\s*\\)/g, \"Ok(1)\");\n  mainContent = mainContent.replace(\"Click button! Count:\", \"Click me! Count:\");\n  mainContent = mainContent.replace(\"asset!('/assets/alternative-style.css')\", \"asset!('/assets/style.css')\");\n  fs.writeFileSync(mainPath, mainContent);\n  styleContent = styleContent.replace(\n    \"background-color: blue;\",\n    \"background-color: red;\"\n  );\n  fs.writeFileSync(stylePath, styleContent);\n\n  await page.goto(\"http://localhost:9981\");\n\n\n  // wait a sec for the serverfn to process\n  // give the page time to finish loading resources\n  await page.waitForLoadState(\"networkidle\");\n  await page.waitForTimeout(2000);\n\n  // ** First test make sure the initial fat build is working **\n  // Expect the page to contain the counter text.\n  const main = page.locator(\"#main\");\n  await expect(main).toContainText(\"Click me! Count: 0\");\n\n  // Click the increment button.\n  let button = page.locator(\"button#increment-button\");\n  await button.click();\n\n  await page.waitForTimeout(1000);\n\n  // Expect the page to contain the updated counter text.\n  await expect(main).toContainText(\"Click me! Count: 1\");\n\n  // Make sure the css is applied correctly.\n  await expect(main).toHaveCSS(\"background-color\", \"rgb(255, 0, 0)\");\n\n  // Make sure the image is loaded.\n  const headerImage = page.locator(\"#toasts\");\n  // expect the attribute src to start with /assets/toasts-\n  await expect(headerImage).toHaveAttribute(\"src\", /\\/assets\\/toasts-/);\n\n  // ** Then make sure the hot patch is working **\n  // Then change the file to increment by 2.\n  const updatedContent = mainContent.replace(/Ok\\(\\s*1\\s*\\)/g, \"Ok(2)\");\n  // Change the click me text to reflect the new increment.\n  const updatedContentWithText = updatedContent.replace(\n    \"Click me! Count:\",\n    \"Click button! Count:\"\n  );\n  fs.writeFileSync(mainPath, updatedContentWithText);\n\n  // Wait for the page to update and show the new text.\n  await expect(main).toContainText(\"Click button! Count: 1\", hotPatchTimeout);\n\n  // Now click the button again.\n  await button.click();\n\n  // wait a sec for the serverfn to process\n  await page.waitForTimeout(1000);\n\n  // Expect the count to update by 2.\n  await expect(main).toContainText(\"Click button! Count: 3\");\n\n  // Next change just the css file to change the background color to blue.\n  const updatedStyleContent = styleContent.replace(\n    \"background-color: red;\",\n    \"background-color: blue;\"\n  );\n  fs.writeFileSync(stylePath, updatedStyleContent);\n\n  // Wait for the page to update the background color.\n  await expect(main).toHaveCSS(\n    \"background-color\",\n    \"rgb(0, 0, 255)\",\n    hotPatchTimeout\n  );\n\n  // Make sure the image is still loaded.\n  // expect the attribute src to start with /assets/toasts-\n  await expect(headerImage).toHaveAttribute(\"src\", /\\/assets\\/toasts-/);\n\n  // ** Then add a new asset to the page **\n  // Switch from style to alternative-style\n  const updatedContentWithAlternativeStyle = updatedContentWithText.replace(\n    'asset!(\"/assets/style.css\")',\n    'asset!(\"/assets/alternative-style.css\")'\n  );\n  fs.writeFileSync(mainPath, updatedContentWithAlternativeStyle);\n\n  // Assert the page has the new alternative style applied.\n  const body = page.locator(\"body\");\n  // Log the page content to debug if needed.\n  console.log(await page.content());\n  await expect(body).toHaveCSS(\n    \"background-color\",\n    \"rgb(100, 100, 100)\",\n    hotPatchTimeout\n  );\n});\n"
  },
  {
    "path": "packages/playwright-tests/web-patch.spec.js",
    "content": "// @ts-check\nconst { test, expect } = require(\"@playwright/test\");\n\nconst hotPatchTimeout = {\n  timeout: 1000 * 60 * 2, // 2 minute\n};\n\ntest(\"button click\", async ({ page }) => {\n  const fs = require(\"fs\");\n  const mainPath = \"web-hot-patch-temp/src/main.rs\";\n  var mainContent = fs.readFileSync(mainPath, \"utf8\");\n  const stylePath = \"web-hot-patch-temp/assets/style.css\";\n  var styleContent = fs.readFileSync(stylePath, \"utf8\");\n\n  // Reset any changes made to the main.rs and style.css files.\n  mainContent = mainContent.replace(/num \\+= 2;/g, \"num += 1;\");\n  mainContent = mainContent.replace(\"Click button! Count:\", \"Click me! Count:\");\n  mainContent = mainContent.replace(\"asset!('/assets/alternative-style.css')\", \"asset!('/assets/style.css')\");\n  fs.writeFileSync(mainPath, mainContent);\n  styleContent = styleContent.replace(\n    \"background-color: blue;\",\n    \"background-color: red;\"\n  );\n  fs.writeFileSync(stylePath, styleContent);\n\n  await page.goto(\"http://localhost:9980\");\n\n  // ** First test make sure the initial fat build is working **\n  // Expect the page to contain the counter text.\n  const main = page.locator(\"#main\");\n  await expect(main).toContainText(\"Click me! Count: 0\");\n\n  // Click the increment button.\n  let button = page.locator(\"button#increment-button\");\n  await button.click();\n\n  // Expect the page to contain the updated counter text.\n  await expect(main).toContainText(\"Click me! Count: 1\");\n\n  // Make sure the css is applied correctly.\n  await expect(main).toHaveCSS(\"background-color\", \"rgb(255, 0, 0)\");\n\n  // Make sure the image is loaded.\n  const headerImage = page.locator(\"#toasts\");\n  // expect the attribute src to start with /assets/toasts-\n  await expect(headerImage).toHaveAttribute(\"src\", /\\/assets\\/toasts-/);\n\n  // ** Then make sure the hot patch is working **\n  // Then change the file to increment by 2.\n  const updatedContent = mainContent.replace(/num \\+= 1;/g, \"num += 2;\");\n  // Change the click me text to reflect the new increment.\n  const updatedContentWithText = updatedContent.replace(\n    \"Click me! Count:\",\n    \"Click button! Count:\"\n  );\n  fs.writeFileSync(mainPath, updatedContentWithText);\n\n  // Wait for the page to update and show the new text.\n  await expect(main).toContainText(\"Click button! Count: 1\", hotPatchTimeout);\n\n  // Now click the button again.\n  await button.click();\n\n  // Expect the count to update by 2.\n  await expect(main).toContainText(\"Click button! Count: 3\");\n\n  // Next change just the css file to change the background color to blue.\n  const updatedStyleContent = styleContent.replace(\n    \"background-color: red;\",\n    \"background-color: blue;\"\n  );\n  fs.writeFileSync(stylePath, updatedStyleContent);\n\n  // Wait for the page to update the background color.\n  await expect(main).toHaveCSS(\n    \"background-color\",\n    \"rgb(0, 0, 255)\",\n    hotPatchTimeout\n  );\n\n  // Make sure the image is still loaded.\n  // expect the attribute src to start with /assets/toasts-\n  await expect(headerImage).toHaveAttribute(\"src\", /\\/assets\\/toasts-/);\n\n  // ** Then add a new asset to the page **\n  // Switch from style to alternative-style\n  const updatedContentWithAlternativeStyle = updatedContentWithText.replace(\n    'asset!(\"/assets/style.css\")',\n    'asset!(\"/assets/alternative-style.css\")'\n  );\n  fs.writeFileSync(mainPath, updatedContentWithAlternativeStyle);\n\n  // Assert the page has the new alternative style applied.\n  const body = page.locator(\"body\");\n  // Log the page content to debug if needed.\n  console.log(await page.content());\n  await expect(body).toHaveCSS(\n    \"background-color\",\n    \"rgb(100, 100, 100)\",\n    hotPatchTimeout\n  );\n});\n"
  },
  {
    "path": "packages/playwright-tests/web-routing/.gitignore",
    "content": "dist\ntarget\n"
  },
  {
    "path": "packages/playwright-tests/web-routing/Cargo.toml",
    "content": "[package]\nname = \"dioxus-playwright-web-routing-test\"\nversion = \"0.0.1\"\nedition = \"2021\"\ndescription = \"Playwright test for Dioxus Web Routing\"\nlicense = \"MIT OR Apache-2.0\"\npublish = false\n\n[dependencies]\ndioxus = { workspace = true, features = [\"web\", \"router\"]}\n"
  },
  {
    "path": "packages/playwright-tests/web-routing/src/main.rs",
    "content": "use dioxus::prelude::*;\n\nfn main() {\n    launch(|| {\n        rsx! {\n            Router::<Route> {}\n        }\n    })\n}\n\n#[derive(Routable, Clone, PartialEq)]\n#[rustfmt::skip]\nenum Route {\n    #[redirect(\"/\",|| Route::Other)]\n    #[route(\"/other\")]\n    Other,\n    #[route(\"/other/:id\")]\n    OtherId { id: String },\n    #[route(\"/:..segments\")]\n    NotFound { segments: Vec<String> },\n}\n\n#[component]\nfn Other() -> Element {\n    rsx! {\n        div {\n            id: \"other\",\n            \"Other\"\n        }\n\n        Link {\n            id: \"other-id-link\",\n            to: Route::OtherId { id: \"123\".to_string() },\n            \"go to OtherId\"\n        }\n    }\n}\n\n#[component]\nfn OtherId(id: String) -> Element {\n    rsx! {\n        div {\n            id: \"other-id\",\n            \"OtherId {id}\"\n        }\n    }\n}\n\n#[component]\nfn NotFound(segments: Vec<String>) -> Element {\n    rsx! {\n        div {\n            id: \"not-found\",\n            \"NotFound {segments:?}\"\n        }\n    }\n}\n"
  },
  {
    "path": "packages/playwright-tests/web-routing.spec.js",
    "content": "// @ts-check\nconst { test, expect } = require(\"@playwright/test\");\n\ntest(\"redirect\", async ({ page }) => {\n  // Going to the root url should redirect to /other.\n  await page.goto(\"http://localhost:2020\");\n\n  // Expect the page to the text Other\n  const main = page.locator(\"#other\");\n  await expect(main).toContainText(\"Other\");\n\n  // Expect the url to be /other\n  await expect(page).toHaveURL(\"http://localhost:2020/other\");\n});\n\ntest(\"links\", async ({ page }) => {\n  await page.goto(\"http://localhost:2020/other\");\n\n  // Expect clicking the link to /other/123 to navigate to /other/123\n  const link = page.locator(\"a[href='/other/123']\");\n  await link.click();\n  await expect(page).toHaveURL(\"http://localhost:2020/other/123\");\n});\n\ntest(\"fallback\", async ({ page }) => {\n  await page.goto(\"http://localhost:2020/my/404/route\");\n\n  // Expect the page to contain the text Fallback\n  const main = page.locator(\"#not-found\");\n  await expect(main).toContainText(\"NotFound\");\n\n});\n"
  },
  {
    "path": "packages/playwright-tests/web.spec.js",
    "content": "// @ts-check\nconst { test, expect, defineConfig } = require(\"@playwright/test\");\n\ntest(\"button click\", async ({ page }) => {\n  await page.goto(\"http://localhost:9990\");\n\n  // Expect the page to contain the counter text.\n  const main = page.locator(\"#main\");\n  await expect(main).toContainText(\"hello axum! 0\");\n  // Expect the title to contain the counter text.\n  await expect(page).toHaveTitle(\"hello axum! 0\");\n\n  // Click the increment button.\n  let button = page.locator(\"button.increment-button\");\n  await button.click();\n\n  // Expect the page to contain the updated counter text.\n  await expect(main).toContainText(\"hello axum! 1\");\n  // Expect the title to contain the updated counter text.\n  await expect(page).toHaveTitle(\"hello axum! 1\");\n});\n\ntest(\"svg\", async ({ page }) => {\n  await page.goto(\"http://localhost:9990\");\n\n  // Expect the page to contain the svg.\n  const svg = page.locator(\"svg\");\n\n  // Expect the svg to contain the circle.\n  const circle = svg.locator(\"circle\");\n  await expect(circle).toHaveAttribute(\"cx\", \"50\");\n  await expect(circle).toHaveAttribute(\"cy\", \"50\");\n  await expect(circle).toHaveAttribute(\"r\", \"40\");\n  await expect(circle).toHaveAttribute(\"stroke\", \"green\");\n  await expect(circle).toHaveAttribute(\"fill\", \"yellow\");\n});\n\ntest(\"raw attribute\", async ({ page }) => {\n  await page.goto(\"http://localhost:9990\");\n\n  // Expect the page to contain the div with the raw attribute.\n  const div = page.locator(\"div.raw-attribute-div\");\n  await expect(div).toHaveAttribute(\"raw-attribute\", \"raw-attribute-value\");\n});\n\ntest(\"hidden attribute\", async ({ page }) => {\n  await page.goto(\"http://localhost:9990\");\n\n  // Expect the page to contain the div with the hidden attribute.\n  const div = page.locator(\"div.hidden-attribute-div\");\n  await expect(div).toHaveAttribute(\"hidden\", \"true\");\n});\n\ntest(\"dangerous inner html\", async ({ page }) => {\n  await page.goto(\"http://localhost:9990\");\n\n  // Expect the page to contain the div with the dangerous inner html.\n  const div = page.locator(\"div.dangerous-inner-html-div\");\n  await expect(div).toContainText(\"hello dangerous inner html\");\n});\n\ntest(\"input value\", async ({ page }) => {\n  await page.goto(\"http://localhost:9990\");\n\n  // Expect the page to contain the input with the value.\n  const input = page.locator(\"input\");\n  await expect(input).toHaveValue(\"hello input\");\n});\n\ntest(\"style\", async ({ page }) => {\n  await page.goto(\"http://localhost:9990\");\n\n  // Expect the page to contain the div with the style.\n  const div = page.locator(\"div.style-div\");\n  await expect(div).toHaveText(\"colored text\");\n  await expect(div).toHaveCSS(\"color\", \"rgb(255, 0, 0)\");\n});\n\ntest(\"eval\", async ({ page }) => {\n  await page.goto(\"http://localhost:9990\");\n\n  // Expect the page to contain the div with the eval and have no text.\n  const div = page.locator(\"div.eval-result\");\n  await expect(div).toHaveText(\"\");\n\n  // Click the button to run the eval.\n  let button = page.locator(\"button.eval-button\");\n  await button.click();\n\n  // Check that the title changed.\n  await expect(page).toHaveTitle(\"Hello from Dioxus Eval!\");\n\n  // Check that the div has the eval value.\n  await expect(div).toHaveText(\"returned eval value\");\n});\n\ntest(\"prevent default\", async ({ page }) => {\n  await page.goto(\"http://localhost:9990\");\n\n  // Expect the page to contain the div with the eval and have no text.\n  const a = page.locator(\"a.prevent-default\");\n  await expect(a).toHaveText(\"View source\");\n\n  // Click the <a> element to change the text\n  await a.click();\n\n  // Check that the <a> element changed.\n  await expect(a).toHaveText(\"Psych!\");\n});\n\ntest(\"onmounted\", async ({ page }) => {\n  await page.goto(\"http://localhost:9990\");\n\n  // Expect the onmounted event to be called exactly once.\n  const mountedDiv = page.locator(\"div.onmounted-div\");\n  await expect(mountedDiv).toHaveText(\"onmounted was called 1 times\");\n});\n\ntest(\"web-sys closure\", async ({ page }) => {\n  await page.goto(\"http://localhost:9990\");\n  // wait until the div is mounted\n  const scrollDiv = page.locator(\"div#web-sys-closure-div\");\n  await scrollDiv.waitFor({ state: \"attached\" });\n  await page.keyboard.press(\"Enter\");\n  await expect(scrollDiv).toHaveText(\"the keydown event was triggered\");\n});\n\ntest(\"document elements\", async ({ page }) => {\n  await page.goto(\"http://localhost:9990\");\n  // wait until the meta element is mounted\n  const meta = page.locator(\"meta#meta-head[name='testing']\");\n  await meta.waitFor({ state: \"attached\" });\n  await expect(meta).toHaveAttribute(\"data\", \"dioxus-meta-element\");\n\n  const link = page.locator(\"link#link-head[rel='stylesheet']\");\n  await link.waitFor({ state: \"attached\" });\n  await expect(link).toHaveAttribute(\n    \"href\",\n    \"https://fonts.googleapis.com/css?family=Roboto+Mono\"\n  );\n\n  const stylesheet = page.locator(\"link#stylesheet-head[rel='stylesheet']\");\n  await stylesheet.waitFor({ state: \"attached\" });\n  await expect(stylesheet).toHaveAttribute(\n    \"href\",\n    \"https://fonts.googleapis.com/css?family=Roboto:300,300italic,700,700italic\"\n  );\n\n  const script = page.locator(\"script#script-head\");\n  await script.waitFor({ state: \"attached\" });\n  await expect(script).toHaveAttribute(\"async\", \"true\");\n\n  const style = page.locator(\"style#style-head\");\n  await style.waitFor({ state: \"attached\" });\n  const main = page.locator(\"#main\");\n  await expect(main).toHaveCSS(\"font-family\", \"Roboto\");\n});\n\ntest(\"link preload and stylesheet with same href are not deduplicated\", async ({ page }) => {\n  await page.goto(\"http://localhost:9990\");\n\n  // Both links should exist (different rel = not deduplicated)\n  await expect(page.locator(\"link#dedup-preload[rel='preload']\")).toHaveCount(1);\n  await expect(page.locator(\"link#dedup-stylesheet[rel='stylesheet']\")).toHaveCount(1);\n});\n\ntest(\"links with same href and rel are deduplicated\", async ({ page }) => {\n  await page.goto(\"http://localhost:9990\");\n\n  // Only first link should exist (same rel = deduplicated)\n  await expect(page.locator(\"link#dedup-first\")).toHaveCount(1);\n  await expect(page.locator(\"link#dedup-second\")).toHaveCount(0);\n});\n\ntest(\"merge styles\", async ({ page }) => {\n  await page.goto(\"http://localhost:9990\");\n  // wait until the div is mounted\n  const div = page.locator(\"div#merge-styles-div\");\n  await div.waitFor({ state: \"attached\" });\n  await expect(div).toHaveCSS(\"background-color\", \"rgb(255, 0, 0)\");\n  await expect(div).toHaveCSS(\"width\", \"100px\");\n  await expect(div).toHaveCSS(\"height\", \"100px\");\n});\n\ntest(\"select multiple\", async ({ page }) => {\n  await page.goto(\"http://localhost:9990\");\n  // wait until the select element is mounted\n  const staticSelect = page.locator(\"select#static-multiple-select\");\n  await staticSelect.waitFor({ state: \"attached\" });\n  await expect(staticSelect).toHaveValues([]);\n  // Make sure the multiple attribute is actually set\n  await staticSelect.selectOption([\"1\", \"2\"]);\n  await expect(staticSelect).toHaveValues([\"1\", \"2\"]);\n\n  // The dynamic select element should act exactly the same\n  const dynamicSelect = page.locator(\"select#dynamic-multiple-select\");\n  await dynamicSelect.waitFor({ state: \"attached\" });\n  await expect(dynamicSelect).toHaveValues([]);\n  await dynamicSelect.selectOption([\"1\", \"2\"]);\n  await expect(dynamicSelect).toHaveValues([\"1\", \"2\"]);\n});\n"
  },
  {
    "path": "packages/playwright-tests/windows-headless/Cargo.toml",
    "content": "[package]\nname = \"windows-headless\"\nedition = \"2024\"\nversion.workspace = true\npublish = false\n\n[dependencies]\ndioxus = { workspace = true, features = [\"desktop\"] }\n"
  },
  {
    "path": "packages/playwright-tests/windows-headless/src/main.rs",
    "content": "use dioxus::LaunchBuilder;\nuse dioxus::prelude::*;\n\nfn main() {\n    LaunchBuilder::new()\n        .with_cfg(\n            dioxus::desktop::Config::new()\n                .with_windows_browser_args(\"--remote-debugging-port=8787\"),\n        )\n        .launch(app);\n}\n\nfn app() -> Element {\n    let mut count = use_signal(|| 0);\n\n    rsx! {\n        h1 { \"High-five counter: {count}\" }\n        button {\n            id: \"increment-button\",\n            onclick: move |_| count += 1, \"Up high!\"\n        }\n    }\n}\n"
  },
  {
    "path": "packages/playwright-tests/windows.spec.js",
    "content": "const { expect, defineConfig } = require(\"@playwright/test\");\nimport { test as base } from '@playwright/test';\nimport fs from 'fs';\nimport os from 'os';\nimport path from 'path';\nimport childProcess from 'child_process';\n\n\nexport const test = base.extend({\n  browser: async ({ playwright }, use, testInfo) => {\n    const browser = await playwright.chromium.connectOverCDP(`http://127.0.0.1:8787`);\n    await use(browser);\n    await browser.close();\n  },\n  context: async ({ browser }, use) => {\n    const context = browser.contexts()[0];\n    await use(context);\n  },\n  page: async ({ context }, use) => {\n    const page = context.pages()[0];\n    await use(page);\n  },\n});\n\ntest(\"button click\", async ({ page }) => {\n  // Expect the page to contain the counter text.\n  const main = page.locator(\"#main\");\n  await expect(main).toContainText(\"High-five counter: 0\");\n\n  // Click the increment button.\n  let button = page.locator(\"button#increment-button\");\n  await button.click();\n\n  // Expect the page to contain the updated counter text.\n  await expect(main).toContainText(\"High-five counter: 1\");\n});\n\n"
  },
  {
    "path": "packages/router/.gitignore",
    "content": "dist/\nstatic"
  },
  {
    "path": "packages/router/Cargo.toml",
    "content": "[package]\nname = \"dioxus-router\"\nauthors = [\"Jonathan Kelley\", \"Evan Almloff\"]\nversion = { workspace = true }\nedition = \"2021\"\ndescription = \"Cross-platform router for Dioxus apps\"\nlicense = \"MIT OR Apache-2.0\"\nrepository = \"https://github.com/DioxusLabs/dioxus/\"\nhomepage = \"https://dioxuslabs.com\"\nkeywords = [\"dom\", \"ui\", \"gui\", \"react\", \"wasm\"]\n\n[dependencies]\ndioxus-core = { workspace = true }\ndioxus-core-macro = { workspace = true }\ndioxus-signals = { workspace = true }\ndioxus-hooks = { workspace = true }\ndioxus-html = { workspace = true, optional = true }\ndioxus-history = { workspace = true }\ndioxus-router-macro = { workspace = true }\ndioxus-fullstack-core = { workspace = true, optional = true }\ntracing = { workspace = true }\npercent-encoding = { workspace = true }\nurl = { workspace = true }\ndioxus-cli-config = { workspace = true }\nrustversion = { workspace = true }\n\n[features]\ndefault = [\"html\"]\nstreaming = [\"dep:dioxus-fullstack-core\"]\nwasm-split = []\nhtml = [\"dep:dioxus-html\"]\n\n[dev-dependencies]\naxum = { workspace = true, features = [\"ws\"] }\ndioxus = { workspace = true, features = [\"router\"] }\ndioxus-ssr = { workspace = true }\ncriterion = { workspace = true, features = [\"async_tokio\", \"html_reports\"] }\nciborium = { workspace = true }\nbase64 = { workspace = true }\nserde = { workspace = true, features = [\"derive\"] }\ntokio = { workspace = true, features = [\"full\"] }\ndioxus-router = { workspace = true }\n\n[package.metadata.docs.rs]\ncargo-args = [\"-Zunstable-options\", \"-Zrustdoc-scrape-examples\"]\n\n# Most of the examples live in the workspace. We include some here so that docs.rs can scrape our examples for better inline docs\n[[example]]\nname = \"hash_fragment_state\"\npath = \"../../examples/06-routing/hash_fragment_state.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"query_segment_search\"\npath = \"../../examples/06-routing/query_segment_search.rs\"\ndoc-scrape-examples = true\n\n[[example]]\nname = \"simple_router\"\npath = \"../../examples/06-routing/simple_router.rs\"\ndoc-scrape-examples = true\n"
  },
  {
    "path": "packages/router/README.md",
    "content": "# Dioxus Router\n\n[![Crates.io][crates-badge]][crates-url]\n[![MIT licensed][mit-badge]][mit-url]\n[![Build Status][actions-badge]][actions-url]\n[![Discord chat][discord-badge]][discord-url]\n\n[crates-badge]: https://img.shields.io/crates/v/dioxus-router.svg\n[crates-url]: https://crates.io/crates/dioxus-router\n[mit-badge]: https://img.shields.io/badge/license-MIT-blue.svg\n[mit-url]: https://github.com/dioxuslabs/dioxus/blob/main/LICENSE-MIT\n[actions-badge]: https://github.com/dioxuslabs/dioxus/actions/workflows/main.yml/badge.svg\n[actions-url]: https://github.com/dioxuslabs/dioxus/actions?query=workflow%3ACI+branch%3Amaster\n[discord-badge]: https://img.shields.io/discord/899851952891002890.svg?logo=discord&style=flat-square\n[discord-url]: https://discord.gg/XgGxMSkvUM\n\n[Website](https://dioxuslabs.com) |\n[Guides](https://dioxuslabs.com/learn/0.7/essentials/router/) |\n[API Docs](https://docs.rs/dioxus-router/latest/dioxus_router) |\n[Chat](https://discord.gg/XgGxMSkvUM)\n\n## Overview\n\nDioxus Router is a first-party Router for all your Dioxus Apps. It provides an\ninterface similar to React Router, but takes advantage of types for more\nexpressiveness.\n\n```rust, no_run\nuse dioxus::prelude::*;\nuse std::str::FromStr;\n\n#[rustfmt::skip]\n#[derive(Clone, Debug, PartialEq, Routable)]\nenum Route {\n    #[nest(\"/blog\")]\n        #[layout(Blog)]\n            #[route(\"/\")]\n            BlogList {},\n\n            #[route(\"/:blog_id\")]\n            BlogPost { blog_id: usize },\n        #[end_layout]\n    #[end_nest]\n    #[route(\"/\")]\n    Index {},\n}\n\n#[component]\nfn App() -> Element {\n    rsx! {\n        Router::<Route> { }\n    }\n}\n\n#[component]\nfn Index() -> Element {\n    rsx! {\n        h1 { \"Index\" }\n        Link {\n            to: Route::BlogList {},\n            \"Go to the blog\"\n        }\n    }\n}\n\n#[component]\nfn Blog() -> Element {\n    rsx! {\n        h1 { \"Blog\" }\n        Outlet::<Route> { }\n    }\n}\n\n#[component]\nfn BlogList() -> Element {\n    rsx! {\n        h2 { \"List of blog posts\" }\n        Link {\n            to: Route::BlogPost { blog_id: 0 },\n            \"Blog post 1\"\n        }\n        Link {\n            to: Route::BlogPost { blog_id: 1 },\n            \"Blog post 2\"\n        }\n    }\n}\n\n#[component]\nfn BlogPost(blog_id: usize) -> Element {\n    rsx! {\n        h2 { \"Blog Post\" }\n    }\n}\n```\n\nYou need to enable the right features for the platform you're targeting since these are not determined automatically!\n\n## Bundle Splitting\n\nThe Dioxus Router supports automatic bundle splitting along route variants. To enable this, you need to manually turn on the `wasm-split` feature explicitly on the dioxus-router crate:\n\n```toml\n[dependencies]\ndioxus = { version = \"*\", features = [\"router\", \"wasm-split\"] }\ndioxus-router = { version = \"*\", features = [\"wasm-split\"] }\n```\n\nNote that `wasm-split` must also be turned on in dioxus since the macro uses the re-exported `wasm-split` from the dioxus prelude.\n\nEnabling splitting disconnects the call graph, meaning if you try to run your app with a normal `dx serve`, it won't work. When running with router splitting, you need to pass `--experimental-wasm-split`.\n\n```sh\ndx serve --experimental-wasm-split\n```\n\nIn practice, we recommend passing `dioxus-router?/wasm-split` as a feature only when bundling:\n\n```sh\ndx bundle --features \"dioxus-router?/wasm-split\"  --experimental-wasm-split\n```\n\nNote that the router will call `.suspend()` so you should add a SuspenseBoundary above the Outlet to prevent suspending the entire page.\n\n## Contributing\n\n- Report issues on our [issue tracker](https://github.com/dioxuslabs/dioxus/issues).\n- Join the discord and ask questions!\n\n## License\n\nThis project is licensed under the [MIT license].\n\n[mit license]: https://github.com/dioxuslabs/dioxus/blob/main/LICENSE-MIT\n\nUnless you explicitly state otherwise, any contribution intentionally submitted\nfor inclusion in Dioxus by you shall be licensed as MIT without any additional\nterms or conditions.\n"
  },
  {
    "path": "packages/router/src/components/child_router.rs",
    "content": "/// Components that allow the macro to add child routers. This component provides a context\n/// to the child router that maps child routes to root routes and vice versa.\nuse crate::{Outlet, OutletContext, Routable};\nuse dioxus_core::{provide_context, try_consume_context, use_hook, Element};\nuse dioxus_core_macro::{component, rsx, Props};\n\n/// Maps a child route into the root router and vice versa\n// NOTE: Currently child routers only support simple static prefixes, but this\n// API could be expanded to support dynamic prefixes as well\npub(crate) struct ChildRouteMapping<R> {\n    format_route_as_root_route: fn(R) -> String,\n    parse_route_from_root_route: fn(&str) -> Option<R>,\n}\n\nimpl<R: Routable> ChildRouteMapping<R> {\n    pub(crate) fn format_route_as_root_route(&self, route: R) -> String {\n        (self.format_route_as_root_route)(route)\n    }\n\n    pub(crate) fn parse_route_from_root_route(&self, route: &str) -> Option<R> {\n        (self.parse_route_from_root_route)(route)\n    }\n}\n\n/// Get the formatter that handles adding and stripping the prefix from a child route\npub(crate) fn consume_child_route_mapping<R: Routable>() -> Option<ChildRouteMapping<R>> {\n    try_consume_context()\n}\n\nimpl<R> Clone for ChildRouteMapping<R> {\n    fn clone(&self) -> Self {\n        Self {\n            format_route_as_root_route: self.format_route_as_root_route,\n            parse_route_from_root_route: self.parse_route_from_root_route,\n        }\n    }\n}\n\n/// Props for the [`ChildRouter`] component.\n#[derive(Props, Clone)]\npub struct ChildRouterProps<R: Routable> {\n    /// The child route to render\n    route: R,\n    /// Take a parent route and return a child route or none if the route is not part of the child\n    parse_route_from_root_route: fn(&str) -> Option<R>,\n    /// Take a child route and return a parent route\n    format_route_as_root_route: fn(R) -> String,\n}\n\nimpl<R: Routable> PartialEq for ChildRouterProps<R> {\n    fn eq(&self, _: &Self) -> bool {\n        false\n    }\n}\n\n/// A component that provides a [`History`](dioxus_history::History) to a child router. The `#[child]` attribute on the router macro will insert this automatically.\n#[component]\n#[allow(missing_docs)]\npub fn ChildRouter<R: Routable>(props: ChildRouterProps<R>) -> Element {\n    use_hook(|| {\n        provide_context(ChildRouteMapping {\n            format_route_as_root_route: props.format_route_as_root_route,\n            parse_route_from_root_route: props.parse_route_from_root_route,\n        });\n        provide_context(OutletContext::<R>::new());\n    });\n\n    rsx! { Outlet::<R> {} }\n}\n"
  },
  {
    "path": "packages/router/src/components/default_errors.rs",
    "content": "use dioxus_core::Element;\nuse dioxus_core_macro::rsx;\nuse dioxus_html as dioxus_elements;\n\n#[allow(deprecated)]\nuse crate::hooks::use_router;\n\n/// The default component to render when an external navigation fails.\n#[allow(non_snake_case)]\npub fn FailureExternalNavigation() -> Element {\n    #[allow(deprecated)]\n    let router = use_router();\n\n    rsx! {\n        h1 { \"External Navigation Failure!\" }\n        p {\n            \"The application tried to programmatically navigate to an external page. This \"\n            \"operation has failed. Click the link below to complete the navigation manually.\"\n        }\n        a { onclick: move |_| { router.clear_error() }, \"Click here to go back\" }\n    }\n}\n"
  },
  {
    "path": "packages/router/src/components/history_buttons.rs",
    "content": "use dioxus_core::{Element, VNode};\nuse dioxus_core_macro::{rsx, Props};\nuse dioxus_html as dioxus_elements;\n\nuse tracing::error;\n\nuse crate::utils::use_router_internal::use_router_internal;\n\n/// The properties for a [`GoBackButton`] or a [`GoForwardButton`].\n#[derive(Debug, Props, Clone, PartialEq)]\npub struct HistoryButtonProps {\n    /// The children to render within the generated HTML button tag.\n    pub children: Element,\n}\n\n/// A button to go back through the navigation history. Similar to a browsers back button.\n///\n/// Only works as descendant of a [`super::Link`] component, otherwise it will be inactive.\n///\n/// The button will disable itself if it is known that no prior history is available.\n///\n/// # Panic\n/// - When the [`GoBackButton`] is not nested within a [`super::Link`] component\n///   hook, but only in debug builds.\n///\n/// # Example\n/// ```rust\n/// # use dioxus::prelude::*;\n/// #[derive(Clone, Routable)]\n/// enum Route {\n///     #[route(\"/\")]\n///     Index {},\n/// }\n///\n/// #[component]\n/// fn App() -> Element {\n///     rsx! {\n///         Router::<Route> {}\n///     }\n/// }\n///\n/// #[component]\n/// fn Index() -> Element {\n///     rsx! {\n///         GoBackButton {\n///             \"go back\"\n///         }\n///     }\n/// }\n/// #\n/// # let mut vdom = VirtualDom::new(App);\n/// # vdom.rebuild_in_place();\n/// # assert_eq!(\n/// #     dioxus_ssr::render(&vdom),\n/// #     r#\"<button disabled=\"true\">go back</button>\"#\n/// # );\n/// ```\npub fn GoBackButton(props: HistoryButtonProps) -> Element {\n    let HistoryButtonProps { children } = props;\n\n    // hook up to router\n    let router = match use_router_internal() {\n        Some(r) => r,\n        #[allow(unreachable_code)]\n        None => {\n            let msg = \"`GoBackButton` must have access to a parent router\";\n            error!(\"{msg}, will be inactive\");\n            #[cfg(debug_assertions)]\n            panic!(\"{}\", msg);\n            return VNode::empty();\n        }\n    };\n\n    let disabled = !router.can_go_back();\n\n    rsx! {\n        button {\n            disabled: \"{disabled}\",\n            onclick: move |evt| {\n                evt.prevent_default();\n                router.go_back()\n            },\n            {children}\n        }\n    }\n}\n\n/// A button to go forward through the navigation history. Similar to a browsers forward button.\n///\n/// Only works as descendant of a [`super::Link`] component, otherwise it will be inactive.\n///\n/// The button will disable itself if it is known that no later history is available.\n///\n/// # Panic\n/// - When the [`GoForwardButton`] is not nested within a [`super::Link`] component\n///   hook, but only in debug builds.\n///\n/// # Example\n/// ```rust\n/// # use dioxus::prelude::*;\n/// #[derive(Clone, Routable)]\n/// enum Route {\n///     #[route(\"/\")]\n///     Index {},\n/// }\n///\n/// #[component]\n/// fn App() -> Element {\n///     rsx! {\n///         Router::<Route> {}\n///     }\n/// }\n///\n/// #[component]\n/// fn Index() -> Element {\n///     rsx! {\n///         GoForwardButton {\n///             \"go forward\"\n///         }\n///     }\n/// }\n/// #\n/// # let mut vdom = VirtualDom::new(App);\n/// # vdom.rebuild_in_place();\n/// # assert_eq!(\n/// #     dioxus_ssr::render(&vdom),\n/// #     r#\"<button disabled=\"true\">go forward</button>\"#\n/// # );\n/// ```\npub fn GoForwardButton(props: HistoryButtonProps) -> Element {\n    let HistoryButtonProps { children } = props;\n\n    // hook up to router\n    let router = match use_router_internal() {\n        Some(r) => r,\n        #[allow(unreachable_code)]\n        None => {\n            let msg = \"`GoForwardButton` must have access to a parent router\";\n            error!(\"{msg}, will be inactive\");\n            #[cfg(debug_assertions)]\n            panic!(\"{}\", msg);\n            return VNode::empty();\n        }\n    };\n\n    let disabled = !router.can_go_forward();\n\n    rsx! {\n        button {\n            disabled: \"{disabled}\",\n            onclick: move |evt| {\n                evt.prevent_default();\n                router.go_forward()\n            },\n            {children}\n        }\n    }\n}\n"
  },
  {
    "path": "packages/router/src/components/history_provider.rs",
    "content": "use dioxus_core::{use_hook, Callback, Element};\nuse dioxus_core_macro::{component, Props};\nuse dioxus_history::{provide_history_context, History};\n\nuse std::rc::Rc;\n\n/// A component that provides a [`History`] for all child [`Router`] components. Renderers generally provide a default history automatically.\n#[component]\n#[allow(missing_docs)]\npub fn HistoryProvider(\n    /// The history to provide to child components.\n    history: Callback<(), Rc<dyn History>>,\n    /// The children to render within the history provider.\n    children: Element,\n) -> Element {\n    use_hook(|| {\n        provide_history_context(history(()));\n    });\n\n    children\n}\n"
  },
  {
    "path": "packages/router/src/components/link.rs",
    "content": "#![allow(clippy::type_complexity)]\n\nuse std::fmt::Debug;\n\nuse dioxus_core::{Attribute, Element, EventHandler, VNode};\nuse dioxus_core_macro::{rsx, Props};\nuse dioxus_html::{\n    self as dioxus_elements, ModifiersInteraction, MountedEvent, MouseEvent, PointerInteraction,\n};\n\nuse tracing::error;\n\nuse crate::navigation::NavigationTarget;\nuse crate::utils::use_router_internal::use_router_internal;\n\n/// The properties for a [`Link`].\n#[derive(Props, Clone, PartialEq)]\npub struct LinkProps {\n    /// The class attribute for the `a` tag.\n    pub class: Option<String>,\n\n    /// A class to apply to the generate HTML anchor tag if the `target` route is active.\n    pub active_class: Option<String>,\n\n    /// The children to render within the generated HTML anchor tag.\n    pub children: Element,\n\n    /// When [`true`], the `target` route will be opened in a new tab.\n    ///\n    /// This does not change whether the [`Link`] is active or not.\n    #[props(default)]\n    pub new_tab: bool,\n\n    /// The onclick event handler.\n    pub onclick: Option<EventHandler<MouseEvent>>,\n\n    /// The onmounted event handler.\n    /// Fired when the `<a>` element is mounted.\n    pub onmounted: Option<EventHandler<MountedEvent>>,\n\n    #[props(default)]\n    /// Whether the default behavior should be executed if an `onclick` handler is provided.\n    ///\n    /// 1. When `onclick` is [`None`] (default if not specified), `onclick_only` has no effect.\n    /// 2. If `onclick_only` is [`false`] (default if not specified), the provided `onclick` handler\n    ///    will be executed after the links regular functionality.\n    /// 3. If `onclick_only` is [`true`], only the provided `onclick` handler will be executed.\n    pub onclick_only: bool,\n\n    /// The rel attribute for the generated HTML anchor tag.\n    ///\n    /// For external `a`s, this defaults to `noopener noreferrer`.\n    pub rel: Option<String>,\n\n    /// The navigation target. Roughly equivalent to the href attribute of an HTML anchor tag.\n    #[props(into)]\n    pub to: NavigationTarget,\n\n    #[props(extends = GlobalAttributes)]\n    attributes: Vec<Attribute>,\n}\n\nimpl Debug for LinkProps {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_struct(\"LinkProps\")\n            .field(\"active_class\", &self.active_class)\n            .field(\"children\", &self.children)\n            .field(\"attributes\", &self.attributes)\n            .field(\"new_tab\", &self.new_tab)\n            .field(\"onclick\", &self.onclick.as_ref().map(|_| \"onclick is set\"))\n            .field(\"onclick_only\", &self.onclick_only)\n            .field(\"rel\", &self.rel)\n            .finish()\n    }\n}\n\n/// A link to navigate to another route.\n///\n/// Only works as descendant of a [`super::Router`] component, otherwise it will be inactive.\n///\n/// Unlike a regular HTML anchor, a [`Link`] allows the router to handle the navigation and doesn't\n/// cause the browser to load a new page.\n///\n/// However, in the background a [`Link`] still generates an anchor, which you can use for styling\n/// as normal.\n///\n/// # External targets\n/// When the [`Link`]s target is an [`NavigationTarget::External`] target, that is used as the `href` directly. This\n/// means that a [`Link`] can always navigate to an [`NavigationTarget::External`] target, even if the [`dioxus_history::History`] does not support it.\n///\n/// # Panic\n/// - When the [`Link`] is not nested within a [`super::Router`], but\n///   only in debug builds.\n///\n/// # Example\n/// ```rust\n/// # use dioxus::prelude::*;\n///\n/// #[derive(Clone, Routable)]\n/// enum Route {\n///     #[route(\"/\")]\n///     Index {},\n/// }\n///\n/// #[component]\n/// fn App() -> Element {\n///     rsx! {\n///         Router::<Route> {}\n///     }\n/// }\n///\n/// #[component]\n/// fn Index() -> Element {\n///     rsx! {\n///         Link {\n///             active_class: \"active\",\n///             class: \"link_class\",\n///             id: \"link_id\",\n///             new_tab: true,\n///             rel: \"link_rel\",\n///             to: Route::Index {},\n///\n///             \"A fully configured link\"\n///         }\n///     }\n/// }\n/// #\n/// # let mut vdom = VirtualDom::new(App);\n/// # vdom.rebuild_in_place();\n/// # assert_eq!(\n/// #     dioxus_ssr::render(&vdom),\n/// #     r#\"<a href=\"/\" class=\"link_class active\" rel=\"link_rel\" target=\"_blank\" aria-current=\"page\" id=\"link_id\">A fully configured link</a>\"#\n/// # );\n/// ```\n#[doc(alias = \"<a>\")]\n#[allow(non_snake_case)]\npub fn Link(props: LinkProps) -> Element {\n    let LinkProps {\n        active_class,\n        children,\n        attributes,\n        new_tab,\n        onclick,\n        onclick_only,\n        rel,\n        to,\n        class,\n        ..\n    } = props;\n\n    // hook up to router\n    let router = match use_router_internal() {\n        Some(r) => r,\n        #[allow(unreachable_code)]\n        None => {\n            let msg = \"`Link` must have access to a parent router\";\n            error!(\"{msg}, will be inactive\");\n            #[cfg(debug_assertions)]\n            panic!(\"{}\", msg);\n            return VNode::empty();\n        }\n    };\n\n    let current_url = router.full_route_string();\n    let href = match &to {\n        NavigationTarget::Internal(url) => url.clone(),\n        NavigationTarget::External(route) => route.clone(),\n    };\n    // Add the history's prefix to internal hrefs for use in the rsx\n    let full_href = match &to {\n        NavigationTarget::Internal(url) => router.prefix().unwrap_or_default() + url,\n        NavigationTarget::External(route) => route.clone(),\n    };\n\n    let mut class_ = String::new();\n    if let Some(c) = class {\n        class_.push_str(&c);\n    }\n    if let Some(c) = active_class {\n        if href == current_url {\n            if !class_.is_empty() {\n                class_.push(' ');\n            }\n            class_.push_str(&c);\n        }\n    }\n\n    let class = if class_.is_empty() {\n        None\n    } else {\n        Some(class_)\n    };\n\n    let aria_current = (href == current_url).then_some(\"page\");\n\n    let tag_target = new_tab.then_some(\"_blank\");\n\n    let is_external = matches!(to, NavigationTarget::External(_));\n    let is_router_nav = !is_external && !new_tab;\n    let rel = rel.or_else(|| is_external.then_some(\"noopener noreferrer\".to_string()));\n\n    let do_default = onclick.is_none() || !onclick_only;\n\n    let action = move |event: MouseEvent| {\n        // Only handle events without modifiers\n        if !event.modifiers().is_empty() {\n            return;\n        }\n        // Only handle left clicks\n        if event.trigger_button() != Some(dioxus_elements::input_data::MouseButton::Primary) {\n            return;\n        }\n\n        // If we need to open in a new tab, let the browser handle it\n        if new_tab {\n            return;\n        }\n\n        // todo(jon): this is extra hacky for no reason - we should fix prevent default on Links\n        if do_default && is_external {\n            return;\n        }\n\n        event.prevent_default();\n\n        if do_default && is_router_nav {\n            router.push_any(to.clone());\n        }\n\n        if let Some(handler) = onclick {\n            handler.call(event);\n        }\n    };\n\n    let onmounted = move |event| {\n        if let Some(handler) = props.onmounted {\n            handler.call(event);\n        }\n    };\n\n    // In liveview, we need to prevent the default action if the user clicks on the link with modifiers\n    // in javascript. The prevent_default method is not available in the liveview renderer because\n    // event handlers are handled over a websocket.\n    let liveview_prevent_default = {\n        // If the event is a click with the left mouse button and no modifiers, prevent the default action\n        // and navigate to the href with client side routing\n        router.include_prevent_default().then_some(\n            \"if (event.button === 0 && !event.ctrlKey && !event.metaKey && !event.shiftKey && !event.altKey) { event.preventDefault() }\"\n        )\n    };\n\n    rsx! {\n        a {\n            onclick: action,\n            \"onclick\": liveview_prevent_default,\n            href: full_href,\n            onmounted: onmounted,\n            class,\n            rel,\n            target: tag_target,\n            aria_current,\n            ..attributes,\n            {children}\n        }\n    }\n}\n"
  },
  {
    "path": "packages/router/src/components/outlet.rs",
    "content": "use crate::{outlet::OutletContext, *};\nuse dioxus_core::Element;\n\n/// An outlet for the current content.\n///\n/// Only works as descendant of a [`Link`] component, otherwise it will be inactive.\n///\n/// The [`Outlet`] is aware of how many [`Outlet`]s it is nested within. It will render the content\n/// of the active route that is __exactly as deep__.\n///\n/// # Panic\n/// - When the [`Outlet`] is not nested a [`Link`] component,\n///   but only in debug builds.\n///\n/// # Example\n/// ```rust\n/// # use dioxus::prelude::*;\n/// #[derive(Clone, Routable)]\n/// #[rustfmt::skip]\n/// enum Route {\n///     #[nest(\"/wrap\")]\n///         #[layout(Wrapper)] // Every layout component must have one Outlet\n///             #[route(\"/\")]\n///             Child {},\n///         #[end_layout]\n///     #[end_nest]\n///     #[route(\"/\")]\n///     Index {},\n/// }\n///\n/// #[component]\n/// fn Index() -> Element {\n///     rsx! {\n///         div {\n///             \"Index\"\n///         }\n///     }\n/// }\n///\n/// #[component]\n/// fn Wrapper() -> Element {\n///     rsx! {\n///         h1 { \"App\" }\n///         Outlet::<Route> {} // The content of child routes will be rendered here\n///     }\n/// }\n///\n/// #[component]\n/// fn Child() -> Element {\n///     rsx! {\n///         p {\n///             \"Child\"\n///         }\n///     }\n/// }\n///\n/// # #[component]\n/// # fn App() -> Element {\n/// #     rsx! {\n/// #         dioxus_router::components::HistoryProvider {\n/// #             history:  move |_| std::rc::Rc::new(dioxus_history::MemoryHistory::with_initial_path(Route::Child {}.to_string())) as std::rc::Rc<dyn dioxus_history::History>,\n/// #             Router::<Route> {}\n/// #         }\n/// #     }\n/// # }\n/// #\n/// # let mut vdom = VirtualDom::new(App);\n/// # vdom.rebuild_in_place();\n/// # assert_eq!(dioxus_ssr::render(&vdom), \"<h1>App</h1><p>Child</p>\");\n/// ```\npub fn Outlet<R: Routable + Clone>() -> Element {\n    OutletContext::<R>::render()\n}\n"
  },
  {
    "path": "packages/router/src/components/router.rs",
    "content": "use crate::{provide_router_context, routable::Routable, router_cfg::RouterConfig, Outlet};\nuse dioxus_core::{provide_context, use_hook, Callback, Element};\nuse dioxus_core_macro::{rsx, Props};\n\n/// The props for [`Router`].\n#[derive(Props)]\npub struct RouterProps<R: Clone + 'static> {\n    #[props(default, into)]\n    config: Callback<(), RouterConfig<R>>,\n}\n\nimpl<T: Clone> Clone for RouterProps<T> {\n    fn clone(&self) -> Self {\n        *self\n    }\n}\n\nimpl<T: Clone> Copy for RouterProps<T> {}\n\nimpl<R: Clone + 'static> Default for RouterProps<R> {\n    fn default() -> Self {\n        Self {\n            config: Callback::new(|_| RouterConfig::default()),\n        }\n    }\n}\n\nimpl<R: Clone> PartialEq for RouterProps<R> {\n    fn eq(&self, _: &Self) -> bool {\n        // prevent the router from re-rendering when the initial url or config changes\n        true\n    }\n}\n\n/// A component that renders the current route.\npub fn Router<R: Routable + Clone>(props: RouterProps<R>) -> Element {\n    use crate::{outlet::OutletContext, RouterContext};\n\n    use_hook(|| {\n        provide_router_context(RouterContext::new(props.config.call(())));\n    });\n\n    #[cfg(feature = \"streaming\")]\n    dioxus_hooks::use_after_suspense_resolved(|| {\n        dioxus_fullstack_core::commit_initial_chunk();\n    });\n\n    use_hook(|| {\n        provide_context(OutletContext::<R>::new());\n    });\n\n    rsx! { Outlet::<R> {} }\n}\n"
  },
  {
    "path": "packages/router/src/contexts/navigator.rs",
    "content": "use crate::{ExternalNavigationFailure, NavigationTarget, RouterContext};\n\n/// Acquire the navigator without subscribing to updates.\n///\n/// Can be called anywhere in the application provided a Router has been initialized.\n///\n/// ## Panics\n///\n/// Panics if there is no router present.\npub fn navigator() -> Navigator {\n    Navigator(\n        dioxus_core::try_consume_context::<RouterContext>()\n            .expect(\"A router must be present to use navigator\"),\n    )\n}\n\n/// A view into the navigation state of a router.\n#[derive(Clone, Copy)]\npub struct Navigator(pub(crate) RouterContext);\n\nimpl Navigator {\n    /// Check whether there is a previous page to navigate back to.\n    #[must_use]\n    pub fn can_go_back(&self) -> bool {\n        self.0.can_go_back()\n    }\n\n    /// Check whether there is a future page to navigate forward to.\n    #[must_use]\n    pub fn can_go_forward(&self) -> bool {\n        self.0.can_go_forward()\n    }\n\n    /// Go back to the previous location.\n    ///\n    /// Will fail silently if there is no previous location to go to.\n    pub fn go_back(&self) {\n        self.0.go_back();\n    }\n\n    /// Go back to the next location.\n    ///\n    /// Will fail silently if there is no next location to go to.\n    pub fn go_forward(&self) {\n        self.0.go_forward();\n    }\n\n    /// Push a new location.\n    ///\n    /// The previous location will be available to go back to.\n    pub fn push(&self, target: impl Into<NavigationTarget>) -> Option<ExternalNavigationFailure> {\n        self.0.push(target)\n    }\n\n    /// Replace the current location.\n    ///\n    /// The previous location will **not** be available to go back to.\n    pub fn replace(\n        &self,\n        target: impl Into<NavigationTarget>,\n    ) -> Option<ExternalNavigationFailure> {\n        self.0.replace(target)\n    }\n}\n"
  },
  {
    "path": "packages/router/src/contexts/outlet.rs",
    "content": "use dioxus_core::{provide_context, try_consume_context, use_hook, Element, VNode};\n\nuse crate::{routable::Routable, utils::use_router_internal::use_router_internal};\n\n/// A context that manages nested routing levels for outlet components.\n///\n/// The outlet context keeps track of the current nesting level of routes and helps\n/// manage the hierarchical structure of nested routes in the application.\n///\n/// # Type Parameters\n///\n/// * `R` - The routable type that implements the routing logic\n#[derive(Clone, Default)]\npub struct OutletContext<R> {\n    current_level: usize,\n    _marker: std::marker::PhantomData<R>,\n}\n\nimpl<R> OutletContext<R> {\n    /// Creates a new outlet context starting at level 0\n    pub fn new() -> Self {\n        Self {\n            current_level: 0,\n            _marker: std::marker::PhantomData,\n        }\n    }\n\n    /// Creates a new outlet context for the next nesting level\n    pub fn next(&self) -> Self {\n        Self {\n            current_level: self.current_level + 1,\n            _marker: std::marker::PhantomData,\n        }\n    }\n\n    /// Returns the current nesting level of this outlet\n    pub fn level(&self) -> usize {\n        self.current_level\n    }\n\n    pub(crate) fn render() -> Element\n    where\n        R: Routable + Clone,\n    {\n        let router = use_router_internal().expect(\"Outlet must be inside of a router\");\n        let outlet: OutletContext<R> = use_outlet_context();\n        let current_level = outlet.level();\n        provide_context(outlet.next());\n\n        if let Some(error) = router.render_error() {\n            return if current_level == 0 {\n                error\n            } else {\n                VNode::empty()\n            };\n        }\n\n        router.current::<R>().render(current_level)\n    }\n}\n\n/// Returns the current outlet context from the component hierarchy.\n///\n/// This hook retrieves the outlet context from the current component scope. If no context is found,\n/// it creates a new context with a default level of 0.\n///\n/// # Type Parameters\n///\n/// * `R` - The routable type that implements the routing logic\n///\n/// # Returns\n///\n/// Returns an [`OutletContext<R>`] containing the current nesting level information.\n///\n/// # Examples\n///\n/// ```rust, no_run\n/// # use dioxus::prelude::*;\n/// # use dioxus_router::use_outlet_context;\n///\n/// # #[derive(Routable,Clone,PartialEq)]\n/// # enum MyRouter {\n/// #   #[route(\"/\")]\n/// #   MyView\n/// # }\n///\n/// # #[component]\n/// # fn MyView() -> Element {\n/// #   rsx!{ div { \"My Text\" } }\n/// # }\n///\n/// let outlet_ctx = use_outlet_context::<MyRouter>();\n/// println!(\"Current nesting level: {}\", outlet_ctx.level());\n/// ```\npub fn use_outlet_context<R: Clone + 'static>() -> OutletContext<R> {\n    use_hook(|| try_consume_context().unwrap_or_else(OutletContext::new))\n}\n"
  },
  {
    "path": "packages/router/src/contexts/router.rs",
    "content": "use std::{\n    collections::HashSet,\n    error::Error,\n    fmt::Display,\n    sync::{Arc, Mutex},\n};\n\nuse dioxus_core::{provide_context, Element, ReactiveContext, ScopeId};\nuse dioxus_history::history;\nuse dioxus_signals::{CopyValue, ReadableExt, Signal, WritableExt};\n\nuse crate::{\n    components::child_router::consume_child_route_mapping, navigation::NavigationTarget,\n    routable::Routable, router_cfg::RouterConfig, SiteMapSegment,\n};\n\n/// An error that is thrown when the router fails to parse a route\n#[derive(Debug, Clone)]\npub struct ParseRouteError {\n    message: String,\n}\n\nimpl Error for ParseRouteError {}\nimpl Display for ParseRouteError {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        self.message.fmt(f)\n    }\n}\n\n/// This context is set in the root of the virtual dom if there is a router present.\n#[derive(Clone, Copy)]\nstruct RootRouterContext(Signal<Option<RouterContext>>);\n\n/// Try to get the router that was created closest to the root of the virtual dom. This may be called outside of the router.\n///\n/// This will return `None` if there is no router present or the router has not been created yet.\npub fn root_router() -> Option<RouterContext> {\n    let rt = dioxus_core::Runtime::current();\n\n    if let Some(ctx) = rt.consume_context::<RootRouterContext>(ScopeId::ROOT) {\n        ctx.0.cloned()\n    } else {\n        rt.provide_context(\n            ScopeId::ROOT,\n            RootRouterContext(Signal::new_in_scope(None, ScopeId::ROOT)),\n        );\n        None\n    }\n}\n\npub(crate) fn provide_router_context(ctx: RouterContext) {\n    if root_router().is_none() {\n        dioxus_core::Runtime::current().provide_context(\n            ScopeId::ROOT,\n            RootRouterContext(Signal::new_in_scope(Some(ctx), ScopeId::ROOT)),\n        );\n    }\n    provide_context(ctx);\n}\n\n/// An error that can occur when navigating.\n#[derive(Debug, Clone)]\npub struct ExternalNavigationFailure(pub String);\n\n/// A function the router will call after every routing update.\npub(crate) type RoutingCallback<R> =\n    Arc<dyn Fn(GenericRouterContext<R>) -> Option<NavigationTarget<R>>>;\npub(crate) type AnyRoutingCallback = Arc<dyn Fn(RouterContext) -> Option<NavigationTarget>>;\n\nstruct RouterContextInner {\n    unresolved_error: Option<ExternalNavigationFailure>,\n\n    subscribers: Arc<Mutex<HashSet<ReactiveContext>>>,\n    routing_callback: Option<AnyRoutingCallback>,\n\n    failure_external_navigation: fn() -> Element,\n\n    internal_route: fn(&str) -> bool,\n\n    site_map: &'static [SiteMapSegment],\n}\n\nimpl RouterContextInner {\n    fn update_subscribers(&self) {\n        for &id in self.subscribers.lock().unwrap().iter() {\n            id.mark_dirty();\n        }\n    }\n\n    fn subscribe_to_current_context(&self) {\n        if let Some(rc) = ReactiveContext::current() {\n            rc.subscribe(self.subscribers.clone());\n        }\n    }\n\n    fn external(&mut self, external: String) -> Option<ExternalNavigationFailure> {\n        match history().external(external.clone()) {\n            true => None,\n            false => {\n                let failure = ExternalNavigationFailure(external);\n                self.unresolved_error = Some(failure.clone());\n\n                self.update_subscribers();\n\n                Some(failure)\n            }\n        }\n    }\n}\n\n/// A collection of router data that manages all routing functionality.\n#[derive(Clone, Copy)]\npub struct RouterContext {\n    inner: CopyValue<RouterContextInner>,\n}\n\nimpl RouterContext {\n    pub(crate) fn new<R: Routable + 'static>(cfg: RouterConfig<R>) -> Self {\n        let subscribers = Arc::new(Mutex::new(HashSet::new()));\n        let mapping = consume_child_route_mapping();\n\n        let myself = RouterContextInner {\n            unresolved_error: None,\n            subscribers: subscribers.clone(),\n            routing_callback: cfg.on_update.map(|update| {\n                Arc::new(move |ctx| {\n                    let ctx = GenericRouterContext {\n                        inner: ctx,\n                        _marker: std::marker::PhantomData,\n                    };\n                    update(ctx).map(|t| match t {\n                        NavigationTarget::Internal(r) => match mapping.as_ref() {\n                            Some(mapping) => {\n                                NavigationTarget::Internal(mapping.format_route_as_root_route(r))\n                            }\n                            None => NavigationTarget::Internal(r.to_string()),\n                        },\n                        NavigationTarget::External(s) => NavigationTarget::External(s),\n                    })\n                }) as Arc<dyn Fn(RouterContext) -> Option<NavigationTarget>>\n            }),\n\n            failure_external_navigation: cfg.failure_external_navigation,\n\n            internal_route: |route| R::from_str(route).is_ok(),\n\n            site_map: R::SITE_MAP,\n        };\n\n        let history = history();\n\n        // set the updater\n        history.updater(Arc::new(move || {\n            for &rc in subscribers.lock().unwrap().iter() {\n                rc.mark_dirty();\n            }\n        }));\n\n        let myself = Self {\n            inner: CopyValue::new_in_scope(myself, ScopeId::ROOT),\n        };\n\n        // If the current route is different from the one in the browser, replace the current route\n        let current_route: R = myself.current();\n\n        if current_route.to_string() != history.current_route() {\n            myself.replace(current_route);\n        }\n\n        myself\n    }\n\n    /// Check if the router is running in a liveview context\n    /// We do some slightly weird things for liveview because of the network boundary\n    pub(crate) fn include_prevent_default(&self) -> bool {\n        history().include_prevent_default()\n    }\n\n    /// Check whether there is a previous page to navigate back to.\n    #[must_use]\n    pub fn can_go_back(&self) -> bool {\n        history().can_go_back()\n    }\n\n    /// Check whether there is a future page to navigate forward to.\n    #[must_use]\n    pub fn can_go_forward(&self) -> bool {\n        history().can_go_forward()\n    }\n\n    /// Go back to the previous location.\n    ///\n    /// Will fail silently if there is no previous location to go to.\n    pub fn go_back(&self) {\n        history().go_back();\n        self.change_route();\n    }\n\n    /// Go back to the next location.\n    ///\n    /// Will fail silently if there is no next location to go to.\n    pub fn go_forward(&self) {\n        history().go_forward();\n        self.change_route();\n    }\n\n    pub(crate) fn push_any(&self, target: NavigationTarget) -> Option<ExternalNavigationFailure> {\n        {\n            let mut write = self.inner.write_unchecked();\n            match target {\n                NavigationTarget::Internal(p) => history().push(p),\n                NavigationTarget::External(e) => return write.external(e),\n            }\n        }\n\n        self.change_route()\n    }\n\n    /// Push a new location.\n    ///\n    /// The previous location will be available to go back to.\n    pub fn push(&self, target: impl Into<NavigationTarget>) -> Option<ExternalNavigationFailure> {\n        let target = target.into();\n        {\n            let mut write = self.inner.write_unchecked();\n            match target {\n                NavigationTarget::Internal(p) => {\n                    let history = history();\n                    history.push(p)\n                }\n                NavigationTarget::External(e) => return write.external(e),\n            }\n        }\n\n        self.change_route()\n    }\n\n    /// Replace the current location.\n    ///\n    /// The previous location will **not** be available to go back to.\n    pub fn replace(\n        &self,\n        target: impl Into<NavigationTarget>,\n    ) -> Option<ExternalNavigationFailure> {\n        let target = target.into();\n        {\n            let mut state = self.inner.write_unchecked();\n            match target {\n                NavigationTarget::Internal(p) => {\n                    let history = history();\n                    history.replace(p)\n                }\n                NavigationTarget::External(e) => return state.external(e),\n            }\n        }\n\n        self.change_route()\n    }\n\n    /// The route that is currently active.\n    pub fn current<R: Routable>(&self) -> R {\n        let absolute_route = self.full_route_string();\n        // If this is a child route, map the absolute route to the child route before parsing\n        let mapping = consume_child_route_mapping::<R>();\n        let route = match mapping.as_ref() {\n            Some(mapping) => mapping\n                .parse_route_from_root_route(&absolute_route)\n                .ok_or_else(|| \"Failed to parse route\".to_string()),\n            None => {\n                R::from_str(&absolute_route).map_err(|err| format!(\"Failed to parse route {err}\"))\n            }\n        };\n\n        match route {\n            Ok(route) => route,\n            Err(err) => {\n                dioxus_core::throw_error(ParseRouteError { message: err });\n                \"/\".parse().unwrap_or_else(|err| panic!(\"{err}\"))\n            }\n        }\n    }\n\n    /// The full route that is currently active. If this is called from inside a child router, this will always return the parent's view of the route.\n    pub fn full_route_string(&self) -> String {\n        let inner = self.inner.read();\n        inner.subscribe_to_current_context();\n        let history = history();\n        history.current_route()\n    }\n\n    /// The prefix that is currently active.\n    pub fn prefix(&self) -> Option<String> {\n        let history = history();\n        history.current_prefix()\n    }\n\n    /// Clear any unresolved errors\n    pub fn clear_error(&self) {\n        let mut write_inner = self.inner.write_unchecked();\n        write_inner.unresolved_error = None;\n\n        write_inner.update_subscribers();\n    }\n\n    /// Get the site map of the router.\n    pub fn site_map(&self) -> &'static [SiteMapSegment] {\n        self.inner.read().site_map\n    }\n\n    pub(crate) fn render_error(&self) -> Option<Element> {\n        let inner_write = self.inner.write_unchecked();\n        inner_write.subscribe_to_current_context();\n        inner_write\n            .unresolved_error\n            .as_ref()\n            .map(|_| (inner_write.failure_external_navigation)())\n    }\n\n    fn change_route(&self) -> Option<ExternalNavigationFailure> {\n        let self_read = self.inner.read();\n        if let Some(callback) = &self_read.routing_callback {\n            let myself = *self;\n            let callback = callback.clone();\n            drop(self_read);\n            if let Some(new) = callback(myself) {\n                let mut self_write = self.inner.write_unchecked();\n                match new {\n                    NavigationTarget::Internal(p) => {\n                        let history = history();\n                        history.replace(p)\n                    }\n                    NavigationTarget::External(e) => return self_write.external(e),\n                }\n            }\n        }\n\n        self.inner.read().update_subscribers();\n\n        None\n    }\n\n    pub(crate) fn internal_route(&self, route: &str) -> bool {\n        (self.inner.read().internal_route)(route)\n    }\n}\n\n/// This context is set to the RouterConfig on_update method\npub struct GenericRouterContext<R> {\n    inner: RouterContext,\n    _marker: std::marker::PhantomData<R>,\n}\n\nimpl<R> GenericRouterContext<R>\nwhere\n    R: Routable,\n{\n    /// Check whether there is a previous page to navigate back to.\n    #[must_use]\n    pub fn can_go_back(&self) -> bool {\n        self.inner.can_go_back()\n    }\n\n    /// Check whether there is a future page to navigate forward to.\n    #[must_use]\n    pub fn can_go_forward(&self) -> bool {\n        self.inner.can_go_forward()\n    }\n\n    /// Go back to the previous location.\n    ///\n    /// Will fail silently if there is no previous location to go to.\n    pub fn go_back(&self) {\n        self.inner.go_back();\n    }\n\n    /// Go back to the next location.\n    ///\n    /// Will fail silently if there is no next location to go to.\n    pub fn go_forward(&self) {\n        self.inner.go_forward();\n    }\n\n    /// Push a new location.\n    ///\n    /// The previous location will be available to go back to.\n    pub fn push(\n        &self,\n        target: impl Into<NavigationTarget<R>>,\n    ) -> Option<ExternalNavigationFailure> {\n        self.inner.push(target.into())\n    }\n\n    /// Replace the current location.\n    ///\n    /// The previous location will **not** be available to go back to.\n    pub fn replace(\n        &self,\n        target: impl Into<NavigationTarget<R>>,\n    ) -> Option<ExternalNavigationFailure> {\n        self.inner.replace(target.into())\n    }\n\n    /// The route that is currently active.\n    pub fn current(&self) -> R\n    where\n        R: Clone,\n    {\n        self.inner.current()\n    }\n\n    /// The prefix that is currently active.\n    pub fn prefix(&self) -> Option<String> {\n        self.inner.prefix()\n    }\n\n    /// Clear any unresolved errors\n    pub fn clear_error(&self) {\n        self.inner.clear_error()\n    }\n}\n"
  },
  {
    "path": "packages/router/src/hooks/use_navigator.rs",
    "content": "use dioxus_core::{try_consume_context, use_hook};\n\nuse crate::{Navigator, RouterContext};\n\n/// A hook that provides access to the navigator to change the router history.\n///\n/// > The Routable macro will define a version of this hook with an explicit type.\n///\n/// ```rust\n/// # use dioxus::prelude::*;\n/// #[derive(Clone, Routable)]\n/// enum Route {\n///     #[route(\"/\")]\n///     Index {},\n///     #[route(\"/:id\")]\n///     Dynamic { id: usize },\n/// }\n///\n/// #[component]\n/// fn App() -> Element {\n///     rsx! {\n///         Router::<Route> {}\n///     }\n/// }\n///\n/// #[component]\n/// fn Index() -> Element {\n///     let navigator = use_navigator();\n///\n///     rsx! {\n///         button {\n///             onclick: move |_| { navigator.push(Route::Dynamic { id: 1234 }); },\n///             \"Go to /1234\"\n///         }\n///     }\n/// }\n///\n/// #[component]\n/// fn Dynamic(id: usize) -> Element {\n///     rsx! {\n///         p {\n///             \"Current ID: {id}\"\n///         }\n///     }\n/// }\n///\n/// # let mut vdom = VirtualDom::new(App);\n/// # vdom.rebuild_in_place();\n/// ```\n#[must_use]\npub fn use_navigator() -> Navigator {\n    use_hook(|| {\n        let router = try_consume_context::<RouterContext>()\n            .expect(\"Must be called in a descendant of a Router component\");\n\n        Navigator(router)\n    })\n}\n"
  },
  {
    "path": "packages/router/src/hooks/use_route.rs",
    "content": "use crate::utils::use_router_internal::use_router_internal;\nuse crate::Routable;\n\n/// A hook that provides access to information about the current routing location.\n///\n/// > The Routable macro will define a version of this hook with an explicit type.\n///\n/// # Panic\n/// - When the calling component is not nested within a [`crate::Router`] component.\n///\n/// # Example\n/// ```rust\n/// # use dioxus::prelude::*;\n///\n/// #[derive(Clone, Routable)]\n/// enum Route {\n///     #[route(\"/\")]\n///     Index {},\n/// }\n///\n/// #[component]\n/// fn App() -> Element {\n///     rsx! {\n///         h1 { \"App\" }\n///         Router::<Route> {}\n///     }\n/// }\n///\n/// #[component]\n/// fn Index() -> Element {\n///     let path: Route = use_route();\n///     rsx! {\n///         h2 { \"Current Path\" }\n///         p { \"{path}\" }\n///     }\n/// }\n/// #\n/// # let mut vdom = VirtualDom::new(App);\n/// # vdom.rebuild_in_place();\n/// # assert_eq!(dioxus_ssr::render(&vdom), \"<h1>App</h1><h2>Current Path</h2><p>/</p>\")\n/// ```\n#[doc(alias = \"use_url\")]\n#[must_use]\npub fn use_route<R: Routable + Clone>() -> R {\n    match use_router_internal() {\n        Some(r) => r.current(),\n        None => {\n            panic!(\"`use_route` must be called in a descendant of a Router component\")\n        }\n    }\n}\n"
  },
  {
    "path": "packages/router/src/hooks/use_router.rs",
    "content": "use crate::{utils::use_router_internal::use_router_internal, RouterContext};\n\n#[deprecated = \"prefer the `router()` function or `use_route` functions\"]\n#[must_use]\n/// A hook that provides access to information about the router.\npub fn use_router() -> RouterContext {\n    use_router_internal().expect(\"use_route must have access to a router\")\n}\n\n/// Acquire the router without subscribing to updates.\n#[doc(alias = \"url\")]\npub fn router() -> RouterContext {\n    dioxus_core::consume_context()\n}\n\n/// Try to acquire the router without subscribing to updates.\n#[doc(alias = \"url\")]\npub fn try_router() -> Option<RouterContext> {\n    dioxus_core::try_consume_context()\n}\n"
  },
  {
    "path": "packages/router/src/lib.rs",
    "content": "#![doc = include_str!(\"../README.md\")]\n#![doc(html_logo_url = \"https://avatars.githubusercontent.com/u/79236386\")]\n#![doc(html_favicon_url = \"https://avatars.githubusercontent.com/u/79236386\")]\n// cannot use forbid, because props derive macro generates #[allow(missing_docs)]\n#![deny(missing_docs)]\n#![allow(non_snake_case)]\n\npub mod navigation;\npub mod routable;\n\n/// Components interacting with the router.\npub mod components {\n    #[cfg(feature = \"html\")]\n    mod default_errors;\n    #[cfg(feature = \"html\")]\n    pub use default_errors::*;\n\n    #[cfg(feature = \"html\")]\n    mod history_buttons;\n    #[cfg(feature = \"html\")]\n    pub use history_buttons::*;\n\n    #[cfg(feature = \"html\")]\n    mod link;\n    #[cfg(feature = \"html\")]\n    pub use link::*;\n\n    mod outlet;\n    pub use outlet::*;\n\n    mod router;\n    pub use router::*;\n\n    mod history_provider;\n    pub use history_provider::*;\n\n    #[doc(hidden)]\n    pub mod child_router;\n}\n\nmod contexts {\n    pub(crate) mod navigator;\n    pub(crate) mod outlet;\n    pub use outlet::{use_outlet_context, OutletContext};\n    pub(crate) mod router;\n    pub use navigator::*;\n    pub(crate) use router::*;\n    pub use router::{root_router, GenericRouterContext, ParseRouteError, RouterContext};\n}\n\nmod router_cfg;\n\n/// Hooks for interacting with the router in components.\npub mod hooks {\n    mod use_router;\n    pub use use_router::*;\n\n    mod use_route;\n\n    pub use use_route::*;\n\n    mod use_navigator;\n    pub use use_navigator::*;\n}\n\npub use hooks::router;\n\n#[cfg(feature = \"html\")]\npub use crate::components::{GoBackButton, GoForwardButton, HistoryButtonProps, Link, LinkProps};\npub use crate::components::{Outlet, Router, RouterProps};\npub use crate::contexts::*;\npub use crate::hooks::*;\npub use crate::navigation::*;\npub use crate::routable::*;\npub use crate::router_cfg::RouterConfig;\npub use dioxus_router_macro::Routable;\n\n#[doc(hidden)]\n/// A component with props used in the macro\npub trait HasProps {\n    /// The props type of the component.\n    type Props;\n}\n\nimpl<P> HasProps for dioxus_core::Component<P> {\n    type Props = P;\n}\n\nmod utils {\n    pub(crate) mod use_router_internal;\n}\n\n#[doc(hidden)]\npub mod exports {\n    pub use crate::query_sets::*;\n    pub use percent_encoding;\n}\n\npub(crate) mod query_sets {\n    //! Url percent encode sets defined [here](https://url.spec.whatwg.org/#percent-encoded-bytes)\n\n    use percent_encoding::AsciiSet;\n\n    /// The ASCII set that must be escaped in query strings.\n    pub const QUERY_ASCII_SET: &AsciiSet = &percent_encoding::CONTROLS\n        .add(b' ')\n        .add(b'\"')\n        .add(b'#')\n        .add(b'<')\n        .add(b'>');\n\n    /// The ASCII set that must be escaped in path segments.\n    pub const PATH_ASCII_SET: &AsciiSet = &QUERY_ASCII_SET\n        .add(b'?')\n        .add(b'^')\n        .add(b'`')\n        .add(b'{')\n        .add(b'}');\n\n    /// The ASCII set that must be escaped in hash fragments.\n    pub const FRAGMENT_ASCII_SET: &AsciiSet = &percent_encoding::CONTROLS\n        .add(b' ')\n        .add(b'\"')\n        .add(b'<')\n        .add(b'>')\n        .add(b'`');\n}\n"
  },
  {
    "path": "packages/router/src/navigation.rs",
    "content": "//! Types pertaining to navigation.\n\nuse std::{\n    fmt::{Debug, Display},\n    str::FromStr,\n};\n\nuse url::{ParseError, Url};\n\nuse crate::{\n    components::child_router::consume_child_route_mapping, hooks::try_router, routable::Routable,\n};\n\nimpl<R: Routable> From<R> for NavigationTarget {\n    fn from(value: R) -> Self {\n        // If this is a child route, map it to the root route first\n        let mapping = consume_child_route_mapping();\n        match mapping.as_ref() {\n            Some(mapping) => NavigationTarget::Internal(mapping.format_route_as_root_route(value)),\n            // Otherwise, just use the internal route\n            None => NavigationTarget::Internal(value.to_string()),\n        }\n    }\n}\n\n/// A target for the router to navigate to.\n#[derive(Clone, PartialEq, Eq, Debug)]\npub enum NavigationTarget<R = String> {\n    /// An internal path that the router can navigate to by itself.\n    ///\n    /// ```rust\n    /// # use dioxus::prelude::*;\n    /// # use dioxus_router::navigation::NavigationTarget;\n    /// # #[component]\n    /// # fn Index() -> Element {\n    /// #     unreachable!()\n    /// # }\n    /// #[derive(Clone, Routable, PartialEq, Debug)]\n    /// enum Route {\n    ///     #[route(\"/\")]\n    ///     Index {},\n    /// }\n    /// let explicit = NavigationTarget::Internal(Route::Index {});\n    /// let implicit: NavigationTarget::<Route> = \"/\".parse().unwrap();\n    /// assert_eq!(explicit, implicit);\n    /// ```\n    Internal(R),\n    /// An external target that the router doesn't control.\n    ///\n    /// ```rust\n    /// # use dioxus::prelude::*;\n    /// # use dioxus_router::navigation::NavigationTarget;\n    /// # #[component]\n    /// # fn Index() -> Element {\n    /// #     unreachable!()\n    /// # }\n    /// #[derive(Clone, Routable, PartialEq, Debug)]\n    /// enum Route {\n    ///     #[route(\"/\")]\n    ///     Index {},\n    /// }\n    /// let explicit = NavigationTarget::<Route>::External(String::from(\"https://dioxuslabs.com/\"));\n    /// let implicit: NavigationTarget::<Route> = \"https://dioxuslabs.com/\".parse().unwrap();\n    /// assert_eq!(explicit, implicit);\n    /// ```\n    External(String),\n}\n\nimpl<R: Routable> From<&str> for NavigationTarget<R> {\n    fn from(value: &str) -> Self {\n        value\n            .parse()\n            .unwrap_or_else(|_| Self::External(value.to_string()))\n    }\n}\n\nimpl<R: Routable> From<&String> for NavigationTarget<R> {\n    fn from(value: &String) -> Self {\n        value.as_str().into()\n    }\n}\n\nimpl<R: Routable> From<String> for NavigationTarget<R> {\n    fn from(value: String) -> Self {\n        value.as_str().into()\n    }\n}\n\nimpl<R: Routable> From<R> for NavigationTarget<R> {\n    fn from(value: R) -> Self {\n        Self::Internal(value)\n    }\n}\n\nimpl From<&str> for NavigationTarget {\n    fn from(value: &str) -> Self {\n        match try_router() {\n            Some(router) => match router.internal_route(value) {\n                true => NavigationTarget::Internal(value.to_string()),\n                false => NavigationTarget::External(value.to_string()),\n            },\n            None => NavigationTarget::External(value.to_string()),\n        }\n    }\n}\n\nimpl From<String> for NavigationTarget {\n    fn from(value: String) -> Self {\n        match try_router() {\n            Some(router) => match router.internal_route(&value) {\n                true => NavigationTarget::Internal(value),\n                false => NavigationTarget::External(value),\n            },\n            None => NavigationTarget::External(value.to_string()),\n        }\n    }\n}\n\nimpl<R: Routable> From<NavigationTarget<R>> for NavigationTarget {\n    fn from(value: NavigationTarget<R>) -> Self {\n        match value {\n            NavigationTarget::Internal(r) => r.into(),\n            NavigationTarget::External(s) => Self::External(s),\n        }\n    }\n}\n\nimpl<R: Routable> Display for NavigationTarget<R> {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        match self {\n            NavigationTarget::Internal(r) => write!(f, \"{}\", r),\n            NavigationTarget::External(s) => write!(f, \"{}\", s),\n        }\n    }\n}\n\n/// An error that can occur when parsing a [`NavigationTarget`].\npub enum NavigationTargetParseError<R: Routable> {\n    /// A URL that is not valid.\n    InvalidUrl(ParseError),\n    /// An internal URL that is not valid.\n    InvalidInternalURL(<R as FromStr>::Err),\n}\n\nimpl<R: Routable> Debug for NavigationTargetParseError<R> {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        match self {\n            NavigationTargetParseError::InvalidUrl(e) => write!(f, \"Invalid URL: {}\", e),\n            NavigationTargetParseError::InvalidInternalURL(_) => {\n                write!(f, \"Invalid internal URL\")\n            }\n        }\n    }\n}\n\nimpl<R: Routable> FromStr for NavigationTarget<R> {\n    type Err = NavigationTargetParseError<R>;\n\n    fn from_str(s: &str) -> Result<Self, Self::Err> {\n        match Url::parse(s) {\n            Ok(_) => Ok(Self::External(s.to_string())),\n            Err(ParseError::RelativeUrlWithoutBase) => {\n                Ok(Self::Internal(R::from_str(s).map_err(|e| {\n                    NavigationTargetParseError::InvalidInternalURL(e)\n                })?))\n            }\n            Err(e) => Err(NavigationTargetParseError::InvalidUrl(e)),\n        }\n    }\n}\n"
  },
  {
    "path": "packages/router/src/routable.rs",
    "content": "#![allow(non_snake_case)]\n//! # Routable\n\nuse dioxus_core::Element;\nuse std::iter::FlatMap;\nuse std::slice::Iter;\nuse std::{fmt::Display, str::FromStr};\n\n/// An error that occurs when parsing a route.\n#[derive(Debug, PartialEq)]\npub struct RouteParseError<E: Display> {\n    /// The attempted routes that failed to match.\n    pub attempted_routes: Vec<E>,\n}\n\nimpl<E: Display> Display for RouteParseError<E> {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(f, \"Route did not match:\\nAttempted Matches:\\n\")?;\n        for (i, route) in self.attempted_routes.iter().enumerate() {\n            writeln!(f, \"{}) {route}\", i + 1)?;\n        }\n        Ok(())\n    }\n}\n\n/// Something that can be created from an entire query string. This trait must be implemented for any type that is spread into the query segment like `#[route(\"/?:..query\")]`.\n///\n///\n/// **This trait is automatically implemented for any types that implement `From<&str>`.**\n///\n/// ```rust\n/// use dioxus::prelude::*;\n///\n/// #[derive(Routable, Clone, PartialEq, Debug)]\n/// enum Route {\n///     // FromQuery must be implemented for any types you spread into the query segment\n///     #[route(\"/?:..query\")]\n///     Home {\n///         query: CustomQuery\n///     },\n/// }\n///\n/// #[derive(Default, Clone, PartialEq, Debug)]\n/// struct CustomQuery {\n///     count: i32,\n/// }\n///\n/// // We implement From<&str> for CustomQuery so that FromQuery is implemented automatically\n/// impl From<&str> for CustomQuery {\n///     fn from(query: &str) -> Self {\n///         CustomQuery {\n///             count: query.parse().unwrap_or(0),\n///         }\n///     }\n/// }\n///\n/// // We also need to implement Display for CustomQuery which will be used to format the query string into the URL\n/// impl std::fmt::Display for CustomQuery {\n///     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n///         write!(f, \"{}\", self.count)\n///     }\n/// }\n///\n/// # #[component]\n/// # fn Home(query: CustomQuery) -> Element {\n/// #     unimplemented!()\n/// # }\n/// ```\n#[rustversion::attr(\n    since(1.78.0),\n    diagnostic::on_unimplemented(\n        message = \"`FromQuery` is not implemented for `{Self}`\",\n        label = \"spread query\",\n        note = \"FromQuery is automatically implemented for types that implement `From<&str>`. You need to either implement From<&str> or implement FromQuery manually.\"\n    )\n)]\npub trait FromQuery {\n    /// Create an instance of `Self` from a query string.\n    fn from_query(query: &str) -> Self;\n}\n\nimpl<T: for<'a> From<&'a str>> FromQuery for T {\n    fn from_query(query: &str) -> Self {\n        T::from(query)\n    }\n}\n\n/// Something that can be created from a query argument. This trait must be implemented for any type that is used as a query argument like `#[route(\"/?:query\")]`.\n///\n/// **This trait is automatically implemented for any types that implement `FromStr` and `Default`.**\n///\n/// ```rust\n/// use dioxus::prelude::*;\n///\n/// #[derive(Routable, Clone, PartialEq, Debug)]\n/// enum Route {\n///     // FromQuerySegment must be implemented for any types you use in the query segment\n///     // When you don't spread the query, you can parse multiple values form the query\n///     // This url will be in the format `/?query=123&other=456`\n///     #[route(\"/?:query&:other\")]\n///     Home {\n///         query: CustomQuery,\n///         other: i32,\n///     },\n/// }\n///\n/// // We can derive Default for CustomQuery\n/// // If the router fails to parse the query value, it will use the default value instead\n/// #[derive(Default, Clone, PartialEq, Debug)]\n/// struct CustomQuery {\n///     count: i32,\n/// }\n///\n/// // We implement FromStr for CustomQuery so that FromQuerySegment is implemented automatically\n/// impl std::str::FromStr for CustomQuery {\n///     type Err = <i32 as std::str::FromStr>::Err;\n///\n///     fn from_str(query: &str) -> Result<Self, Self::Err> {\n///         Ok(CustomQuery {\n///             count: query.parse()?,\n///         })\n///     }\n/// }\n///\n/// // We also need to implement Display for CustomQuery so that ToQueryArgument is implemented automatically\n/// impl std::fmt::Display for CustomQuery {\n///     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n///         write!(f, \"{}\", self.count)\n///     }\n/// }\n///\n/// # #[component]\n/// # fn Home(query: CustomQuery, other: i32) -> Element {\n/// #     unimplemented!()\n/// # }\n/// ```\n#[rustversion::attr(\n    since(1.78.0),\n    diagnostic::on_unimplemented(\n        message = \"`FromQueryArgument` is not implemented for `{Self}`\",\n        label = \"query argument\",\n        note = \"FromQueryArgument is automatically implemented for types that implement `FromStr` and `Default`. You need to either implement FromStr and Default or implement FromQueryArgument manually.\"\n    )\n)]\npub trait FromQueryArgument<P = ()>: Default {\n    /// The error that can occur when parsing a query argument.\n    type Err;\n\n    /// Create an instance of `Self` from a query string.\n    fn from_query_argument(argument: &str) -> Result<Self, Self::Err>;\n}\n\nimpl<T: Default + FromStr> FromQueryArgument for T\nwhere\n    <T as FromStr>::Err: Display,\n{\n    type Err = <T as FromStr>::Err;\n\n    fn from_query_argument(argument: &str) -> Result<Self, Self::Err> {\n        match T::from_str(argument) {\n            Ok(result) => Ok(result),\n            Err(err) => {\n                tracing::error!(\"Failed to parse query argument: {}\", err);\n                Err(err)\n            }\n        }\n    }\n}\n\n/// A marker type for `Option<T>` to implement `FromQueryArgument`.\npub struct OptionMarker;\n\nimpl<T: Default + FromStr> FromQueryArgument<OptionMarker> for Option<T>\nwhere\n    <T as FromStr>::Err: Display,\n{\n    type Err = <T as FromStr>::Err;\n\n    fn from_query_argument(argument: &str) -> Result<Self, Self::Err> {\n        match T::from_str(argument) {\n            Ok(result) => Ok(Some(result)),\n            Err(err) => {\n                tracing::error!(\"Failed to parse query argument: {}\", err);\n                Err(err)\n            }\n        }\n    }\n}\n\n/// Something that can be formatted as a query argument. This trait must be implemented for any type that is used as a query argument like `#[route(\"/?:query\")]`.\n///\n/// **This trait is automatically implemented for any types that implement [`Display`].**\n///\n/// ```rust\n/// use dioxus::prelude::*;\n///\n/// #[derive(Routable, Clone, PartialEq, Debug)]\n/// enum Route {\n///     // FromQuerySegment must be implemented for any types you use in the query segment\n///     // When you don't spread the query, you can parse multiple values form the query\n///     // This url will be in the format `/?query=123&other=456`\n///     #[route(\"/?:query&:other\")]\n///     Home {\n///         query: CustomQuery,\n///         other: i32,\n///     },\n/// }\n///\n/// // We can derive Default for CustomQuery\n/// // If the router fails to parse the query value, it will use the default value instead\n/// #[derive(Default, Clone, PartialEq, Debug)]\n/// struct CustomQuery {\n///     count: i32,\n/// }\n///\n/// // We implement FromStr for CustomQuery so that FromQuerySegment is implemented automatically\n/// impl std::str::FromStr for CustomQuery {\n///     type Err = <i32 as std::str::FromStr>::Err;\n///\n///     fn from_str(query: &str) -> Result<Self, Self::Err> {\n///         Ok(CustomQuery {\n///             count: query.parse()?,\n///         })\n///     }\n/// }\n///\n/// // We also need to implement Display for CustomQuery so that ToQueryArgument is implemented automatically\n/// impl std::fmt::Display for CustomQuery {\n///     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n///         write!(f, \"{}\", self.count)\n///     }\n/// }\n///\n/// # #[component]\n/// # fn Home(query: CustomQuery, other: i32) -> Element {\n/// #     unimplemented!()\n/// # }\n/// ```\npub trait ToQueryArgument<T = ()> {\n    /// Display the query argument as a string.\n    fn display_query_argument(\n        &self,\n        query_name: &str,\n        f: &mut std::fmt::Formatter<'_>,\n    ) -> std::fmt::Result;\n}\n\nimpl<T> ToQueryArgument for T\nwhere\n    T: Display,\n{\n    fn display_query_argument(\n        &self,\n        query_name: &str,\n        f: &mut std::fmt::Formatter<'_>,\n    ) -> std::fmt::Result {\n        write!(f, \"{}={}\", query_name, self)\n    }\n}\n\nimpl<T: Display> ToQueryArgument<OptionMarker> for Option<T> {\n    fn display_query_argument(\n        &self,\n        query_name: &str,\n        f: &mut std::fmt::Formatter<'_>,\n    ) -> std::fmt::Result {\n        if let Some(value) = self {\n            write!(f, \"{}={}\", query_name, value)\n        } else {\n            Ok(())\n        }\n    }\n}\n\n/// A type that implements [`ToQueryArgument`] along with the query name. This type implements Display and can be used to format the query argument into a string.\npub struct DisplayQueryArgument<'a, T, M = ()> {\n    /// The query name.\n    query_name: &'a str,\n    /// The value to format.\n    value: &'a T,\n    /// The `ToQueryArgument` marker type, which can be used to differentiate between different types of query arguments.\n    _marker: std::marker::PhantomData<M>,\n}\n\nimpl<'a, T, M> DisplayQueryArgument<'a, T, M> {\n    /// Create a new `DisplayQueryArgument`.\n    pub fn new(query_name: &'a str, value: &'a T) -> Self {\n        Self {\n            query_name,\n            value,\n            _marker: std::marker::PhantomData,\n        }\n    }\n}\n\nimpl<T: ToQueryArgument<M>, M> Display for DisplayQueryArgument<'_, T, M> {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        self.value.display_query_argument(self.query_name, f)\n    }\n}\n\n/// Something that can be created from an entire hash fragment. This must be implemented for any type that is used as a hash fragment like `#[route(\"/#:hash_fragment\")]`.\n///\n///\n/// **This trait is automatically implemented for any types that implement `FromStr` and `Default`.**\n///\n/// # Example\n///\n/// ```rust\n/// use dioxus::prelude::*;\n/// use dioxus_router::FromHashFragment;\n///\n/// #[derive(Routable, Clone)]\n/// #[rustfmt::skip]\n/// enum Route {\n///     // State is stored in the url hash\n///     #[route(\"/#:url_hash\")]\n///     Home {\n///         url_hash: State,\n///     },\n/// }\n///\n/// #[component]\n/// fn Home(url_hash: State) -> Element {\n///     unimplemented!()\n/// }\n///\n///\n/// #[derive(Clone, PartialEq, Default)]\n/// struct State {\n///     count: usize,\n///     other_count: usize\n/// }\n///\n/// // The hash segment will be displayed as a string (this will be url encoded automatically)\n/// impl std::fmt::Display for State {\n///     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n///         write!(f, \"{}-{}\", self.count, self.other_count)\n///     }\n/// }\n///\n/// // We need to parse the hash fragment into a struct from the string (this will be url decoded automatically)\n/// impl FromHashFragment for State {\n///     fn from_hash_fragment(hash: &str) -> Self {\n///         let Some((first, second)) = hash.split_once('-') else {\n///             // URL fragment parsing shouldn't fail. You can return a default value if you want\n///             return Default::default();\n///         };\n///\n///         let first = first.parse().unwrap();\n///         let second = second.parse().unwrap();\n///\n///         State {\n///             count: first,\n///             other_count: second,\n///         }\n///     }\n/// }\n/// ```\n#[rustversion::attr(\n    since(1.78.0),\n    diagnostic::on_unimplemented(\n        message = \"`FromHashFragment` is not implemented for `{Self}`\",\n        label = \"hash fragment\",\n        note = \"FromHashFragment is automatically implemented for types that implement `FromStr` and `Default`. You need to either implement FromStr and Default or implement FromHashFragment manually.\"\n    )\n)]\npub trait FromHashFragment {\n    /// Create an instance of `Self` from a hash fragment.\n    fn from_hash_fragment(hash: &str) -> Self;\n}\n\nimpl<T> FromHashFragment for T\nwhere\n    T: FromStr + Default,\n    T::Err: std::fmt::Display,\n{\n    fn from_hash_fragment(hash: &str) -> Self {\n        match T::from_str(hash) {\n            Ok(value) => value,\n            Err(err) => {\n                tracing::error!(\"Failed to parse hash fragment: {}\", err);\n                Default::default()\n            }\n        }\n    }\n}\n\n/// Something that can be created from a single route segment. This must be implemented for any type that is used as a route segment like `#[route(\"/:route_segment\")]`.\n///\n///\n/// **This trait is automatically implemented for any types that implement `FromStr` and `Default`.**\n///\n/// ```rust\n/// use dioxus::prelude::*;\n///\n/// #[derive(Routable, Clone, PartialEq, Debug)]\n/// enum Route {\n///     // FromRouteSegment must be implemented for any types you use in the route segment\n///     // When you don't spread the route, you can parse multiple values from the route\n///     // This url will be in the format `/123/456`\n///     #[route(\"/:route_segment_one/:route_segment_two\")]\n///     Home {\n///         route_segment_one: CustomRouteSegment,\n///         route_segment_two: i32,\n///     },\n/// }\n///\n/// // We can derive Default for CustomRouteSegment\n/// // If the router fails to parse the route segment, it will use the default value instead\n/// #[derive(Default, PartialEq, Clone, Debug)]\n/// struct CustomRouteSegment {\n///     count: i32,\n/// }\n///\n/// // We implement FromStr for CustomRouteSegment so that FromRouteSegment is implemented automatically\n/// impl std::str::FromStr for CustomRouteSegment {\n///     type Err = <i32 as std::str::FromStr>::Err;\n///\n///     fn from_str(route_segment: &str) -> Result<Self, Self::Err> {\n///         Ok(CustomRouteSegment {\n///             count: route_segment.parse()?,\n///         })\n///     }\n/// }\n///\n/// // We also need to implement Display for CustomRouteSegment which will be used to format the route segment into the URL\n/// impl std::fmt::Display for CustomRouteSegment {\n///     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n///         write!(f, \"{}\", self.count)\n///     }\n/// }\n///\n/// # #[component]\n/// # fn Home(route_segment_one: CustomRouteSegment, route_segment_two: i32) -> Element {\n/// #     unimplemented!()\n/// # }\n/// ```\n#[rustversion::attr(\n    since(1.78.0),\n    diagnostic::on_unimplemented(\n        message = \"`FromRouteSegment` is not implemented for `{Self}`\",\n        label = \"route segment\",\n        note = \"FromRouteSegment is automatically implemented for types that implement `FromStr` and `Default`. You need to either implement FromStr and Default or implement FromRouteSegment manually.\"\n    )\n)]\npub trait FromRouteSegment: Sized {\n    /// The error that can occur when parsing a route segment.\n    type Err;\n\n    /// Create an instance of `Self` from a route segment.\n    fn from_route_segment(route: &str) -> Result<Self, Self::Err>;\n}\n\nimpl<T: FromStr> FromRouteSegment for T\nwhere\n    <T as FromStr>::Err: Display,\n{\n    type Err = <T as FromStr>::Err;\n\n    fn from_route_segment(route: &str) -> Result<Self, Self::Err> {\n        T::from_str(route)\n    }\n}\n\n#[test]\nfn full_circle() {\n    let route = \"testing 1234 hello world\";\n    assert_eq!(String::from_route_segment(route).unwrap(), route);\n}\n\n/// Something that can be converted into multiple route segments. This must be implemented for any type that is spread into the route segment like `#[route(\"/:..route_segments\")]`.\n///\n///\n/// **This trait is automatically implemented for any types that implement `IntoIterator<Item=impl Display>`.**\n///\n/// ```rust\n/// use dioxus::prelude::*;\n/// use dioxus_router::{ToRouteSegments, FromRouteSegments};\n///\n/// #[derive(Routable, Clone, PartialEq, Debug)]\n/// enum Route {\n///     // FromRouteSegments must be implemented for any types you use in the route segment\n///     // When you spread the route, you can parse multiple values from the route\n///     // This url will be in the format `/123/456/789`\n///     #[route(\"/:..numeric_route_segments\")]\n///     Home {\n///         numeric_route_segments: NumericRouteSegments,\n///     },\n/// }\n///\n/// // We can derive Default for NumericRouteSegments\n/// // If the router fails to parse the route segment, it will use the default value instead\n/// #[derive(Default, PartialEq, Clone, Debug)]\n/// struct NumericRouteSegments {\n///     numbers: Vec<i32>,\n/// }\n///\n/// // Implement ToRouteSegments for NumericRouteSegments so that we can display the route segments\n/// impl ToRouteSegments for NumericRouteSegments {\n///     fn display_route_segments(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n///         for number in &self.numbers {\n///             write!(f, \"/{}\", number)?;\n///         }\n///         Ok(())\n///     }\n/// }\n///\n/// // We also need to parse the route segments with `FromRouteSegments`\n/// impl FromRouteSegments for NumericRouteSegments {\n///     type Err = <i32 as std::str::FromStr>::Err;\n///\n///     fn from_route_segments(segments: &[&str]) -> Result<Self, Self::Err> {\n///         let mut numbers = Vec::new();\n///         for segment in segments {\n///             numbers.push(segment.parse()?);\n///         }\n///         Ok(NumericRouteSegments { numbers })\n///     }\n/// }\n///\n/// # #[component]\n/// # fn Home(numeric_route_segments: NumericRouteSegments) -> Element {\n/// #     unimplemented!()\n/// # }\n/// ```\npub trait ToRouteSegments {\n    /// Display the route segments with each route segment separated by a `/`. This should not start with a `/`.\n    ///\n    fn display_route_segments(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result;\n}\n\n// Implement ToRouteSegments for any type that can turn &self into an iterator of &T where T: Display\nimpl<I, T: Display> ToRouteSegments for I\nwhere\n    for<'a> &'a I: IntoIterator<Item = &'a T>,\n{\n    fn display_route_segments(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        for segment in self {\n            write!(f, \"/\")?;\n            let segment = segment.to_string();\n            let encoded =\n                percent_encoding::utf8_percent_encode(&segment, crate::query_sets::PATH_ASCII_SET);\n            write!(f, \"{}\", encoded)?;\n        }\n        Ok(())\n    }\n}\n\n#[test]\nfn to_route_segments() {\n    struct DisplaysRoute;\n\n    impl Display for DisplaysRoute {\n        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n            let segments = vec![\"hello\", \"world\"];\n            segments.display_route_segments(f)\n        }\n    }\n\n    assert_eq!(DisplaysRoute.to_string(), \"/hello/world\");\n}\n\n/// Something that can be created from multiple route segments. This must be implemented for any type that is spread into the route segment like `#[route(\"/:..route_segments\")]`.\n///\n///\n/// **This trait is automatically implemented for any types that implement `FromIterator<impl Display>`.**\n///\n/// ```rust\n/// use dioxus::prelude::*;\n/// use dioxus_router::{ToRouteSegments, FromRouteSegments};\n///\n/// #[derive(Routable, Clone, PartialEq, Debug)]\n/// enum Route {\n///     // FromRouteSegments must be implemented for any types you use in the route segment\n///     // When you spread the route, you can parse multiple values from the route\n///     // This url will be in the format `/123/456/789`\n///     #[route(\"/:..numeric_route_segments\")]\n///     Home {\n///         numeric_route_segments: NumericRouteSegments,\n///     },\n/// }\n///\n/// // We can derive Default for NumericRouteSegments\n/// // If the router fails to parse the route segment, it will use the default value instead\n/// #[derive(Default, Clone, PartialEq, Debug)]\n/// struct NumericRouteSegments {\n///     numbers: Vec<i32>,\n/// }\n///\n/// // Implement ToRouteSegments for NumericRouteSegments so that we can display the route segments\n/// impl ToRouteSegments for NumericRouteSegments {\n///     fn display_route_segments(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n///         for number in &self.numbers {\n///             write!(f, \"/{}\", number)?;\n///         }\n///         Ok(())\n///     }\n/// }\n///\n/// // We also need to parse the route segments with `FromRouteSegments`\n/// impl FromRouteSegments for NumericRouteSegments {\n///     type Err = <i32 as std::str::FromStr>::Err;\n///\n///     fn from_route_segments(segments: &[&str]) -> Result<Self, Self::Err> {\n///         let mut numbers = Vec::new();\n///         for segment in segments {\n///             numbers.push(segment.parse()?);\n///         }\n///         Ok(NumericRouteSegments { numbers })\n///     }\n/// }\n///\n/// # #[component]\n/// # fn Home(numeric_route_segments: NumericRouteSegments) -> Element {\n/// #     unimplemented!()\n/// # }\n/// ```\n#[rustversion::attr(\n    since(1.78.0),\n    diagnostic::on_unimplemented(\n        message = \"`FromRouteSegments` is not implemented for `{Self}`\",\n        label = \"spread route segments\",\n        note = \"FromRouteSegments is automatically implemented for types that implement `FromIterator` with an `Item` type that implements `Display`. You need to either implement FromIterator or implement FromRouteSegments manually.\"\n    )\n)]\npub trait FromRouteSegments: Sized {\n    /// The error that can occur when parsing route segments.\n    type Err: std::fmt::Display;\n\n    /// Create an instance of `Self` from route segments.\n    ///\n    /// NOTE: This method must parse the output of `ToRouteSegments::display_route_segments` into the type `Self`.\n    fn from_route_segments(segments: &[&str]) -> Result<Self, Self::Err>;\n}\n\nimpl<I: std::iter::FromIterator<String>> FromRouteSegments for I {\n    type Err = <String as FromRouteSegment>::Err;\n\n    fn from_route_segments(segments: &[&str]) -> Result<Self, Self::Err> {\n        segments\n            .iter()\n            .map(|s| String::from_route_segment(s))\n            .collect()\n    }\n}\n\n/// A flattened version of [`Routable::SITE_MAP`].\n/// This essentially represents a `Vec<Vec<SegmentType>>`, which you can collect it into.\ntype SiteMapFlattened<'a> = FlatMap<\n    Iter<'a, SiteMapSegment>,\n    Vec<Vec<SegmentType>>,\n    fn(&SiteMapSegment) -> Vec<Vec<SegmentType>>,\n>;\n\n/// The Routable trait is implemented for types that can be converted to and from a route and be rendered as a page.\n///\n/// A Routable object is something that can be:\n/// 1. Converted from a route.\n/// 2. Converted to a route.\n/// 3. Rendered as a component.\n///\n/// This trait should generally be derived using the [`dioxus_router_macro::Routable`] macro which has more information about the syntax.\n///\n/// ## Example\n/// ```rust\n/// use dioxus::prelude::*;\n///\n/// fn App() -> Element {\n///     rsx! {\n///         Router::<Route> { }\n///     }\n/// }\n///\n/// // Routes are generally enums that derive `Routable`\n/// #[derive(Routable, Clone, PartialEq, Debug)]\n/// enum Route {\n///     // Each enum has an associated url\n///     #[route(\"/\")]\n///     Home {},\n///     // Routes can include dynamic segments that are parsed from the url\n///     #[route(\"/blog/:blog_id\")]\n///     Blog { blog_id: usize },\n///     // Or query segments that are parsed from the url\n///     #[route(\"/edit?:blog_id\")]\n///     Edit { blog_id: usize },\n///     // Or hash segments that are parsed from the url\n///     #[route(\"/hashtag/#:hash\")]\n///     Hash { hash: String },\n/// }\n///\n/// // Each route variant defaults to rendering a component of the same name\n/// #[component]\n/// fn Home() -> Element {\n///     rsx! {\n///         h1 { \"Home\" }\n///     }\n/// }\n///\n/// // If the variant has dynamic parameters, those are passed to the component\n/// #[component]\n/// fn Blog(blog_id: usize) -> Element {\n///     rsx! {\n///         h1 { \"Blog\" }\n///     }\n/// }\n///\n/// #[component]\n/// fn Edit(blog_id: usize) -> Element {\n///     rsx! {\n///         h1 { \"Edit\" }\n///     }\n/// }\n///\n/// #[component]\n/// fn Hash(hash: String) -> Element {\n///     rsx! {\n///         h1 { \"Hashtag #{hash}\" }\n///     }\n/// }\n/// ```\n#[rustversion::attr(\n    since(1.78.0),\n    diagnostic::on_unimplemented(\n        message = \"`Routable` is not implemented for `{Self}`\",\n        label = \"Route\",\n        note = \"Routable should generally be derived using the `#[derive(Routable)]` macro.\"\n    )\n)]\npub trait Routable: FromStr<Err: Display> + Display + Clone + 'static {\n    /// The error that can occur when parsing a route.\n    const SITE_MAP: &'static [SiteMapSegment];\n\n    /// Render the route at the given level\n    fn render(&self, level: usize) -> Element;\n\n    /// Checks if this route is a child of the given route.\n    ///\n    /// # Example\n    /// ```rust\n    /// use dioxus::prelude::*;\n    ///\n    /// #[component]\n    /// fn Home() -> Element { VNode::empty() }\n    /// #[component]\n    /// fn About() -> Element { VNode::empty() }\n    ///\n    /// #[derive(Routable, Clone, PartialEq, Debug)]\n    /// enum Route {\n    ///     #[route(\"/\")]\n    ///     Home {},\n    ///     #[route(\"/about\")]\n    ///     About {},\n    /// }\n    ///\n    /// let route = Route::About {};\n    /// let parent = Route::Home {};\n    /// assert!(route.is_child_of(&parent));\n    /// ```\n    fn is_child_of(&self, other: &Self) -> bool {\n        let self_str = self.to_string();\n        let self_str = self_str\n            .split_once('#')\n            .map(|(route, _)| route)\n            .unwrap_or(&self_str);\n        let self_str = self_str\n            .split_once('?')\n            .map(|(route, _)| route)\n            .unwrap_or(self_str);\n        let self_str = self_str.trim_end_matches('/');\n        let other_str = other.to_string();\n        let other_str = other_str\n            .split_once('#')\n            .map(|(route, _)| route)\n            .unwrap_or(&other_str);\n        let other_str = other_str\n            .split_once('?')\n            .map(|(route, _)| route)\n            .unwrap_or(other_str);\n        let other_str = other_str.trim_end_matches('/');\n\n        let mut self_segments = self_str.split('/');\n        let mut other_segments = other_str.split('/');\n        loop {\n            match (self_segments.next(), other_segments.next()) {\n                // If the two routes are the same length, or this route has less segments, then this segment\n                // cannot be the child of the other segment\n                (None, Some(_)) | (None, None) => {\n                    return false;\n                }\n                // If two segments are not the same, then this segment cannot be the child of the other segment\n                (Some(self_seg), Some(other_seg)) => {\n                    if self_seg != other_seg {\n                        return false;\n                    }\n                }\n                // If the other route has less segments, then this route is the child of the other route\n                (Some(_), None) => break,\n            }\n        }\n        true\n    }\n\n    /// Get the parent route of this route.\n    ///\n    /// # Example\n    /// ```rust\n    /// use dioxus::prelude::*;\n    ///\n    /// #[component]\n    /// fn Home() -> Element { VNode::empty() }\n    /// #[component]\n    /// fn About() -> Element { VNode::empty() }\n    ///\n    /// #[derive(Routable, Clone, PartialEq, Debug)]\n    /// enum Route {\n    ///     #[route(\"/home\")]\n    ///     Home {},\n    ///     #[route(\"/home/about\")]\n    ///     About {},\n    /// }\n    ///\n    /// let route = Route::About {};\n    /// let parent = route.parent().unwrap();\n    /// assert_eq!(parent, Route::Home {});\n    /// ```\n    fn parent(&self) -> Option<Self> {\n        let as_str = self.to_string();\n        let (route_and_query, _) = as_str.split_once('#').unwrap_or((&as_str, \"\"));\n        let (route, _) = route_and_query\n            .split_once('?')\n            .unwrap_or((route_and_query, \"\"));\n        let route = route.trim_end_matches('/');\n        let segments = route.split_inclusive('/');\n        let segment_count = segments.clone().count();\n        let new_route: String = segments.take(segment_count.saturating_sub(1)).collect();\n        Self::from_str(&new_route).ok()\n    }\n\n    /// Returns a flattened version of [`Self::SITE_MAP`].\n    fn flatten_site_map<'a>() -> SiteMapFlattened<'a> {\n        Self::SITE_MAP.iter().flat_map(SiteMapSegment::flatten)\n    }\n\n    /// Gets a list of all the static routes.\n    /// Example static route: `#[route(\"/static/route\")]`\n    fn static_routes() -> Vec<Self> {\n        Self::flatten_site_map()\n            .filter_map(|segments| {\n                let mut route = String::new();\n                for segment in segments.iter() {\n                    match segment {\n                        SegmentType::Static(s) => {\n                            route.push('/');\n                            route.push_str(s)\n                        }\n                        SegmentType::Child => {}\n                        _ => return None,\n                    }\n                }\n\n                route.parse().ok()\n            })\n            .collect()\n    }\n}\n\n/// A type erased map of the site structure.\n#[derive(Debug, Clone, PartialEq)]\npub struct SiteMapSegment {\n    /// The type of the route segment.\n    pub segment_type: SegmentType,\n    /// The children of the route segment.\n    pub children: &'static [SiteMapSegment],\n}\n\nimpl SiteMapSegment {\n    /// Take a map of the site structure and flatten it into a vector of routes.\n    pub fn flatten(&self) -> Vec<Vec<SegmentType>> {\n        let mut routes = Vec::new();\n        self.flatten_inner(&mut routes, Vec::new());\n        routes\n    }\n\n    fn flatten_inner(&self, routes: &mut Vec<Vec<SegmentType>>, current: Vec<SegmentType>) {\n        let mut current = current;\n        current.push(self.segment_type.clone());\n        if self.children.is_empty() {\n            routes.push(current);\n        } else {\n            for child in self.children {\n                child.flatten_inner(routes, current.clone());\n            }\n        }\n    }\n}\n\n/// The type of a route segment.\n#[derive(Debug, Clone, PartialEq)]\n#[non_exhaustive]\npub enum SegmentType {\n    /// A static route segment.\n    Static(&'static str),\n    /// A dynamic route segment.\n    Dynamic(&'static str),\n    /// A catch all route segment.\n    CatchAll(&'static str),\n    /// A child router.\n    Child,\n}\n\nimpl SegmentType {\n    /// Try to convert this segment into a static segment.\n    pub fn to_static(&self) -> Option<&'static str> {\n        match self {\n            SegmentType::Static(s) => Some(*s),\n            _ => None,\n        }\n    }\n\n    /// Try to convert this segment into a dynamic segment.\n    pub fn to_dynamic(&self) -> Option<&'static str> {\n        match self {\n            SegmentType::Dynamic(s) => Some(*s),\n            _ => None,\n        }\n    }\n\n    /// Try to convert this segment into a catch all segment.\n    pub fn to_catch_all(&self) -> Option<&'static str> {\n        match self {\n            SegmentType::CatchAll(s) => Some(*s),\n            _ => None,\n        }\n    }\n\n    /// Try to convert this segment into a child segment.\n    pub fn to_child(&self) -> Option<()> {\n        match self {\n            SegmentType::Child => Some(()),\n            _ => None,\n        }\n    }\n}\n\nimpl Display for SegmentType {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        match &self {\n            SegmentType::Static(s) => write!(f, \"/{}\", s),\n            SegmentType::Child => Ok(()),\n            SegmentType::Dynamic(s) => write!(f, \"/:{}\", s),\n            SegmentType::CatchAll(s) => write!(f, \"/:..{}\", s),\n        }\n    }\n}\n\n// /// Routable is implemented for String to allow stringly-typed apps.\n// impl Routable for String {\n//     const SITE_MAP: &'static [SiteMapSegment] = &[];\n\n//     #[doc = \" Render the route at the given level\"]\n//     fn render(&self, _level: usize) -> Element {\n//         unimplemented!(\"String routes cannot be rendered as components\")\n//     }\n// }\n"
  },
  {
    "path": "packages/router/src/router_cfg.rs",
    "content": "use crate::{GenericRouterContext, NavigationTarget, Routable, RoutingCallback};\nuse dioxus_core::Element;\nuse std::sync::Arc;\n\n/// Global configuration options for the router.\n///\n/// This implements [`Default`] and follows the builder pattern, so you can use it like this:\n/// ```rust,no_run\n/// # use dioxus::prelude::*;\n/// # use dioxus_router::RouterConfig;\n/// # #[component]\n/// # fn Index() -> Element {\n/// #     VNode::empty()\n/// # }\n/// #[derive(Clone, Routable)]\n/// enum Route {\n///     #[route(\"/\")]\n///     Index {},\n/// }\n///\n/// fn ExternalNavigationFailure() -> Element {\n///     rsx! {\n///         \"Failed to navigate to external URL\"\n///     }\n/// }\n///\n/// let cfg = RouterConfig::<Route>::default().failure_external_navigation(ExternalNavigationFailure);\n/// ```\npub struct RouterConfig<R> {\n    pub(crate) failure_external_navigation: fn() -> Element,\n    pub(crate) on_update: Option<RoutingCallback<R>>,\n}\n\n#[cfg(not(feature = \"html\"))]\nimpl<R> Default for RouterConfig<R> {\n    fn default() -> Self {\n        Self {\n            failure_external_navigation: || VNode::empty(),\n            on_update: None,\n        }\n    }\n}\n\n#[cfg(feature = \"html\")]\nimpl<R> Default for RouterConfig<R> {\n    fn default() -> Self {\n        Self {\n            failure_external_navigation: crate::components::FailureExternalNavigation,\n            on_update: None,\n        }\n    }\n}\n\nimpl<R> RouterConfig<R>\nwhere\n    R: Routable,\n{\n    /// A function to be called whenever the routing is updated.\n    ///\n    /// The callback is invoked after the routing is updated, but before components and hooks are\n    /// updated.\n    ///\n    /// If the callback returns a [`NavigationTarget`] the router will replace the current location\n    /// with it. If no navigation failure was triggered, the router will then updated dependent\n    /// components and hooks.\n    ///\n    /// The callback is called no more than once per rerouting. It will not be called if a\n    /// navigation failure occurs.\n    ///\n    /// Defaults to [`None`].\n    pub fn on_update(\n        self,\n        callback: impl Fn(GenericRouterContext<R>) -> Option<NavigationTarget<R>> + 'static,\n    ) -> Self {\n        Self {\n            on_update: Some(Arc::new(callback)),\n            ..self\n        }\n    }\n\n    /// A component to render when an external navigation fails.\n    ///\n    #[cfg_attr(\n        feature = \"html\",\n        doc = \"Defaults to [`crate::components::FailureExternalNavigation`].\"\n    )]\n    pub fn failure_external_navigation(self, component: fn() -> Element) -> Self {\n        Self {\n            failure_external_navigation: component,\n            ..self\n        }\n    }\n}\n"
  },
  {
    "path": "packages/router/src/utils/use_router_internal.rs",
    "content": "use crate::RouterContext;\nuse dioxus_core::{try_consume_context, use_hook};\n\n/// A private hook to subscribe to the router.\n///\n/// Used to reduce redundancy within other components/hooks. Safe to call multiple times for a\n/// single component, but not recommended. Multiple subscriptions will be discarded.\n///\n/// # Return values\n/// - [`None`], when the current component isn't a descendant of a [`crate::Router`] component.\n/// - Otherwise [`Some`].\npub(crate) fn use_router_internal() -> Option<RouterContext> {\n    use_hook(try_consume_context)\n}\n"
  },
  {
    "path": "packages/router/tests/parent.rs",
    "content": "#![allow(unused)]\n\nuse std::rc::Rc;\n\nuse dioxus::prelude::*;\n\n#[derive(Routable, Clone, PartialEq, Debug)]\n#[rustfmt::skip]\nenum Route {\n    #[route(\"/\")]\n    RootIndex {},\n    #[nest(\"/fixed\")]\n        #[layout(Fixed)]\n            #[route(\"/\")]\n            FixedIndex {},\n            #[route(\"/fixed\")]\n            FixedFixed {},\n        #[end_layout]\n    #[end_nest]\n    #[nest(\"/:id\")]\n        #[layout(Parameter)]\n            #[route(\"/\")]\n            ParameterIndex { id: u8 },\n            #[route(\"/fixed\")]\n            ParameterFixed { id: u8 },\n        #[end_layout]\n    #[end_nest]\n    #[nest(\"/hash\")]\n        #[route(\"/\")]\n        HashIndex {},\n        #[nest(\"/:id\")]\n            #[route(\"/?:query\")]\n            HashId { id: u8, query: String },\n            #[layout(Parameter)]\n                #[route(\"/path/?:query#:hash\")]\n                HashQuery { id: u8, query: String, hash: String },\n}\n\n#[test]\nfn get_parent() {\n    assert_eq!(Route::RootIndex {}.parent(), None);\n    assert_eq!(Route::FixedIndex {}.parent(), Some(Route::RootIndex {}));\n    assert_eq!(Route::FixedFixed {}.parent(), Some(Route::FixedIndex {}));\n    assert_eq!(\n        Route::ParameterIndex { id: 0 }.parent(),\n        Some(Route::RootIndex {})\n    );\n    assert_eq!(\n        Route::ParameterFixed { id: 0 }.parent(),\n        Some(Route::ParameterIndex { id: 0 })\n    );\n    assert_eq!(\n        Route::HashQuery {\n            id: 0,\n            query: \"query\".into(),\n            hash: \"hash\".into()\n        }\n        .parent(),\n        Some(Route::HashId {\n            id: 0,\n            query: \"\".into()\n        })\n    );\n    assert_eq!(\n        Route::HashId {\n            id: 0,\n            query: \"query\".into()\n        }\n        .parent(),\n        Some(Route::HashIndex {})\n    );\n    assert_eq!(Route::HashIndex {}.parent(), Some(Route::RootIndex {}));\n}\n\n#[test]\nfn is_child() {\n    assert!(!Route::RootIndex {}.is_child_of(&Route::RootIndex {}));\n    assert!(Route::FixedIndex {}.is_child_of(&Route::RootIndex {}));\n    assert!(!Route::FixedIndex {}.is_child_of(&Route::FixedIndex {}));\n    assert!(Route::FixedFixed {}.is_child_of(&Route::FixedIndex {}));\n    assert!(!Route::FixedFixed {}.is_child_of(&Route::FixedFixed {}));\n    assert!(Route::ParameterIndex { id: 0 }.is_child_of(&Route::RootIndex {}));\n    assert!(!Route::ParameterIndex { id: 0 }.is_child_of(&Route::ParameterIndex { id: 0 }));\n    assert!(Route::ParameterFixed { id: 0 }.is_child_of(&Route::ParameterIndex { id: 0 }));\n    assert!(!Route::ParameterFixed { id: 0 }.is_child_of(&Route::ParameterFixed { id: 0 }));\n    assert!(Route::HashQuery {\n        id: 0,\n        query: \"query\".into(),\n        hash: \"hash\".into()\n    }\n    .is_child_of(&Route::HashId {\n        id: 0,\n        query: \"query\".into()\n    }));\n    assert!(!Route::HashQuery {\n        id: 0,\n        query: \"query\".into(),\n        hash: \"hash\".into()\n    }\n    .is_child_of(&Route::HashQuery {\n        id: 0,\n        query: \"query\".into(),\n        hash: \"hash\".into()\n    }));\n    assert!(Route::HashId {\n        id: 0,\n        query: \"query\".into()\n    }\n    .is_child_of(&Route::HashIndex {}));\n    assert!(!Route::HashId {\n        id: 0,\n        query: \"query\".into()\n    }\n    .is_child_of(&Route::HashId {\n        id: 0,\n        query: \"query\".into()\n    }));\n    assert!(Route::HashIndex {}.is_child_of(&Route::RootIndex {}));\n    assert!(!Route::HashIndex {}.is_child_of(&Route::HashIndex {}));\n}\n\n#[component]\nfn RootIndex() -> Element {\n    rsx! { h2 { \"Root Index\" } }\n}\n\n#[component]\nfn Fixed() -> Element {\n    rsx! {\n        h2 { \"Fixed\" }\n        Outlet::<Route> { }\n    }\n}\n\n#[component]\nfn FixedIndex() -> Element {\n    rsx! { h3 { \"Fixed - Index\" } }\n}\n\n#[component]\nfn FixedFixed() -> Element {\n    rsx! { h3 { \"Fixed - Fixed\"} }\n}\n\n#[component]\nfn Parameter(id: u8) -> Element {\n    rsx! {\n        h2 { \"Parameter {id}\" }\n        Outlet::<Route> { }\n    }\n}\n\n#[component]\nfn ParameterIndex(id: u8) -> Element {\n    rsx! { h3 { \"Parameter - Index\" } }\n}\n\n#[component]\nfn ParameterFixed(id: u8) -> Element {\n    rsx! { h3 { \"Parameter - Fixed\" } }\n}\n\n#[component]\nfn HashQuery(id: u8, query: String, hash: String) -> Element {\n    rsx! {\n        h2 { \"Hash Query\" }\n        h3 { \"id: {id}\" }\n        h3 { \"query: {query}\" }\n        h3 { \"hash: {hash}\" }\n    }\n}\n\n#[component]\nfn HashIndex() -> Element {\n    rsx! { h3 { \"Hash Index\" } }\n}\n\n#[component]\nfn HashId(id: u8, query: String) -> Element {\n    rsx! {\n        h3 { \"Hash Id {id}\" }\n        h3 { \"query: {query}\" }\n    }\n}\n"
  },
  {
    "path": "packages/router/tests/parsing.rs",
    "content": "use dioxus::prelude::*;\nuse std::{\n    fmt::{self, Display},\n    str::FromStr,\n};\n\n#[component]\nfn Root() -> Element {\n    unimplemented!()\n}\n\n#[component]\nfn Test() -> Element {\n    unimplemented!()\n}\n\n#[component]\nfn Dynamic(id: usize) -> Element {\n    unimplemented!()\n}\n\n// Make sure trailing '/'s work correctly\n#[test]\nfn trailing_slashes_parse() {\n    #[derive(Routable, Clone, Copy, PartialEq, Debug)]\n    enum Route {\n        #[route(\"/\")]\n        Root {},\n        #[route(\"/test/\")]\n        Test {},\n        #[route(\"/:id/test/\")]\n        Dynamic { id: usize },\n    }\n\n    assert_eq!(Route::from_str(\"/\").unwrap(), Route::Root {});\n    assert_eq!(Route::from_str(\"/test/\").unwrap(), Route::Test {});\n    assert_eq!(Route::from_str(\"/test\").unwrap(), Route::Test {});\n    assert_eq!(\n        Route::from_str(\"/123/test/\").unwrap(),\n        Route::Dynamic { id: 123 }\n    );\n    assert_eq!(\n        Route::from_str(\"/123/test\").unwrap(),\n        Route::Dynamic { id: 123 }\n    );\n}\n\n#[test]\nfn without_trailing_slashes_parse() {\n    #[derive(Routable, Clone, Copy, PartialEq, Debug)]\n    enum RouteWithoutTrailingSlash {\n        #[route(\"/\")]\n        Root {},\n        #[route(\"/test\")]\n        Test {},\n        #[route(\"/:id/test\")]\n        Dynamic { id: usize },\n    }\n\n    assert_eq!(\n        RouteWithoutTrailingSlash::from_str(\"/\").unwrap(),\n        RouteWithoutTrailingSlash::Root {}\n    );\n    assert_eq!(\n        RouteWithoutTrailingSlash::from_str(\"/test/\").unwrap(),\n        RouteWithoutTrailingSlash::Test {}\n    );\n    assert_eq!(\n        RouteWithoutTrailingSlash::from_str(\"/test\").unwrap(),\n        RouteWithoutTrailingSlash::Test {}\n    );\n    assert_eq!(\n        RouteWithoutTrailingSlash::from_str(\"/123/test/\").unwrap(),\n        RouteWithoutTrailingSlash::Dynamic { id: 123 }\n    );\n    assert_eq!(\n        RouteWithoutTrailingSlash::from_str(\"/123/test\").unwrap(),\n        RouteWithoutTrailingSlash::Dynamic { id: 123 }\n    );\n}\n\n// Regression test for https://github.com/DioxusLabs/dioxus/issues/2984\n#[test]\nfn query_segments_parse() {\n    #[derive(Debug, Clone, PartialEq)]\n    enum Query {\n        Id(u64),\n    }\n\n    impl From<&str> for Query {\n        fn from(_: &str) -> Self {\n            // e.g. split query on `&` and split pairs on `=`\n            Query::Id(10)\n        }\n    }\n\n    impl Display for Query {\n        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {\n            write!(f, \"id=10\")\n        }\n    }\n\n    #[component]\n    fn Index(query: Query) -> Element {\n        rsx! {\n            h1 { \"Index\" }\n        }\n    }\n\n    #[derive(Debug, Clone, PartialEq, Routable)]\n    enum Route {\n        #[route(\"/?:..query\")]\n        Index { query: Query },\n    }\n\n    let route = Route::Index {\n        query: Query::Id(10),\n    };\n    assert_eq!(route.to_string(), \"/?id=10\");\n    let parsed_route = \"/?id=10\".parse::<Route>().unwrap();\n    assert_eq!(parsed_route, route);\n}\n\n#[test]\nfn optional_query_segments_parse() {\n    #[derive(Debug, Clone, PartialEq, Routable)]\n    enum Route {\n        #[route(\"/?:query&:other\")]\n        Index { query: Option<u64>, other: u64 },\n    }\n\n    #[component]\n    fn Index(query: Option<u64>, other: u64) -> Element {\n        rsx! {\n            h1 { \"Index\" }\n        }\n    }\n\n    let route = Route::Index {\n        query: Some(10),\n        other: 20,\n    };\n    assert_eq!(route.to_string(), \"/?query=10&other=20\");\n    let parsed_route = \"/?query=10&other=20\".parse::<Route>().unwrap();\n    assert_eq!(parsed_route, route);\n\n    let route_without_query = Route::Index {\n        query: None,\n        other: 20,\n    };\n    assert_eq!(route_without_query.to_string(), \"/?other=20\");\n    let parsed_route_without_query = \"/?other=20\".parse::<Route>().unwrap();\n    assert_eq!(parsed_route_without_query, route_without_query);\n    let route_without_query_and_other = Route::Index {\n        query: None,\n        other: 0,\n    };\n    assert_eq!(route_without_query_and_other.to_string(), \"/?other=0\");\n    let parsed_route_without_query_and_other = \"/\".parse::<Route>().unwrap();\n    assert_eq!(\n        parsed_route_without_query_and_other,\n        route_without_query_and_other\n    );\n}\n"
  },
  {
    "path": "packages/router/tests/site_map.rs",
    "content": "use dioxus::prelude::*;\n\n#[test]\nfn with_class() {\n    #[derive(Routable, Clone, PartialEq, Debug)]\n    enum ChildRoute {\n        #[route(\"/\")]\n        ChildRoot {},\n        #[route(\"/:not_static\")]\n        NotStatic { not_static: String },\n    }\n\n    #[derive(Routable, Clone, PartialEq, Debug)]\n    enum Route {\n        #[route(\"/\")]\n        Root {},\n        #[route(\"/test\")]\n        Test {},\n        #[child(\"/child\")]\n        Nested { child: ChildRoute },\n    }\n\n    #[component]\n    fn Test() -> Element {\n        unimplemented!()\n    }\n\n    #[component]\n    fn Root() -> Element {\n        unimplemented!()\n    }\n\n    #[component]\n    fn ChildRoot() -> Element {\n        unimplemented!()\n    }\n\n    #[component]\n    fn NotStatic(not_static: String) -> Element {\n        unimplemented!()\n    }\n\n    assert_eq!(\n        Route::static_routes(),\n        vec![\n            Route::Root {},\n            Route::Test {},\n            Route::Nested {\n                child: ChildRoute::ChildRoot {}\n            },\n        ],\n    );\n}\n"
  },
  {
    "path": "packages/router/tests/via_ssr/child_outlet.rs",
    "content": "#![allow(unused)]\n\nuse std::rc::Rc;\n\nuse dioxus::prelude::*;\nuse dioxus_history::{History, MemoryHistory};\nuse dioxus_router::components::HistoryProvider;\n\nfn prepare(path: impl Into<String>) -> VirtualDom {\n    let mut vdom = VirtualDom::new_with_props(\n        App,\n        AppProps {\n            path: path.into().parse().unwrap(),\n        },\n    );\n    vdom.rebuild_in_place();\n    return vdom;\n\n    #[derive(Routable, Clone, PartialEq)]\n    #[rustfmt::skip]\n    enum Route {\n        #[layout(Layout)]\n            #[child(\"/\")]\n            Child { child: ChildRoute },\n    }\n\n    #[derive(Routable, Clone, PartialEq)]\n    #[rustfmt::skip]\n    enum ChildRoute{\n        #[layout(ChildLayout)]\n            #[route(\"/\")]\n            RootIndex {}\n    }\n\n    #[component]\n    fn App(path: Route) -> Element {\n        rsx! {\n            h1 { \"App\" }\n            HistoryProvider {\n                history:  move |_| Rc::new(MemoryHistory::with_initial_path(path.clone())) as Rc<dyn History>,\n                Router::<Route> {}\n            }\n        }\n    }\n\n    #[component]\n    fn RootIndex() -> Element {\n        rsx! { h2 { \"Root Index\" } }\n    }\n\n    #[component]\n    fn Layout() -> Element {\n        rsx! {\n            h2 { \"parent layout\" }\n            Outlet::<Route> { }\n        }\n    }\n\n    #[component]\n    fn ChildLayout() -> Element {\n        rsx! {\n            h2 { \"child layout\" }\n            Outlet::<ChildRoute> { }\n        }\n    }\n}\n\n#[test]\nfn root_index() {\n    let vdom = prepare(\"/\");\n    let html = dioxus_ssr::render(&vdom);\n\n    assert_eq!(\n        html,\n        \"<h1>App</h1><h2>parent layout</h2><h2>child layout</h2><h2>Root Index</h2>\"\n    );\n}\n"
  },
  {
    "path": "packages/router/tests/via_ssr/link.rs",
    "content": "use dioxus::prelude::*;\nuse dioxus_history::{History, MemoryHistory};\nuse dioxus_router::components::HistoryProvider;\nuse std::rc::Rc;\n\nfn prepare<R: Routable>() -> String {\n    prepare_at::<R>(\"/\")\n}\n\nfn prepare_at<R: Routable>(at: impl ToString) -> String {\n    prepare_at_with_base_path::<R>(at, \"\")\n}\n\nfn prepare_at_with_base_path<R: Routable>(at: impl ToString, base_path: impl ToString) -> String {\n    let mut vdom = VirtualDom::new_with_props(\n        App,\n        AppProps::<R> {\n            at: at.to_string(),\n            base_path: base_path.to_string(),\n            phantom: std::marker::PhantomData,\n        },\n    );\n    vdom.rebuild_in_place();\n    return dioxus_ssr::render(&vdom);\n\n    #[derive(Props)]\n    struct AppProps<R: Routable> {\n        at: String,\n        base_path: String,\n        phantom: std::marker::PhantomData<R>,\n    }\n\n    impl<R: Routable> Clone for AppProps<R> {\n        fn clone(&self) -> Self {\n            Self {\n                at: self.at.clone(),\n                base_path: self.base_path.clone(),\n                phantom: std::marker::PhantomData,\n            }\n        }\n    }\n\n    impl<R: Routable> PartialEq for AppProps<R> {\n        fn eq(&self, _other: &Self) -> bool {\n            false\n        }\n    }\n\n    #[allow(non_snake_case)]\n    fn App<R: Routable>(props: AppProps<R>) -> Element {\n        rsx! {\n            h1 { \"App\" }\n            HistoryProvider {\n                history:  move |_| Rc::new(MemoryHistory::with_initial_path(props.at.clone()).with_prefix(props.base_path.clone())) as Rc<dyn History>,\n                Router::<R> {}\n            }\n        }\n    }\n}\n\n#[test]\nfn href_internal() {\n    #[derive(Routable, Clone)]\n    enum Route {\n        #[route(\"/\")]\n        Root {},\n        #[route(\"/test\")]\n        Test {},\n    }\n\n    #[component]\n    fn Test() -> Element {\n        unimplemented!()\n    }\n\n    #[component]\n    fn Root() -> Element {\n        rsx! {\n            Link {\n                to: Route::Test {},\n                \"Link\"\n            }\n        }\n    }\n\n    let expected = format!(\"<h1>App</h1><a {href}>Link</a>\", href = r#\"href=\"/test\"\"#,);\n\n    assert_eq!(prepare::<Route>(), expected);\n\n    // The base path should be added to the front of internal links\n    let base_path = \"/deeply/nested/path\";\n    let expected = format!(\n        \"<h1>App</h1><a {href}>Link</a>\",\n        href = r#\"href=\"/deeply/nested/path/test\"\"#,\n    );\n\n    assert_eq!(prepare_at_with_base_path::<Route>(\"/\", base_path), expected);\n}\n\n#[test]\nfn href_external() {\n    #[derive(Routable, Clone)]\n    enum Route {\n        #[route(\"/\")]\n        Root {},\n        #[route(\"/test\")]\n        Test {},\n    }\n\n    #[component]\n    fn Test() -> Element {\n        unimplemented!()\n    }\n\n    #[component]\n    fn Root() -> Element {\n        rsx! {\n            Link {\n                to: \"https://dioxuslabs.com/\",\n                \"Link\"\n            }\n        }\n    }\n\n    let expected = format!(\n        \"<h1>App</h1><a {href} {rel}>Link</a>\",\n        href = r#\"href=\"https://dioxuslabs.com/\"\"#,\n        rel = r#\"rel=\"noopener noreferrer\"\"#,\n    );\n\n    assert_eq!(prepare::<Route>(), expected);\n    // The base path should not effect external links\n    assert_eq!(\n        prepare_at_with_base_path::<Route>(\"/\", \"/deeply/nested/path\"),\n        expected\n    );\n}\n\n#[test]\nfn with_class() {\n    #[derive(Routable, Clone)]\n    enum Route {\n        #[route(\"/\")]\n        Root {},\n        #[route(\"/test\")]\n        Test {},\n    }\n\n    #[component]\n    fn Test() -> Element {\n        unimplemented!()\n    }\n\n    #[component]\n    fn Root() -> Element {\n        rsx! {\n            Link {\n                to: Route::Test {},\n                class: \"test_class\",\n                \"Link\"\n            }\n        }\n    }\n\n    let expected = format!(\n        \"<h1>App</h1><a {href} {class}>Link</a>\",\n        href = r#\"href=\"/test\"\"#,\n        class = r#\"class=\"test_class\"\"#,\n    );\n\n    assert_eq!(prepare::<Route>(), expected);\n}\n\n#[test]\nfn with_active_class_active() {\n    #[derive(Routable, Clone)]\n    enum Route {\n        #[route(\"/\")]\n        Root {},\n    }\n\n    #[component]\n    fn Root() -> Element {\n        rsx! {\n            Link {\n                to: Route::Root {},\n                active_class: \"active_class\".to_string(),\n                class: \"test_class\",\n                \"Link\"\n            }\n        }\n    }\n\n    let expected = format!(\n        \"<h1>App</h1><a {href} {class} {aria}>Link</a>\",\n        href = r#\"href=\"/\"\"#,\n        class = r#\"class=\"test_class active_class\"\"#,\n        aria = r#\"aria-current=\"page\"\"#,\n    );\n\n    assert_eq!(prepare::<Route>(), expected);\n}\n\n#[test]\nfn with_active_class_inactive() {\n    #[derive(Routable, Clone)]\n    enum Route {\n        #[route(\"/\")]\n        Root {},\n        #[route(\"/test\")]\n        Test {},\n    }\n\n    #[component]\n    fn Test() -> Element {\n        unimplemented!()\n    }\n\n    #[component]\n    fn Root() -> Element {\n        rsx! {\n            Link {\n                to: Route::Test {},\n                active_class: \"active_class\".to_string(),\n                class: \"test_class\",\n                \"Link\"\n            }\n        }\n    }\n\n    let expected = format!(\n        \"<h1>App</h1><a {href} {class}>Link</a>\",\n        href = r#\"href=\"/test\"\"#,\n        class = r#\"class=\"test_class\"\"#,\n    );\n\n    assert_eq!(prepare::<Route>(), expected);\n}\n\n#[test]\nfn with_id() {\n    #[derive(Routable, Clone)]\n    enum Route {\n        #[route(\"/\")]\n        Root {},\n        #[route(\"/test\")]\n        Test {},\n    }\n\n    #[component]\n    fn Test() -> Element {\n        unimplemented!()\n    }\n\n    #[component]\n    fn Root() -> Element {\n        rsx! {\n            Link {\n                to: Route::Test {},\n                id: \"test_id\",\n                \"Link\"\n            }\n        }\n    }\n\n    let expected = format!(\n        \"<h1>App</h1><a {href} {id}>Link</a>\",\n        href = r#\"href=\"/test\"\"#,\n        id = r#\"id=\"test_id\"\"#,\n    );\n\n    assert_eq!(prepare::<Route>(), expected);\n}\n\n#[test]\nfn with_new_tab() {\n    #[derive(Routable, Clone)]\n    enum Route {\n        #[route(\"/\")]\n        Root {},\n        #[route(\"/test\")]\n        Test {},\n    }\n\n    #[component]\n    fn Test() -> Element {\n        unimplemented!()\n    }\n\n    #[component]\n    fn Root() -> Element {\n        rsx! {\n            Link {\n                to: Route::Test {},\n                new_tab: true,\n                \"Link\"\n            }\n        }\n    }\n\n    let expected = format!(\n        \"<h1>App</h1><a {href} {target}>Link</a>\",\n        href = r#\"href=\"/test\"\"#,\n        target = r#\"target=\"_blank\"\"#\n    );\n\n    assert_eq!(prepare::<Route>(), expected);\n}\n\n#[test]\nfn with_new_tab_external() {\n    #[derive(Routable, Clone)]\n    enum Route {\n        #[route(\"/\")]\n        Root {},\n    }\n\n    #[component]\n    fn Root() -> Element {\n        rsx! {\n            Link {\n                to: \"https://dioxuslabs.com/\",\n                new_tab: true,\n                \"Link\"\n            }\n        }\n    }\n\n    let expected = format!(\n        \"<h1>App</h1><a {href} {rel} {target}>Link</a>\",\n        href = r#\"href=\"https://dioxuslabs.com/\"\"#,\n        rel = r#\"rel=\"noopener noreferrer\"\"#,\n        target = r#\"target=\"_blank\"\"#\n    );\n\n    assert_eq!(prepare::<Route>(), expected);\n}\n\n#[test]\nfn with_rel() {\n    #[derive(Routable, Clone)]\n    enum Route {\n        #[route(\"/\")]\n        Root {},\n        #[route(\"/test\")]\n        Test {},\n    }\n\n    #[component]\n    fn Test() -> Element {\n        unimplemented!()\n    }\n\n    #[component]\n    fn Root() -> Element {\n        rsx! {\n            Link {\n                to: Route::Test {},\n                rel: \"test_rel\".to_string(),\n                \"Link\"\n            }\n        }\n    }\n\n    let expected = format!(\n        \"<h1>App</h1><a {href} {rel}>Link</a>\",\n        href = r#\"href=\"/test\"\"#,\n        rel = r#\"rel=\"test_rel\"\"#,\n    );\n\n    assert_eq!(prepare::<Route>(), expected);\n}\n\n#[test]\nfn with_child_route() {\n    #[derive(Routable, Clone, PartialEq, Debug)]\n    enum ChildRoute {\n        #[route(\"/\")]\n        ChildRoot {},\n        #[route(\"/:not_static\")]\n        NotStatic { not_static: String },\n    }\n\n    #[derive(Routable, Clone, PartialEq, Debug)]\n    enum Route {\n        #[route(\"/\")]\n        Root {},\n        #[route(\"/test\")]\n        Test {},\n        #[child(\"/child\")]\n        Nested { child: ChildRoute },\n    }\n\n    #[component]\n    fn Test() -> Element {\n        unimplemented!()\n    }\n\n    #[component]\n    fn Root() -> Element {\n        rsx! {\n            Link {\n                to: Route::Test {},\n                \"Parent Link\"\n            }\n            Link {\n                to: Route::Nested { child: ChildRoute::NotStatic { not_static: \"this-is-a-child-route\".to_string() } },\n                \"Child Link\"\n            }\n        }\n    }\n\n    #[component]\n    fn ChildRoot() -> Element {\n        rsx! {\n            Link {\n                to: Route::Test {},\n                \"Parent Link\"\n            }\n            Link {\n                to: ChildRoute::NotStatic { not_static: \"this-is-a-child-route\".to_string() },\n                \"Child Link 1\"\n            }\n            Link {\n                to: Route::Nested { child: ChildRoute::NotStatic { not_static: \"this-is-a-child-route\".to_string() } },\n                \"Child Link 2\"\n            }\n        }\n    }\n\n    #[component]\n    fn NotStatic(not_static: String) -> Element {\n        unimplemented!()\n    }\n\n    assert_eq!(\n        prepare_at::<Route>(\"/\"),\n        \"<h1>App</h1><a href=\\\"/test\\\">Parent Link</a><a href=\\\"/child/this-is-a-child-route\\\">Child Link</a>\"\n    );\n\n    assert_eq!(\n        prepare_at::<Route>(\"/child\"),\n        \"<h1>App</h1><a href=\\\"/test\\\">Parent Link</a><a href=\\\"/child/this-is-a-child-route\\\">Child Link 1</a><a href=\\\"/child/this-is-a-child-route\\\">Child Link 2</a>\"\n    );\n}\n\n#[test]\nfn with_hash_segment() {\n    #[derive(Routable, Clone)]\n    enum Route {\n        #[route(\"/#:data\")]\n        Root { data: String },\n    }\n\n    #[component]\n    fn Root(data: String) -> Element {\n        rsx! {\n            Link {\n                to: Route::Root { data: \"test\".to_string() },\n                \"Link\"\n            }\n            Link {\n                to: Route::Root { data: \"\".to_string() },\n                \"Empty\"\n            }\n        }\n    }\n\n    assert_eq!(\n        prepare_at::<Route>(\"/#test\"),\n        \"<h1>App</h1><a href=\\\"/#test\\\" aria-current=\\\"page\\\">Link</a><a href=\\\"/\\\">Empty</a>\"\n    );\n}\n"
  },
  {
    "path": "packages/router/tests/via_ssr/main.rs",
    "content": "mod child_outlet;\nmod link;\nmod navigation;\nmod outlet;\nmod redirect;\nmod without_index;\n"
  },
  {
    "path": "packages/router/tests/via_ssr/navigation.rs",
    "content": "use dioxus::prelude::*;\nuse dioxus_core::NoOpMutations;\nuse std::sync::atomic::AtomicUsize;\n\n// Regression test for <https://github.com/DioxusLabs/dioxus/issues/3235>\n#[test]\nfn layout_retains_state_after_navigation() {\n    let mut vdom = VirtualDom::new(app);\n    vdom.rebuild_in_place();\n\n    vdom.render_immediate(&mut NoOpMutations);\n    let as_string = dioxus_ssr::render(&vdom);\n    assert_eq!(as_string, \"Other\");\n}\n\nfn app() -> Element {\n    rsx! {\n        Router::<Route> {}\n    }\n}\n\n// Turn off rustfmt since we're doing layouts and routes in the same enum\n#[derive(Routable, Clone, Debug, PartialEq)]\n#[rustfmt::skip]\nenum Route {\n    // Wrap Home in a Navbar Layout\n    #[layout(NavBar)]\n        // The default route is always \"/\" unless otherwise specified\n        #[route(\"/\")]\n        Home {},\n            \n        #[route(\"/other\")]\n        Other {},\n}\n\n#[component]\nfn NavBar() -> Element {\n    static NAVBARS_CREATED: AtomicUsize = AtomicUsize::new(0);\n    use_hook(|| {\n        let navbars_created = NAVBARS_CREATED.fetch_add(1, std::sync::atomic::Ordering::Relaxed);\n        println!(\"creating navbar #{navbars_created}\");\n        if navbars_created > 0 {\n            panic!(\"layouts should not be recreated when switching between two routes under the nav bar\");\n        }\n    });\n\n    // Queue an effect to navigate to the other route after rebuild_in_place\n    use_effect(|| {\n        router().push(Route::Other {});\n    });\n\n    rsx! {\n        Outlet::<Route> {}\n    }\n}\n\n#[component]\nfn Home() -> Element {\n    rsx! {\n        \"Home!\"\n    }\n}\n\n#[component]\nfn Other() -> Element {\n    rsx! {\n        \"Other\"\n    }\n}\n"
  },
  {
    "path": "packages/router/tests/via_ssr/outlet.rs",
    "content": "#![allow(unused)]\n\nuse std::rc::Rc;\n\nuse dioxus::prelude::*;\nuse dioxus_history::{History, MemoryHistory};\nuse dioxus_router::components::HistoryProvider;\n\nfn prepare(path: impl Into<String>) -> VirtualDom {\n    let mut vdom = VirtualDom::new_with_props(\n        App,\n        AppProps {\n            path: path.into().parse().unwrap(),\n        },\n    );\n    vdom.rebuild_in_place();\n    return vdom;\n\n    #[derive(Routable, Clone, PartialEq)]\n    #[rustfmt::skip]\n    enum Route {\n        #[route(\"/\")]\n        RootIndex {},\n        #[nest(\"/fixed\")]\n            #[layout(Fixed)]\n                #[route(\"/\")]\n                FixedIndex {},\n                #[route(\"/fixed\")]\n                FixedFixed {},\n            #[end_layout]\n        #[end_nest]\n        #[nest(\"/:id\")]\n            #[layout(Parameter)]\n                #[route(\"/\")]\n                ParameterIndex { id: u8 },\n                #[route(\"/fixed\")]\n                ParameterFixed { id: u8 },\n    }\n\n    #[component]\n    fn App(path: Route) -> Element {\n        rsx! {\n            h1 { \"App\" }\n            HistoryProvider {\n                history:  move |_| Rc::new(MemoryHistory::with_initial_path(path.clone())) as Rc<dyn History>,\n                Router::<Route> {}\n            }\n        }\n    }\n\n    #[component]\n    fn RootIndex() -> Element {\n        rsx! { h2 { \"Root Index\" } }\n    }\n\n    #[component]\n    fn Fixed() -> Element {\n        rsx! {\n            h2 { \"Fixed\" }\n            Outlet::<Route> { }\n        }\n    }\n\n    #[component]\n    fn FixedIndex() -> Element {\n        rsx! { h3 { \"Fixed - Index\" } }\n    }\n\n    #[component]\n    fn FixedFixed() -> Element {\n        rsx! { h3 { \"Fixed - Fixed\"} }\n    }\n\n    #[component]\n    fn Parameter(id: u8) -> Element {\n        rsx! {\n            h2 { \"Parameter {id}\" }\n            Outlet::<Route> { }\n        }\n    }\n\n    #[component]\n    fn ParameterIndex(id: u8) -> Element {\n        rsx! { h3 { \"Parameter - Index\" } }\n    }\n\n    #[component]\n    fn ParameterFixed(id: u8) -> Element {\n        rsx! { h3 { \"Parameter - Fixed\" } }\n    }\n}\n\n#[test]\nfn root_index() {\n    let vdom = prepare(\"/\");\n    let html = dioxus_ssr::render(&vdom);\n\n    assert_eq!(html, \"<h1>App</h1><h2>Root Index</h2>\");\n}\n\n#[test]\nfn fixed() {\n    let vdom = prepare(\"/fixed\");\n    let html = dioxus_ssr::render(&vdom);\n\n    assert_eq!(html, \"<h1>App</h1><h2>Fixed</h2><h3>Fixed - Index</h3>\");\n}\n\n#[test]\nfn fixed_fixed() {\n    let vdom = prepare(\"/fixed/fixed\");\n    let html = dioxus_ssr::render(&vdom);\n\n    assert_eq!(html, \"<h1>App</h1><h2>Fixed</h2><h3>Fixed - Fixed</h3>\");\n}\n\n#[test]\nfn parameter() {\n    let vdom = prepare(\"/18\");\n    let html = dioxus_ssr::render(&vdom);\n\n    assert_eq!(\n        html,\n        \"<h1>App</h1><h2>Parameter 18</h2><h3>Parameter - Index</h3>\"\n    );\n}\n\n#[test]\nfn parameter_fixed() {\n    let vdom = prepare(\"/18/fixed\");\n    let html = dioxus_ssr::render(&vdom);\n\n    assert_eq!(\n        html,\n        \"<h1>App</h1><h2>Parameter 18</h2><h3>Parameter - Fixed</h3>\"\n    );\n}\n"
  },
  {
    "path": "packages/router/tests/via_ssr/redirect.rs",
    "content": "use dioxus::prelude::*;\nuse dioxus_history::{History, MemoryHistory};\nuse dioxus_router::components::HistoryProvider;\nuse std::{rc::Rc, str::FromStr};\n\n// Tests for regressions of <https://github.com/DioxusLabs/dioxus/issues/2549>\n#[test]\nfn redirects_apply_in_order() {\n    let path = Route::from_str(\"/\").unwrap();\n    assert_eq!(\n        path,\n        Route::Home {\n            lang: \"en\".to_string()\n        }\n    );\n    let mut vdom = VirtualDom::new_with_props(App, AppProps { path });\n    vdom.rebuild_in_place();\n    let as_string = dioxus_ssr::render(&vdom);\n    assert_eq!(as_string, \"en\");\n}\n\n#[derive(Clone, Routable, Debug, PartialEq)]\nenum Route {\n    // The redirect should try to parse first because it is placed first in the enum\n    #[redirect(\"/\", || Route::Home { lang: \"en\".to_string() })]\n    #[route(\"/?:lang\")]\n    Home { lang: String },\n}\n\n#[component]\nfn Home(lang: String) -> Element {\n    rsx! { \"{lang}\" }\n}\n\n#[component]\nfn App(path: Route) -> Element {\n    rsx! {\n        HistoryProvider {\n            history:  move |_| Rc::new(MemoryHistory::with_initial_path(path.clone())) as Rc<dyn History>,\n            Router::<Route> {}\n        }\n    }\n}\n"
  },
  {
    "path": "packages/router/tests/via_ssr/without_index.rs",
    "content": "use std::rc::Rc;\n\nuse dioxus::prelude::*;\nuse dioxus_history::{History, MemoryHistory};\nuse dioxus_router::components::HistoryProvider;\n\n// Tests for regressions of <https://github.com/DioxusLabs/dioxus/issues/2468>\n#[test]\nfn router_without_index_route_parses() {\n    let mut vdom = VirtualDom::new_with_props(\n        App,\n        AppProps {\n            path: Route::Test {},\n        },\n    );\n    vdom.rebuild_in_place();\n    let as_string = dioxus_ssr::render(&vdom);\n    assert_eq!(as_string, \"<div>router with no index route renders</div>\")\n}\n\n#[derive(Routable, Clone, Copy, PartialEq, Debug)]\nenum Route {\n    #[route(\"/test\")]\n    Test {},\n}\n\n#[component]\nfn Test() -> Element {\n    rsx! {\n        div {\n            \"router with no index route renders\"\n        }\n    }\n}\n\n#[component]\nfn App(path: Route) -> Element {\n    rsx! {\n        HistoryProvider {\n            history:  move |_| Rc::new(MemoryHistory::with_initial_path(path)) as Rc<dyn History>,\n            Router::<Route> {}\n        }\n    }\n}\n"
  },
  {
    "path": "packages/router-macro/Cargo.toml",
    "content": "[package]\nname = \"dioxus-router-macro\"\nversion = { workspace = true }\nauthors = [\"Evan Almloff\"]\nedition = \"2021\"\ndescription = \"Macro for Dioxus Router\"\nlicense = \"MIT OR Apache-2.0\"\nrepository = \"https://github.com/DioxusLabs/dioxus/\"\nhomepage = \"https://dioxuslabs.com\"\ndocumentation = \"https://dioxuslabs.com\"\nkeywords = [\"dom\", \"ui\", \"gui\", \"react\", \"router\"]\n# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html\n\n[lib]\nproc-macro = true\n\n[dependencies]\nsyn = { workspace = true, features = [\"extra-traits\", \"full\"] }\nquote = { workspace = true }\nproc-macro2 = { workspace = true }\nslab = { workspace = true }\nbase16 = { workspace = true }\ndigest = { workspace = true }\nsha2 = { workspace = true }\n\n[dev-dependencies]\ndioxus = { workspace = true, features = [\"router\"] }\n\n[features]\ndefault = []\nweb = [] #todo(jon) remove this before releasing 0.7!\n\n[package.metadata.docs.rs]\ncargo-args = [\"-Zunstable-options\", \"-Zrustdoc-scrape-examples\"]\n"
  },
  {
    "path": "packages/router-macro/src/hash.rs",
    "content": "use quote::quote;\nuse syn::{Ident, Type};\n\nuse proc_macro2::TokenStream as TokenStream2;\n\n#[derive(Debug)]\npub struct HashFragment {\n    pub ident: Ident,\n    pub ty: Type,\n}\n\nimpl HashFragment {\n    pub fn contains_ident(&self, ident: &Ident) -> bool {\n        self.ident == *ident\n    }\n\n    pub fn parse(&self) -> TokenStream2 {\n        let ident = &self.ident;\n        let ty = &self.ty;\n        quote! {\n            let #ident = <#ty as dioxus_router::routable::FromHashFragment>::from_hash_fragment(&*hash);\n        }\n    }\n\n    pub fn write(&self) -> TokenStream2 {\n        let ident = &self.ident;\n        quote! {\n            {\n                let __hash = #ident.to_string();\n                if !__hash.is_empty() {\n                    write!(f, \"#{}\", dioxus_router::exports::percent_encoding::utf8_percent_encode(&__hash, dioxus_router::exports::FRAGMENT_ASCII_SET))?;\n                }\n            }\n        }\n    }\n\n    pub fn parse_from_str<'a>(\n        route_span: proc_macro2::Span,\n        mut fields: impl Iterator<Item = (&'a Ident, &'a Type)>,\n        hash: &str,\n    ) -> syn::Result<Self> {\n        // check if the route has a hash string\n        let Some(hash) = hash.strip_prefix(':') else {\n            return Err(syn::Error::new(\n                route_span,\n                \"Failed to parse `:`. Hash fragments must be in the format '#:<field>'\",\n            ));\n        };\n\n        let hash_ident = Ident::new(hash, proc_macro2::Span::call_site());\n        let field = fields.find(|(name, _)| *name == &hash_ident);\n\n        let ty = if let Some((_, ty)) = field {\n            ty.clone()\n        } else {\n            return Err(syn::Error::new(\n                route_span,\n                format!(\"Could not find a field with the name '{}'\", hash_ident),\n            ));\n        };\n\n        Ok(Self {\n            ident: hash_ident,\n            ty,\n        })\n    }\n}\n"
  },
  {
    "path": "packages/router-macro/src/layout.rs",
    "content": "use proc_macro2::TokenStream;\nuse quote::quote;\nuse syn::Path;\n\nuse crate::nest::{Nest, NestId};\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]\npub struct LayoutId(pub usize);\n\n#[derive(Debug)]\npub struct Layout {\n    pub comp: Path,\n    pub active_nests: Vec<NestId>,\n}\n\nimpl Layout {\n    pub fn routable_match(&self, nests: &[Nest]) -> TokenStream {\n        let comp_name = &self.comp;\n        let dynamic_segments = self\n            .active_nests\n            .iter()\n            .flat_map(|id| nests[id.0].dynamic_segments());\n\n        quote! {\n            rsx! {\n                #comp_name { #(#dynamic_segments: #dynamic_segments,)* }\n            }\n        }\n    }\n}\n\nimpl Layout {\n    pub fn parse(input: syn::parse::ParseStream, active_nests: Vec<NestId>) -> syn::Result<Self> {\n        // Then parse the component name\n        let _ = input.parse::<syn::Token![,]>();\n        let comp: Path = input.parse()?;\n\n        Ok(Self { comp, active_nests })\n    }\n}\n"
  },
  {
    "path": "packages/router-macro/src/lib.rs",
    "content": "#![doc(html_logo_url = \"https://avatars.githubusercontent.com/u/79236386\")]\n#![doc(html_favicon_url = \"https://avatars.githubusercontent.com/u/79236386\")]\n\nextern crate proc_macro;\n\nuse layout::Layout;\nuse nest::{Nest, NestId};\nuse proc_macro::TokenStream;\nuse proc_macro2::Span;\nuse quote::{format_ident, quote, ToTokens};\nuse redirect::Redirect;\nuse route::{Route, RouteType};\nuse segment::RouteSegment;\nuse syn::{parse::ParseStream, parse_macro_input, Ident, Token, Type};\n\nuse proc_macro2::TokenStream as TokenStream2;\n\nuse crate::{layout::LayoutId, route_tree::ParseRouteTree};\n\nmod hash;\nmod layout;\nmod nest;\nmod query;\nmod redirect;\nmod route;\nmod route_tree;\nmod segment;\n\n/// Derives the Routable trait for an enum of routes\n///\n/// Each variant must:\n/// 1. Be struct-like with {}'s\n/// 2. Contain all of the dynamic parameters of the current and nested routes\n/// 3. Have a `#[route(\"route\")]` attribute\n///\n/// Route Segments:\n/// 1. Static Segments: \"/static\"\n/// 2. Dynamic Segments: \"/:dynamic\" (where dynamic has a type that is FromStr in all child Variants)\n/// 3. Catch all Segments: \"/:..segments\" (where segments has a type that is FromSegments in all child Variants)\n/// 4. Query Segments: \"/?:..query\" (where query has a type that is FromQuery in all child Variants) or \"/?:query&:other_query\" (where query and other_query has a type that is FromQueryArgument in all child Variants)\n///\n/// Routes are matched:\n/// 1. By there specificity this order: Query Routes (\"/?:query\"), Static Routes (\"/route\"), Dynamic Routes (\"/:route\"), Catch All Routes (\"/:..route\")\n/// 2. By the order they are defined in the enum\n///\n/// All features:\n/// ```rust\n/// use dioxus::prelude::*;\n///\n/// #[rustfmt::skip]\n/// #[derive(Clone, Debug, PartialEq, Routable)]\n/// enum Route {\n///     // Define routes with the route macro. If the name of the component is not the same as the variant, you can specify it as the second parameter\n///     #[route(\"/\", IndexComponent)]\n///     Index {},\n///     // Nests with parameters have types taken from child routes\n///     // Everything inside the nest has the added parameter `user_id: usize`\n///     #[nest(\"/user/:user_id\")]\n///         // All children of layouts will be rendered inside the Outlet in the layout component\n///         // Creates a Layout UserFrame that has the parameter `user_id: usize`\n///         #[layout(UserFrame)]\n///             // If there is a component with the name Route1, you do not need to pass in the component name\n///             #[route(\"/:dynamic?:query\")]\n///             Route1 {\n///                 // The type is taken from the first instance of the dynamic parameter\n///                 user_id: usize,\n///                 dynamic: usize,\n///                 query: String,\n///             },\n///             #[route(\"/hello_world\")]\n///             // You can opt out of the layout by using the `!` prefix\n///             #[layout(!UserFrame)]\n///             Route2 { user_id: usize },\n///         // End layouts with #[end_layout]\n///         #[end_layout]\n///     // End nests with #[end_nest]\n///     #[end_nest]\n///     // Redirects take a path and a function that takes the parameters from the path and returns a new route\n///     #[redirect(\"/:id/user\", |id: usize| Route::Route3 { dynamic: id.to_string()})]\n///     #[route(\"/:dynamic\")]\n///     Route3 { dynamic: String },\n/// }\n/// # #[component]\n/// # fn Route1(user_id: usize, dynamic: usize, query: String) -> Element { VNode::empty() }\n/// # #[component]\n/// # fn Route2(user_id: usize) -> Element { VNode::empty() }\n/// # #[component]\n/// # fn Route3(dynamic: String) -> Element { VNode::empty() }\n/// # #[component]\n/// # fn UserFrame(user_id: usize) -> Element { VNode::empty() }\n/// # #[component]\n/// # fn IndexComponent() -> Element { VNode::empty() }\n/// ```\n///\n/// # `#[route(\"path\", component)]`\n///\n/// The `#[route]` attribute is used to define a route. It takes up to 2 parameters:\n/// - `path`: The path to the enum variant (relative to the parent nest)\n/// - (optional) `component`: The component to render when the route is matched. If not specified, the name of the variant is used\n///\n/// Routes are the most basic attribute. They allow you to define a route and the component to render when the route is matched. The component must take all dynamic parameters of the route and all parent nests.\n/// The next variant will be tied to the component. If you link to that variant, the component will be rendered.\n///\n/// ```rust\n/// use dioxus::prelude::*;\n///\n/// #[derive(Clone, Debug, PartialEq, Routable)]\n/// enum Route {\n///     // Define routes that renders the IndexComponent\n///     // The Index component will be rendered when the route is matched (e.g. when the user navigates to /)\n///     #[route(\"/\", Index)]\n///     Index {},\n/// }\n/// # #[component]\n/// # fn Index() -> Element { VNode::empty() }\n/// ```\n///\n/// # `#[redirect(\"path\", function)]`\n///\n/// The `#[redirect]` attribute is used to define a redirect. It takes 2 parameters:\n/// - `path`: The path to the enum variant (relative to the parent nest)\n/// - `function`: A function that takes the parameters from the path and returns a new route\n///\n/// ```rust\n/// use dioxus::prelude::*;\n///\n/// #[derive(Clone, Debug, PartialEq, Routable)]\n/// enum Route {\n///     // Redirects the /:id route to the Index route\n///     #[redirect(\"/:id\", |id: usize| Route::Index {})]\n///     #[route(\"/\", Index)]\n///     Index {},\n/// }\n/// # #[component]\n/// # fn Index() -> Element { VNode::empty() }\n/// ```\n///\n/// Redirects allow you to redirect a route to another route. The function must take all dynamic parameters of the route and all parent nests.\n///\n/// # `#[nest(\"path\")]`\n///\n/// The `#[nest]` attribute is used to define a nest. It takes 1 parameter:\n/// - `path`: The path to the nest (relative to the parent nest)\n///\n/// Nests effect all nests, routes and redirects defined until the next `#[end_nest]` attribute. All children of nests are relative to the nest route and must include all dynamic parameters of the nest.\n///\n/// ```rust\n/// use dioxus::prelude::*;\n///\n/// #[derive(Clone, Debug, PartialEq, Routable)]\n/// enum Route {\n///     // Nests all child routes in the /blog route\n///     #[nest(\"/blog\")]\n///         // This is at /blog/:id\n///         #[redirect(\"/:id\", |id: usize| Route::Index {})]\n///         // This is at /blog\n///         #[route(\"/\", Index)]\n///         Index {},\n/// }\n/// # #[component]\n/// # fn Index() -> Element { VNode::empty() }\n/// ```\n///\n/// # `#[end_nest]`\n///\n/// The `#[end_nest]` attribute is used to end a nest. It takes no parameters.\n///\n/// ```rust\n/// use dioxus::prelude::*;\n///\n/// #[derive(Clone, Debug, PartialEq, Routable)]\n/// enum Route {\n///     #[nest(\"/blog\")]\n///         // This is at /blog/:id\n///         #[redirect(\"/:id\", |id: usize| Route::Index {})]\n///         // This is at /blog\n///         #[route(\"/\", Index)]\n///         Index {},\n///     // Ends the nest\n///     #[end_nest]\n///     // This is at /\n///     #[route(\"/\")]\n///     Home {},\n/// }\n/// # #[component]\n/// # fn Index() -> Element { VNode::empty() }\n/// # #[component]\n/// # fn Home() -> Element { VNode::empty() }\n/// ```\n///\n/// # `#[layout(component)]`\n///\n/// The `#[layout]` attribute is used to define a layout. It takes 1 parameter:\n/// - `component`: The component to render when the route is matched. If not specified, the name of the variant is used\n///\n/// The layout component allows you to wrap all children of the layout in a component. The child routes are rendered in the Outlet of the layout component. The layout component must take all dynamic parameters of the nests it is nested in.\n///\n/// ```rust\n/// use dioxus::prelude::*;\n///\n/// #[derive(Clone, Debug, PartialEq, Routable)]\n/// enum Route {\n///     #[layout(BlogFrame)]\n///         #[redirect(\"/:id\", |id: usize| Route::Index {})]\n///         // Index will be rendered in the Outlet of the BlogFrame component\n///         #[route(\"/\", Index)]\n///         Index {},\n/// }\n/// # #[component]\n/// # fn Index() -> Element { VNode::empty() }\n/// # #[component]\n/// # fn BlogFrame() -> Element { VNode::empty() }\n/// ```\n///\n/// # `#[end_layout]`\n///\n/// The `#[end_layout]` attribute is used to end a layout. It takes no parameters.\n///\n/// ```rust\n/// use dioxus::prelude::*;\n///\n/// #[derive(Clone, Debug, PartialEq, Routable)]\n/// enum Route {\n///     #[layout(BlogFrame)]\n///         #[redirect(\"/:id\", |id: usize| Route::Index {})]\n///         // Index will be rendered in the Outlet of the BlogFrame component\n///         #[route(\"/\", Index)]\n///         Index {},\n///     // Ends the layout\n///     #[end_layout]\n///     // This will be rendered standalone\n///     #[route(\"/\")]\n///     Home {},\n/// }\n/// # #[component]\n/// # fn Index() -> Element { VNode::empty() }\n/// # #[component]\n/// # fn BlogFrame() -> Element { VNode::empty() }\n/// # #[component]\n/// # fn Home() -> Element { VNode::empty() }\n/// ```\n#[doc(alias = \"route\")]\n#[proc_macro_derive(\n    Routable,\n    attributes(route, nest, end_nest, layout, end_layout, redirect, child)\n)]\npub fn routable(input: TokenStream) -> TokenStream {\n    let routes_enum = parse_macro_input!(input as syn::ItemEnum);\n\n    let route_enum = match RouteEnum::parse(routes_enum) {\n        Ok(route_enum) => route_enum,\n        Err(err) => return err.to_compile_error().into(),\n    };\n\n    let error_type = route_enum.error_type();\n    let parse_impl = route_enum.parse_impl();\n    let display_impl = route_enum.impl_display();\n    let routable_impl = route_enum.routable_impl();\n\n    (quote! {\n        const _: () = {\n            #error_type\n\n            #display_impl\n\n            #routable_impl\n\n            #parse_impl\n        };\n    })\n    .into()\n}\n\nstruct RouteEnum {\n    name: Ident,\n    endpoints: Vec<RouteEndpoint>,\n    nests: Vec<Nest>,\n    layouts: Vec<Layout>,\n    site_map: Vec<SiteMapSegment>,\n}\n\nimpl RouteEnum {\n    fn parse(data: syn::ItemEnum) -> syn::Result<Self> {\n        let name = &data.ident;\n\n        let mut site_map = Vec::new();\n        let mut site_map_stack: Vec<Vec<SiteMapSegment>> = Vec::new();\n\n        let mut endpoints = Vec::new();\n\n        let mut layouts: Vec<Layout> = Vec::new();\n        let mut layout_stack = Vec::new();\n\n        let mut nests = Vec::new();\n        let mut nest_stack = Vec::new();\n\n        for variant in &data.variants {\n            let mut excluded = Vec::new();\n            // Apply the any nesting attributes in order\n            for attr in &variant.attrs {\n                if attr.path().is_ident(\"nest\") {\n                    let mut children_routes = Vec::new();\n                    {\n                        // add all of the variants of the enum to the children_routes until we hit an end_nest\n                        let mut level = 0;\n                        'o: for variant in &data.variants {\n                            children_routes.push(variant.fields.clone());\n                            for attr in &variant.attrs {\n                                if attr.path().is_ident(\"nest\") {\n                                    level += 1;\n                                } else if attr.path().is_ident(\"end_nest\") {\n                                    level -= 1;\n                                    if level < 0 {\n                                        break 'o;\n                                    }\n                                }\n                            }\n                        }\n                    }\n\n                    let nest_index = nests.len();\n\n                    let parser = |input: ParseStream| {\n                        Nest::parse(\n                            input,\n                            children_routes\n                                .iter()\n                                .filter_map(|f: &syn::Fields| match f {\n                                    syn::Fields::Named(fields) => Some(fields.clone()),\n                                    _ => None,\n                                })\n                                .collect(),\n                            nest_index,\n                        )\n                    };\n                    let nest = attr.parse_args_with(parser)?;\n\n                    // add the current segment to the site map stack\n                    let segments: Vec<_> = nest\n                        .segments\n                        .iter()\n                        .map(|seg| {\n                            let segment_type = seg.into();\n                            SiteMapSegment {\n                                segment_type,\n                                children: Vec::new(),\n                            }\n                        })\n                        .collect();\n                    if !segments.is_empty() {\n                        site_map_stack.push(segments);\n                    }\n\n                    nests.push(nest);\n                    nest_stack.push(NestId(nest_index));\n                } else if attr.path().is_ident(\"end_nest\") {\n                    nest_stack.pop();\n                    // pop the current nest segment off the stack and add it to the parent or the site map\n                    if let Some(segment) = site_map_stack.pop() {\n                        let children = site_map_stack\n                            .last_mut()\n                            .map(|seg| &mut seg.last_mut().unwrap().children)\n                            .unwrap_or(&mut site_map);\n\n                        // Turn the list of segments in the segments stack into a tree\n                        let mut iter = segment.into_iter().rev();\n                        let mut current = iter.next().unwrap();\n                        for mut segment in iter {\n                            segment.children.push(current);\n                            current = segment;\n                        }\n\n                        children.push(current);\n                    }\n                } else if attr.path().is_ident(\"layout\") {\n                    let parser = |input: ParseStream| {\n                        let bang: Option<Token![!]> = input.parse().ok();\n                        let exclude = bang.is_some();\n                        Ok((exclude, Layout::parse(input, nest_stack.clone())?))\n                    };\n                    let (exclude, layout): (bool, Layout) = attr.parse_args_with(parser)?;\n\n                    if exclude {\n                        let Some(layout_index) = layouts.iter().position(|l| l.comp == layout.comp)\n                        else {\n                            return Err(syn::Error::new(\n                                Span::call_site(),\n                                \"Attempted to exclude a layout that does not exist\",\n                            ));\n                        };\n                        excluded.push(LayoutId(layout_index));\n                    } else {\n                        let layout_index = layouts.len();\n                        layouts.push(layout);\n                        layout_stack.push(LayoutId(layout_index));\n                    }\n                } else if attr.path().is_ident(\"end_layout\") {\n                    layout_stack.pop();\n                } else if attr.path().is_ident(\"redirect\") {\n                    let parser = |input: ParseStream| {\n                        Redirect::parse(input, nest_stack.clone(), endpoints.len())\n                    };\n                    let redirect = attr.parse_args_with(parser)?;\n                    endpoints.push(RouteEndpoint::Redirect(redirect));\n                }\n            }\n\n            let active_nests = nest_stack.clone();\n            let mut active_layouts = layout_stack.clone();\n            active_layouts.retain(|&id| !excluded.contains(&id));\n\n            let route = Route::parse(active_nests, active_layouts, variant.clone())?;\n\n            // add the route to the site map\n            let mut segment = SiteMapSegment::new(&route.segments);\n            if let RouteType::Child(child) = &route.ty {\n                let new_segment = SiteMapSegment {\n                    segment_type: SegmentType::Child(child.ty.clone()),\n                    children: Vec::new(),\n                };\n                match &mut segment {\n                    Some(segment) => {\n                        fn set_last_child_to(\n                            segment: &mut SiteMapSegment,\n                            new_segment: SiteMapSegment,\n                        ) {\n                            if let Some(last) = segment.children.last_mut() {\n                                set_last_child_to(last, new_segment);\n                            } else {\n                                segment.children = vec![new_segment];\n                            }\n                        }\n                        set_last_child_to(segment, new_segment);\n                    }\n                    None => {\n                        segment = Some(new_segment);\n                    }\n                }\n            }\n\n            if let Some(segment) = segment {\n                let parent = site_map_stack.last_mut();\n                let children = match parent {\n                    Some(parent) => &mut parent.last_mut().unwrap().children,\n                    None => &mut site_map,\n                };\n                children.push(segment);\n            }\n\n            endpoints.push(RouteEndpoint::Route(route));\n        }\n\n        // pop any remaining site map segments\n        while let Some(segment) = site_map_stack.pop() {\n            let children = site_map_stack\n                .last_mut()\n                .map(|seg| &mut seg.last_mut().unwrap().children)\n                .unwrap_or(&mut site_map);\n\n            // Turn the list of segments in the segments stack into a tree\n            let mut iter = segment.into_iter().rev();\n            let mut current = iter.next().unwrap();\n            for mut segment in iter {\n                segment.children.push(current);\n                current = segment;\n            }\n\n            children.push(current);\n        }\n\n        let myself = Self {\n            name: name.clone(),\n            endpoints,\n            nests,\n            layouts,\n            site_map,\n        };\n\n        Ok(myself)\n    }\n\n    fn impl_display(&self) -> TokenStream2 {\n        let mut display_match = Vec::new();\n\n        for route in &self.endpoints {\n            if let RouteEndpoint::Route(route) = route {\n                display_match.push(route.display_match(&self.nests));\n            }\n        }\n\n        let name = &self.name;\n\n        quote! {\n            impl std::fmt::Display for #name {\n                fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {\n                    #[allow(unused)]\n                    match self {\n                        #(#display_match)*\n                    }\n                    Ok(())\n                }\n            }\n        }\n    }\n\n    fn parse_impl(&self) -> TokenStream2 {\n        let tree = ParseRouteTree::new(&self.endpoints, &self.nests);\n        let name = &self.name;\n\n        let error_name = format_ident!(\"{}MatchError\", self.name);\n        let tokens = tree.roots.iter().map(|&id| {\n            let route = tree.get(id).unwrap();\n            route.to_tokens(&self.nests, &tree, self.name.clone(), error_name.clone())\n        });\n\n        quote! {\n            impl<'a> ::core::convert::TryFrom<&'a str> for #name {\n                type Error = <Self as ::std::str::FromStr>::Err;\n\n                fn try_from(s: &'a str) -> ::std::result::Result<Self, Self::Error> {\n                    s.parse()\n                }\n            }\n\n            impl ::std::str::FromStr for #name {\n                type Err = dioxus_router::routable::RouteParseError<#error_name>;\n\n                fn from_str(s: &str) -> ::std::result::Result<Self, Self::Err> {\n                    let route = s;\n                    let (route, hash) = route.split_once('#').unwrap_or((route, \"\"));\n                    let (route, query) = route.split_once('?').unwrap_or((route, \"\"));\n                    // Remove any trailing slashes. We parse /route/ and /route in the same way\n                    // Note: we don't use trim because it includes more code\n                    let route = route.strip_suffix('/').unwrap_or(route);\n                    let query = dioxus_router::exports::percent_encoding::percent_decode_str(query)\n                        .decode_utf8()\n                        .unwrap_or(query.into());\n                    let hash = dioxus_router::exports::percent_encoding::percent_decode_str(hash)\n                        .decode_utf8()\n                        .unwrap_or(hash.into());\n                    let mut segments = route.split('/').map(|s| {\n                        dioxus_router::exports::percent_encoding::percent_decode_str(s)\n                            .decode_utf8()\n                            .unwrap_or(s.into())\n                    });\n                    // skip the first empty segment\n                    if s.starts_with('/') {\n                        let _ = segments.next();\n                    } else {\n                        // if this route does not start with a slash, it is not a valid route\n                        return Err(dioxus_router::routable::RouteParseError {\n                            attempted_routes: Vec::new(),\n                        });\n                    }\n                    let mut errors = Vec::new();\n\n                    #(#tokens)*\n\n                    Err(dioxus_router::routable::RouteParseError {\n                        attempted_routes: errors,\n                    })\n                }\n            }\n        }\n    }\n\n    fn error_name(&self) -> Ident {\n        Ident::new(&(self.name.to_string() + \"MatchError\"), Span::call_site())\n    }\n\n    fn error_type(&self) -> TokenStream2 {\n        let match_error_name = self.error_name();\n\n        let mut type_defs = Vec::new();\n        let mut error_variants = Vec::new();\n        let mut display_match = Vec::new();\n\n        for endpoint in &self.endpoints {\n            match endpoint {\n                RouteEndpoint::Route(route) => {\n                    let route_name = &route.route_name;\n\n                    let error_name = route.error_ident();\n                    let route_str = &route.route;\n                    let comment = format!(\n                        \" An error that can occur when trying to parse the route [`{}::{}`] ('{}').\",\n                        self.name,\n                        route_name,\n                        route_str\n                    );\n\n                    error_variants.push(quote! {\n                        #[doc = #comment]\n                        #route_name(#error_name)\n                    });\n                    display_match.push(quote! { Self::#route_name(err) => write!(f, \"Route '{}' ('{}') did not match:\\n{}\", stringify!(#route_name), #route_str, err)? });\n                    type_defs.push(route.error_type());\n                }\n                RouteEndpoint::Redirect(redirect) => {\n                    let error_variant = redirect.error_variant();\n                    let error_name = redirect.error_ident();\n                    let route_str = &redirect.route;\n                    let comment = format!(\n                        \" An error that can occur when trying to parse the redirect '{}'.\",\n                        route_str.value()\n                    );\n\n                    error_variants.push(quote! {\n                        #[doc = #comment]\n                        #error_variant(#error_name)\n                    });\n                    display_match.push(quote! { Self::#error_variant(err) => write!(f, \"Redirect '{}' ('{}') did not match:\\n{}\", stringify!(#error_name), #route_str, err)? });\n                    type_defs.push(redirect.error_type());\n                }\n            }\n        }\n\n        for nest in &self.nests {\n            let error_variant = nest.error_variant();\n            let error_name = nest.error_ident();\n            let route_str = &nest.route;\n            let comment = format!(\n                \" An error that can occur when trying to parse the nested segment {} ('{}').\",\n                error_name, route_str\n            );\n\n            error_variants.push(quote! {\n                #[doc = #comment]\n                #error_variant(#error_name)\n            });\n            display_match.push(quote! { Self::#error_variant(err) => write!(f, \"Nest '{}' ('{}') did not match:\\n{}\", stringify!(#error_name), #route_str, err)? });\n            type_defs.push(nest.error_type());\n        }\n\n        let comment = format!(\n            \" An error that can occur when trying to parse the route enum [`{}`].\",\n            self.name\n        );\n\n        quote! {\n            #(#type_defs)*\n\n            #[doc = #comment]\n            #[allow(non_camel_case_types)]\n            #[allow(clippy::derive_partial_eq_without_eq)]\n            pub enum #match_error_name {\n                #(#error_variants),*\n            }\n\n            impl ::std::fmt::Debug for #match_error_name {\n                fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {\n                    write!(f, \"{}({})\", stringify!(#match_error_name), self)\n                }\n            }\n\n            impl ::std::fmt::Display for #match_error_name {\n                fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {\n                    match self {\n                        #(#display_match),*\n                    }\n                    Ok(())\n                }\n            }\n        }\n    }\n\n    fn routable_impl(&self) -> TokenStream2 {\n        let name = &self.name;\n        let site_map = &self.site_map;\n\n        let mut matches = Vec::new();\n\n        // Collect all routes matches\n        for route in &self.endpoints {\n            if let RouteEndpoint::Route(route) = route {\n                matches.push(route.routable_match(&self.layouts, &self.nests, name));\n            }\n        }\n\n        quote! {\n            impl dioxus_router::routable::Routable for #name where Self: Clone {\n                const SITE_MAP: &'static [dioxus_router::routable::SiteMapSegment] = &[\n                    #(#site_map,)*\n                ];\n\n                fn render(&self, level: usize) -> dioxus_core::Element {\n                    let myself = self.clone();\n                    match (level, myself) {\n                        #(#matches)*\n                        _ => VNode::empty()\n                    }\n                }\n            }\n        }\n    }\n}\n\n#[allow(clippy::large_enum_variant)]\nenum RouteEndpoint {\n    Route(Route),\n    Redirect(Redirect),\n}\n\nstruct SiteMapSegment {\n    pub segment_type: SegmentType,\n    pub children: Vec<SiteMapSegment>,\n}\n\nimpl SiteMapSegment {\n    fn new(segments: &[RouteSegment]) -> Option<Self> {\n        let mut current = None;\n        // walk backwards through the new segments, adding children as we go\n        for segment in segments.iter().rev() {\n            let segment_type = segment.into();\n            let mut segment = SiteMapSegment {\n                segment_type,\n                children: Vec::new(),\n            };\n            // if we have a current segment, add it as a child\n            if let Some(current) = current.take() {\n                segment.children.push(current)\n            }\n            current = Some(segment);\n        }\n        current\n    }\n}\n\nimpl ToTokens for SiteMapSegment {\n    fn to_tokens(&self, tokens: &mut TokenStream2) {\n        let segment_type = &self.segment_type;\n        let children = if let SegmentType::Child(ty) = &self.segment_type {\n            quote! { #ty::SITE_MAP }\n        } else {\n            let children = self\n                .children\n                .iter()\n                .map(|child| child.to_token_stream())\n                .collect::<Vec<_>>();\n            quote! {\n                &[\n                    #(#children,)*\n                ]\n            }\n        };\n\n        tokens.extend(quote! {\n            dioxus_router::routable::SiteMapSegment {\n                segment_type: #segment_type,\n                children: #children,\n            }\n        });\n    }\n}\n\n#[allow(clippy::large_enum_variant)]\nenum SegmentType {\n    Static(String),\n    Dynamic(String),\n    CatchAll(String),\n    Child(Type),\n}\n\nimpl ToTokens for SegmentType {\n    fn to_tokens(&self, tokens: &mut TokenStream2) {\n        match self {\n            SegmentType::Static(s) => {\n                tokens.extend(quote! { dioxus_router::routable::SegmentType::Static(#s) })\n            }\n            SegmentType::Dynamic(s) => {\n                tokens.extend(quote! { dioxus_router::routable::SegmentType::Dynamic(#s) })\n            }\n            SegmentType::CatchAll(s) => {\n                tokens.extend(quote! { dioxus_router::routable::SegmentType::CatchAll(#s) })\n            }\n            SegmentType::Child(_) => {\n                tokens.extend(quote! { dioxus_router::routable::SegmentType::Child })\n            }\n        }\n    }\n}\n\nimpl<'a> From<&'a RouteSegment> for SegmentType {\n    fn from(value: &'a RouteSegment) -> Self {\n        match value {\n            RouteSegment::Static(s) => SegmentType::Static(s.to_string()),\n            RouteSegment::Dynamic(s, _) => SegmentType::Dynamic(s.to_string()),\n            RouteSegment::CatchAll(s, _) => SegmentType::CatchAll(s.to_string()),\n        }\n    }\n}\n"
  },
  {
    "path": "packages/router-macro/src/nest.rs",
    "content": "use proc_macro2::TokenStream;\nuse quote::{format_ident, quote};\nuse syn::{Ident, LitStr};\n\nuse crate::segment::{create_error_type, parse_route_segments, RouteSegment};\n\n#[derive(Debug, Clone, Copy)]\npub struct NestId(pub usize);\n\n#[derive(Debug, Clone)]\npub struct Nest {\n    pub route: String,\n    pub segments: Vec<RouteSegment>,\n    index: usize,\n}\n\nimpl Nest {\n    pub fn parse(\n        input: syn::parse::ParseStream,\n        children_routes: Vec<syn::FieldsNamed>,\n        index: usize,\n    ) -> syn::Result<Self> {\n        // Parse the route\n        let route: LitStr = input.parse()?;\n\n        let route_segments = parse_route_segments(\n            route.span(),\n            children_routes\n                .iter()\n                .flat_map(|f| f.named.iter())\n                .map(|f| (f.ident.as_ref().unwrap(), &f.ty)),\n            &route.value(),\n        )?\n        .0;\n        for seg in &route_segments {\n            if let RouteSegment::CatchAll(name, _) = seg {\n                return Err(syn::Error::new_spanned(\n                    name,\n                    format!(\n                        \"Catch-all segments are not allowed in nested routes: {}\",\n                        route.value()\n                    ),\n                ));\n            }\n        }\n\n        Ok(Self {\n            route: route.value(),\n            segments: route_segments,\n            index,\n        })\n    }\n}\n\nimpl Nest {\n    pub fn dynamic_segments(&self) -> impl Iterator<Item = TokenStream> + '_ {\n        self.dynamic_segments_names().map(|i| quote! {#i})\n    }\n\n    pub fn dynamic_segments_names(&self) -> impl Iterator<Item = Ident> + '_ {\n        self.segments.iter().filter_map(|seg| seg.name())\n    }\n\n    pub fn write(&self) -> TokenStream {\n        let write_segments = self.segments.iter().map(|s| s.write_segment());\n\n        quote! {\n            {\n                #(#write_segments)*\n            }\n        }\n    }\n\n    pub fn error_ident(&self) -> Ident {\n        format_ident!(\"Nest{}ParseError\", self.index)\n    }\n\n    pub fn error_variant(&self) -> Ident {\n        format_ident!(\"Nest{}\", self.index)\n    }\n\n    pub fn error_type(&self) -> TokenStream {\n        let error_name = self.error_ident();\n\n        create_error_type(&self.route, error_name, &self.segments, None)\n    }\n}\n"
  },
  {
    "path": "packages/router-macro/src/query.rs",
    "content": "use quote::quote;\nuse syn::{Ident, Type};\n\nuse proc_macro2::TokenStream as TokenStream2;\n\n#[allow(clippy::large_enum_variant)]\n#[derive(Debug)]\npub enum QuerySegment {\n    Single(FullQuerySegment),\n    Segments(Vec<QueryArgument>),\n}\n\nimpl QuerySegment {\n    pub fn contains_ident(&self, ident: &Ident) -> bool {\n        match self {\n            QuerySegment::Single(segment) => segment.ident == *ident,\n            QuerySegment::Segments(segments) => {\n                segments.iter().any(|segment| segment.ident == *ident)\n            }\n        }\n    }\n\n    pub fn parse(&self) -> TokenStream2 {\n        match self {\n            QuerySegment::Single(segment) => segment.parse(),\n            QuerySegment::Segments(segments) => {\n                let mut tokens = TokenStream2::new();\n                tokens.extend(quote! { let split_query: std::collections::HashMap<&str, &str> = query.split('&').filter_map(|s| s.split_once('=')).collect(); });\n                for segment in segments {\n                    tokens.extend(segment.parse());\n                }\n                tokens\n            }\n        }\n    }\n\n    pub fn write(&self) -> TokenStream2 {\n        match self {\n            QuerySegment::Single(segment) => segment.write(),\n            QuerySegment::Segments(segments) => {\n                let mut tokens = TokenStream2::new();\n                tokens.extend(quote! { write!(f, \"?\")?; });\n                for (i, segment) in segments.iter().enumerate() {\n                    tokens.extend(segment.write(i == segments.len() - 1));\n                }\n                tokens\n            }\n        }\n    }\n\n    pub fn parse_from_str<'a>(\n        route_span: proc_macro2::Span,\n        mut fields: impl Iterator<Item = (&'a Ident, &'a Type)>,\n        query: &str,\n    ) -> syn::Result<Self> {\n        // check if the route has a query string\n        if let Some(query) = query.strip_prefix(\":..\") {\n            let query_ident = Ident::new(query, proc_macro2::Span::call_site());\n            let field = fields.find(|(name, _)| *name == &query_ident);\n\n            let ty = if let Some((_, ty)) = field {\n                ty.clone()\n            } else {\n                return Err(syn::Error::new(\n                    route_span,\n                    format!(\"Could not find a field with the name '{}'\", query_ident),\n                ));\n            };\n\n            Ok(QuerySegment::Single(FullQuerySegment {\n                ident: query_ident,\n                ty,\n            }))\n        } else {\n            let mut query_arguments = Vec::new();\n            for segment in query.split('&') {\n                if segment.is_empty() {\n                    return Err(syn::Error::new(\n                        route_span,\n                        \"Query segments should be non-empty\",\n                    ));\n                }\n                if let Some(query_argument) = segment.strip_prefix(':') {\n                    let query_ident = Ident::new(query_argument, proc_macro2::Span::call_site());\n                    let field = fields.find(|(name, _)| *name == &query_ident);\n\n                    let ty = if let Some((_, ty)) = field {\n                        ty.clone()\n                    } else {\n                        return Err(syn::Error::new(\n                            route_span,\n                            format!(\"Could not find a field with the name '{}'\", query_ident),\n                        ));\n                    };\n\n                    query_arguments.push(QueryArgument {\n                        ident: query_ident,\n                        ty,\n                    });\n                } else {\n                    return Err(syn::Error::new(\n                        route_span,\n                        \"Query segments should be a : followed by the name of the query argument\",\n                    ));\n                }\n            }\n            Ok(QuerySegment::Segments(query_arguments))\n        }\n    }\n}\n\n#[derive(Debug)]\npub struct FullQuerySegment {\n    pub ident: Ident,\n    pub ty: Type,\n}\n\nimpl FullQuerySegment {\n    pub fn parse(&self) -> TokenStream2 {\n        let ident = &self.ident;\n        let ty = &self.ty;\n        quote! {\n            let #ident = <#ty as dioxus_router::routable::FromQuery>::from_query(&*query);\n        }\n    }\n\n    pub fn write(&self) -> TokenStream2 {\n        let ident = &self.ident;\n        quote! {\n            {\n                let as_string = #ident.to_string();\n                write!(f, \"?{}\", dioxus_router::exports::percent_encoding::utf8_percent_encode(&as_string, dioxus_router::exports::QUERY_ASCII_SET))?;\n            }\n        }\n    }\n}\n\n#[derive(Debug)]\npub struct QueryArgument {\n    pub ident: Ident,\n    pub ty: Type,\n}\n\nimpl QueryArgument {\n    pub fn parse(&self) -> TokenStream2 {\n        let ident = &self.ident;\n        let ty = &self.ty;\n        quote! {\n            let #ident = match split_query.get(stringify!(#ident)) {\n                Some(query_argument) => {\n                    use dioxus_router::routable::FromQueryArgument;\n                    <#ty>::from_query_argument(query_argument).unwrap_or_default()\n                },\n                None => <#ty as Default>::default(),\n            };\n        }\n    }\n\n    pub fn write(&self, trailing: bool) -> TokenStream2 {\n        let ident = &self.ident;\n        let write_ampersand = if !trailing {\n            quote! { if !as_string.is_empty() { write!(f, \"&\")?; } }\n        } else {\n            quote! {}\n        };\n        quote! {\n            {\n                let as_string = dioxus_router::routable::DisplayQueryArgument::new(stringify!(#ident), #ident).to_string();\n                write!(f, \"{}\", dioxus_router::exports::percent_encoding::utf8_percent_encode(&as_string, dioxus_router::exports::QUERY_ASCII_SET))?;\n                #write_ampersand\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "packages/router-macro/src/redirect.rs",
    "content": "use proc_macro2::{Ident, TokenStream};\nuse quote::{format_ident, quote};\nuse syn::LitStr;\n\nuse crate::{\n    hash::HashFragment,\n    nest::NestId,\n    query::QuerySegment,\n    segment::{create_error_type, parse_route_segments, RouteSegment},\n};\n\n#[derive(Debug)]\npub(crate) struct Redirect {\n    pub route: LitStr,\n    pub nests: Vec<NestId>,\n    pub segments: Vec<RouteSegment>,\n    pub query: Option<QuerySegment>,\n    pub hash: Option<HashFragment>,\n    pub function: syn::ExprClosure,\n    pub index: usize,\n}\n\nimpl Redirect {\n    pub fn error_ident(&self) -> Ident {\n        format_ident!(\"Redirect{}ParseError\", self.index)\n    }\n\n    pub fn error_variant(&self) -> Ident {\n        format_ident!(\"Redirect{}\", self.index)\n    }\n\n    pub fn error_type(&self) -> TokenStream {\n        let error_name = self.error_ident();\n\n        create_error_type(&self.route.value(), error_name, &self.segments, None)\n    }\n\n    pub fn parse_query(&self) -> TokenStream {\n        match &self.query {\n            Some(query) => query.parse(),\n            None => quote! {},\n        }\n    }\n\n    pub fn parse_hash(&self) -> TokenStream {\n        match &self.hash {\n            Some(hash) => hash.parse(),\n            None => quote! {},\n        }\n    }\n\n    pub fn parse(\n        input: syn::parse::ParseStream,\n        active_nests: Vec<NestId>,\n        index: usize,\n    ) -> syn::Result<Self> {\n        let path = input.parse::<syn::LitStr>()?;\n\n        let _ = input.parse::<syn::Token![,]>();\n        let function = input.parse::<syn::ExprClosure>()?;\n\n        let mut closure_arguments = Vec::new();\n        for arg in function.inputs.iter() {\n            match arg {\n                syn::Pat::Type(pat) => match &*pat.pat {\n                    syn::Pat::Ident(ident) => {\n                        closure_arguments.push((ident.ident.clone(), (*pat.ty).clone()));\n                    }\n                    _ => {\n                        return Err(syn::Error::new_spanned(\n                            arg,\n                            \"Expected closure argument to be a typed pattern\",\n                        ))\n                    }\n                },\n                _ => {\n                    return Err(syn::Error::new_spanned(\n                        arg,\n                        \"Expected closure argument to be a typed pattern\",\n                    ))\n                }\n            }\n        }\n\n        let (segments, query, hash) = parse_route_segments(\n            path.span(),\n            #[allow(clippy::map_identity)]\n            closure_arguments.iter().map(|(name, ty)| (name, ty)),\n            &path.value(),\n        )?;\n\n        Ok(Redirect {\n            route: path,\n            nests: active_nests,\n            segments,\n            query,\n            hash,\n            function,\n            index,\n        })\n    }\n}\n"
  },
  {
    "path": "packages/router-macro/src/route.rs",
    "content": "use quote::{format_ident, quote, quote_spanned};\nuse syn::parse::Parse;\nuse syn::parse::ParseStream;\nuse syn::parse_quote;\nuse syn::Field;\nuse syn::Path;\nuse syn::Type;\nuse syn::{Ident, LitStr};\n\nuse proc_macro2::TokenStream as TokenStream2;\n\nuse crate::hash::HashFragment;\nuse crate::layout::Layout;\nuse crate::layout::LayoutId;\nuse crate::nest::Nest;\nuse crate::nest::NestId;\nuse crate::query::QuerySegment;\nuse crate::segment::create_error_type;\nuse crate::segment::parse_route_segments;\nuse crate::segment::RouteSegment;\n\nstruct RouteArgs {\n    route: LitStr,\n    comp_name: Option<Path>,\n}\n\nimpl Parse for RouteArgs {\n    fn parse(input: ParseStream<'_>) -> syn::Result<Self> {\n        let route = input.parse::<LitStr>()?;\n\n        Ok(RouteArgs {\n            route,\n            comp_name: {\n                let _ = input.parse::<syn::Token![,]>();\n                input.parse().ok()\n            },\n        })\n    }\n}\n\nstruct ChildArgs {\n    route: LitStr,\n}\n\nimpl Parse for ChildArgs {\n    fn parse(input: ParseStream<'_>) -> syn::Result<Self> {\n        let route = input.parse::<LitStr>()?;\n\n        Ok(ChildArgs { route })\n    }\n}\n\n#[derive(Debug)]\npub(crate) struct Route {\n    pub route_name: Ident,\n    pub ty: RouteType,\n    pub route: String,\n    pub segments: Vec<RouteSegment>,\n    pub query: Option<QuerySegment>,\n    pub hash: Option<HashFragment>,\n    pub nests: Vec<NestId>,\n    pub layouts: Vec<LayoutId>,\n    fields: Vec<(Ident, Type)>,\n}\n\nimpl Route {\n    pub(crate) fn parse(\n        nests: Vec<NestId>,\n        layouts: Vec<LayoutId>,\n        variant: syn::Variant,\n    ) -> syn::Result<Self> {\n        let route_attr = variant\n            .attrs\n            .iter()\n            .find(|attr| attr.path().is_ident(\"route\"));\n        let route;\n        let ty;\n        let route_name = variant.ident.clone();\n        match route_attr {\n            Some(attr) => {\n                let args = attr.parse_args::<RouteArgs>()?;\n                let comp_name = args.comp_name.unwrap_or_else(|| parse_quote!(#route_name));\n                ty = RouteType::Leaf {\n                    component: comp_name,\n                };\n                route = args.route.value();\n            }\n            None => {\n                if let Some(route_attr) = variant\n                    .attrs\n                    .iter()\n                    .find(|attr| attr.path().is_ident(\"child\"))\n                {\n                    let args = route_attr.parse_args::<ChildArgs>()?;\n                    route = args.route.value();\n                    match &variant.fields {\n                        syn::Fields::Named(fields) => {\n                            // find either a field with #[child] or a field named \"child\"\n                            let child_field = fields.named.iter().find(|f| {\n                                f.attrs\n                                    .iter()\n                                    .any(|attr| attr.path().is_ident(\"child\"))\n                                    || *f.ident.as_ref().unwrap() == \"child\"\n                            });\n                            match child_field{\n                                Some(child) => {\n                                    ty = RouteType::Child(child.clone());\n                                }\n                                None => {\n                                    return Err(syn::Error::new_spanned(\n                                        variant.clone(),\n                                        \"Routable variants with a #[child(..)] attribute must have a field named \\\"child\\\" or a field with a #[child] attribute\",\n                                    ));\n                                }\n                            }\n                        }\n                        _ => {\n                            return Err(syn::Error::new_spanned(\n                                variant.clone(),\n                                \"Routable variants with a #[child(..)] attribute must have named fields\",\n                            ))\n                        }\n                    }\n                } else {\n                    return Err(syn::Error::new_spanned(\n                            variant.clone(),\n                            \"Routable variants must either have a #[route(..)] attribute or a #[child(..)] attribute\",\n                        ));\n                }\n            }\n        };\n\n        let fields = match &variant.fields {\n            syn::Fields::Named(fields) => fields\n                .named\n                .iter()\n                .filter_map(|f| {\n                    if let RouteType::Child(child) = &ty {\n                        if f.ident == child.ident {\n                            return None;\n                        }\n                    }\n                    Some((f.ident.clone().unwrap(), f.ty.clone()))\n                })\n                .collect(),\n            _ => Vec::new(),\n        };\n\n        let (route_segments, query, hash) = {\n            parse_route_segments(\n                variant.ident.span(),\n                fields.iter().map(|f| (&f.0, &f.1)),\n                &route,\n            )?\n        };\n\n        Ok(Self {\n            ty,\n            route_name,\n            segments: route_segments,\n            route,\n            query,\n            hash,\n            nests,\n            layouts,\n            fields,\n        })\n    }\n\n    pub(crate) fn display_match(&self, nests: &[Nest]) -> TokenStream2 {\n        let name = &self.route_name;\n        let dynamic_segments = self.dynamic_segments();\n        let write_query: Option<TokenStream2> = self.query.as_ref().map(|q| q.write());\n        let write_hash = self.hash.as_ref().map(|q| q.write());\n\n        match &self.ty {\n            RouteType::Child(field) => {\n                let write_nests = self.nests.iter().map(|id| nests[id.0].write());\n                let write_segments = self.segments.iter().map(|s| s.write_segment());\n                let child = field.ident.as_ref().unwrap();\n                quote! {\n                    Self::#name { #(#dynamic_segments,)* #child } => {\n                        use ::std::fmt::Display;\n                        use ::std::fmt::Write;\n                        let mut route = String::new();\n                        {\n                            let f = &mut route;\n                            #(#write_nests)*\n                            #(#write_segments)*\n                        }\n                        if route.ends_with('/') {\n                            route.pop();\n                        }\n                        f.write_str(&route)?;\n                        #child.fmt(f)?;\n                    }\n                }\n            }\n            RouteType::Leaf { .. } => {\n                let write_nests = self.nests.iter().map(|id| nests[id.0].write());\n                let write_segments = self.segments.iter().map(|s| s.write_segment());\n                quote! {\n                    Self::#name { #(#dynamic_segments,)* } => {\n                        #(#write_nests)*\n                        #(#write_segments)*\n                        #write_query\n                        #write_hash\n                    }\n                }\n            }\n        }\n    }\n\n    pub(crate) fn routable_match(\n        &self,\n        layouts: &[Layout],\n        nests: &[Nest],\n        router_name: &Ident,\n    ) -> TokenStream2 {\n        let name = &self.route_name;\n\n        let mut tokens = TokenStream2::new();\n\n        // First match all layouts\n        for (idx, layout_id) in self.layouts.iter().copied().enumerate() {\n            let render_layout = layouts[layout_id.0].routable_match(nests);\n            let dynamic_segments = self.dynamic_segments();\n            let mut field_name = None;\n            if let RouteType::Child(field) = &self.ty {\n                field_name = field.ident.as_ref();\n            }\n            let field_name = field_name.map(|f| quote!(#f,));\n            // This is a layout\n            tokens.extend(quote! {\n                #[allow(unused)]\n                (#idx, Self::#name { #(#dynamic_segments,)* #field_name .. }) => {\n                    #render_layout\n                }\n            });\n        }\n\n        // Then match the route\n        let last_index = self.layouts.len();\n        tokens.extend(match &self.ty {\n            RouteType::Child(field) => {\n                let field_name = field.ident.as_ref().unwrap();\n                quote! {\n                    #[allow(unused)]\n                    (#last_index.., Self::#name { #field_name, .. }) => {\n                        rsx! {\n                            dioxus_router::components::child_router::ChildRouter {\n                                route: #field_name,\n                                // Try to parse the current route as a parent route, and then match it as a child route\n                                parse_route_from_root_route: |__route| if let Ok(__route) = __route.parse() {\n                                    if let Self::#name { #field_name, .. } = __route {\n                                        Some(#field_name)\n                                    } else {\n                                        None\n                                    }\n                                } else {\n                                    None\n                                },\n                                // Try to parse the child route and turn it into a parent route\n                                format_route_as_root_route: |#field_name| Self::#name { #field_name: #field_name }.to_string(),\n                            }\n                        }\n                    }\n                }\n            }\n            RouteType::Leaf { component } => {\n                let dynamic_segments = self.dynamic_segments();\n                let dynamic_segments_from_route = self.dynamic_segments();\n\n\n                let component = quote_spanned! { name.span() =>\n                    #component\n                };\n\n                /*\n                The implementation of this is pretty gnarly/gross.\n\n                We achieve the bundle splitting by wrapping the incoming function in a new component\n                that suspends based on an internal lazy loader. This lets us use suspense features\n                without breaking the rules of hooks. The router derive is quite complex so this shoves\n                the complexity towards the \"leaf\" of the codegen rather to its core. In the future though,\n                we should think about restructuring the router macro completely since its codegen\n                makes up nearly 30-40% of the binary size in the dioxus docsite.\n                */\n                use sha2::Digest;\n                let dynamic_segments_receiver = self.dynamic_segments();\n                let dynamic_segments_from_route_ = self.dynamic_segments();\n                let dynamic_segments_from_route__ = self.dynamic_segments();\n                    let unique_identifier = base16::encode_lower(\n                    &sha2::Sha256::digest(format!(\"{name} {span:?}\", span = name.span()))[..16],\n                );\n                let module_name = format_ident!(\"module{}{unique_identifier}\", name).to_string();\n                let comp_name = format_ident!(\"route{}{unique_identifier}\", name);\n\n                quote! {\n                    #[allow(unused)]\n                    (#last_index, Self::#name { #(#dynamic_segments,)* }) => {\n                        dioxus::config_macros::maybe_wasm_split! {\n                            if wasm_split {\n                                {\n                                    fn #comp_name(args: #router_name) -> Element {\n                                        match args {\n                                            #router_name::#name { #(#dynamic_segments_from_route_,)* } => {\n                                                rsx! {\n                                                    #component {\n                                                        #(#dynamic_segments_from_route__: #dynamic_segments_from_route__,)*\n                                                    }\n                                                }\n                                            }\n                                            _ => unreachable!()\n                                        }\n                                    }\n\n\n\n                                    #[component]\n                                    fn LoaderInner(args: NoPartialEq<#router_name>) -> Element {\n                                        static MODULE: wasm_split::LazyLoader<#router_name, Element> =\n                                            wasm_split::lazy_loader!(extern #module_name fn #comp_name(props: #router_name) -> Element);\n\n                                        use_resource(|| async move { MODULE.load().await }).suspend()?;\n                                        MODULE.call(args.0).unwrap()\n                                    }\n\n                                    struct NoPartialEq<T>(T);\n\n                                    impl<T: Clone> Clone for NoPartialEq<T> {\n                                        fn clone(&self) -> Self {\n                                            Self(self.0.clone())\n                                        }\n                                    }\n\n                                    impl<T: ::std::fmt::Display> ::std::fmt::Display for NoPartialEq<T> {\n                                        fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {\n                                            self.0.fmt(f)\n                                        }\n                                    }\n\n                                    impl<T> PartialEq for NoPartialEq<T> {\n                                        fn eq(&self, _other: &Self) -> bool {\n                                            false\n                                        }\n                                    }\n\n                                    rsx! {\n                                        LoaderInner {\n                                            args: NoPartialEq(#router_name::#name { #(#dynamic_segments_receiver,)* } )\n                                        }\n                                    }\n                                }\n                            } else {\n                                {\n                                    rsx! {\n                                        #component {\n                                            #(#dynamic_segments_from_route: #dynamic_segments_from_route,)*\n                                        }\n                                    }\n                                }\n                            }\n                        }\n                    }\n                }\n            }\n        });\n\n        tokens\n    }\n\n    fn dynamic_segments(&self) -> impl Iterator<Item = TokenStream2> + '_ {\n        self.fields.iter().map(|(name, _)| {\n            quote! {#name}\n        })\n    }\n\n    pub(crate) fn construct(&self, nests: &[Nest], enum_name: Ident) -> TokenStream2 {\n        let segments = self.fields.iter().map(|(name, _)| {\n            let mut from_route = false;\n\n            for id in &self.nests {\n                let nest = &nests[id.0];\n                if nest.dynamic_segments_names().any(|i| &i == name) {\n                    from_route = true\n                }\n            }\n            for segment in &self.segments {\n                if segment.name().as_ref() == Some(name) {\n                    from_route = true\n                }\n            }\n            if let Some(query) = &self.query {\n                if query.contains_ident(name) {\n                    from_route = true\n                }\n            }\n            if let Some(hash) = &self.hash {\n                if hash.contains_ident(name) {\n                    from_route = true\n                }\n            }\n\n            if from_route {\n                quote! {#name}\n            } else {\n                quote! {#name: Default::default()}\n            }\n        });\n        match &self.ty {\n            RouteType::Child(field) => {\n                let name = &self.route_name;\n                let child_name = field.ident.as_ref().unwrap();\n\n                quote! {\n                    #enum_name::#name {\n                        #child_name,\n                        #(#segments,)*\n                    }\n                }\n            }\n            RouteType::Leaf { .. } => {\n                let name = &self.route_name;\n\n                quote! {\n                    #enum_name::#name {\n                        #(#segments,)*\n                    }\n                }\n            }\n        }\n    }\n\n    pub(crate) fn error_ident(&self) -> Ident {\n        format_ident!(\"{}ParseError\", self.route_name)\n    }\n\n    pub(crate) fn error_type(&self) -> TokenStream2 {\n        let error_name = self.error_ident();\n        let child_type = match &self.ty {\n            RouteType::Child(field) => Some(&field.ty),\n            RouteType::Leaf { .. } => None,\n        };\n\n        create_error_type(&self.route, error_name, &self.segments, child_type)\n    }\n\n    pub(crate) fn parse_query(&self) -> TokenStream2 {\n        match &self.query {\n            Some(query) => query.parse(),\n            None => quote! {},\n        }\n    }\n\n    pub(crate) fn parse_hash(&self) -> TokenStream2 {\n        match &self.hash {\n            Some(hash) => hash.parse(),\n            None => quote! {},\n        }\n    }\n}\n\n#[allow(clippy::large_enum_variant)]\n#[derive(Debug)]\npub(crate) enum RouteType {\n    Child(Field),\n    Leaf { component: Path },\n}\n"
  },
  {
    "path": "packages/router-macro/src/route_tree.rs",
    "content": "use proc_macro2::TokenStream;\nuse quote::quote;\nuse slab::Slab;\nuse syn::Ident;\n\nuse crate::{\n    nest::{Nest, NestId},\n    redirect::Redirect,\n    route::{Route, RouteType},\n    segment::{static_segment_idx, RouteSegment},\n    RouteEndpoint,\n};\n\n#[derive(Debug, Clone, Default)]\npub(crate) struct ParseRouteTree<'a> {\n    pub roots: Vec<usize>,\n    entries: Slab<RouteTreeSegmentData<'a>>,\n}\n\nimpl<'a> ParseRouteTree<'a> {\n    pub fn get(&self, index: usize) -> Option<&RouteTreeSegmentData<'a>> {\n        self.entries.get(index)\n    }\n\n    pub fn get_mut(&mut self, element: usize) -> Option<&mut RouteTreeSegmentData<'a>> {\n        self.entries.get_mut(element)\n    }\n\n    fn sort_children(&mut self) {\n        let mut old_roots = self.roots.clone();\n        self.sort_ids(&mut old_roots);\n        self.roots = old_roots;\n\n        for id in self.roots.clone() {\n            self.sort_children_of_id(id);\n        }\n    }\n\n    fn sort_ids(&self, ids: &mut [usize]) {\n        ids.sort_by_key(|&seg| {\n            let seg = self.get(seg).unwrap();\n            match seg {\n                RouteTreeSegmentData::Static { .. } => 0,\n                RouteTreeSegmentData::Nest { .. } => 1,\n                RouteTreeSegmentData::Route(route) => {\n                    // Routes that end in a catch all segment should be checked last\n                    match route.segments.last() {\n                        Some(RouteSegment::CatchAll(..)) => 2,\n                        _ => 1,\n                    }\n                }\n                RouteTreeSegmentData::Redirect(redirect) => {\n                    // Routes that end in a catch all segment should be checked last\n                    match redirect.segments.last() {\n                        Some(RouteSegment::CatchAll(..)) => 2,\n                        _ => 1,\n                    }\n                }\n            }\n        });\n    }\n\n    fn sort_children_of_id(&mut self, id: usize) {\n        // Sort segments so that all static routes are checked before dynamic routes\n        let mut children = self.children(id);\n\n        self.sort_ids(&mut children);\n\n        if let Some(old) = self.try_children_mut(id) {\n            old.clone_from(&children)\n        }\n\n        for id in children {\n            self.sort_children_of_id(id);\n        }\n    }\n\n    fn children(&self, element: usize) -> Vec<usize> {\n        let element = self.entries.get(element).unwrap();\n        match element {\n            RouteTreeSegmentData::Static { children, .. } => children.clone(),\n            RouteTreeSegmentData::Nest { children, .. } => children.clone(),\n            _ => Vec::new(),\n        }\n    }\n\n    fn try_children_mut(&mut self, element: usize) -> Option<&mut Vec<usize>> {\n        let element = self.entries.get_mut(element).unwrap();\n        match element {\n            RouteTreeSegmentData::Static { children, .. } => Some(children),\n            RouteTreeSegmentData::Nest { children, .. } => Some(children),\n            _ => None,\n        }\n    }\n\n    fn children_mut(&mut self, element: usize) -> &mut Vec<usize> {\n        self.try_children_mut(element)\n            .expect(\"Cannot get children of non static or nest segment\")\n    }\n\n    pub(crate) fn new(endpoints: &'a [RouteEndpoint], nests: &'a [Nest]) -> Self {\n        let routes = endpoints\n            .iter()\n            .map(|endpoint| match endpoint {\n                RouteEndpoint::Route(route) => PathIter::new_route(route, nests),\n                RouteEndpoint::Redirect(redirect) => PathIter::new_redirect(redirect, nests),\n            })\n            .collect::<Vec<_>>();\n\n        let mut myself = Self::default();\n        myself.roots = myself.construct(routes);\n        myself.sort_children();\n\n        myself\n    }\n\n    pub fn construct(&mut self, routes: Vec<PathIter<'a>>) -> Vec<usize> {\n        let mut segments = Vec::new();\n\n        // Add all routes to the tree\n        for mut route in routes {\n            let mut current_route: Option<usize> = None;\n\n            // First add all nests\n            while let Some(nest) = route.next_nest() {\n                let segments_iter = nest.segments.iter();\n\n                // Add all static segments of the nest\n                'o: for (index, segment) in segments_iter.enumerate() {\n                    match segment {\n                        RouteSegment::Static(segment) => {\n                            // Check if the segment already exists\n                            {\n                                // Either look for the segment in the current route or in the static segments\n                                let segments = current_route\n                                    .map(|id| self.children(id))\n                                    .unwrap_or_else(|| segments.clone());\n\n                                for &seg_id in segments.iter() {\n                                    let seg = self.get(seg_id).unwrap();\n                                    if let RouteTreeSegmentData::Static { segment: s, .. } = seg {\n                                        if *s == segment {\n                                            // If it does, just update the current route\n                                            current_route = Some(seg_id);\n                                            continue 'o;\n                                        }\n                                    }\n                                }\n                            }\n\n                            let static_segment = RouteTreeSegmentData::Static {\n                                segment,\n                                children: Vec::new(),\n                                error_variant: StaticErrorVariant {\n                                    variant_parse_error: nest.error_ident(),\n                                    enum_variant: nest.error_variant(),\n                                },\n                                index,\n                            };\n\n                            // If it doesn't, add the segment to the current route\n                            let static_segment = self.entries.insert(static_segment);\n\n                            let current_children = current_route\n                                .map(|id| self.children_mut(id))\n                                .unwrap_or_else(|| &mut segments);\n                            current_children.push(static_segment);\n\n                            // Update the current route\n                            current_route = Some(static_segment);\n                        }\n                        // If there is a dynamic segment, stop adding static segments\n                        RouteSegment::Dynamic(..) => break,\n                        RouteSegment::CatchAll(..) => {\n                            unimplemented!(\"Catch all segments are not allowed in nests\")\n                        }\n                    }\n                }\n\n                // Add the nest to the current route\n                let nest = RouteTreeSegmentData::Nest {\n                    nest,\n                    children: Vec::new(),\n                };\n\n                let nest = self.entries.insert(nest);\n                let segments = match current_route.and_then(|id| self.get_mut(id)) {\n                    Some(RouteTreeSegmentData::Static { children, .. }) => children,\n                    Some(RouteTreeSegmentData::Nest { children, .. }) => children,\n                    Some(r) => {\n                        unreachable!(\"{current_route:?}\\n{r:?} is not a static or nest segment\",)\n                    }\n                    None => &mut segments,\n                };\n                segments.push(nest);\n\n                // Update the current route\n                current_route = Some(nest);\n            }\n\n            match route.next_static_segment() {\n                // If there is a static segment, check if it already exists in the tree\n                Some((i, segment)) => {\n                    let current_children = current_route\n                        .map(|id| self.children(id))\n                        .unwrap_or_else(|| segments.clone());\n                    let found = current_children.iter().find_map(|&id| {\n                        let seg = self.get(id).unwrap();\n                        match seg {\n                            RouteTreeSegmentData::Static { segment: s, .. } => {\n                                (s == &segment).then_some(id)\n                            }\n                            _ => None,\n                        }\n                    });\n\n                    match found {\n                        Some(id) => {\n                            // If it exists, add the route to the children of the segment\n                            let new_children = self.construct(vec![route]);\n                            self.children_mut(id).extend(new_children);\n                        }\n                        None => {\n                            // If it doesn't exist, add the route as a new segment\n                            let data = RouteTreeSegmentData::Static {\n                                segment,\n                                error_variant: route.error_variant(),\n                                children: self.construct(vec![route]),\n                                index: i,\n                            };\n                            let id = self.entries.insert(data);\n                            let current_children_mut = current_route\n                                .map(|id| self.children_mut(id))\n                                .unwrap_or_else(|| &mut segments);\n                            current_children_mut.push(id);\n                        }\n                    }\n                }\n                // If there is no static segment, add the route to the current_route\n                None => {\n                    let id = self.entries.insert(route.final_segment);\n                    let current_children_mut = current_route\n                        .map(|id| self.children_mut(id))\n                        .unwrap_or_else(|| &mut segments);\n                    current_children_mut.push(id);\n                }\n            }\n        }\n\n        segments\n    }\n}\n\n#[derive(Debug, Clone)]\npub struct StaticErrorVariant {\n    variant_parse_error: Ident,\n    enum_variant: Ident,\n}\n\n// First deduplicate the routes by the static part of the route\n#[derive(Debug, Clone)]\npub(crate) enum RouteTreeSegmentData<'a> {\n    Static {\n        segment: &'a str,\n        error_variant: StaticErrorVariant,\n        index: usize,\n        children: Vec<usize>,\n    },\n    Nest {\n        nest: &'a Nest,\n        children: Vec<usize>,\n    },\n    Route(&'a Route),\n    Redirect(&'a Redirect),\n}\n\nimpl RouteTreeSegmentData<'_> {\n    pub fn to_tokens(\n        &self,\n        nests: &[Nest],\n        tree: &ParseRouteTree,\n        enum_name: syn::Ident,\n        error_enum_name: syn::Ident,\n    ) -> TokenStream {\n        match self {\n            RouteTreeSegmentData::Static {\n                segment,\n                children,\n                index,\n                error_variant:\n                    StaticErrorVariant {\n                        variant_parse_error,\n                        enum_variant,\n                    },\n            } => {\n                let children = children.iter().map(|child| {\n                    let child = tree.get(*child).unwrap();\n                    child.to_tokens(nests, tree, enum_name.clone(), error_enum_name.clone())\n                });\n\n                if segment.is_empty() {\n                    return quote! {\n                        {\n                            #(#children)*\n                        }\n                    };\n                }\n\n                let error_ident = static_segment_idx(*index);\n\n                quote! {\n                    {\n                        let mut segments = segments.clone();\n                        let segment = segments.next();\n                        if let Some(segment) = segment.as_deref() {\n                            if #segment == segment {\n                                #(#children)*\n                            } else {\n                                errors.push(#error_enum_name::#enum_variant(#variant_parse_error::#error_ident(segment.to_string())))\n                            }\n                        }\n                    }\n                }\n            }\n            RouteTreeSegmentData::Route(route) => {\n                // At this point, we have matched all static segments, so we can just check if the remaining segments match the route\n                let variant_parse_error = route.error_ident();\n                let enum_variant = &route.route_name;\n\n                let route_segments = route\n                    .segments\n                    .iter()\n                    .enumerate()\n                    .skip_while(|(_, seg)| matches!(seg, RouteSegment::Static(_)))\n                    .filter(|(i, _)| {\n                        // Don't add any trailing static segments. We strip them during parsing so that routes can accept either `/route/` and `/route`\n                        !is_trailing_static_segment(&route.segments, *i)\n                    });\n\n                let construct_variant = route.construct(nests, enum_name);\n                let parse_query = route.parse_query();\n                let parse_hash = route.parse_hash();\n\n                let insure_not_trailing = match route.ty {\n                    RouteType::Leaf { .. } => route\n                        .segments\n                        .last()\n                        .map(|seg| !matches!(seg, RouteSegment::CatchAll(_, _)))\n                        .unwrap_or(true),\n                    RouteType::Child(_) => false,\n                };\n\n                let print_route_segment = print_route_segment(\n                    route_segments.peekable(),\n                    return_constructed(\n                        insure_not_trailing,\n                        construct_variant,\n                        &error_enum_name,\n                        enum_variant,\n                        &variant_parse_error,\n                        parse_query,\n                        parse_hash,\n                    ),\n                    &error_enum_name,\n                    enum_variant,\n                    &variant_parse_error,\n                );\n\n                match &route.ty {\n                    RouteType::Child(child) => {\n                        let ty = &child.ty;\n                        let child_name = &child.ident;\n\n                        quote! {\n                            let mut trailing = String::from(\"/\");\n                            for seg in segments.clone() {\n                                trailing += &*seg;\n                                trailing += \"/\";\n                            }\n                            match #ty::from_str(&trailing).map_err(|err| #error_enum_name::#enum_variant(#variant_parse_error::ChildRoute(err))) {\n                                Ok(#child_name) => {\n                                    #print_route_segment\n                                }\n                                Err(err) => {\n                                    errors.push(err);\n                                }\n                            }\n                        }\n                    }\n                    RouteType::Leaf { .. } => print_route_segment,\n                }\n            }\n            Self::Nest { nest, children } => {\n                // At this point, we have matched all static segments, so we can just check if the remaining segments match the route\n                let variant_parse_error: Ident = nest.error_ident();\n                let enum_variant = nest.error_variant();\n\n                let route_segments = nest\n                    .segments\n                    .iter()\n                    .enumerate()\n                    .skip_while(|(_, seg)| matches!(seg, RouteSegment::Static(_)));\n\n                let parse_children = children\n                    .iter()\n                    .map(|child| {\n                        let child = tree.get(*child).unwrap();\n                        child.to_tokens(nests, tree, enum_name.clone(), error_enum_name.clone())\n                    })\n                    .collect();\n\n                print_route_segment(\n                    route_segments.peekable(),\n                    parse_children,\n                    &error_enum_name,\n                    &enum_variant,\n                    &variant_parse_error,\n                )\n            }\n            Self::Redirect(redirect) => {\n                // At this point, we have matched all static segments, so we can just check if the remaining segments match the route\n                let variant_parse_error = redirect.error_ident();\n                let enum_variant = &redirect.error_variant();\n\n                let route_segments = redirect\n                    .segments\n                    .iter()\n                    .enumerate()\n                    .skip_while(|(_, seg)| matches!(seg, RouteSegment::Static(_)));\n\n                let parse_query = redirect.parse_query();\n                let parse_hash = redirect.parse_hash();\n\n                let insure_not_trailing = redirect\n                    .segments\n                    .last()\n                    .map(|seg| !matches!(seg, RouteSegment::CatchAll(_, _)))\n                    .unwrap_or(true);\n\n                let redirect_function = &redirect.function;\n                let args = redirect_function.inputs.iter().map(|pat| match pat {\n                    syn::Pat::Type(ident) => {\n                        let name = &ident.pat;\n                        quote! {#name}\n                    }\n                    _ => panic!(\"Expected closure argument to be a typed pattern\"),\n                });\n                let return_redirect = quote! {\n                    (#redirect_function)(#(#args,)*)\n                };\n\n                print_route_segment(\n                    route_segments.peekable(),\n                    return_constructed(\n                        insure_not_trailing,\n                        return_redirect,\n                        &error_enum_name,\n                        enum_variant,\n                        &variant_parse_error,\n                        parse_query,\n                        parse_hash,\n                    ),\n                    &error_enum_name,\n                    enum_variant,\n                    &variant_parse_error,\n                )\n            }\n        }\n    }\n}\n\nfn print_route_segment<'a, I: Iterator<Item = (usize, &'a RouteSegment)>>(\n    mut s: std::iter::Peekable<I>,\n    success_tokens: TokenStream,\n    error_enum_name: &Ident,\n    enum_variant: &Ident,\n    variant_parse_error: &Ident,\n) -> TokenStream {\n    if let Some((i, route)) = s.next() {\n        let children = print_route_segment(\n            s,\n            success_tokens,\n            error_enum_name,\n            enum_variant,\n            variant_parse_error,\n        );\n\n        route.try_parse(\n            i,\n            error_enum_name,\n            enum_variant,\n            variant_parse_error,\n            children,\n        )\n    } else {\n        quote! {\n            #success_tokens\n        }\n    }\n}\n\nfn return_constructed(\n    insure_not_trailing: bool,\n    construct_variant: TokenStream,\n    error_enum_name: &Ident,\n    enum_variant: &Ident,\n    variant_parse_error: &Ident,\n    parse_query: TokenStream,\n    parse_hash: TokenStream,\n) -> TokenStream {\n    if insure_not_trailing {\n        quote! {\n            let remaining_segments = segments.clone();\n            let mut segments_clone = segments.clone();\n            let next_segment = segments_clone.next();\n            // This is the last segment, return the parsed route\n            if next_segment.is_none() {\n                #parse_query\n                #parse_hash\n                return Ok(#construct_variant);\n            } else {\n                let mut trailing = String::new();\n                for seg in remaining_segments {\n                    trailing += &*seg;\n                    trailing += \"/\";\n                }\n                trailing.pop();\n                errors.push(#error_enum_name::#enum_variant(#variant_parse_error::ExtraSegments(trailing)))\n            }\n        }\n    } else {\n        quote! {\n            #parse_query\n            #parse_hash\n            return Ok(#construct_variant);\n        }\n    }\n}\n\npub struct PathIter<'a> {\n    final_segment: RouteTreeSegmentData<'a>,\n    active_nests: &'a [NestId],\n    all_nests: &'a [Nest],\n    segments: &'a [RouteSegment],\n    error_ident: Ident,\n    error_variant: Ident,\n    nest_index: usize,\n    static_segment_index: usize,\n}\n\nimpl<'a> PathIter<'a> {\n    fn new_route(route: &'a Route, nests: &'a [Nest]) -> Self {\n        Self {\n            final_segment: RouteTreeSegmentData::Route(route),\n            active_nests: &*route.nests,\n            segments: &*route.segments,\n            error_ident: route.error_ident(),\n            error_variant: route.route_name.clone(),\n            all_nests: nests,\n            nest_index: 0,\n            static_segment_index: 0,\n        }\n    }\n\n    fn new_redirect(redirect: &'a Redirect, nests: &'a [Nest]) -> Self {\n        Self {\n            final_segment: RouteTreeSegmentData::Redirect(redirect),\n            active_nests: &*redirect.nests,\n            segments: &*redirect.segments,\n            error_ident: redirect.error_ident(),\n            error_variant: redirect.error_variant(),\n            all_nests: nests,\n            nest_index: 0,\n            static_segment_index: 0,\n        }\n    }\n\n    fn next_nest(&mut self) -> Option<&'a Nest> {\n        let idx = self.nest_index;\n        let nest_index = self.active_nests.get(idx)?;\n        let nest = &self.all_nests[nest_index.0];\n        self.nest_index += 1;\n        Some(nest)\n    }\n\n    fn next_static_segment(&mut self) -> Option<(usize, &'a str)> {\n        let idx = self.static_segment_index;\n        let segment = self.segments.get(idx)?;\n        // Don't add any trailing static segments. We strip them during parsing so that routes can accept either `/route/` and `/route`\n        if is_trailing_static_segment(self.segments, idx) {\n            return None;\n        }\n        match segment {\n            RouteSegment::Static(segment) => {\n                self.static_segment_index += 1;\n                Some((idx, segment))\n            }\n            _ => None,\n        }\n    }\n\n    fn error_variant(&self) -> StaticErrorVariant {\n        StaticErrorVariant {\n            variant_parse_error: self.error_ident.clone(),\n            enum_variant: self.error_variant.clone(),\n        }\n    }\n}\n\n// If this is the last segment and it is an empty trailing segment, skip parsing it. The parsing code handles parsing /path/ and /path\npub(crate) fn is_trailing_static_segment(segments: &[RouteSegment], index: usize) -> bool {\n    // This can only be a trailing segment if we have more than one segment and this is the last segment\n    matches!(segments.get(index), Some(RouteSegment::Static(segment)) if segment.is_empty() && index == segments.len() - 1 && segments.len() > 1)\n}\n"
  },
  {
    "path": "packages/router-macro/src/segment.rs",
    "content": "use quote::{format_ident, quote, ToTokens};\nuse syn::{Ident, Type};\n\nuse proc_macro2::{Span, TokenStream as TokenStream2};\n\nuse crate::{hash::HashFragment, query::QuerySegment};\n\n#[derive(Debug, Clone)]\npub enum RouteSegment {\n    Static(String),\n    Dynamic(Ident, Type),\n    CatchAll(Ident, Type),\n}\n\nimpl RouteSegment {\n    pub fn name(&self) -> Option<Ident> {\n        match self {\n            Self::Static(_) => None,\n            Self::Dynamic(ident, _) => Some(ident.clone()),\n            Self::CatchAll(ident, _) => Some(ident.clone()),\n        }\n    }\n\n    pub fn write_segment(&self) -> TokenStream2 {\n        match self {\n            Self::Static(segment) => quote! { write!(f, \"/{}\", #segment)?; },\n            Self::Dynamic(ident, _) => quote! {\n                {\n                    let as_string = #ident.to_string();\n                    write!(f, \"/{}\", dioxus_router::exports::percent_encoding::utf8_percent_encode(&as_string, dioxus_router::exports::PATH_ASCII_SET))?;\n                }\n            },\n            Self::CatchAll(ident, _) => quote! {\n                dioxus_router::ToRouteSegments::display_route_segments(#ident,f)?;\n            },\n        }\n    }\n\n    pub fn error_name(&self, idx: usize) -> Ident {\n        match self {\n            Self::Static(_) => static_segment_idx(idx),\n            Self::Dynamic(ident, _) => format_ident!(\"{}ParseError\", ident),\n            Self::CatchAll(ident, _) => format_ident!(\"{}ParseError\", ident),\n        }\n    }\n\n    pub fn missing_error_name(&self) -> Option<Ident> {\n        match self {\n            Self::Dynamic(ident, _) => Some(format_ident!(\"{}MissingError\", ident)),\n            _ => None,\n        }\n    }\n\n    pub fn try_parse(\n        &self,\n        idx: usize,\n        error_enum_name: &Ident,\n        error_enum_variant: &Ident,\n        inner_parse_enum: &Ident,\n        parse_children: TokenStream2,\n    ) -> TokenStream2 {\n        let error_name = self.error_name(idx);\n        match self {\n            Self::Static(segment) => {\n                quote! {\n                    {\n                        let mut segments = segments.clone();\n                        let segment = segments.next();\n                        let segment = segment.as_deref();\n                        if let Some(#segment) = segment {\n                            #parse_children\n                        } else {\n                            errors.push(#error_enum_name::#error_enum_variant(#inner_parse_enum::#error_name(segment.map(|s|s.to_string()).unwrap_or_default())));\n                        }\n                    }\n                }\n            }\n            Self::Dynamic(name, ty) => {\n                let missing_error_name = self.missing_error_name().unwrap();\n                quote! {\n                    {\n                        let mut segments = segments.clone();\n                        let segment = segments.next();\n                        let parsed = if let Some(segment) = segment.as_deref() {\n                            <#ty as dioxus_router::routable::FromRouteSegment>::from_route_segment(segment).map_err(|err| #error_enum_name::#error_enum_variant(#inner_parse_enum::#error_name(err)))\n                        } else {\n                            Err(#error_enum_name::#error_enum_variant(#inner_parse_enum::#missing_error_name))\n                        };\n                        match parsed {\n                            Ok(#name) => {\n                                #parse_children\n                            }\n                            Err(err) => {\n                                errors.push(err);\n                            }\n                        }\n                    }\n                }\n            }\n            Self::CatchAll(name, ty) => {\n                quote! {\n                    {\n                        let parsed = {\n                            let remaining_segments: Vec<_> = segments.collect();\n                            let mut new_segments: Vec<&str> = Vec::new();\n                            for segment in &remaining_segments {\n                                new_segments.push(&*segment);\n                            }\n                            <#ty as dioxus_router::routable::FromRouteSegments>::from_route_segments(&new_segments).map_err(|err| #error_enum_name::#error_enum_variant(#inner_parse_enum::#error_name(err)))\n                        };\n                        match parsed {\n                            Ok(#name) => {\n                                #parse_children\n                            }\n                            Err(err) => {\n                                errors.push(err);\n                            }\n                        }\n                    }\n                }\n            }\n        }\n    }\n}\n\npub fn static_segment_idx(idx: usize) -> Ident {\n    format_ident!(\"StaticSegment{}ParseError\", idx)\n}\n\npub fn parse_route_segments<'a>(\n    route_span: Span,\n    fields: impl Iterator<Item = (&'a Ident, &'a Type)> + Clone,\n    route: &str,\n) -> syn::Result<(\n    Vec<RouteSegment>,\n    Option<QuerySegment>,\n    Option<HashFragment>,\n)> {\n    let mut route_segments = Vec::new();\n\n    let (route_string, hash) = match route.rsplit_once('#') {\n        Some((route, hash)) => (\n            route,\n            Some(HashFragment::parse_from_str(\n                route_span,\n                fields.clone(),\n                hash,\n            )?),\n        ),\n        None => (route, None),\n    };\n\n    let (route_string, query) = match route_string.rsplit_once('?') {\n        Some((route, query)) => (\n            route,\n            Some(QuerySegment::parse_from_str(\n                route_span,\n                fields.clone(),\n                query,\n            )?),\n        ),\n        None => (route_string, None),\n    };\n    let mut iterator = route_string.split('/');\n\n    // skip the first empty segment\n    let first = iterator.next();\n    if first != Some(\"\") {\n        return Err(syn::Error::new(\n            route_span,\n            format!(\n                \"Routes should start with /. Error found in the route '{}'\",\n                route\n            ),\n        ));\n    }\n\n    while let Some(segment) = iterator.next() {\n        if let Some(segment) = segment.strip_prefix(':') {\n            let spread = segment.starts_with(\"..\");\n\n            let ident = if spread {\n                segment[2..].to_string()\n            } else {\n                segment.to_string()\n            };\n\n            let field = fields.clone().find(|(name, _)| **name == ident);\n\n            let ty = if let Some(field) = field {\n                field.1.clone()\n            } else {\n                return Err(syn::Error::new(\n                    route_span,\n                    format!(\"Could not find a field with the name '{}'\", ident,),\n                ));\n            };\n            if spread {\n                route_segments.push(RouteSegment::CatchAll(\n                    Ident::new(&ident, Span::call_site()),\n                    ty,\n                ));\n\n                if iterator.next().is_some() {\n                    return Err(syn::Error::new(\n                        route_span,\n                        \"Catch-all route segments must be the last segment in a route. The route segments after the catch-all segment will never be matched.\",\n                    ));\n                } else {\n                    break;\n                }\n            } else {\n                route_segments.push(RouteSegment::Dynamic(\n                    Ident::new(&ident, Span::call_site()),\n                    ty,\n                ));\n            }\n        } else {\n            route_segments.push(RouteSegment::Static(segment.to_string()));\n        }\n    }\n\n    Ok((route_segments, query, hash))\n}\n\npub(crate) fn create_error_type(\n    route: &str,\n    error_name: Ident,\n    segments: &[RouteSegment],\n    child_type: Option<&Type>,\n) -> TokenStream2 {\n    let mut error_variants = Vec::new();\n    let mut display_match = Vec::new();\n\n    for (i, segment) in segments.iter().enumerate() {\n        let error_name = segment.error_name(i);\n        match segment {\n            RouteSegment::Static(index) => {\n                let comment = format!(\n                    \" An error that can occur when trying to parse the static segment '/{}'.\",\n                    index\n                );\n                error_variants.push(quote! {\n                    #[doc = #comment]\n                    #error_name(String)\n                });\n                display_match.push(quote! { Self::#error_name(found) => write!(f, \"Static segment '{}' did not match instead found '{}'\", #index, found)? });\n            }\n            RouteSegment::Dynamic(ident, ty) => {\n                let missing_error = segment.missing_error_name().unwrap();\n                let comment = format!(\n                    \" An error that can occur when trying to parse the dynamic segment '/:{}'.\",\n                    ident\n                );\n                error_variants.push(quote! {\n                    #[doc = #comment]\n                    #error_name(<#ty as dioxus_router::routable::FromRouteSegment>::Err)\n                });\n                display_match.push(quote! { Self::#error_name(err) => write!(f, \"Dynamic segment '({}:{})' did not match: {}\", stringify!(#ident), stringify!(#ty), err)? });\n                error_variants.push(quote! {\n                    #[doc = #comment]\n                    #missing_error\n                });\n                display_match.push(quote! { Self::#missing_error => write!(f, \"Dynamic segment '({}:{})' was missing\", stringify!(#ident), stringify!(#ty))? });\n            }\n            RouteSegment::CatchAll(ident, ty) => {\n                let comment = format!(\n                    \" An error that can occur when trying to parse the catch-all segment '/:..{}'.\",\n                    ident\n                );\n                error_variants.push(quote! {\n                    #[doc = #comment]\n                    #error_name(<#ty as dioxus_router::routable::FromRouteSegments>::Err)\n                });\n                display_match.push(quote! { Self::#error_name(err) => write!(f, \"Catch-all segment '({}:{})' did not match: {}\", stringify!(#ident), stringify!(#ty), err)? });\n            }\n        }\n    }\n\n    let child_type_variant = child_type\n        .map(|child_type| {\n            let comment = format!(\n                \" An error that can occur when trying to parse the child route [`{}`].\",\n                child_type.to_token_stream()\n            );\n            quote! {\n                #[doc = #comment]\n                ChildRoute(<#child_type as std::str::FromStr>::Err)\n            }\n        })\n        .into_iter();\n\n    let child_type_error = child_type\n        .map(|_| {\n            quote! {\n                Self::ChildRoute(error) => {\n                    write!(f, \"{}\", error)?\n                }\n            }\n        })\n        .into_iter();\n\n    let comment = format!(\n        \" An error that can occur when trying to parse the route variant `{}`.\",\n        route\n    );\n\n    quote! {\n        #[doc = #comment]\n        #[allow(non_camel_case_types)]\n        #[allow(clippy::derive_partial_eq_without_eq)]\n        pub enum #error_name {\n            #[doc = \" An error that can occur when extra segments are provided after the route.\"]\n            ExtraSegments(String),\n            #(#child_type_variant,)*\n            #(#error_variants,)*\n        }\n\n        impl ::std::fmt::Debug for #error_name {\n            fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {\n                write!(f, \"{}({})\", stringify!(#error_name), self)\n            }\n        }\n\n        impl ::std::fmt::Display for #error_name {\n            fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {\n                match self {\n                    Self::ExtraSegments(segments) => {\n                        write!(f, \"Found additional trailing segments: {}\", segments)?\n                    },\n                    #(#child_type_error,)*\n                    #(#display_match,)*\n                }\n                Ok(())\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "packages/rsx/.vscode/settings.json",
    "content": "{\n    \"rust-analyzer.check.workspace\": false,\n    \"rust-analyzer.cargo.features\": \"all\"\n}\n"
  },
  {
    "path": "packages/rsx/Cargo.toml",
    "content": "[package]\nname = \"dioxus-rsx\"\nversion = { workspace = true }\nauthors = [\"Jonathan Kelley\", \"Evan Almloff\"]\nedition = \"2021\"\nlicense = \"MIT OR Apache-2.0\"\ndescription = \"Core functionality for Dioxus - a concurrent renderer-agnostic Virtual DOM for interactive user experiences\"\nrepository = \"https://github.com/DioxusLabs/dioxus/\"\nhomepage = \"https://dioxuslabs.com\"\ndocumentation = \"https://dioxuslabs.com\"\nkeywords = [\"dom\", \"ui\", \"gui\", \"react\"]\n\n# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html\n\n[dependencies]\nproc-macro2 = { workspace = true, features = [\"span-locations\"] }\nproc-macro2-diagnostics = { workspace = true }\nquote = { workspace = true }\nrustversion.workspace = true\nsyn = { workspace = true, features = [\"full\", \"extra-traits\", \"visit\", \"visit-mut\"] }\n\n[features]\ndefault = []\n\n[dev-dependencies]\nprettyplease = { workspace = true }\nprettier-please = { workspace = true }\n\n[package.metadata.docs.rs]\ncargo-args = [\"-Zunstable-options\", \"-Zrustdoc-scrape-examples\"]\n"
  },
  {
    "path": "packages/rsx/README.md",
    "content": "# Dioxus-RSX\n\n[![Crates.io][crates-badge]][crates-url]\n[![MIT licensed][mit-badge]][mit-url]\n[![Build Status][actions-badge]][actions-url]\n[![Discord chat][discord-badge]][discord-url]\n\n[crates-badge]: https://img.shields.io/crates/v/dioxus-rsx.svg\n[crates-url]: https://crates.io/crates/dioxus-rsx\n[mit-badge]: https://img.shields.io/badge/license-MIT-blue.svg\n[mit-url]: https://github.com/dioxuslabs/dioxus/blob/main/LICENSE-MIT\n[actions-badge]: https://github.com/dioxuslabs/dioxus/actions/workflows/main.yml/badge.svg\n[actions-url]: https://github.com/dioxuslabs/dioxus/actions?query=workflow%3ACI+branch%3Amaster\n[discord-badge]: https://img.shields.io/discord/899851952891002890.svg?logo=discord&style=flat-square\n[discord-url]: https://discord.gg/XgGxMSkvUM\n\n[Website](https://dioxuslabs.com) |\n[Guides](https://dioxuslabs.com/learn/0.7/) |\n[API Docs](https://docs.rs/dioxus-rsx/latest/dioxus_rsx) |\n[Chat](https://discord.gg/XgGxMSkvUM)\n\n## Overview\n\nThis crate provides the actual DSL that Dioxus uses in the `rsx!` macro. This crate is separate from the macro crate to enable tooling like autoformat, translation, and AST manipulation (extract to component).\n\n## Contributing\n\n- Report issues on our [issue tracker](https://github.com/dioxuslabs/dioxus/issues).\n- Join the discord and ask questions!\n\n## Design\n\nThe architecture of this crate has gone through several redesigns and we're hoping that we've finally landed on a final design that we're happy with.\n\nThere is still some thinking that we could approach this by flattening the AST to have better incremental parsing and performance without having to add metadata to the data structures.\n\nFor now, however, the design we've settled on:\n\n- A top-level CallBody containing a single TemplateBody\n- Each TemplateBody has its dynamic mapping on it, filled in after parsing (...not during)\n- Hotreload Information for things like literals is propagated as a final pass into nodes on the tree\n\nThis lets us incrementally build up the tree to make it modular/easier to test in exchange for extra \"bookkeeping passes\".\n\nThe good part of bookkeeping passes is:\n- They're optional\n- They're generally pretty quick\n- They can be enforced as part of tree construction in the various levels\n\nOf course the major downside is they're 1) slower and 2) need a way to store metadata, which they do on the nodes themselves. Sometimes you might not even want to collect metadata which we currently have no way of opting-out of.\n\nThis can make global reasoning about the tree a bit harder since you need to dig into each node for its metadata, but the local reasoning is much better. Previously we stored the metadata on a secondary \"view\" type item - and this was fine... but it's not ideal for long-lived programs that persist a queryable tree-object.\n\nWe want to query the tree:\n- For its dynamic mappings\n- For its hot-reloadable contents\n\nAn alternative approach would be\n- flattened AST\n- single \"parser\" IE `self.parse_element` / `self.parse_component`\n\nI've done a bunch of research on parsers and cannot find many (any?) parsers that are implemented statefully.\n\nWe *do* want a reference to these items - currently its their path, but it could be done via just an ID for each node. Unfortunately there's no easy way to \"pool\" these up other than Arc/Rc.\n\nAnyways, until our applications are limited by query performance, keeping the tree unflattened seems fine. The JS DOM has a similar API where it flushes changes after modifications, but also has an ID system via pointers. We could try adding IDs with something like generational-box or just Arc/Rc.\n\nNote that we still don't get the \"goodies\" of a proper tree-sitter type parser (skipping tokenization incrementally) but that really isn't achievable without being a part of the LSP. In theory, we can eventually use the LSP to help modify the tree in place and then \"fix\" it if you wanted to modify trees in place. That being said, we still need to perform diffing of two rsx! trees even when we know their shape has changed.\n\n## License\n\nThis project is licensed under the [MIT license].\n\n[mit license]: https://github.com/dioxuslabs/dioxus/blob/main/LICENSE-MIT\n\nUnless you explicitly state otherwise, any contribution intentionally submitted\nfor inclusion in Dioxus by you shall be licensed as MIT, without any additional\nterms or conditions.\n"
  },
  {
    "path": "packages/rsx/src/assign_dyn_ids.rs",
    "content": "use crate::attribute::Attribute;\nuse crate::{\n    AttributeValue, BodyNode, HotLiteral, HotReloadFormattedSegment, Segment, TemplateBody,\n};\n\n/// A visitor that assigns dynamic ids to nodes and attributes and accumulates paths to dynamic nodes and attributes\nstruct DynIdVisitor<'a> {\n    body: &'a mut TemplateBody,\n    current_path: Vec<u8>,\n    dynamic_text_index: usize,\n    component_literal_index: usize,\n}\n\nimpl<'a> DynIdVisitor<'a> {\n    fn new(body: &'a mut TemplateBody) -> Self {\n        Self {\n            body,\n            current_path: Vec::new(),\n            dynamic_text_index: 0,\n            component_literal_index: 0,\n        }\n    }\n\n    fn visit_children(&mut self, children: &[BodyNode]) {\n        for (idx, node) in children.iter().enumerate() {\n            self.current_path.push(idx as u8);\n            self.visit(node);\n            self.current_path.pop();\n        }\n    }\n\n    fn visit(&mut self, node: &BodyNode) {\n        match node {\n            // Just descend into elements - they're not dynamic\n            BodyNode::Element(el) => {\n                for (idx, attr) in el.merged_attributes.iter().enumerate() {\n                    if !attr.is_static_str_literal() {\n                        self.assign_path_to_attribute(attr, idx);\n                        if let AttributeValue::AttrLiteral(HotLiteral::Fmted(lit)) = &attr.value {\n                            self.assign_formatted_segment(lit);\n                        }\n                    }\n                }\n                // Assign formatted segments to the key which is not included in the merged_attributes\n                if let Some(AttributeValue::AttrLiteral(HotLiteral::Fmted(fmted))) = el.key() {\n                    self.assign_formatted_segment(fmted);\n                }\n\n                self.visit_children(&el.children);\n            }\n\n            // Text nodes are dynamic if they contain dynamic segments\n            BodyNode::Text(txt) => {\n                if !txt.is_static() {\n                    self.assign_path_to_node(node);\n                    self.assign_formatted_segment(&txt.input);\n                }\n            }\n\n            // Raw exprs are always dynamic\n            BodyNode::RawExpr(_) | BodyNode::ForLoop(_) | BodyNode::IfChain(_) => {\n                self.assign_path_to_node(node)\n            }\n            BodyNode::Component(component) => {\n                self.assign_path_to_node(node);\n                let mut index = 0;\n                for property in &component.fields {\n                    if let AttributeValue::AttrLiteral(literal) = &property.value {\n                        if let HotLiteral::Fmted(segments) = literal {\n                            self.assign_formatted_segment(segments);\n                        }\n                        // Don't include keys in the component dynamic pool\n                        if !property.name.is_likely_key() {\n                            component.component_literal_dyn_idx[index]\n                                .set(self.component_literal_index);\n                            self.component_literal_index += 1;\n                            index += 1;\n                        }\n                    }\n                }\n            }\n        };\n    }\n\n    /// Assign ids to a formatted segment\n    fn assign_formatted_segment(&mut self, segments: &HotReloadFormattedSegment) {\n        let mut dynamic_node_indexes = segments.dynamic_node_indexes.iter();\n        for segment in &segments.segments {\n            if let Segment::Formatted(segment) = segment {\n                dynamic_node_indexes\n                    .next()\n                    .unwrap()\n                    .set(self.dynamic_text_index);\n                self.dynamic_text_index += 1;\n                self.body.dynamic_text_segments.push(segment.clone());\n            }\n        }\n    }\n\n    /// Assign a path to a node and give it its dynamic index\n    /// This simplifies the ToTokens implementation for the macro to be a little less centralized\n    fn assign_path_to_node(&mut self, node: &BodyNode) {\n        // Assign the TemplateNode::Dynamic index to the node\n        node.set_dyn_idx(self.body.node_paths.len());\n\n        // And then save the current path as the corresponding path\n        self.body.node_paths.push(self.current_path.clone());\n    }\n\n    /// Assign a path to a attribute and give it its dynamic index\n    /// This simplifies the ToTokens implementation for the macro to be a little less centralized\n    pub(crate) fn assign_path_to_attribute(\n        &mut self,\n        attribute: &Attribute,\n        attribute_index: usize,\n    ) {\n        // Assign the dynamic index to the attribute\n        attribute.set_dyn_idx(self.body.attr_paths.len());\n\n        // And then save the current path as the corresponding path\n        self.body\n            .attr_paths\n            .push((self.current_path.clone(), attribute_index));\n    }\n}\n\nimpl TemplateBody {\n    /// Cascade down path information into the children of this template\n    ///\n    /// This provides the necessary path and index information for the children of this template\n    /// so that they can render out their dynamic nodes correctly. Also does plumbing for things like\n    /// hotreloaded literals which need to be tracked on a per-template basis.\n    ///\n    /// This can only operate with knowledge of this template, not the surrounding callbody. Things like\n    /// wiring of ifmt literals need to be done at the callbody level since those final IDs need to\n    /// be unique to the entire app.\n    pub(crate) fn assign_paths_inner(&mut self, nodes: &[BodyNode]) {\n        let mut visitor = DynIdVisitor::new(self);\n        visitor.visit_children(nodes);\n    }\n}\n"
  },
  {
    "path": "packages/rsx/src/attribute.rs",
    "content": "//! Parser for the attribute shared both by elements and components\n//!\n//! ```rust, ignore\n//! rsx! {\n//!     div {\n//!         class: \"my-class\",\n//!         onclick: move |_| println!(\"clicked\")\n//!     }\n//!\n//!     Component {\n//!         class: \"my-class\",\n//!         onclick: move |_| println!(\"clicked\")\n//!     }\n//! }\n//! ```\n\nuse super::literal::HotLiteral;\nuse crate::{innerlude::*, partial_closure::PartialClosure};\n\nuse proc_macro2::{Span, TokenStream as TokenStream2};\nuse quote::{quote, quote_spanned, ToTokens, TokenStreamExt};\nuse std::fmt::Display;\nuse syn::{\n    ext::IdentExt,\n    parse::{Parse, ParseStream},\n    parse_quote,\n    spanned::Spanned,\n    Block, Expr, ExprBlock, ExprClosure, ExprIf, Ident, Lit, LitBool, LitFloat, LitInt, LitStr,\n    Stmt, Token,\n};\n\n/// A property value in the from of a `name: value` pair with an optional comma.\n/// Note that the colon and value are optional in the case of shorthand attributes. We keep them around\n/// to support \"lossless\" parsing in case that ever might be useful.\n#[derive(PartialEq, Eq, Clone, Debug, Hash)]\npub struct Attribute {\n    /// The name of the attribute (ident or custom)\n    ///\n    /// IE `class` or `onclick`\n    pub name: AttributeName,\n\n    /// The colon that separates the name and value - keep this for lossless parsing\n    pub colon: Option<Token![:]>,\n\n    /// The value of the attribute\n    ///\n    /// IE `class=\"my-class\"` or `onclick: move |_| println!(\"clicked\")`\n    pub value: AttributeValue,\n\n    /// The comma that separates this attribute from the next one\n    /// Used for more accurate completions\n    pub comma: Option<Token![,]>,\n\n    /// The dynamic index of this attribute - used by the template system\n    pub dyn_idx: DynIdx,\n\n    /// The element name of this attribute if it is bound to an element.\n    /// When parsed for components or freestanding, this will be None\n    pub el_name: Option<ElementName>,\n}\n\nimpl Parse for Attribute {\n    fn parse(content: ParseStream) -> syn::Result<Self> {\n        // if there's an ident not followed by a colon, it's a shorthand attribute\n        if content.peek(Ident::peek_any) && !content.peek2(Token![:]) {\n            let ident = parse_raw_ident(content)?;\n            let comma = content.parse().ok();\n\n            return Ok(Attribute {\n                name: AttributeName::BuiltIn(ident.clone()),\n                colon: None,\n                value: AttributeValue::Shorthand(ident),\n                comma,\n                dyn_idx: DynIdx::default(),\n                el_name: None,\n            });\n        }\n\n        // Parse the name as either a known or custom attribute\n        let name = match content.peek(LitStr) {\n            true => AttributeName::Custom(content.parse::<LitStr>()?),\n            false => AttributeName::BuiltIn(parse_raw_ident(content)?),\n        };\n\n        // Ensure there's a colon\n        let colon = Some(content.parse::<Token![:]>()?);\n\n        // todo: make this cleaner please\n        // if statements in attributes get automatic closing in some cases\n        // we shouldn't be handling it any differently.\n        let value = AttributeValue::parse(content)?;\n\n        let comma = content.parse::<Token![,]>().ok();\n\n        let attr = Attribute {\n            name,\n            value,\n            colon,\n            comma,\n            dyn_idx: DynIdx::default(),\n            el_name: None,\n        };\n\n        Ok(attr)\n    }\n}\n\nimpl Attribute {\n    /// Create a new attribute from a name and value\n    pub fn from_raw(name: AttributeName, value: AttributeValue) -> Self {\n        Self {\n            name,\n            colon: Default::default(),\n            value,\n            comma: Default::default(),\n            dyn_idx: Default::default(),\n            el_name: None,\n        }\n    }\n\n    /// Set the dynamic index of this attribute\n    pub fn set_dyn_idx(&self, idx: usize) {\n        self.dyn_idx.set(idx);\n    }\n\n    /// Get the dynamic index of this attribute\n    pub fn get_dyn_idx(&self) -> usize {\n        self.dyn_idx.get()\n    }\n\n    pub fn span(&self) -> proc_macro2::Span {\n        self.name.span()\n    }\n\n    pub fn as_lit(&self) -> Option<&HotLiteral> {\n        match &self.value {\n            AttributeValue::AttrLiteral(lit) => Some(lit),\n            _ => None,\n        }\n    }\n\n    /// Run this closure against the attribute if it's hotreloadable\n    pub fn with_literal(&self, f: impl FnOnce(&HotLiteral)) {\n        if let AttributeValue::AttrLiteral(ifmt) = &self.value {\n            f(ifmt);\n        }\n    }\n\n    pub fn ifmt(&self) -> Option<&IfmtInput> {\n        match &self.value {\n            AttributeValue::AttrLiteral(HotLiteral::Fmted(input)) => Some(input),\n            _ => None,\n        }\n    }\n\n    pub fn as_static_str_literal(&self) -> Option<(&AttributeName, &IfmtInput)> {\n        match &self.value {\n            AttributeValue::AttrLiteral(lit) => match &lit {\n                HotLiteral::Fmted(input) if input.is_static() => Some((&self.name, input)),\n                _ => None,\n            },\n            _ => None,\n        }\n    }\n\n    pub fn is_static_str_literal(&self) -> bool {\n        self.as_static_str_literal().is_some()\n    }\n\n    pub fn rendered_as_dynamic_attr(&self) -> TokenStream2 {\n        // Shortcut out with spreads\n        if let AttributeName::Spread(_) = self.name {\n            let AttributeValue::AttrExpr(expr) = &self.value else {\n                unreachable!(\"Spread attributes should always be expressions\")\n            };\n            return quote_spanned! { expr.span() => {#expr}.into_boxed_slice() };\n        }\n\n        let el_name = self\n            .el_name\n            .as_ref()\n            .expect(\"el_name rendered as a dynamic attribute should always have an el_name set\");\n\n        let ns = |name: &AttributeName| match (el_name, name) {\n            (ElementName::Ident(i), AttributeName::BuiltIn(_)) => {\n                quote! { dioxus_elements::#i::#name.1 }\n            }\n            _ => quote! { None },\n        };\n\n        let volatile = |name: &AttributeName| match (el_name, name) {\n            (ElementName::Ident(i), AttributeName::BuiltIn(_)) => {\n                quote! { dioxus_elements::#i::#name.2 }\n            }\n            _ => quote! { false },\n        };\n\n        let attribute = |name: &AttributeName| match name {\n            AttributeName::BuiltIn(name) => match el_name {\n                ElementName::Ident(_) => quote! { dioxus_elements::#el_name::#name.0 },\n                ElementName::Custom(_) => {\n                    let as_string = name.to_string();\n                    quote!(#as_string)\n                }\n            },\n            AttributeName::Custom(s) => quote! { #s },\n            AttributeName::Spread(_) => unreachable!(\"Spread attributes are handled elsewhere\"),\n        };\n\n        let attribute = {\n            let value = &self.value;\n            let name = &self.name;\n            let is_not_event = !self.name.is_likely_event();\n\n            match &self.value {\n                AttributeValue::AttrLiteral(_)\n                | AttributeValue::AttrExpr(_)\n                | AttributeValue::Shorthand(_)\n                | AttributeValue::IfExpr { .. }\n                    if is_not_event =>\n                {\n                    let name = &self.name;\n                    let ns = ns(name);\n                    let volatile = volatile(name);\n                    let attribute = attribute(name);\n                    let value = quote! { #value };\n\n                    quote! {\n                        dioxus_core::Attribute::new(\n                            #attribute,\n                            #value,\n                            #ns,\n                            #volatile\n                        )\n                    }\n                }\n                AttributeValue::EventTokens(_) | AttributeValue::AttrExpr(_) => {\n                    let (tokens, span) = match &self.value {\n                        AttributeValue::EventTokens(tokens) => {\n                            (tokens.to_token_stream(), tokens.span())\n                        }\n                        AttributeValue::AttrExpr(tokens) => {\n                            (tokens.to_token_stream(), tokens.span())\n                        }\n                        _ => unreachable!(),\n                    };\n\n                    fn check_tokens_is_closure(tokens: &TokenStream2) -> bool {\n                        if syn::parse2::<ExprClosure>(tokens.to_token_stream()).is_ok() {\n                            return true;\n                        }\n                        let Ok(block) = syn::parse2::<ExprBlock>(tokens.to_token_stream()) else {\n                            return false;\n                        };\n                        let mut block = &block;\n                        loop {\n                            match block.block.stmts.last() {\n                                Some(Stmt::Expr(Expr::Closure(_), _)) => return true,\n                                Some(Stmt::Expr(Expr::Block(b), _)) => {\n                                    block = b;\n                                    continue;\n                                }\n                                _ => return false,\n                            }\n                        }\n                    }\n                    match &self.name {\n                        AttributeName::BuiltIn(name) => {\n                            let event_tokens_is_closure = check_tokens_is_closure(&tokens);\n                            let function_name =\n                                quote_spanned! { span => dioxus_elements::events::#name };\n                            let function = if event_tokens_is_closure {\n                                // If we see an explicit closure, we can call the `call_with_explicit_closure` version of the event for better type inference\n                                quote_spanned! { span => #function_name::call_with_explicit_closure }\n                            } else {\n                                function_name\n                            };\n                            quote_spanned! { span =>\n                                #function(#tokens)\n                            }\n                        }\n                        AttributeName::Custom(_) => unreachable!(\"Handled elsewhere in the macro\"),\n                        AttributeName::Spread(_) => unreachable!(\"Handled elsewhere in the macro\"),\n                    }\n                }\n                _ => {\n                    quote_spanned! { value.span() => dioxus_elements::events::#name(#value) }\n                }\n            }\n        };\n\n        let attr_span = attribute.span();\n        let completion_hints = self.completion_hints();\n        quote_spanned! { attr_span =>\n            Box::new([\n                {\n                    #completion_hints\n                    #attribute\n                }\n            ])\n        }\n        .to_token_stream()\n    }\n\n    pub fn can_be_shorthand(&self) -> bool {\n        // If it's a shorthand...\n        if matches!(self.value, AttributeValue::Shorthand(_)) {\n            return true;\n        }\n\n        // Or if it is a builtin attribute with a single ident value\n        if let (AttributeName::BuiltIn(name), AttributeValue::AttrExpr(expr)) =\n            (&self.name, &self.value)\n        {\n            if let Ok(Expr::Path(path)) = expr.as_expr() {\n                if path.path.get_ident() == Some(name) {\n                    return true;\n                }\n            }\n        }\n\n        false\n    }\n\n    /// If this is the last attribute of an element and it doesn't have a tailing comma,\n    /// we add hints so that rust analyzer completes it either as an attribute or element\n    fn completion_hints(&self) -> TokenStream2 {\n        let Attribute {\n            name,\n            value,\n            comma,\n            el_name,\n            ..\n        } = self;\n\n        // If there is a trailing comma, rust analyzer does a good job of completing the attribute by itself\n        if comma.is_some() {\n            return quote! {};\n        }\n\n        // Only add hints if the attribute is:\n        // - a built in attribute (not a literal)\n        // - an build in element (not a custom element)\n        // - a shorthand attribute\n        let (\n            Some(ElementName::Ident(el)),\n            AttributeName::BuiltIn(name),\n            AttributeValue::Shorthand(_),\n        ) = (&el_name, &name, &value)\n        else {\n            return quote! {};\n        };\n        // If the attribute is a shorthand attribute, but it is an event handler, rust analyzer already does a good job of completing the attribute by itself\n        if name.to_string().starts_with(\"on\") {\n            return quote! {};\n        }\n\n        quote! {\n            {\n                #[allow(dead_code)]\n                #[doc(hidden)]\n                mod __completions {\n                    // Autocomplete as an attribute\n                    pub use super::dioxus_elements::#el::*;\n                    // Autocomplete as an element\n                    pub use super::dioxus_elements::elements::completions::CompleteWithBraces::*;\n                    fn ignore() {\n                        #name;\n                    }\n                }\n            }\n        }\n    }\n}\n\n#[derive(PartialEq, Eq, Clone, Debug, Hash)]\npub enum AttributeName {\n    Spread(Token![..]),\n\n    /// an attribute in the form of `name: value`\n    BuiltIn(Ident),\n\n    /// an attribute in the form of `\"name\": value` - notice that the name is a string literal\n    /// this is to allow custom attributes in the case of missing built-in attributes\n    ///\n    /// we might want to change this one day to be ticked or something and simply a boolean\n    Custom(LitStr),\n}\n\nimpl AttributeName {\n    pub fn is_likely_event(&self) -> bool {\n        matches!(self, Self::BuiltIn(ident) if ident.to_string().starts_with(\"on\"))\n    }\n\n    pub fn is_likely_key(&self) -> bool {\n        matches!(self, Self::BuiltIn(ident) if ident == \"key\")\n    }\n\n    pub fn span(&self) -> proc_macro2::Span {\n        match self {\n            Self::Custom(lit) => lit.span(),\n            Self::BuiltIn(ident) => ident.span(),\n            Self::Spread(dots) => dots.span(),\n        }\n    }\n}\n\nimpl Display for AttributeName {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        match self {\n            Self::Custom(lit) => write!(f, \"{}\", lit.value()),\n            Self::BuiltIn(ident) => write!(f, \"{}\", ident),\n            Self::Spread(_) => write!(f, \"..\"),\n        }\n    }\n}\n\nimpl ToTokens for AttributeName {\n    fn to_tokens(&self, tokens: &mut TokenStream2) {\n        match self {\n            Self::Custom(lit) => lit.to_tokens(tokens),\n            Self::BuiltIn(ident) => ident.to_tokens(tokens),\n            Self::Spread(dots) => dots.to_tokens(tokens),\n        }\n    }\n}\n\n// ..spread attribute\n#[derive(PartialEq, Eq, Clone, Debug, Hash)]\npub struct Spread {\n    pub dots: Token![..],\n    pub expr: Expr,\n    pub dyn_idx: DynIdx,\n    pub comma: Option<Token![,]>,\n}\n\nimpl Spread {\n    pub fn span(&self) -> proc_macro2::Span {\n        self.dots.span()\n    }\n}\n\n#[derive(PartialEq, Eq, Clone, Debug, Hash)]\npub enum AttributeValue {\n    /// Just a regular shorthand attribute - an ident. Makes our parsing a bit more opaque.\n    /// attribute,\n    Shorthand(Ident),\n\n    /// Any attribute that's a literal. These get hotreloading super powers\n    ///\n    /// attribute: \"value\"\n    /// attribute: bool,\n    /// attribute: 1,\n    AttrLiteral(HotLiteral),\n\n    /// A series of tokens that represent an event handler\n    ///\n    /// We use a special type here so we can get autocomplete in the closure using partial expansion.\n    /// We also do some extra wrapping for improved type hinting since rust sometimes has trouble with\n    /// generics and closures.\n    EventTokens(PartialClosure),\n\n    /// Conditional expression\n    ///\n    /// attribute: if bool { \"value\" } else if bool { \"other value\" } else { \"default value\" }\n    ///\n    /// Currently these don't get hotreloading super powers, but they could, depending on how far\n    /// we want to go with it\n    IfExpr(IfAttributeValue),\n\n    /// attribute: some_expr\n    /// attribute: {some_expr} ?\n    AttrExpr(PartialExpr),\n}\n\nimpl Parse for AttributeValue {\n    fn parse(content: ParseStream) -> syn::Result<Self> {\n        // Attempt to parse the unterminated if statement\n        if content.peek(Token![if]) {\n            return Ok(Self::IfExpr(content.parse::<IfAttributeValue>()?));\n        }\n\n        // Use the move and/or bars as an indicator that we have an event handler\n        if content.peek(Token![move]) || content.peek(Token![|]) {\n            let value = content.parse()?;\n            return Ok(AttributeValue::EventTokens(value));\n        }\n\n        if content.peek(LitStr)\n            || content.peek(LitBool)\n            || content.peek(LitFloat)\n            || content.peek(LitInt)\n        {\n            let fork = content.fork();\n            _ = fork.parse::<Lit>().unwrap();\n\n            if content.peek2(Token![,]) || fork.is_empty() {\n                let value = content.parse()?;\n                return Ok(AttributeValue::AttrLiteral(value));\n            }\n        }\n\n        let value = content.parse::<PartialExpr>()?;\n        Ok(AttributeValue::AttrExpr(value))\n    }\n}\n\nimpl ToTokens for AttributeValue {\n    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {\n        match self {\n            Self::Shorthand(ident) => ident.to_tokens(tokens),\n            Self::AttrLiteral(ifmt) => ifmt.to_tokens(tokens),\n            Self::IfExpr(if_expr) => if_expr.to_tokens(tokens),\n            Self::AttrExpr(expr) => expr.to_tokens(tokens),\n            Self::EventTokens(closure) => closure.to_tokens(tokens),\n        }\n    }\n}\n\nimpl AttributeValue {\n    pub fn span(&self) -> proc_macro2::Span {\n        match self {\n            Self::Shorthand(ident) => ident.span(),\n            Self::AttrLiteral(ifmt) => ifmt.span(),\n            Self::IfExpr(if_expr) => if_expr.span(),\n            Self::AttrExpr(expr) => expr.span(),\n            Self::EventTokens(closure) => closure.span(),\n        }\n    }\n}\n\n/// A if else chain attribute value\n#[derive(PartialEq, Eq, Clone, Debug, Hash)]\npub struct IfAttributeValue {\n    pub if_expr: ExprIf,\n    pub condition: Expr,\n    pub then_value: Box<AttributeValue>,\n    pub else_value: Option<Box<AttributeValue>>,\n}\n\nimpl IfAttributeValue {\n    /// Convert the if expression to an expression that returns a string. If the unterminated case is hit, it returns an empty string\n    pub(crate) fn quote_as_string(&self, diagnostics: &mut Diagnostics) -> Expr {\n        let mut expression = quote! {};\n        let mut current_if_value = self;\n\n        let mut non_string_diagnostic = |span: proc_macro2::Span| -> Expr {\n            Element::add_merging_non_string_diagnostic(diagnostics, span);\n            parse_quote! { ::std::string::String::new() }\n        };\n\n        loop {\n            let AttributeValue::AttrLiteral(lit) = current_if_value.then_value.as_ref() else {\n                return non_string_diagnostic(current_if_value.span());\n            };\n\n            let HotLiteral::Fmted(HotReloadFormattedSegment {\n                formatted_input: new,\n                ..\n            }) = &lit\n            else {\n                return non_string_diagnostic(current_if_value.span());\n            };\n\n            let condition = &current_if_value.if_expr.cond;\n            expression.extend(quote! {\n                if #condition {\n                    #new.to_string()\n                } else\n            });\n            match current_if_value.else_value.as_deref() {\n                // If the else value is another if expression, then we need to continue the loop\n                Some(AttributeValue::IfExpr(else_value)) => {\n                    current_if_value = else_value;\n                }\n                // If the else value is a literal, then we need to append it to the expression and break\n                Some(AttributeValue::AttrLiteral(lit)) => {\n                    if let HotLiteral::Fmted(new) = &lit {\n                        let fmted = &new.formatted_input;\n                        expression.extend(quote! { { #fmted.to_string() } });\n                        break;\n                    } else {\n                        return non_string_diagnostic(current_if_value.span());\n                    }\n                }\n                // If it is the end of the if expression without an else, then we need to append the default value and break\n                None => {\n                    expression.extend(quote! { { ::std::string::String::new() } });\n                    break;\n                }\n                _ => {\n                    return non_string_diagnostic(current_if_value.else_value.span());\n                }\n            }\n        }\n\n        parse_quote! {\n            {\n                #expression\n            }\n        }\n    }\n\n    fn span(&self) -> Span {\n        self.if_expr.span()\n    }\n\n    fn is_terminated(&self) -> bool {\n        match &self.else_value {\n            Some(attribute) => match attribute.as_ref() {\n                AttributeValue::IfExpr(if_expr) => if_expr.is_terminated(),\n                _ => true,\n            },\n            None => false,\n        }\n    }\n\n    fn contains_expression(&self) -> bool {\n        fn attribute_value_contains_expression(expr: &AttributeValue) -> bool {\n            match expr {\n                AttributeValue::IfExpr(if_expr) => if_expr.contains_expression(),\n                AttributeValue::AttrLiteral(_) => false,\n                _ => true,\n            }\n        }\n\n        attribute_value_contains_expression(&self.then_value)\n            || self\n                .else_value\n                .as_deref()\n                .is_some_and(attribute_value_contains_expression)\n    }\n\n    fn parse_attribute_value_from_block(block: &Block) -> syn::Result<Box<AttributeValue>> {\n        let stmts = &block.stmts;\n\n        if stmts.len() != 1 {\n            return Err(syn::Error::new(\n                block.span(),\n                \"Expected a single statement in the if block\",\n            ));\n        }\n\n        // either an ifmt or an expr in the block\n        let stmt = &stmts[0];\n\n        // Either it's a valid ifmt or an expression\n        match stmt {\n            syn::Stmt::Expr(exp, None) => {\n                // Try parsing the statement as an IfmtInput by passing it through tokens\n                let value: Result<HotLiteral, syn::Error> = syn::parse2(exp.to_token_stream());\n                Ok(match value {\n                    Ok(res) => Box::new(AttributeValue::AttrLiteral(res)),\n                    Err(_) => Box::new(AttributeValue::AttrExpr(PartialExpr::from_expr(exp))),\n                })\n            }\n            _ => Err(syn::Error::new(stmt.span(), \"Expected an expression\")),\n        }\n    }\n\n    fn to_tokens_with_terminated(\n        &self,\n        tokens: &mut TokenStream2,\n        terminated: bool,\n        contains_expression: bool,\n    ) {\n        let IfAttributeValue {\n            if_expr,\n            then_value,\n            else_value,\n            ..\n        } = self;\n\n        // Quote an attribute value and convert the value to a string if it is formatted\n        // We always quote formatted segments as strings inside if statements so they have a consistent type\n        // This fixes https://github.com/DioxusLabs/dioxus/issues/2997\n        fn quote_attribute_value_string(\n            value: &AttributeValue,\n            contains_expression: bool,\n        ) -> TokenStream2 {\n            if let AttributeValue::AttrLiteral(HotLiteral::Fmted(fmted)) = value {\n                if let Some(str) = fmted.to_static().filter(|_| contains_expression) {\n                    // If this is actually a static string, the user may be using a static string expression in another branch\n                    // use into to convert the string to whatever the other branch is using\n                    quote! {\n                        {\n                            #[allow(clippy::useless_conversion)]\n                            #str.into()\n                        }\n                    }\n                } else {\n                    quote! { #value.to_string() }\n                }\n            } else {\n                value.to_token_stream()\n            }\n        }\n\n        let then_value = quote_attribute_value_string(then_value, contains_expression);\n\n        let then_value = if terminated {\n            quote! { #then_value }\n        }\n        // Otherwise we need to return an Option and a None if the else value is None\n        else {\n            quote! { Some(#then_value) }\n        };\n\n        let else_value = match else_value.as_deref() {\n            Some(AttributeValue::IfExpr(else_value)) => {\n                let mut tokens = TokenStream2::new();\n                else_value.to_tokens_with_terminated(&mut tokens, terminated, contains_expression);\n                tokens\n            }\n            Some(other) => {\n                let other = quote_attribute_value_string(other, contains_expression);\n                if terminated {\n                    other\n                } else {\n                    quote_spanned! { other.span() => Some(#other) }\n                }\n            }\n            None => quote! { None },\n        };\n\n        let condition = &if_expr.cond;\n        tokens.append_all(quote_spanned! { if_expr.span()=>\n            if #condition {\n                #then_value\n            } else {\n                #else_value\n            }\n        });\n    }\n}\n\nimpl Parse for IfAttributeValue {\n    fn parse(input: ParseStream) -> syn::Result<Self> {\n        let if_expr = input.parse::<ExprIf>()?;\n\n        let stmts = &if_expr.then_branch.stmts;\n\n        if stmts.len() != 1 {\n            return Err(syn::Error::new(\n                if_expr.then_branch.span(),\n                \"Expected a single statement in the if block\",\n            ));\n        }\n\n        // Parse the then branch into a single attribute value\n        let then_value = Self::parse_attribute_value_from_block(&if_expr.then_branch)?;\n\n        // If there's an else branch, parse it as a single attribute value or an if expression\n        let else_value = match if_expr.else_branch.as_ref() {\n            Some((_, else_branch)) => {\n                // The else branch if either a block or another if expression\n                let attribute_value = match else_branch.as_ref() {\n                    // If it is a block, then the else is terminated\n                    Expr::Block(block) => Self::parse_attribute_value_from_block(&block.block)?,\n                    // Otherwise try to parse it as an if expression\n                    _ => Box::new(syn::parse2(else_branch.to_token_stream())?),\n                };\n                Some(attribute_value)\n            }\n            None => None,\n        };\n\n        Ok(Self {\n            condition: *if_expr.cond.clone(),\n            if_expr,\n            then_value,\n            else_value,\n        })\n    }\n}\n\nimpl ToTokens for IfAttributeValue {\n    fn to_tokens(&self, tokens: &mut TokenStream2) {\n        // If the if expression is terminated, we can just return the then value\n        let terminated = self.is_terminated();\n        let contains_expression = self.contains_expression();\n        self.to_tokens_with_terminated(tokens, terminated, contains_expression)\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use quote::quote;\n    use syn::parse2;\n\n    #[test]\n    fn parse_attrs() {\n        let _parsed: Attribute = parse2(quote! { name: \"value\" }).unwrap();\n        let _parsed: Attribute = parse2(quote! { name: value }).unwrap();\n        let _parsed: Attribute = parse2(quote! { name: \"value {fmt}\" }).unwrap();\n        let _parsed: Attribute = parse2(quote! { name: 123 }).unwrap();\n        let _parsed: Attribute = parse2(quote! { name: false }).unwrap();\n        let _parsed: Attribute = parse2(quote! { \"custom\": false }).unwrap();\n        let _parsed: Attribute = parse2(quote! { prop: \"blah\".to_string() }).unwrap();\n\n        // with commas\n        let _parsed: Attribute = parse2(quote! { \"custom\": false, }).unwrap();\n        let _parsed: Attribute = parse2(quote! { name: false, }).unwrap();\n\n        // with if chains\n        let parsed: Attribute = parse2(quote! { name: if true { \"value\" } }).unwrap();\n        assert!(matches!(parsed.value, AttributeValue::IfExpr(_)));\n        let parsed: Attribute =\n            parse2(quote! { name: if true { \"value\" } else { \"other\" } }).unwrap();\n        assert!(matches!(parsed.value, AttributeValue::IfExpr(_)));\n        let parsed: Attribute =\n            parse2(quote! { name: if true { \"value\" } else if false { \"other\" } }).unwrap();\n        assert!(matches!(parsed.value, AttributeValue::IfExpr(_)));\n\n        // with shorthand\n        let _parsed: Attribute = parse2(quote! { name }).unwrap();\n        let _parsed: Attribute = parse2(quote! { name, }).unwrap();\n\n        // Events - make sure they get partial expansion\n        let parsed: Attribute = parse2(quote! { onclick: |e| {} }).unwrap();\n        assert!(matches!(parsed.value, AttributeValue::EventTokens(_)));\n        let parsed: Attribute = parse2(quote! { onclick: |e| { \"value\" } }).unwrap();\n        assert!(matches!(parsed.value, AttributeValue::EventTokens(_)));\n        let parsed: Attribute = parse2(quote! { onclick: |e| { value. } }).unwrap();\n        assert!(matches!(parsed.value, AttributeValue::EventTokens(_)));\n        let parsed: Attribute = parse2(quote! { onclick: move |e| { value. } }).unwrap();\n        assert!(matches!(parsed.value, AttributeValue::EventTokens(_)));\n        let parsed: Attribute = parse2(quote! { onclick: move |e| value }).unwrap();\n        assert!(matches!(parsed.value, AttributeValue::EventTokens(_)));\n        let parsed: Attribute = parse2(quote! { onclick: |e| value, }).unwrap();\n        assert!(matches!(parsed.value, AttributeValue::EventTokens(_)));\n    }\n\n    #[test]\n    fn merge_attrs() {\n        let _a: Attribute = parse2(quote! { class: \"value1\" }).unwrap();\n        let _b: Attribute = parse2(quote! { class: \"value2\" }).unwrap();\n\n        let _b: Attribute = parse2(quote! { class: \"value2 {something}\" }).unwrap();\n        let _b: Attribute = parse2(quote! { class: if value { \"other thing\" } }).unwrap();\n        let _b: Attribute = parse2(quote! { class: if value { some_expr } }).unwrap();\n\n        let _b: Attribute = parse2(quote! { class: if value { \"some_expr\" } }).unwrap();\n        dbg!(_b);\n    }\n\n    #[test]\n    fn static_literals() {\n        let a: Attribute = parse2(quote! { class: \"value1\" }).unwrap();\n        let b: Attribute = parse2(quote! { class: \"value {some}\" }).unwrap();\n\n        assert!(a.is_static_str_literal());\n        assert!(!b.is_static_str_literal());\n    }\n\n    #[test]\n    fn partial_eqs() {\n        // Basics\n        let a: Attribute = parse2(quote! { class: \"value1\" }).unwrap();\n        let b: Attribute = parse2(quote! { class: \"value1\" }).unwrap();\n        assert_eq!(a, b);\n\n        // Exprs\n        let a: Attribute = parse2(quote! { class: var }).unwrap();\n        let b: Attribute = parse2(quote! { class: var }).unwrap();\n        assert_eq!(a, b);\n\n        // Events\n        let a: Attribute = parse2(quote! { onclick: |e| {} }).unwrap();\n        let b: Attribute = parse2(quote! { onclick: |e| {} }).unwrap();\n        let c: Attribute = parse2(quote! { onclick: move |e| {} }).unwrap();\n        let d: Attribute = parse2(quote! { onclick: { |e| {} } }).unwrap();\n        assert_eq!(a, b);\n        assert_ne!(a, c);\n        assert_ne!(a, d);\n    }\n\n    #[test]\n    fn call_with_explicit_closure() {\n        let mut a: Attribute = parse2(quote! { onclick: |e| {} }).unwrap();\n        a.el_name = Some(parse_quote!(button));\n        assert!(a\n            .rendered_as_dynamic_attr()\n            .to_string()\n            .contains(\"call_with_explicit_closure\"));\n\n        let mut a: Attribute = parse2(quote! { onclick: { let a = 1; |e| {} } }).unwrap();\n        a.el_name = Some(parse_quote!(button));\n        assert!(a\n            .rendered_as_dynamic_attr()\n            .to_string()\n            .contains(\"call_with_explicit_closure\"));\n\n        let mut a: Attribute = parse2(quote! { onclick: { let b = 2; { |e| { b } } } }).unwrap();\n        a.el_name = Some(parse_quote!(button));\n        assert!(a\n            .rendered_as_dynamic_attr()\n            .to_string()\n            .contains(\"call_with_explicit_closure\"));\n\n        let mut a: Attribute = parse2(quote! { onclick: { let r = |e| { b }; r } }).unwrap();\n        a.el_name = Some(parse_quote!(button));\n        assert!(!a\n            .rendered_as_dynamic_attr()\n            .to_string()\n            .contains(\"call_with_explicit_closure\"));\n    }\n\n    /// Make sure reserved keywords are parsed as attributes\n    /// HTML gets annoying sometimes so we just accept them\n    #[test]\n    fn reserved_keywords() {\n        let _a: Attribute = parse2(quote! { for: \"class\" }).unwrap();\n        let _b: Attribute = parse2(quote! { type: \"class\" }).unwrap();\n    }\n}\n"
  },
  {
    "path": "packages/rsx/src/component.rs",
    "content": "//! Parse components into the VNode::Component variant\n//!\n//! Uses the regular robust RsxBlock parser and then validates the component, emitting errors as\n//! diagnostics. This was refactored from a straightforward parser to this validation approach so\n//! that we can emit errors as diagnostics instead of returning results.\n//!\n//! Using this approach we can provide *much* better errors as well as partial expansion wherever\n//! possible.\n//!\n//! It does lead to the code actually being larger than it was before, but it should be much easier\n//! to work with and extend. To add new syntax, we add it to the RsxBlock parser and then add a\n//! validation step here. This does make using the component as a source of truth not as good, but\n//! oddly enoughly, we want the tree to actually be capable of being technically invalid. This is not\n//! usual for building in Rust - you want strongly typed things to be valid - but in this case, we\n//! want to accept all sorts of malformed input and then provide the best possible error messages.\n//!\n//! If you're generally parsing things, you'll just want to parse and then check if it's valid.\n\nuse crate::innerlude::*;\nuse proc_macro2::TokenStream as TokenStream2;\nuse proc_macro2_diagnostics::SpanDiagnosticExt;\nuse quote::{quote, quote_spanned, ToTokens, TokenStreamExt};\nuse std::{collections::HashSet, vec};\nuse syn::{\n    parse::{Parse, ParseStream},\n    spanned::Spanned,\n    token, AngleBracketedGenericArguments, Expr, Ident, PathArguments, Result,\n};\n\n#[derive(PartialEq, Eq, Clone, Debug)]\npub struct Component {\n    pub name: syn::Path,\n    pub generics: Option<AngleBracketedGenericArguments>,\n    pub fields: Vec<Attribute>,\n    pub component_literal_dyn_idx: Vec<DynIdx>,\n    pub spreads: Vec<Spread>,\n    pub brace: Option<token::Brace>,\n    pub children: TemplateBody,\n    pub dyn_idx: DynIdx,\n    pub diagnostics: Diagnostics,\n}\n\nimpl Parse for Component {\n    fn parse(input: ParseStream) -> Result<Self> {\n        let mut name = input.parse::<syn::Path>()?;\n        let generics = normalize_path(&mut name);\n\n        if !input.peek(token::Brace) {\n            return Ok(Self::empty(name, generics));\n        };\n\n        let RsxBlock {\n            attributes: fields,\n            children,\n            brace,\n            spreads,\n            diagnostics,\n        } = input.parse::<RsxBlock>()?;\n\n        let literal_properties_count = fields\n            .iter()\n            .filter(|attr| matches!(attr.value, AttributeValue::AttrLiteral(_)))\n            .count();\n        let component_literal_dyn_idx = vec![DynIdx::default(); literal_properties_count];\n\n        let mut component = Self {\n            dyn_idx: DynIdx::default(),\n            children: TemplateBody::new(children),\n            name,\n            generics,\n            fields,\n            brace: Some(brace),\n            component_literal_dyn_idx,\n            spreads,\n            diagnostics,\n        };\n\n        // We've received a valid rsx block, but it's not necessarily a valid component\n        // validating it will dump diagnostics into the output\n        component.validate_component_path();\n        component.validate_fields();\n        component.validate_component_spread();\n\n        Ok(component)\n    }\n}\n\nimpl ToTokens for Component {\n    fn to_tokens(&self, tokens: &mut TokenStream2) {\n        let Self { name, generics, .. } = self;\n\n        // Create props either from manual props or from the builder approach\n        let props = self.create_props();\n\n        // Make sure we emit any errors\n        let diagnostics = &self.diagnostics;\n\n        tokens.append_all(quote! {\n            dioxus_core::DynamicNode::Component({\n\n                // todo: ensure going through the trait actually works\n                // we want to avoid importing traits\n                use dioxus_core::Properties;\n                let __comp = ({\n                    #props\n                }).into_vcomponent(\n                    #name #generics,\n                );\n                #diagnostics\n                __comp\n            })\n        })\n    }\n}\n\nimpl Component {\n    // Make sure this a proper component path (uppercase ident, a path, or contains an underscorea)\n    // This should be validated by the RsxBlock parser when it peeks bodynodes\n    fn validate_component_path(&mut self) {\n        let path = &self.name;\n\n        // First, ensure the path is not a single lowercase ident with no underscores\n        if path.segments.len() == 1 {\n            let seg = path.segments.first().unwrap();\n            if seg.ident.to_string().chars().next().unwrap().is_lowercase()\n                && !seg.ident.to_string().contains('_')\n            {\n                self.diagnostics.push(seg.ident.span().error(\n                    \"Component names must be uppercase, contain an underscore, or abe a path.\",\n                ));\n            }\n        }\n\n        // ensure path segments doesn't have PathArguments, only the last\n        // segment is allowed to have one.\n        if path\n            .segments\n            .iter()\n            .take(path.segments.len() - 1)\n            .any(|seg| seg.arguments != PathArguments::None)\n        {\n            self.diagnostics.push(path.span().error(\n                \"Component names must not have path arguments. Only the last segment is allowed to have one.\",\n            ));\n        }\n\n        // ensure last segment only have value of None or AngleBracketed\n        if !matches!(\n            path.segments.last().unwrap().arguments,\n            PathArguments::None | PathArguments::AngleBracketed(_)\n        ) {\n            self.diagnostics.push(\n                path.span()\n                    .error(\"Component names must have no arguments or angle bracketed arguments.\"),\n            );\n        }\n    }\n\n    // Make sure the spread argument is being used as props spreading\n    fn validate_component_spread(&mut self) {\n        // Next, ensure that there's only one spread argument in the attributes *and* it's the last one\n        for spread in self.spreads.iter().skip(1) {\n            self.diagnostics.push(\n                spread\n                    .expr\n                    .span()\n                    .error(\"Only one set of manual props is allowed for a component.\"),\n            );\n        }\n    }\n\n    pub fn get_key(&self) -> Option<&AttributeValue> {\n        self.fields\n            .iter()\n            .find(|attr| attr.name.is_likely_key())\n            .map(|attr| &attr.value)\n    }\n\n    /// Ensure there's no duplicate props - this will be a compile error but we can move it to a\n    /// diagnostic, thankfully\n    fn validate_fields(&mut self) {\n        let mut seen = HashSet::new();\n\n        for field in self.fields.iter() {\n            match &field.name {\n                AttributeName::Custom(_) => {}\n                AttributeName::BuiltIn(k) => {\n                    if !seen.contains(k) {\n                        seen.insert(k);\n                    } else {\n                        self.diagnostics.push(k.span().error(\n                            \"Duplicate prop field found. Only one prop field per name is allowed.\",\n                        ));\n                    }\n                }\n                AttributeName::Spread(_) => {\n                    unreachable!(\n                        \"Spread attributes should be handled in the spread validation step.\"\n                    )\n                }\n            }\n        }\n    }\n\n    /// Create the tokens we'll use for the props of the component\n    ///\n    /// todo: don't create the tokenstream from scratch and instead dump it into the existing streama\n    fn create_props(&self) -> TokenStream2 {\n        let manual_props = self.manual_props();\n\n        let name = &self.name;\n        let generics = &self.generics;\n        let inner_scope_span = self\n            .brace\n            .as_ref()\n            .map(|b| b.span.join())\n            .unwrap_or(self.name.span());\n\n        let mut tokens = if let Some(props) = manual_props.as_ref() {\n            quote_spanned! { props.span() => let mut __manual_props = #props; }\n        } else {\n            // we only want to span the name and generics, not the `fc_to_builder` call so jump-to-def\n            // only finds the single entry (#name)\n            let spanned = quote_spanned! { self.name.span() => #name #generics };\n            quote! { dioxus_core::fc_to_builder(#spanned) }\n        };\n\n        tokens.append_all(self.add_fields_to_builder(\n            manual_props.map(|_| Ident::new(\"__manual_props\", proc_macro2::Span::call_site())),\n        ));\n\n        if !self.children.is_empty() {\n            let children = &self.children;\n            // If the props don't accept children, attach the error to the first child\n            if manual_props.is_some() {\n                tokens.append_all(\n                    quote_spanned! { children.first_root_span() => __manual_props.children = #children; },\n                )\n            } else {\n                tokens.append_all(\n                    quote_spanned! { children.first_root_span() => .children( #children ) },\n                )\n            }\n        }\n\n        if manual_props.is_some() {\n            tokens.append_all(quote! { __manual_props })\n        } else {\n            // If this does fail to build, point the compiler error at the Prop braces\n            tokens.append_all(quote_spanned! { inner_scope_span => .build() })\n        }\n\n        tokens\n    }\n\n    fn manual_props(&self) -> Option<&Expr> {\n        self.spreads.first().map(|spread| &spread.expr)\n    }\n\n    // Iterate over the props of the component (without spreads, key, and custom attributes)\n    pub fn component_props(&self) -> impl Iterator<Item = &Attribute> {\n        self.fields\n            .iter()\n            .filter(move |attr| !attr.name.is_likely_key())\n    }\n\n    fn add_fields_to_builder(&self, manual_props: Option<Ident>) -> TokenStream2 {\n        let mut dynamic_literal_index = 0;\n        let mut tokens = TokenStream2::new();\n        for attribute in self.component_props() {\n            let release_value = attribute.value.to_token_stream();\n\n            // In debug mode, we try to grab the value from the dynamic literal pool if possible\n            let value = if let AttributeValue::AttrLiteral(literal) = &attribute.value {\n                let idx = self.component_literal_dyn_idx[dynamic_literal_index].get();\n                dynamic_literal_index += 1;\n                let debug_value = quote! { __dynamic_literal_pool.component_property(#idx, &*__template_read, #literal) };\n                quote! {\n                    {\n                        #[cfg(debug_assertions)]\n                        {\n                            #debug_value\n                        }\n                        #[cfg(not(debug_assertions))]\n                        {\n                            #release_value\n                        }\n                    }\n                }\n            } else {\n                release_value\n            };\n\n            match &attribute.name {\n                AttributeName::BuiltIn(name) => {\n                    if let Some(manual_props) = &manual_props {\n                        tokens.append_all(quote! { #manual_props.#name = #value; })\n                    } else {\n                        tokens.append_all(quote! { .#name(#value) })\n                    }\n                }\n                AttributeName::Custom(name) => {\n                    if manual_props.is_some() {\n                        tokens.append_all(name.span().error(\n                            \"Custom attributes are not supported for components that are spread\",\n                        ).emit_as_expr_tokens());\n                    } else {\n                        // tokens = quote! {\n                        //     dioxus_core::HasAttributes::push_attribute(\n                        //         #tokens,\n                        //         #name,\n                        //         None,\n                        //         #value,\n                        //         false\n                        //     )\n                        // };\n\n                        tokens.append_all(quote! {\n                            .push_attribute(#name, None, #value, false)\n                        })\n                    }\n                }\n                // spreads are handled elsewhere\n                AttributeName::Spread(_) => {}\n            }\n        }\n\n        tokens\n    }\n\n    fn empty(name: syn::Path, generics: Option<AngleBracketedGenericArguments>) -> Self {\n        let mut diagnostics = Diagnostics::new();\n        diagnostics.push(\n            name.span()\n                .error(\"Components must have a body\")\n                .help(\"Components must have a body, for example `Component {}`\"),\n        );\n        Component {\n            name,\n            generics,\n            brace: None,\n            fields: vec![],\n            spreads: vec![],\n            children: TemplateBody::new(vec![]),\n            component_literal_dyn_idx: vec![],\n            dyn_idx: DynIdx::default(),\n            diagnostics,\n        }\n    }\n}\n\n/// Normalize the generics of a path\n///\n/// Ensure there's a `::` after the last segment if there are generics\nfn normalize_path(name: &mut syn::Path) -> Option<AngleBracketedGenericArguments> {\n    let seg = name.segments.last_mut()?;\n\n    let mut generics = match seg.arguments.clone() {\n        PathArguments::AngleBracketed(args) => {\n            seg.arguments = PathArguments::None;\n            Some(args)\n        }\n        _ => None,\n    };\n\n    if let Some(generics) = generics.as_mut() {\n        generics.colon2_token = Some(syn::Token![::](proc_macro2::Span::call_site()));\n    }\n\n    generics\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use prettier_please::PrettyUnparse;\n    use syn::parse_quote;\n\n    /// Ensure we can parse a component\n    #[test]\n    fn parses() {\n        let input = quote! {\n            MyComponent {\n                key: \"value {something}\",\n                prop: \"value\",\n                ..props,\n                div {\n                    \"Hello, world!\"\n                }\n            }\n        };\n\n        let component: Component = syn::parse2(input).unwrap();\n\n        dbg!(component);\n\n        let input_without_manual_props = quote! {\n            MyComponent {\n                key: \"value {something}\",\n                prop: \"value\",\n                div { \"Hello, world!\" }\n            }\n        };\n\n        let component: Component = syn::parse2(input_without_manual_props).unwrap();\n        dbg!(component);\n    }\n\n    /// Ensure we reject invalid forms\n    ///\n    /// Maybe want to snapshot the errors?\n    #[test]\n    fn rejects() {\n        let input = quote! {\n            myComponent {\n                key: \"value\",\n                prop: \"value\",\n                prop: \"other\",\n                ..props,\n                ..other_props,\n                div {\n                    \"Hello, world!\"\n                }\n            }\n        };\n\n        let component: Component = syn::parse2(input).unwrap();\n        dbg!(component.diagnostics);\n    }\n\n    #[test]\n    fn to_tokens_properly() {\n        let input = quote! {\n            MyComponent {\n                key: \"value {something}\",\n                prop: \"value\",\n                prop: \"value\",\n                prop: \"value\",\n                prop: \"value\",\n                prop: 123,\n                ..props,\n                div { \"Hello, world!\" }\n            }\n        };\n\n        let component: Component = syn::parse2(input).unwrap();\n        println!(\"{}\", component.to_token_stream());\n    }\n\n    #[test]\n    fn to_tokens_no_manual_props() {\n        let input_without_manual_props = quote! {\n            MyComponent {\n                key: \"value {something}\",\n                named: \"value {something}\",\n                prop: \"value\",\n                count: 1,\n                div { \"Hello, world!\" }\n            }\n        };\n        let component: Component = syn::parse2(input_without_manual_props).unwrap();\n        println!(\"{}\", component.to_token_stream().pretty_unparse());\n    }\n\n    #[test]\n    fn generics_params() {\n        let input_without_children = quote! {\n             Outlet::<R> {}\n        };\n        let component: crate::CallBody = syn::parse2(input_without_children).unwrap();\n        println!(\"{}\", component.to_token_stream().pretty_unparse());\n    }\n\n    #[test]\n    fn generics_no_fish() {\n        let name = quote! { Outlet<R> };\n        let mut p = syn::parse2::<syn::Path>(name).unwrap();\n        let generics = normalize_path(&mut p);\n        assert!(generics.is_some());\n\n        let input_without_children = quote! {\n            div {\n                Component<Generic> {}\n            }\n        };\n        let component: BodyNode = syn::parse2(input_without_children).unwrap();\n        println!(\"{}\", component.to_token_stream().pretty_unparse());\n    }\n\n    #[test]\n    fn fmt_passes_properly() {\n        let input = quote! {\n            Link { to: Route::List, class: \"pure-button\", \"Go back\" }\n        };\n\n        let component: Component = syn::parse2(input).unwrap();\n\n        println!(\"{}\", component.to_token_stream().pretty_unparse());\n    }\n\n    #[test]\n    fn incomplete_components() {\n        let input = quote::quote! {\n            some::cool::Component\n        };\n\n        let _parsed: Component = syn::parse2(input).unwrap();\n\n        let input = quote::quote! {\n            some::cool::C\n        };\n\n        let _parsed: syn::Path = syn::parse2(input).unwrap();\n    }\n\n    #[test]\n    fn identifies_key() {\n        let input = quote! {\n            Link { key: \"{value}\", to: Route::List, class: \"pure-button\", \"Go back\" }\n        };\n\n        let component: Component = syn::parse2(input).unwrap();\n\n        // The key should exist\n        assert_eq!(component.get_key(), Some(&parse_quote!(\"{value}\")));\n\n        // The key should not be included in the properties\n        let properties = component\n            .component_props()\n            .map(|attr| attr.name.to_string())\n            .collect::<Vec<_>>();\n        assert_eq!(properties, [\"to\", \"class\"]);\n    }\n}\n"
  },
  {
    "path": "packages/rsx/src/diagnostics.rs",
    "content": "use proc_macro2_diagnostics::Diagnostic;\nuse quote::ToTokens;\n\n/// A collection of diagnostics\n///\n/// This is a wrapper type since we want it to be transparent in terms of PartialEq and Eq.\n/// This also lets us choose the expansion strategy for the diagnostics.\n#[derive(Debug, Clone, Default)]\npub struct Diagnostics {\n    pub diagnostics: Vec<Diagnostic>,\n}\n\nimpl Diagnostics {\n    pub fn new() -> Self {\n        Self {\n            diagnostics: vec![],\n        }\n    }\n\n    pub fn push(&mut self, diagnostic: Diagnostic) {\n        self.diagnostics.push(diagnostic);\n    }\n\n    pub fn extend(&mut self, diagnostics: Vec<Diagnostic>) {\n        self.diagnostics.extend(diagnostics);\n    }\n\n    pub fn is_empty(&self) -> bool {\n        self.diagnostics.is_empty()\n    }\n\n    pub fn into_diagnostics(self) -> Vec<Diagnostic> {\n        self.diagnostics\n    }\n\n    pub fn len(&self) -> usize {\n        self.diagnostics.len()\n    }\n}\n\nimpl PartialEq for Diagnostics {\n    fn eq(&self, _other: &Self) -> bool {\n        true\n    }\n}\n\nimpl Eq for Diagnostics {}\n\nimpl ToTokens for Diagnostics {\n    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {\n        for diagnostic in &self.diagnostics {\n            tokens.extend(diagnostic.clone().emit_as_expr_tokens());\n        }\n    }\n}\n\n// TODO: Ideally this would be integrated into the existing diagnostics struct, but currently all fields are public so adding\n// new fields would be a breaking change. Diagnostics also doesn't expose the message directly so we can't just modify\n// the expansion\npub(crate) mod new_diagnostics {\n    use proc_macro2_diagnostics::SpanDiagnosticExt;\n    use std::fmt::Display;\n\n    use proc_macro2::{Span, TokenStream as TokenStream2};\n    use quote::quote_spanned;\n\n    pub(crate) fn warning_diagnostic(span: Span, message: impl Display) -> TokenStream2 {\n        let note = message.to_string();\n        // If we are compiling on nightly, use diagnostics directly which supports proper warnings through new span apis\n        if rustversion::cfg!(nightly) {\n            return span.warning(note).emit_as_item_tokens();\n        }\n        quote_spanned! { span =>\n            const _: () = {\n                #[deprecated(note = #note)]\n                struct Warning;\n                _ = Warning;\n            };\n        }\n    }\n}\n"
  },
  {
    "path": "packages/rsx/src/element.rs",
    "content": "use crate::innerlude::*;\nuse proc_macro2::{Span, TokenStream as TokenStream2};\nuse proc_macro2_diagnostics::SpanDiagnosticExt;\nuse quote::{quote, ToTokens, TokenStreamExt};\nuse std::fmt::{Display, Formatter};\nuse syn::{\n    parse::{Parse, ParseStream},\n    punctuated::Punctuated,\n    spanned::Spanned,\n    token::Brace,\n    Ident, LitStr, Result, Token,\n};\n\n/// Parse the VNode::Element type\n#[derive(PartialEq, Eq, Clone, Debug)]\npub struct Element {\n    /// div { } -> div\n    pub name: ElementName,\n\n    /// The actual attributes that were parsed\n    pub raw_attributes: Vec<Attribute>,\n\n    /// The attributes after merging - basically the formatted version of the combined attributes\n    /// where possible.\n    ///\n    /// These are the actual attributes that get rendered out\n    pub merged_attributes: Vec<Attribute>,\n\n    /// The `...` spread attributes.\n    pub spreads: Vec<Spread>,\n\n    // /// Elements can have multiple, unlike components which can only have one\n    // pub spreads: Vec<Spread>,\n    /// The children of the element\n    pub children: Vec<BodyNode>,\n\n    /// the brace of the `div { }`\n    pub brace: Option<Brace>,\n\n    /// A list of diagnostics that were generated during parsing. This element might be a valid rsx_block\n    /// but not technically a valid element - these diagnostics tell us what's wrong and then are used\n    /// when rendering\n    pub diagnostics: Diagnostics,\n}\n\nimpl Parse for Element {\n    fn parse(stream: ParseStream) -> Result<Self> {\n        let name = stream.parse::<ElementName>()?;\n\n        // We very liberally parse elements - they might not even have a brace!\n        // This is designed such that we can throw a compile error but still give autocomplete\n        // ... partial completions mean we do some weird parsing to get the right completions\n        let mut brace = None;\n        let mut block = RsxBlock::default();\n\n        match stream.peek(Brace) {\n            // If the element is followed by a brace, it is complete. Parse the body\n            true => {\n                block = stream.parse::<RsxBlock>()?;\n                brace = Some(block.brace);\n            }\n\n            // Otherwise, it is incomplete. Add a diagnostic\n            false => block.diagnostics.push(\n                name.span()\n                    .error(\"Elements must be followed by braces\")\n                    .help(\"Did you forget a brace?\"),\n            ),\n        }\n\n        // Make sure these attributes have an el_name set for completions and Template generation\n        for attr in block.attributes.iter_mut() {\n            attr.el_name = Some(name.clone());\n        }\n\n        // Assemble the new element from the contents of the block\n        let mut element = Element {\n            brace,\n            name: name.clone(),\n            raw_attributes: block.attributes,\n            children: block.children,\n            diagnostics: block.diagnostics,\n            spreads: block.spreads.clone(),\n            merged_attributes: Vec::new(),\n        };\n\n        // And then merge the various attributes together\n        // The original raw_attributes are kept for lossless parsing used by hotreload/autofmt\n        element.merge_attributes();\n\n        // And then merge the spreads *after* the attributes are merged. This ensures walking the\n        // merged attributes in path order stops before we hit the spreads, but spreads are still\n        // counted as dynamic attributes\n        for spread in block.spreads.iter() {\n            element.merged_attributes.push(Attribute {\n                name: AttributeName::Spread(spread.dots),\n                colon: None,\n                value: AttributeValue::AttrExpr(PartialExpr::from_expr(&spread.expr)),\n                comma: spread.comma,\n                dyn_idx: spread.dyn_idx.clone(),\n                el_name: Some(name.clone()),\n            });\n        }\n\n        Ok(element)\n    }\n}\n\nimpl ToTokens for Element {\n    fn to_tokens(&self, tokens: &mut TokenStream2) {\n        let el = self;\n        let el_name = &el.name;\n\n        let ns = |name| match el_name {\n            ElementName::Ident(i) => quote! { dioxus_elements::#i::#name },\n            ElementName::Custom(_) => quote! { None },\n        };\n\n        let static_attrs = el\n            .merged_attributes\n            .iter()\n            .map(|attr| {\n                // Rendering static attributes requires a bit more work than just a dynamic attrs\n                // Early return for dynamic attributes\n                let Some((name, value)) = attr.as_static_str_literal() else {\n                    let id = attr.dyn_idx.get();\n                    return quote! { dioxus_core::TemplateAttribute::Dynamic { id: #id  } };\n                };\n\n                let ns = match name {\n                    AttributeName::BuiltIn(name) => ns(quote!(#name.1)),\n                    AttributeName::Custom(_) => quote!(None),\n                    AttributeName::Spread(_) => {\n                        unreachable!(\"spread attributes should not be static\")\n                    }\n                };\n\n                let name = match (el_name, name) {\n                    (ElementName::Ident(_), AttributeName::BuiltIn(_)) => {\n                        quote! { dioxus_elements::#el_name::#name.0 }\n                    }\n                    //hmmmm I think we could just totokens this, but the to_string might be inserting quotes\n                    _ => {\n                        let as_string = name.to_string();\n                        quote! { #as_string }\n                    }\n                };\n\n                let value = value.to_static().unwrap();\n\n                quote! {\n                    dioxus_core::TemplateAttribute::Static {\n                        name: #name,\n                        namespace: #ns,\n                        value: #value,\n                    }\n                }\n            })\n            .collect::<Vec<_>>();\n\n        // Render either the child\n        let children = el.children.iter().map(|c| match c {\n            BodyNode::Element(el) => quote! { #el },\n            BodyNode::Text(text) if text.is_static() => {\n                let text = text.input.to_static().unwrap();\n                quote! { dioxus_core::TemplateNode::Text { text: #text } }\n            }\n            BodyNode::Text(text) => {\n                let id = text.dyn_idx.get();\n                quote! { dioxus_core::TemplateNode::Dynamic { id: #id } }\n            }\n            BodyNode::ForLoop(floop) => {\n                let id = floop.dyn_idx.get();\n                quote! { dioxus_core::TemplateNode::Dynamic { id: #id } }\n            }\n            BodyNode::RawExpr(exp) => {\n                let id = exp.dyn_idx.get();\n                quote! { dioxus_core::TemplateNode::Dynamic { id: #id } }\n            }\n            BodyNode::Component(exp) => {\n                let id = exp.dyn_idx.get();\n                quote! { dioxus_core::TemplateNode::Dynamic { id: #id } }\n            }\n            BodyNode::IfChain(exp) => {\n                let id = exp.dyn_idx.get();\n                quote! { dioxus_core::TemplateNode::Dynamic { id: #id } }\n            }\n        });\n\n        let ns = ns(quote!(NAME_SPACE));\n        let el_name = el_name.tag_name();\n        let diagnostics = &el.diagnostics;\n        let completion_hints = &el.completion_hints();\n\n        // todo: generate less code if there's no diagnostics by not including the curlies\n        tokens.append_all(quote! {\n            {\n                #completion_hints\n\n                #diagnostics\n\n                dioxus_core::TemplateNode::Element {\n                    tag: #el_name,\n                    namespace: #ns,\n                    attrs: &[ #(#static_attrs),* ],\n                    children: &[ #(#children),* ],\n                }\n            }\n        })\n    }\n}\n\nimpl Element {\n    pub(crate) fn add_merging_non_string_diagnostic(diagnostics: &mut Diagnostics, span: Span) {\n        diagnostics.push(span.error(\"Cannot merge non-fmt literals\").help(\n            \"Only formatted strings can be merged together. If you want to merge literals, you can use a format string.\",\n        ));\n    }\n\n    /// Collapses ifmt attributes into a single dynamic attribute using a space or `;` as a delimiter\n    ///\n    /// ```ignore,\n    /// div {\n    ///     class: \"abc-def\",\n    ///     class: if some_expr { \"abc\" },\n    /// }\n    /// ```\n    fn merge_attributes(&mut self) {\n        let mut attrs: Vec<&Attribute> = vec![];\n\n        for attr in &self.raw_attributes {\n            if attrs.iter().any(|old_attr| old_attr.name == attr.name) {\n                continue;\n            }\n\n            attrs.push(attr);\n        }\n\n        for attr in attrs {\n            if attr.name.is_likely_key() {\n                continue;\n            }\n\n            // Collect all the attributes with the same name\n            let matching_attrs = self\n                .raw_attributes\n                .iter()\n                .filter(|a| a.name == attr.name)\n                .collect::<Vec<_>>();\n\n            // if there's only one attribute with this name, then we don't need to merge anything\n            if matching_attrs.len() == 1 {\n                self.merged_attributes.push(attr.clone());\n                continue;\n            }\n\n            // If there are multiple attributes with the same name, then we need to merge them\n            // This will be done by creating an ifmt attribute that combines all the segments\n            // We might want to throw a diagnostic of trying to merge things together that might not\n            // make a whole lot of sense - like merging two exprs together\n            let mut out = IfmtInput::new(attr.span());\n\n            for (idx, matching_attr) in matching_attrs.iter().enumerate() {\n                // If this is the first attribute, then we don't need to add a delimiter\n                if idx != 0 {\n                    // FIXME: I don't want to special case anything - but our delimiter is special cased to a space\n                    // We really don't want to special case anything in the macro, but the hope here is that\n                    // multiline strings can be merged with a space\n                    out.push_raw_str(\" \".to_string());\n                }\n\n                // Merge raw literals into the output\n                if let AttributeValue::AttrLiteral(HotLiteral::Fmted(lit)) = &matching_attr.value {\n                    out.push_ifmt(lit.formatted_input.clone());\n                    continue;\n                }\n\n                // Merge `if cond { \"abc\" } else if ...` into the output\n                if let AttributeValue::IfExpr(value) = &matching_attr.value {\n                    out.push_expr(value.quote_as_string(&mut self.diagnostics));\n                    continue;\n                }\n\n                Self::add_merging_non_string_diagnostic(\n                    &mut self.diagnostics,\n                    matching_attr.span(),\n                );\n            }\n\n            let out_lit = HotLiteral::Fmted(out.into());\n\n            self.merged_attributes.push(Attribute {\n                name: attr.name.clone(),\n                value: AttributeValue::AttrLiteral(out_lit),\n                colon: attr.colon,\n                dyn_idx: attr.dyn_idx.clone(),\n                comma: matching_attrs.last().unwrap().comma,\n                el_name: attr.el_name.clone(),\n            });\n        }\n    }\n\n    pub(crate) fn key(&self) -> Option<&AttributeValue> {\n        self.raw_attributes\n            .iter()\n            .find(|attr| attr.name.is_likely_key())\n            .map(|attr| &attr.value)\n    }\n\n    fn completion_hints(&self) -> TokenStream2 {\n        // If there is already a brace, we don't need any completion hints\n        if self.brace.is_some() {\n            return quote! {};\n        }\n\n        let ElementName::Ident(name) = &self.name else {\n            return quote! {};\n        };\n\n        quote! {\n            {\n                #[allow(dead_code)]\n                #[doc(hidden)]\n                mod __completions {\n                    fn ignore() {\n                        super::dioxus_elements::elements::completions::CompleteWithBraces::#name\n                    }\n                }\n            }\n        }\n    }\n}\n\n#[derive(PartialEq, Eq, Clone, Debug, Hash)]\npub enum ElementName {\n    Ident(Ident),\n    Custom(LitStr),\n}\n\nimpl ToTokens for ElementName {\n    fn to_tokens(&self, tokens: &mut TokenStream2) {\n        match self {\n            ElementName::Ident(i) => tokens.append_all(quote! { #i }),\n            ElementName::Custom(s) => s.to_tokens(tokens),\n        }\n    }\n}\n\nimpl Parse for ElementName {\n    fn parse(stream: ParseStream) -> Result<Self> {\n        let raw =\n            Punctuated::<Ident, Token![-]>::parse_separated_nonempty_with(stream, parse_raw_ident)?;\n        if raw.len() == 1 {\n            Ok(ElementName::Ident(raw.into_iter().next().unwrap()))\n        } else {\n            let span = raw.span();\n            let tag = raw\n                .into_iter()\n                .map(|ident| ident.to_string())\n                .collect::<Vec<_>>()\n                .join(\"-\");\n            let tag = LitStr::new(&tag, span);\n            Ok(ElementName::Custom(tag))\n        }\n    }\n}\n\nimpl ElementName {\n    pub(crate) fn tag_name(&self) -> TokenStream2 {\n        match self {\n            ElementName::Ident(i) => quote! { dioxus_elements::elements::#i::TAG_NAME },\n            ElementName::Custom(s) => quote! { #s },\n        }\n    }\n\n    pub fn span(&self) -> Span {\n        match self {\n            ElementName::Ident(i) => i.span(),\n            ElementName::Custom(s) => s.span(),\n        }\n    }\n}\n\nimpl PartialEq<&str> for ElementName {\n    fn eq(&self, other: &&str) -> bool {\n        match self {\n            ElementName::Ident(i) => i == *other,\n            ElementName::Custom(s) => s.value() == *other,\n        }\n    }\n}\n\nimpl Display for ElementName {\n    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {\n        match self {\n            ElementName::Ident(i) => write!(f, \"{}\", i),\n            ElementName::Custom(s) => write!(f, \"{}\", s.value()),\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use prettier_please::PrettyUnparse;\n\n    #[test]\n    fn parses_name() {\n        let _parsed: ElementName = syn::parse2(quote::quote! { div }).unwrap();\n        let _parsed: ElementName = syn::parse2(quote::quote! { some-cool-element }).unwrap();\n\n        let _parsed: Element = syn::parse2(quote::quote! { div {} }).unwrap();\n        let _parsed: Element = syn::parse2(quote::quote! { some-cool-element {} }).unwrap();\n\n        let parsed: Element = syn::parse2(quote::quote! {\n            some-cool-div {\n                id: \"hi\",\n                id: \"hi {abc}\",\n                id: \"hi {def}\",\n                class: 123,\n                something: bool,\n                data_attr: \"data\",\n                data_attr: \"data2\",\n                data_attr: \"data3\",\n                exp: { some_expr },\n                something: {cool},\n                something: bool,\n                something: 123,\n                onclick: move |_| {\n                    println!(\"hello world\");\n                },\n                \"some-attr\": \"hello world\",\n                onclick: move |_| {},\n                class: \"hello world\",\n                id: \"my-id\",\n                data_attr: \"data\",\n                data_attr: \"data2\",\n                data_attr: \"data3\",\n                \"somte_attr3\": \"hello world\",\n                something: {cool},\n                something: bool,\n                something: 123,\n                onclick: move |_| {\n                    println!(\"hello world\");\n                },\n                ..attrs1,\n                ..attrs2,\n                ..attrs3\n            }\n        })\n        .unwrap();\n\n        dbg!(parsed);\n    }\n\n    #[test]\n    fn parses_variety() {\n        let input = quote::quote! {\n            div {\n                class: \"hello world\",\n                id: \"my-id\",\n                data_attr: \"data\",\n                data_attr: \"data2\",\n                data_attr: \"data3\",\n                \"somte_attr3\": \"hello world\",\n                something: {cool},\n                something: bool,\n                something: 123,\n                onclick: move |_| {\n                    println!(\"hello world\");\n                },\n                ..attrs,\n                ..attrs2,\n                ..attrs3\n            }\n        };\n\n        let parsed: Element = syn::parse2(input).unwrap();\n        dbg!(parsed);\n    }\n\n    #[test]\n    fn to_tokens_properly() {\n        let input = quote::quote! {\n            div {\n                class: \"hello world\",\n                class2: \"hello {world}\",\n                class3: \"goodbye {world}\",\n                class4: \"goodbye world\",\n                \"something\": \"cool {blah}\",\n                \"something2\": \"cooler\",\n                div {\n                    div {\n                        h1 { class: \"h1 col\" }\n                        h2 { class: \"h2 col\" }\n                        h3 { class: \"h3 col\" }\n                        div {}\n                    }\n                }\n            }\n        };\n\n        let parsed: Element = syn::parse2(input).unwrap();\n        println!(\"{}\", parsed.to_token_stream().pretty_unparse());\n    }\n\n    #[test]\n    fn to_tokens_with_diagnostic() {\n        let input = quote::quote! {\n            div {\n                class: \"hello world\",\n                id: \"my-id\",\n                ..attrs,\n                div {\n                    ..attrs,\n                    class: \"hello world\",\n                    id: \"my-id\",\n                }\n            }\n        };\n\n        let parsed: Element = syn::parse2(input).unwrap();\n        println!(\"{}\", parsed.to_token_stream().pretty_unparse());\n    }\n\n    #[test]\n    fn merge_trivial_attributes() {\n        let input = quote::quote! {\n            div {\n                class: \"foo\",\n                class: \"bar\",\n            }\n        };\n\n        let parsed: Element = syn::parse2(input).unwrap();\n        assert_eq!(parsed.diagnostics.len(), 0);\n        assert_eq!(parsed.merged_attributes.len(), 1);\n        assert_eq!(\n            parsed.merged_attributes[0].name.to_string(),\n            \"class\".to_string()\n        );\n\n        let attr = &parsed.merged_attributes[0].value;\n\n        assert_eq!(\n            attr.to_token_stream().pretty_unparse().as_str(),\n            \"\\\"foo bar\\\"\"\n        );\n\n        if let AttributeValue::AttrLiteral(_) = attr {\n        } else {\n            panic!(\"expected literal\")\n        }\n    }\n\n    #[test]\n    fn merge_formatted_attributes() {\n        let input = quote::quote! {\n            div {\n                class: \"foo\",\n                class: \"{bar}\",\n            }\n        };\n\n        let parsed: Element = syn::parse2(input).unwrap();\n        assert_eq!(parsed.diagnostics.len(), 0);\n        assert_eq!(parsed.merged_attributes.len(), 1);\n        assert_eq!(\n            parsed.merged_attributes[0].name.to_string(),\n            \"class\".to_string()\n        );\n\n        let attr = &parsed.merged_attributes[0].value;\n\n        assert_eq!(\n            attr.to_token_stream().pretty_unparse().as_str(),\n            \"::std::format!(\\\"foo {0:}\\\", bar)\"\n        );\n\n        if let AttributeValue::AttrLiteral(_) = attr {\n        } else {\n            panic!(\"expected literal\")\n        }\n    }\n\n    #[test]\n    fn merge_conditional_attributes() {\n        let input = quote::quote! {\n            div {\n                class: \"foo\",\n                class: if true { \"bar\" },\n                class: if false { \"baz\" } else { \"qux\" }\n            }\n        };\n\n        let parsed: Element = syn::parse2(input).unwrap();\n        assert_eq!(parsed.diagnostics.len(), 0);\n        assert_eq!(parsed.merged_attributes.len(), 1);\n        assert_eq!(\n            parsed.merged_attributes[0].name.to_string(),\n            \"class\".to_string()\n        );\n\n        let attr = &parsed.merged_attributes[0].value;\n\n        assert_eq!(\n            attr.to_token_stream().pretty_unparse().as_str(),\n            \"::std::format!(\\n    \\\n                \\\"foo {0:} {1:}\\\",\\n    \\\n                { if true { \\\"bar\\\".to_string() } else { ::std::string::String::new() } },\\n    \\\n                { if false { \\\"baz\\\".to_string() } else { \\\"qux\\\".to_string() } },\\n\\\n            )\"\n        );\n\n        if let AttributeValue::AttrLiteral(_) = attr {\n        } else {\n            panic!(\"expected literal\")\n        }\n    }\n\n    #[test]\n    fn merge_all_attributes() {\n        let input = quote::quote! {\n            div {\n                class: \"foo\",\n                class: \"{bar}\",\n                class: if true { \"baz\" },\n                class: if false { \"{qux}\" } else { \"quux\" }\n            }\n        };\n\n        let parsed: Element = syn::parse2(input).unwrap();\n        assert_eq!(parsed.diagnostics.len(), 0);\n        assert_eq!(parsed.merged_attributes.len(), 1);\n        assert_eq!(\n            parsed.merged_attributes[0].name.to_string(),\n            \"class\".to_string()\n        );\n\n        let attr = &parsed.merged_attributes[0].value;\n\n        if cfg!(debug_assertions) {\n            assert_eq!(\n                attr.to_token_stream().pretty_unparse().as_str(),\n                \"::std::format!(\\n    \\\n                    \\\"foo {0:} {1:} {2:}\\\",\\n    \\\n                    bar,\\n    \\\n                    { if true { \\\"baz\\\".to_string() } else { ::std::string::String::new() } },\\n    \\\n                    { if false { ::std::format!(\\\"{qux}\\\").to_string() } else { \\\"quux\\\".to_string() } },\\n\\\n                )\"\n            );\n        } else {\n            assert_eq!(\n                attr.to_token_stream().pretty_unparse().as_str(),\n                \"::std::format!(\\n    \\\n                    \\\"foo {0:} {1:} {2:}\\\",\\n    \\\n                    bar,\\n    \\\n                    { if true { \\\"baz\\\".to_string() } else { ::std::string::String::new() } },\\n    \\\n                    { if false { (qux).to_string().to_string() } else { \\\"quux\\\".to_string() } },\\n\\\n                )\"\n            );\n        }\n\n        if let AttributeValue::AttrLiteral(_) = attr {\n        } else {\n            panic!(\"expected literal\")\n        }\n    }\n\n    /// There are a number of cases where merging attributes doesn't make sense\n    /// - merging two expressions together\n    /// - merging two literals together\n    /// - merging a literal and an expression together\n    ///\n    /// etc\n    ///\n    /// We really only want to merge formatted things together\n    ///\n    /// IE\n    /// class: \"hello world \",\n    /// class: if some_expr { \"abc\" }\n    ///\n    /// Some open questions - should the delimiter be explicit?\n    #[test]\n    fn merging_weird_fails() {\n        let input = quote::quote! {\n            div {\n                class: \"hello world\",\n                class: if some_expr { 123 },\n\n                style: \"color: red;\",\n                style: \"color: blue;\",\n\n                width: \"1px\",\n                width: 1,\n                width: false,\n                contenteditable: true,\n            }\n        };\n\n        let parsed: Element = syn::parse2(input).unwrap();\n\n        assert_eq!(parsed.merged_attributes.len(), 4);\n        assert_eq!(parsed.diagnostics.len(), 3);\n\n        // style should not generate a diagnostic\n        assert!(!parsed\n            .diagnostics\n            .diagnostics\n            .into_iter()\n            .any(|f| f.emit_as_item_tokens().to_string().contains(\"style\")));\n    }\n\n    #[test]\n    fn diagnostics() {\n        let input = quote::quote! {\n            p {\n                class: \"foo bar\"\n                \"Hello world\"\n            }\n        };\n\n        let _parsed: Element = syn::parse2(input).unwrap();\n    }\n\n    #[test]\n    fn parses_raw_elements() {\n        let input = quote::quote! {\n            use {\n                \"hello\"\n            }\n        };\n\n        let _parsed: Element = syn::parse2(input).unwrap();\n    }\n}\n"
  },
  {
    "path": "packages/rsx/src/expr_node.rs",
    "content": "use crate::{DynIdx, PartialExpr};\nuse quote::{quote, ToTokens, TokenStreamExt};\nuse syn::parse::Parse;\n\n#[derive(PartialEq, Eq, Clone, Debug)]\npub struct ExprNode {\n    pub expr: PartialExpr,\n    pub dyn_idx: DynIdx,\n}\n\nimpl ExprNode {\n    pub fn span(&self) -> proc_macro2::Span {\n        self.expr.span()\n    }\n}\n\nimpl Parse for ExprNode {\n    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {\n        // // If it's a single-line expression, we want to parse it without braces, fixing some issues with rust 2024 lifetimes\n        // use syn::braced;\n        // let forked = input.fork();\n        // if forked.peek(syn::token::Brace) {\n        //     let content;\n        //     let _brace = braced!(content in forked);\n        //     let as_expr: Result<syn::Expr, syn::Error> = content.parse();\n        //     if as_expr.is_ok() && content.is_empty() {\n        //         let content;\n        //         let _brace = braced!(content in input);\n        //         return Ok(Self {\n        //             expr: content.parse()?,\n        //             dyn_idx: DynIdx::default(),\n        //         });\n        //     }\n        // }\n\n        Ok(Self {\n            expr: input.parse()?,\n            dyn_idx: DynIdx::default(),\n        })\n    }\n}\n\nimpl ToTokens for ExprNode {\n    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {\n        let expr = &self.expr;\n        tokens.append_all(quote! {\n            {\n                #[allow(unused_braces)]\n                let ___nodes = dioxus_core::IntoDynNode::into_dyn_node(#expr);\n                ___nodes\n            }\n        })\n    }\n}\n\n#[test]\nfn no_commas() {\n    use prettier_please::PrettyUnparse;\n    let input = quote! {\n        div {\n            {label(\"Hello, world!\")},\n        }\n    };\n\n    let _expr: crate::BodyNode = syn::parse2(input).unwrap();\n    println!(\"{}\", _expr.to_token_stream().pretty_unparse());\n}\n"
  },
  {
    "path": "packages/rsx/src/forloop.rs",
    "content": "use super::*;\nuse location::DynIdx;\nuse proc_macro2::TokenStream as TokenStream2;\nuse syn::{braced, token::Brace, Expr, Pat};\n\n#[non_exhaustive]\n#[derive(PartialEq, Eq, Clone, Debug)]\npub struct ForLoop {\n    pub for_token: Token![for],\n    pub pat: Pat,\n    pub in_token: Token![in],\n    pub expr: Box<Expr>,\n    pub brace: Brace,\n    pub body: TemplateBody,\n    pub dyn_idx: DynIdx,\n}\n\nimpl Parse for ForLoop {\n    fn parse(input: ParseStream) -> Result<Self> {\n        // todo: better partial parsing\n        // A bit stolen from `ExprForLoop` in the `syn` crate\n        let for_token = input.parse()?;\n        let pat = input.call(Pat::parse_single)?;\n        let in_token = input.parse()?;\n        let expr = input.call(Expr::parse_without_eager_brace)?;\n\n        let content;\n        let brace = braced!(content in input);\n        let body = content.parse()?;\n\n        Ok(Self {\n            for_token,\n            pat,\n            in_token,\n            brace,\n            expr: Box::new(expr),\n            body,\n            dyn_idx: DynIdx::default(),\n        })\n    }\n}\n\nimpl ToTokens for ForLoop {\n    fn to_tokens(&self, tokens: &mut TokenStream2) {\n        let ForLoop {\n            pat, expr, body, ..\n        } = self;\n\n        // the temporary is important so we create a lifetime binding\n        tokens.append_all(quote! {\n            {\n                let ___nodes = dioxus_core::IntoDynNode::into_dyn_node((#expr).into_iter().map(|#pat| { #body }));\n                ___nodes\n            }\n        });\n    }\n}\n\n#[test]\nfn parses_for_loop() {\n    let toks = quote! {\n        for item in 0..10 {\n            div { \"cool-{item}\" }\n            div { \"cool-{item}\" }\n            div { \"cool-{item}\" }\n        }\n    };\n\n    let for_loop: ForLoop = syn::parse2(toks).unwrap();\n    assert!(for_loop.body.roots.len() == 3);\n}\n"
  },
  {
    "path": "packages/rsx/src/ifchain.rs",
    "content": "use crate::location::DynIdx;\nuse proc_macro2::TokenStream as TokenStream2;\nuse quote::quote;\nuse quote::{ToTokens, TokenStreamExt};\nuse syn::{\n    parse::{Parse, ParseStream},\n    token::Brace,\n    Expr, Result, Token,\n};\n\nuse crate::TemplateBody;\n\n#[non_exhaustive]\n#[derive(PartialEq, Eq, Clone, Debug)]\npub struct IfChain {\n    pub if_token: Token![if],\n    pub cond: Box<Expr>,\n    pub then_brace: Brace,\n    pub then_branch: TemplateBody,\n    pub else_if_branch: Option<Box<IfChain>>,\n    pub else_brace: Option<Brace>,\n    pub else_branch: Option<TemplateBody>,\n    pub dyn_idx: DynIdx,\n}\n\nimpl IfChain {\n    pub fn for_each_branch(&self, f: &mut impl FnMut(&TemplateBody)) {\n        f(&self.then_branch);\n\n        if let Some(else_if) = &self.else_if_branch {\n            else_if.for_each_branch(f);\n        }\n\n        if let Some(else_branch) = &self.else_branch {\n            f(else_branch);\n        }\n    }\n}\n\nimpl Parse for IfChain {\n    fn parse(input: ParseStream) -> Result<Self> {\n        let if_token: Token![if] = input.parse()?;\n\n        // stolen from ExprIf\n        let cond = Box::new(input.call(Expr::parse_without_eager_brace)?);\n\n        let content;\n        let then_brace = syn::braced!(content in input);\n\n        let then_branch = content.parse()?;\n\n        let mut else_brace = None;\n        let mut else_branch = None;\n        let mut else_if_branch = None;\n\n        // if the next token is `else`, set the else branch as the next if chain\n        if input.peek(Token![else]) {\n            input.parse::<Token![else]>()?;\n            if input.peek(Token![if]) {\n                else_if_branch = Some(Box::new(input.parse::<IfChain>()?));\n            } else {\n                let content;\n                else_brace = Some(syn::braced!(content in input));\n                else_branch = Some(content.parse()?);\n            }\n        }\n\n        Ok(Self {\n            cond,\n            if_token,\n            then_branch,\n            else_if_branch,\n            else_branch,\n            then_brace,\n            else_brace,\n            dyn_idx: DynIdx::default(),\n        })\n    }\n}\n\nimpl ToTokens for IfChain {\n    fn to_tokens(&self, tokens: &mut TokenStream2) {\n        let mut body = TokenStream2::new();\n        let mut terminated = false;\n\n        let mut elif = Some(self);\n\n        while let Some(chain) = elif {\n            let IfChain {\n                if_token,\n                cond,\n                then_branch,\n                else_if_branch,\n                else_branch,\n                ..\n            } = chain;\n\n            body.append_all(quote! {\n                #if_token #cond {\n                    { #then_branch }\n                }\n            });\n\n            if let Some(next) = else_if_branch {\n                body.append_all(quote! {\n                    else\n                });\n                elif = Some(next);\n            } else if let Some(else_branch) = else_branch {\n                body.append_all(quote! {\n                    else {\n                        {#else_branch}\n                    }\n                });\n                terminated = true;\n                break;\n            } else {\n                elif = None;\n            }\n        }\n\n        if !terminated {\n            body.append_all(quote! {\n                else { dioxus_core::VNode::empty() }\n            });\n        }\n\n        tokens.append_all(quote! {\n            {\n                let ___nodes = dioxus_core::IntoDynNode::into_dyn_node(#body);\n                ___nodes\n            }\n        })\n    }\n}\n\n#[test]\nfn parses_if_chain() {\n    let input = quote! {\n        if true {\n            \"one\"\n        } else if false {\n            \"two\"\n        } else {\n            \"three\"\n        }\n    };\n\n    let _chain: IfChain = syn::parse2(input).unwrap();\n}\n"
  },
  {
    "path": "packages/rsx/src/ifmt.rs",
    "content": "use proc_macro2::{Span, TokenStream};\nuse quote::{quote, quote_spanned, ToTokens, TokenStreamExt};\nuse std::collections::HashMap;\nuse syn::{\n    parse::{Parse, ParseStream},\n    *,\n};\n\n/// A hot-reloadable formatted string, boolean, number or other literal\n///\n/// This wraps LitStr with some extra goodies like inline expressions and hot-reloading.\n/// Originally this was intended to provide named inline string interpolation but eventually Rust\n/// actually shipped this!\n#[derive(Debug, PartialEq, Eq, Clone, Hash)]\npub struct IfmtInput {\n    pub source: LitStr,\n    pub segments: Vec<Segment>,\n}\n\nimpl IfmtInput {\n    pub fn new(span: Span) -> Self {\n        Self {\n            source: LitStr::new(\"\", span),\n            segments: Vec::new(),\n        }\n    }\n\n    pub fn new_litstr(source: LitStr) -> Result<Self> {\n        let segments = IfmtInput::from_raw(&source.value()).map_err(|e| {\n            // If there is an error creating the formatted string, attribute it to the litstr span\n            let span = source.span();\n            syn::Error::new(span, e)\n        })?;\n        Ok(Self { segments, source })\n    }\n\n    pub fn span(&self) -> Span {\n        self.source.span()\n    }\n\n    pub fn push_raw_str(&mut self, other: String) {\n        self.segments.push(Segment::Literal(other.to_string()))\n    }\n\n    pub fn push_ifmt(&mut self, other: IfmtInput) {\n        self.segments.extend(other.segments);\n    }\n\n    pub fn push_expr(&mut self, expr: Expr) {\n        self.segments.push(Segment::Formatted(FormattedSegment {\n            format_args: String::new(),\n            segment: FormattedSegmentType::Expr(Box::new(expr)),\n        }));\n    }\n\n    pub fn is_static(&self) -> bool {\n        self.segments\n            .iter()\n            .all(|seg| matches!(seg, Segment::Literal(_)))\n    }\n\n    pub fn to_static(&self) -> Option<String> {\n        self.segments\n            .iter()\n            .try_fold(String::new(), |acc, segment| {\n                if let Segment::Literal(seg) = segment {\n                    Some(acc + seg)\n                } else {\n                    None\n                }\n            })\n    }\n\n    pub fn dynamic_segments(&self) -> Vec<&FormattedSegment> {\n        self.segments\n            .iter()\n            .filter_map(|seg| match seg {\n                Segment::Formatted(seg) => Some(seg),\n                _ => None,\n            })\n            .collect::<Vec<_>>()\n    }\n\n    pub fn dynamic_seg_frequency_map(&self) -> HashMap<&FormattedSegment, usize> {\n        let mut map = HashMap::new();\n        for seg in self.dynamic_segments() {\n            *map.entry(seg).or_insert(0) += 1;\n        }\n        map\n    }\n\n    fn is_simple_expr(&self) -> bool {\n        // If there are segments but the source is empty, it's not a simple expression.\n        if !self.segments.is_empty() && self.source.span().byte_range().is_empty() {\n            return false;\n        }\n\n        self.segments.iter().all(|seg| match seg {\n            Segment::Literal(_) => true,\n            Segment::Formatted(FormattedSegment { segment, .. }) => {\n                matches!(segment, FormattedSegmentType::Ident(_))\n            }\n        })\n    }\n\n    /// Try to convert this into a single _.to_string() call if possible\n    ///\n    /// Using \"{single_expression}\" is pretty common, but you don't need to go through the whole format! machinery for that, so we optimize it here.\n    fn try_to_string(&self) -> Option<TokenStream> {\n        let mut single_dynamic = None;\n        for segment in &self.segments {\n            match segment {\n                Segment::Literal(literal) => {\n                    if !literal.is_empty() {\n                        return None;\n                    }\n                }\n                Segment::Formatted(FormattedSegment {\n                    segment,\n                    format_args,\n                }) => {\n                    if format_args.is_empty() {\n                        match single_dynamic {\n                            Some(current_string) => {\n                                single_dynamic =\n                                    Some(quote!(#current_string + &(#segment).to_string()));\n                            }\n                            None => {\n                                single_dynamic = Some(quote!((#segment).to_string()));\n                            }\n                        }\n                    } else {\n                        return None;\n                    }\n                }\n            }\n        }\n        single_dynamic\n    }\n\n    /// print the original source string - this handles escapes and stuff for us\n    pub fn to_string_with_quotes(&self) -> String {\n        self.source.to_token_stream().to_string()\n    }\n\n    /// Parse the source into segments\n    fn from_raw(input: &str) -> Result<Vec<Segment>> {\n        let mut chars = input.chars().peekable();\n        let mut segments = Vec::new();\n        let mut current_literal = String::new();\n        while let Some(c) = chars.next() {\n            if c == '{' {\n                if let Some(c) = chars.next_if(|c| *c == '{') {\n                    current_literal.push(c);\n                    continue;\n                }\n                if !current_literal.is_empty() {\n                    segments.push(Segment::Literal(current_literal));\n                }\n                current_literal = String::new();\n                let mut current_captured = String::new();\n                while let Some(c) = chars.next() {\n                    if c == ':' {\n                        // two :s in a row is a path, not a format arg\n                        if chars.next_if(|c| *c == ':').is_some() {\n                            current_captured.push_str(\"::\");\n                            continue;\n                        }\n                        let mut current_format_args = String::new();\n                        for c in chars.by_ref() {\n                            if c == '}' {\n                                segments.push(Segment::Formatted(FormattedSegment {\n                                    format_args: current_format_args,\n                                    segment: FormattedSegmentType::parse(&current_captured)?,\n                                }));\n                                break;\n                            }\n                            current_format_args.push(c);\n                        }\n                        break;\n                    }\n                    if c == '}' {\n                        segments.push(Segment::Formatted(FormattedSegment {\n                            format_args: String::new(),\n                            segment: FormattedSegmentType::parse(&current_captured)?,\n                        }));\n                        break;\n                    }\n                    current_captured.push(c);\n                }\n            } else {\n                if '}' == c {\n                    if let Some(c) = chars.next_if(|c| *c == '}') {\n                        current_literal.push(c);\n                        continue;\n                    } else {\n                        return Err(Error::new(\n                            Span::call_site(),\n                            \"unmatched closing '}' in format string\",\n                        ));\n                    }\n                }\n                current_literal.push(c);\n            }\n        }\n\n        if !current_literal.is_empty() {\n            segments.push(Segment::Literal(current_literal));\n        }\n\n        Ok(segments)\n    }\n}\n\nimpl ToTokens for IfmtInput {\n    fn to_tokens(&self, tokens: &mut TokenStream) {\n        // If the input is a string literal, we can just return it\n        if let Some(static_str) = self.to_static() {\n            return quote_spanned! { self.span() => #static_str }.to_tokens(tokens);\n        }\n\n        // Try to turn it into a single _.to_string() call\n        if !cfg!(debug_assertions) {\n            if let Some(single_dynamic) = self.try_to_string() {\n                tokens.extend(single_dynamic);\n                return;\n            }\n        }\n\n        // If the segments are not complex exprs, we can just use format! directly to take advantage of RA rename/expansion\n        if self.is_simple_expr() {\n            let raw = &self.source;\n            return quote_spanned! { raw.span() => ::std::format!(#raw) }.to_tokens(tokens);\n        }\n\n        // build format_literal\n        let mut format_literal = String::new();\n        let mut expr_counter = 0;\n        for segment in self.segments.iter() {\n            match segment {\n                Segment::Literal(s) => format_literal += &s.replace('{', \"{{\").replace('}', \"}}\"),\n                Segment::Formatted(FormattedSegment { format_args, .. }) => {\n                    format_literal += \"{\";\n                    format_literal += &expr_counter.to_string();\n                    expr_counter += 1;\n                    format_literal += \":\";\n                    format_literal += format_args;\n                    format_literal += \"}\";\n                }\n            }\n        }\n\n        let span = self.span();\n\n        let positional_args = self.segments.iter().filter_map(|seg| {\n            if let Segment::Formatted(FormattedSegment { segment, .. }) = seg {\n                let mut segment = segment.clone();\n                // We set the span of the ident here, so that we can use it in diagnostics\n                if let FormattedSegmentType::Ident(ident) = &mut segment {\n                    ident.set_span(span);\n                }\n                Some(segment)\n            } else {\n                None\n            }\n        });\n\n        quote_spanned! {\n            span =>\n            ::std::format!(\n                #format_literal\n                #(, #positional_args)*\n            )\n        }\n        .to_tokens(tokens)\n    }\n}\n\n#[derive(Debug, PartialEq, Eq, Clone, Hash)]\npub enum Segment {\n    Literal(String),\n    Formatted(FormattedSegment),\n}\n\nimpl Segment {\n    pub fn is_literal(&self) -> bool {\n        matches!(self, Segment::Literal(_))\n    }\n\n    pub fn is_formatted(&self) -> bool {\n        matches!(self, Segment::Formatted(_))\n    }\n}\n\n#[derive(Debug, PartialEq, Eq, Clone, Hash)]\npub struct FormattedSegment {\n    pub format_args: String,\n    pub segment: FormattedSegmentType,\n}\n\nimpl ToTokens for FormattedSegment {\n    fn to_tokens(&self, tokens: &mut TokenStream) {\n        let (fmt, seg) = (&self.format_args, &self.segment);\n        let fmt = format!(\"{{0:{fmt}}}\");\n        tokens.append_all(quote! {\n            format!(#fmt, #seg)\n        });\n    }\n}\n\n#[derive(Debug, PartialEq, Eq, Clone, Hash)]\npub enum FormattedSegmentType {\n    Expr(Box<Expr>),\n    Ident(Ident),\n}\n\nimpl FormattedSegmentType {\n    fn parse(input: &str) -> Result<Self> {\n        if let Ok(ident) = parse_str::<Ident>(input) {\n            if ident == input {\n                return Ok(Self::Ident(ident));\n            }\n        }\n        if let Ok(expr) = parse_str(input) {\n            Ok(Self::Expr(Box::new(expr)))\n        } else {\n            Err(Error::new(\n                Span::call_site(),\n                \"Failed to parse formatted segment: Expected Ident or Expression\",\n            ))\n        }\n    }\n}\n\nimpl ToTokens for FormattedSegmentType {\n    fn to_tokens(&self, tokens: &mut TokenStream) {\n        match self {\n            Self::Expr(expr) => expr.to_tokens(tokens),\n            Self::Ident(ident) => ident.to_tokens(tokens),\n        }\n    }\n}\n\nimpl Parse for IfmtInput {\n    fn parse(input: ParseStream) -> Result<Self> {\n        let source: LitStr = input.parse()?;\n        Self::new_litstr(source)\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use prettier_please::PrettyUnparse;\n\n    #[test]\n    fn raw_tokens() {\n        let input = syn::parse2::<IfmtInput>(quote! { r#\"hello world\"# }).unwrap();\n        println!(\"{}\", input.to_token_stream().pretty_unparse());\n        assert_eq!(input.source.value(), \"hello world\");\n        assert_eq!(input.to_string_with_quotes(), \"r#\\\"hello world\\\"#\");\n    }\n\n    #[test]\n    fn segments_parse() {\n        let input: IfmtInput = parse_quote! { \"blah {abc} {def}\" };\n        assert_eq!(\n            input.segments,\n            vec![\n                Segment::Literal(\"blah \".to_string()),\n                Segment::Formatted(FormattedSegment {\n                    format_args: String::new(),\n                    segment: FormattedSegmentType::Ident(Ident::new(\"abc\", Span::call_site()))\n                }),\n                Segment::Literal(\" \".to_string()),\n                Segment::Formatted(FormattedSegment {\n                    format_args: String::new(),\n                    segment: FormattedSegmentType::Ident(Ident::new(\"def\", Span::call_site()))\n                }),\n            ]\n        );\n    }\n\n    #[test]\n    fn printing_raw() {\n        let input = syn::parse2::<IfmtInput>(quote! { \"hello {world}\" }).unwrap();\n        println!(\"{}\", input.to_string_with_quotes());\n\n        let input = syn::parse2::<IfmtInput>(quote! { \"hello {world} {world} {world}\" }).unwrap();\n        println!(\"{}\", input.to_string_with_quotes());\n\n        let input = syn::parse2::<IfmtInput>(quote! { \"hello {world} {world} {world()}\" }).unwrap();\n        println!(\"{}\", input.to_string_with_quotes());\n\n        let input =\n            syn::parse2::<IfmtInput>(quote! { r#\"hello {world} {world} {world()}\"# }).unwrap();\n        println!(\"{}\", input.to_string_with_quotes());\n        assert!(!input.is_static());\n\n        let input = syn::parse2::<IfmtInput>(quote! { r#\"hello\"# }).unwrap();\n        println!(\"{}\", input.to_string_with_quotes());\n        assert!(input.is_static());\n    }\n\n    #[test]\n    fn to_static() {\n        let input = syn::parse2::<IfmtInput>(quote! { \"body {{ background: red; }}\" }).unwrap();\n        assert_eq!(\n            input.to_static(),\n            Some(\"body { background: red; }\".to_string())\n        );\n    }\n\n    #[test]\n    fn error_spans() {\n        let input = syn::parse2::<IfmtInput>(quote! { \"body {{ background: red; }\" }).unwrap_err();\n        assert_eq!(input.span().byte_range(), 0..28);\n    }\n}\n"
  },
  {
    "path": "packages/rsx/src/lib.rs",
    "content": "#![doc(html_logo_url = \"https://avatars.githubusercontent.com/u/79236386\")]\n#![doc(html_favicon_url = \"https://avatars.githubusercontent.com/u/79236386\")]\n\n//! Parse the root tokens in the rsx! { } macro\n//! =========================================\n//!\n//! This parsing path emerges directly from the macro call, with `RsxRender` being the primary entrance into parsing.\n//! This feature must support:\n//! - [x] Optionally rendering if the `in XYZ` pattern is present\n//! - [x] Fragments as top-level element (through ambiguous)\n//! - [x] Components as top-level element (through ambiguous)\n//! - [x] Tags as top-level elements (through ambiguous)\n//! - [x] Good errors if parsing fails\n//!\n//! Any errors in using rsx! will likely occur when people start using it, so the first errors must be really helpful.\n//!\n//! # Completions\n//! Rust analyzer completes macros by looking at the expansion of the macro and trying to match the start of identifiers in the macro to identifiers in the current scope\n//!\n//! Eg, if a macro expands to this:\n//! ```rust, ignore\n//! struct MyStruct;\n//!\n//! // macro expansion\n//! My\n//! ```\n//! Then the analyzer will try to match the start of the identifier \"My\" to an identifier in the current scope (MyStruct in this case).\n//!\n//! In dioxus, our macros expand to the completions module if we know the identifier is incomplete:\n//! ```rust, ignore\n//! // In the root of the macro, identifiers must be elements\n//! // rsx! { di }\n//! dioxus_elements::elements::di\n//!\n//! // Before the first child element, every following identifier is either an attribute or an element\n//! // rsx! { div { ta } }\n//! // Isolate completions scope\n//! mod completions__ {\n//!     // import both the attributes and elements this could complete to\n//!     use dioxus_elements::elements::div::*;\n//!     use dioxus_elements::elements::*;\n//!     fn complete() {\n//!         ta;\n//!     }\n//! }\n//!\n//! // After the first child element, every following identifier is another element\n//! // rsx! { div { attribute: value, child {} di } }\n//! dioxus_elements::elements::di\n//! ```\n\nmod assign_dyn_ids;\nmod attribute;\nmod component;\nmod element;\nmod forloop;\nmod ifchain;\nmod node;\nmod raw_expr;\nmod rsx_block;\nmod rsx_call;\nmod template_body;\nmod text_node;\n\nmod diagnostics;\nmod expr_node;\nmod ifmt;\nmod literal;\nmod location;\nmod partial_closure;\nmod util;\n\n// Re-export the namespaces into each other\npub use diagnostics::Diagnostics;\npub use ifmt::*;\npub use node::*;\npub use partial_closure::PartialClosure;\npub use rsx_call::*;\npub use template_body::TemplateBody;\n\nuse quote::{quote, ToTokens, TokenStreamExt};\nuse syn::{\n    parse::{Parse, ParseStream},\n    Result, Token,\n};\n\npub use innerlude::*;\npub(crate) mod innerlude {\n    pub use crate::attribute::*;\n    pub use crate::component::*;\n    pub use crate::element::*;\n    pub use crate::expr_node::*;\n    pub use crate::forloop::*;\n    pub use crate::ifchain::*;\n    pub use crate::location::*;\n    pub use crate::node::*;\n    pub use crate::raw_expr::*;\n    pub use crate::rsx_block::*;\n    pub use crate::template_body::*;\n    pub use crate::text_node::*;\n\n    pub use crate::diagnostics::*;\n    pub use crate::ifmt::*;\n    pub use crate::literal::*;\n    pub use crate::util::*;\n}\n"
  },
  {
    "path": "packages/rsx/src/literal.rs",
    "content": "use proc_macro2::Span;\nuse quote::quote;\nuse quote::ToTokens;\nuse std::fmt::Display;\nuse std::ops::Deref;\nuse syn::{\n    parse::{Parse, ParseStream},\n    Lit, LitBool, LitFloat, LitInt, LitStr,\n};\n\nuse crate::{location::DynIdx, IfmtInput, Segment};\nuse proc_macro2::TokenStream as TokenStream2;\n\n/// A literal value in the rsx! macro\n///\n/// These get hotreloading super powers, making them a bit more complex than a normal literal.\n/// In debug mode we need to generate a bunch of extra code to support hotreloading.\n///\n/// Eventually we want to remove this notion of hot literals since we're generating different code\n/// in debug than in release, which is harder to maintain and can lead to bugs.\n#[derive(PartialEq, Eq, Clone, Debug, Hash)]\npub enum HotLiteral {\n    /// A *formatted* string literal\n    /// We know this will generate a String, not an &'static str\n    ///\n    /// The raw str type will generate a &'static str, but we need to distinguish the two for component props\n    ///\n    /// \"hello {world}\"\n    Fmted(HotReloadFormattedSegment),\n\n    /// A float literal\n    ///\n    /// 1.0\n    Float(LitFloat),\n\n    /// An int literal\n    ///\n    /// 1\n    Int(LitInt),\n\n    /// A bool literal\n    ///\n    /// true\n    Bool(LitBool),\n}\n\nimpl HotLiteral {\n    pub fn quote_as_hot_reload_literal(&self) -> TokenStream2 {\n        match &self {\n            HotLiteral::Fmted(f) => quote! { dioxus_core::internal::HotReloadLiteral::Fmted(#f) },\n            HotLiteral::Float(f) => {\n                quote! { dioxus_core::internal::HotReloadLiteral::Float(#f as _) }\n            }\n            HotLiteral::Int(f) => quote! { dioxus_core::internal::HotReloadLiteral::Int(#f as _) },\n            HotLiteral::Bool(f) => quote! { dioxus_core::internal::HotReloadLiteral::Bool(#f) },\n        }\n    }\n}\n\nimpl Parse for HotLiteral {\n    fn parse(input: ParseStream) -> syn::Result<Self> {\n        let raw = input.parse::<Lit>()?;\n\n        let value = match raw.clone() {\n            Lit::Int(a) => HotLiteral::Int(a),\n            Lit::Bool(a) => HotLiteral::Bool(a),\n            Lit::Float(a) => HotLiteral::Float(a),\n            Lit::Str(a) => HotLiteral::Fmted(IfmtInput::new_litstr(a)?.into()),\n            _ => {\n                return Err(syn::Error::new(\n                    raw.span(),\n                    \"Only string, int, float, and bool literals are supported\",\n                ))\n            }\n        };\n\n        Ok(value)\n    }\n}\n\nimpl ToTokens for HotLiteral {\n    fn to_tokens(&self, out: &mut proc_macro2::TokenStream) {\n        match &self {\n            HotLiteral::Fmted(f) => {\n                f.formatted_input.to_tokens(out);\n            }\n            HotLiteral::Float(f) => f.to_tokens(out),\n            HotLiteral::Int(f) => f.to_tokens(out),\n            HotLiteral::Bool(f) => f.to_tokens(out),\n        }\n    }\n}\n\nimpl HotLiteral {\n    pub fn span(&self) -> Span {\n        match self {\n            HotLiteral::Fmted(f) => f.span(),\n            HotLiteral::Float(f) => f.span(),\n            HotLiteral::Int(f) => f.span(),\n            HotLiteral::Bool(f) => f.span(),\n        }\n    }\n}\n\nimpl HotLiteral {\n    // We can only handle a few types of literals - the rest need to be expressions\n    // todo on adding more of course - they're not hard to support, just work\n    pub fn peek(input: ParseStream) -> bool {\n        if input.peek(Lit) {\n            let lit = input.fork().parse::<Lit>().unwrap();\n\n            matches!(\n                lit,\n                Lit::Str(_) | Lit::Int(_) | Lit::Float(_) | Lit::Bool(_)\n            )\n        } else {\n            false\n        }\n    }\n\n    pub fn is_static(&self) -> bool {\n        match &self {\n            HotLiteral::Fmted(fmt) => fmt.is_static(),\n            _ => false,\n        }\n    }\n\n    pub fn from_raw_text(text: &str) -> Self {\n        HotLiteral::Fmted(HotReloadFormattedSegment::from(IfmtInput {\n            source: LitStr::new(text, Span::call_site()),\n            segments: vec![],\n        }))\n    }\n}\n\nimpl Display for HotLiteral {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        match &self {\n            HotLiteral::Fmted(l) => l.to_string_with_quotes().fmt(f),\n            HotLiteral::Float(l) => l.fmt(f),\n            HotLiteral::Int(l) => l.fmt(f),\n            HotLiteral::Bool(l) => l.value().fmt(f),\n        }\n    }\n}\n\n/// A formatted segment that can be hot reloaded\n#[derive(PartialEq, Eq, Clone, Debug, Hash)]\npub struct HotReloadFormattedSegment {\n    pub formatted_input: IfmtInput,\n    pub dynamic_node_indexes: Vec<DynIdx>,\n}\n\nimpl HotReloadFormattedSegment {\n    /// This method is very important!\n    /// Deref + Spanned + .span() methods leads to name collisions\n    pub fn span(&self) -> Span {\n        self.formatted_input.span()\n    }\n}\n\nimpl Deref for HotReloadFormattedSegment {\n    type Target = IfmtInput;\n\n    fn deref(&self) -> &Self::Target {\n        &self.formatted_input\n    }\n}\n\nimpl From<IfmtInput> for HotReloadFormattedSegment {\n    fn from(input: IfmtInput) -> Self {\n        let mut dynamic_node_indexes = Vec::new();\n        for segment in &input.segments {\n            if let Segment::Formatted { .. } = segment {\n                dynamic_node_indexes.push(DynIdx::default());\n            }\n        }\n        Self {\n            formatted_input: input,\n            dynamic_node_indexes,\n        }\n    }\n}\n\nimpl Parse for HotReloadFormattedSegment {\n    fn parse(input: ParseStream) -> syn::Result<Self> {\n        let ifmt: IfmtInput = input.parse()?;\n        Ok(Self::from(ifmt))\n    }\n}\n\nimpl ToTokens for HotReloadFormattedSegment {\n    fn to_tokens(&self, tokens: &mut TokenStream2) {\n        let mut idx = 0_usize;\n        let segments = self.segments.iter().map(|s| match s {\n            Segment::Literal(lit) => quote! {\n                dioxus_core::internal::FmtSegment::Literal { value: #lit }\n            },\n            Segment::Formatted(_fmt) => {\n                // increment idx for the dynamic segment so we maintain the mapping\n                let _idx = self.dynamic_node_indexes[idx].get();\n                idx += 1;\n                quote! {\n                   dioxus_core::internal::FmtSegment::Dynamic { id: #_idx }\n                }\n            }\n        });\n\n        // The static segments with idxs for locations\n        tokens.extend(quote! {\n            dioxus_core::internal::FmtedSegments::new( vec![ #(#segments),* ], )\n        });\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use prettier_please::PrettyUnparse;\n\n    #[test]\n    fn parses_lits() {\n        let _ = syn::parse2::<HotLiteral>(quote! { \"hello\" }).unwrap();\n        let _ = syn::parse2::<HotLiteral>(quote! { \"hello {world}\" }).unwrap();\n        let _ = syn::parse2::<HotLiteral>(quote! { 1 }).unwrap();\n        let _ = syn::parse2::<HotLiteral>(quote! { 1.0 }).unwrap();\n        let _ = syn::parse2::<HotLiteral>(quote! { false }).unwrap();\n        let _ = syn::parse2::<HotLiteral>(quote! { true }).unwrap();\n\n        // Refuses the other unsupported types - we could add them if we wanted to\n        assert!(syn::parse2::<HotLiteral>(quote! { b\"123\" }).is_err());\n        assert!(syn::parse2::<HotLiteral>(quote! { 'a' }).is_err());\n\n        let lit = syn::parse2::<HotLiteral>(quote! { \"hello\" }).unwrap();\n        assert!(matches!(lit, HotLiteral::Fmted(_)));\n\n        let lit = syn::parse2::<HotLiteral>(quote! { \"hello {world}\" }).unwrap();\n        assert!(matches!(lit, HotLiteral::Fmted(_)));\n    }\n\n    #[test]\n    fn outputs_a_signal() {\n        // Should output a type of f64 which we convert into whatever the expected type is via \"into\"\n        // todo: hmmmmmmmmmmmm might not always work\n        let lit = syn::parse2::<HotLiteral>(quote! { 1.0 }).unwrap();\n        println!(\"{}\", lit.to_token_stream().pretty_unparse());\n\n        let lit = syn::parse2::<HotLiteral>(quote! { \"hi\" }).unwrap();\n        println!(\"{}\", lit.to_token_stream().pretty_unparse());\n\n        let lit = syn::parse2::<HotLiteral>(quote! { \"hi {world}\" }).unwrap();\n        println!(\"{}\", lit.to_token_stream().pretty_unparse());\n    }\n\n    #[test]\n    fn static_str_becomes_str() {\n        let lit = syn::parse2::<HotLiteral>(quote! { \"hello\" }).unwrap();\n        let HotLiteral::Fmted(segments) = &lit else {\n            panic!(\"expected a formatted string\");\n        };\n        assert!(segments.is_static());\n        assert_eq!(r##\"\"hello\"\"##, segments.to_string_with_quotes());\n        println!(\"{}\", lit.to_token_stream().pretty_unparse());\n    }\n\n    #[test]\n    fn formatted_prints_as_formatted() {\n        let lit = syn::parse2::<HotLiteral>(quote! { \"hello {world}\" }).unwrap();\n        let HotLiteral::Fmted(segments) = &lit else {\n            panic!(\"expected a formatted string\");\n        };\n        assert!(!segments.is_static());\n        assert_eq!(r##\"\"hello {world}\"\"##, segments.to_string_with_quotes());\n        println!(\"{}\", lit.to_token_stream().pretty_unparse());\n    }\n}\n"
  },
  {
    "path": "packages/rsx/src/location.rs",
    "content": "use std::{cell::Cell, hash::Hash};\n\n/// A simple idx in the code that can be used to track back to the original source location\n///\n/// Used in two places:\n/// - In the `CallBody` to track the location of hotreloadable literals\n/// - In the `Body` to track the ID of each template\n///\n/// We need an ID system, unfortunately, to properly disambiguate between different templates since\n/// rustc assigns them all the same line!() and column!() information. Before, we hashed spans but\n/// that has collision issues and is eventually relied on specifics of proc macros that aren't available\n/// in testing (like snapshot testing). So, we just need an ID for each of these items, hence this struct.\n///\n/// This is \"transparent\" to partialeq and eq, so it will always return true when compared to another DynIdx.\n#[derive(Clone, Debug, Default)]\npub struct DynIdx {\n    idx: Cell<Option<usize>>,\n}\n\nimpl PartialEq for DynIdx {\n    fn eq(&self, _other: &Self) -> bool {\n        true\n    }\n}\n\nimpl Eq for DynIdx {}\n\nimpl DynIdx {\n    pub fn set(&self, idx: usize) {\n        self.idx.set(Some(idx));\n    }\n\n    pub fn get(&self) -> usize {\n        self.idx.get().unwrap_or(usize::MAX)\n    }\n}\n\nimpl Hash for DynIdx {\n    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {\n        self.idx.get().hash(state);\n    }\n}\n"
  },
  {
    "path": "packages/rsx/src/node.rs",
    "content": "use crate::innerlude::*;\nuse proc_macro2::{Span, TokenStream as TokenStream2};\nuse quote::ToTokens;\nuse syn::{\n    ext::IdentExt,\n    parse::{Parse, ParseStream},\n    spanned::Spanned,\n    token::{self},\n    Ident, LitStr, Result, Token,\n};\n\n#[derive(PartialEq, Eq, Clone, Debug)]\npub enum BodyNode {\n    /// div {}\n    Element(Element),\n\n    /// Component {}\n    Component(Component),\n\n    /// \"text {formatted}\"\n    Text(TextNode),\n\n    /// {expr}\n    RawExpr(ExprNode),\n\n    /// for item in items {}\n    ForLoop(ForLoop),\n\n    /// if cond {} else if cond {} (else {}?)\n    IfChain(IfChain),\n}\n\nimpl Parse for BodyNode {\n    fn parse(stream: ParseStream) -> Result<Self> {\n        if stream.peek(LitStr) {\n            return Ok(BodyNode::Text(stream.parse()?));\n        }\n\n        // Transform for loops into into_iter calls\n        if stream.peek(Token![for]) {\n            return Ok(BodyNode::ForLoop(stream.parse()?));\n        }\n\n        // Transform unterminated if statements into terminated optional if statements\n        if stream.peek(Token![if]) {\n            return Ok(BodyNode::IfChain(stream.parse()?));\n        }\n\n        // Match statements are special but have no special arm syntax\n        // we could allow arm syntax if we wanted.\n        //\n        // And it might even backwards compatible? - I think it is with the right fallback\n        // -> parse as bodynode (BracedRawExpr will kick in on multiline arms)\n        // -> if that fails parse as an expr, since that arm might be a one-liner\n        //\n        // ```\n        // match expr {\n        //    val => rsx! { div {} },\n        //    other_val => rsx! { div {} }\n        // }\n        // ```\n        if stream.peek(Token![match]) {\n            return Ok(BodyNode::RawExpr(stream.parse()?));\n        }\n\n        // Raw expressions need to be wrapped in braces - let RawBracedExpr handle partial expansion\n        if stream.peek(token::Brace) {\n            return Ok(BodyNode::RawExpr(stream.parse()?));\n        }\n\n        // If there's an ident immediately followed by a dash, it's a web component\n        // Web components support no namespacing, so just parse it as an element directly\n        if stream.peek(Ident::peek_any) && stream.peek2(Token![-]) {\n            return Ok(BodyNode::Element(stream.parse::<Element>()?));\n        }\n\n        // this is an Element if the path is:\n        //\n        // - one ident\n        // - 1st char is lowercase\n        // - no underscores (reserved for components)\n        // And it is not:\n        // - the start of a path with components\n        //\n        // example:\n        // div {}\n        if stream.peek(Ident::peek_any) && !stream.peek2(Token![::]) {\n            let ident = parse_raw_ident(&stream.fork()).unwrap();\n            let el_name = ident.to_string();\n            let first_char = el_name.chars().next().unwrap();\n\n            if first_char.is_ascii_lowercase() && !el_name.contains('_') {\n                return Ok(BodyNode::Element(stream.parse::<Element>()?));\n            }\n        }\n\n        // Otherwise this should be Component, allowed syntax:\n        // - syn::Path\n        // - PathArguments can only apper in last segment\n        // - followed by `{` or `(`, note `(` cannot be used with one ident\n        //\n        // example\n        // Div {}\n        // ::Div {}\n        // crate::Div {}\n        // component {} <-- already handled by elements\n        // ::component {}\n        // crate::component{}\n        // Input::<InputProps<'_, i32> {}\n        // crate::Input::<InputProps<'_, i32> {}\n        Ok(BodyNode::Component(stream.parse()?))\n    }\n}\n\nimpl ToTokens for BodyNode {\n    fn to_tokens(&self, tokens: &mut TokenStream2) {\n        match self {\n            BodyNode::Element(ela) => ela.to_tokens(tokens),\n            BodyNode::RawExpr(exp) => exp.to_tokens(tokens),\n            BodyNode::Text(txt) => txt.to_tokens(tokens),\n            BodyNode::ForLoop(floop) => floop.to_tokens(tokens),\n            BodyNode::Component(comp) => comp.to_tokens(tokens),\n            BodyNode::IfChain(ifchain) => ifchain.to_tokens(tokens),\n        }\n    }\n}\n\nimpl BodyNode {\n    pub fn get_dyn_idx(&self) -> usize {\n        match self {\n            BodyNode::Text(text) => text.dyn_idx.get(),\n            BodyNode::RawExpr(exp) => exp.dyn_idx.get(),\n            BodyNode::Component(comp) => comp.dyn_idx.get(),\n            BodyNode::ForLoop(floop) => floop.dyn_idx.get(),\n            BodyNode::IfChain(chain) => chain.dyn_idx.get(),\n            BodyNode::Element(_) => panic!(\"Cannot get dyn_idx for this node\"),\n        }\n    }\n\n    pub fn set_dyn_idx(&self, idx: usize) {\n        match self {\n            BodyNode::Text(text) => text.dyn_idx.set(idx),\n            BodyNode::RawExpr(exp) => exp.dyn_idx.set(idx),\n            BodyNode::Component(comp) => comp.dyn_idx.set(idx),\n            BodyNode::ForLoop(floop) => floop.dyn_idx.set(idx),\n            BodyNode::IfChain(chain) => chain.dyn_idx.set(idx),\n            BodyNode::Element(_) => panic!(\"Cannot set dyn_idx for this node\"),\n        }\n    }\n\n    pub fn is_litstr(&self) -> bool {\n        matches!(self, BodyNode::Text { .. })\n    }\n\n    pub fn span(&self) -> Span {\n        match self {\n            BodyNode::Element(el) => el.name.span(),\n            BodyNode::Component(component) => component.name.span(),\n            BodyNode::Text(text) => text.input.span(),\n            BodyNode::RawExpr(exp) => exp.span(),\n            BodyNode::ForLoop(fl) => fl.for_token.span(),\n            BodyNode::IfChain(f) => f.if_token.span(),\n        }\n    }\n\n    pub fn element_children(&self) -> &[BodyNode] {\n        match self {\n            BodyNode::Element(el) => &el.children,\n            _ => panic!(\"Children not available for this node\"),\n        }\n    }\n\n    pub fn el_name(&self) -> &ElementName {\n        match self {\n            BodyNode::Element(el) => &el.name,\n            _ => panic!(\"Element name not available for this node\"),\n        }\n    }\n\n    pub(crate) fn key(&self) -> Option<&AttributeValue> {\n        match self {\n            Self::Element(el) => el.key(),\n            Self::Component(comp) => comp.get_key(),\n            _ => None,\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use quote::quote;\n\n    #[test]\n    fn parsing_matches() {\n        let element = quote! { div { class: \"inline-block mr-4\", icons::icon_14 {} } };\n        assert!(matches!(\n            syn::parse2::<BodyNode>(element).unwrap(),\n            BodyNode::Element(_)\n        ));\n\n        let text = quote! { \"Hello, world!\" };\n        assert!(matches!(\n            syn::parse2::<BodyNode>(text).unwrap(),\n            BodyNode::Text(_)\n        ));\n\n        let component = quote! { Component {} };\n        assert!(matches!(\n            syn::parse2::<BodyNode>(component).unwrap(),\n            BodyNode::Component(_)\n        ));\n\n        let raw_expr = quote! { { 1 + 1 } };\n        assert!(matches!(\n            syn::parse2::<BodyNode>(raw_expr).unwrap(),\n            BodyNode::RawExpr(_)\n        ));\n\n        let for_loop = quote! { for item in items {} };\n        assert!(matches!(\n            syn::parse2::<BodyNode>(for_loop).unwrap(),\n            BodyNode::ForLoop(_)\n        ));\n\n        let if_chain = quote! { if cond {} else if cond {} };\n        assert!(matches!(\n            syn::parse2::<BodyNode>(if_chain).unwrap(),\n            BodyNode::IfChain(_)\n        ));\n\n        let match_expr = quote! {\n            match blah {\n                val => rsx! { div {} },\n                other_val => rsx! { div {} }\n            }\n        };\n        assert!(matches!(\n            syn::parse2::<BodyNode>(match_expr).unwrap(),\n            BodyNode::RawExpr(_)\n        ),);\n\n        let incomplete_component = quote! {\n            some::cool::Component\n        };\n        assert!(matches!(\n            syn::parse2::<BodyNode>(incomplete_component).unwrap(),\n            BodyNode::Component(_)\n        ),);\n    }\n}\n"
  },
  {
    "path": "packages/rsx/src/partial_closure.rs",
    "content": "use crate::PartialExpr;\nuse proc_macro2::TokenStream;\nuse quote::ToTokens;\nuse std::hash::{Hash, Hasher};\nuse syn::{\n    parse::{Parse, ParseStream},\n    punctuated::Punctuated,\n    Attribute, Expr, Pat, PatType, Result, ReturnType, Token, Type,\n};\nuse syn::{BoundLifetimes, ExprClosure};\n\n/// A closure whose body might not be valid rust code but we want to interpret it regardless.\n/// This lets us provide expansions in way more cases than normal closures at the expense of an\n/// increased maintenance burden and complexity.\n///\n/// We do our best to reuse the same logic from partial exprs for the body of the PartialClosure.\n/// The code here is simply stolen from `syn::ExprClosure` and lightly modified to work with\n/// PartialExprs. We only removed the attrs field and changed the body to be a PartialExpr.\n/// Otherwise, it's a direct copy of the original.\n#[derive(Debug, Clone)]\npub struct PartialClosure {\n    pub lifetimes: Option<BoundLifetimes>,\n    pub constness: Option<Token![const]>,\n    pub movability: Option<Token![static]>,\n    pub asyncness: Option<Token![async]>,\n    pub capture: Option<Token![move]>,\n    pub or1_token: Token![|],\n    pub inputs: Punctuated<Pat, Token![,]>,\n    pub or2_token: Token![|],\n    pub output: ReturnType,\n    pub body: PartialExpr,\n}\n\nimpl Parse for PartialClosure {\n    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {\n        let lifetimes: Option<BoundLifetimes> = input.parse()?;\n        let constness: Option<Token![const]> = input.parse()?;\n        let movability: Option<Token![static]> = input.parse()?;\n        let asyncness: Option<Token![async]> = input.parse()?;\n        let capture: Option<Token![move]> = input.parse()?;\n        let or1_token: Token![|] = input.parse()?;\n\n        let mut inputs = Punctuated::new();\n        loop {\n            if input.peek(Token![|]) {\n                break;\n            }\n            let value = closure_arg(input)?;\n            inputs.push_value(value);\n            if input.peek(Token![|]) {\n                break;\n            }\n            let punct: Token![,] = input.parse()?;\n            inputs.push_punct(punct);\n        }\n\n        let or2_token: Token![|] = input.parse()?;\n\n        let output = if input.peek(Token![->]) {\n            let arrow_token: Token![->] = input.parse()?;\n            let ty: Type = input.parse()?;\n            ReturnType::Type(arrow_token, Box::new(ty))\n        } else {\n            ReturnType::Default\n        };\n\n        let body = PartialExpr::parse(input)?;\n\n        Ok(PartialClosure {\n            lifetimes,\n            constness,\n            movability,\n            asyncness,\n            capture,\n            or1_token,\n            inputs,\n            or2_token,\n            output,\n            body,\n        })\n    }\n}\n\nimpl ToTokens for PartialClosure {\n    fn to_tokens(&self, tokens: &mut TokenStream) {\n        self.lifetimes.to_tokens(tokens);\n        self.constness.to_tokens(tokens);\n        self.movability.to_tokens(tokens);\n        self.asyncness.to_tokens(tokens);\n        self.capture.to_tokens(tokens);\n        self.or1_token.to_tokens(tokens);\n        self.inputs.to_tokens(tokens);\n        self.or2_token.to_tokens(tokens);\n        self.output.to_tokens(tokens);\n        self.body.to_tokens(tokens);\n    }\n}\n\nimpl PartialEq for PartialClosure {\n    fn eq(&self, other: &Self) -> bool {\n        self.lifetimes == other.lifetimes\n            && self.constness == other.constness\n            && self.movability == other.movability\n            && self.asyncness == other.asyncness\n            && self.capture == other.capture\n            && self.or1_token == other.or1_token\n            && self.inputs == other.inputs\n            && self.or2_token == other.or2_token\n            && self.output == other.output\n            && self.body == other.body\n    }\n}\n\nimpl Eq for PartialClosure {}\nimpl Hash for PartialClosure {\n    fn hash<H: Hasher>(&self, state: &mut H) {\n        self.lifetimes.hash(state);\n        self.constness.hash(state);\n        self.movability.hash(state);\n        self.asyncness.hash(state);\n        self.capture.hash(state);\n        self.or1_token.hash(state);\n        self.inputs.hash(state);\n        self.or2_token.hash(state);\n        self.output.hash(state);\n        self.body.hash(state);\n    }\n}\n\nimpl PartialClosure {\n    /// Convert this partial closure into a full closure if it is valid\n    /// Returns err if the internal tokens can't be parsed as a closure\n    pub fn as_expr(&self) -> Result<Expr> {\n        let expr_closure = ExprClosure {\n            attrs: Vec::new(),\n            asyncness: self.asyncness,\n            capture: self.capture,\n            inputs: self.inputs.clone(),\n            output: self.output.clone(),\n            lifetimes: self.lifetimes.clone(),\n            constness: self.constness,\n            movability: self.movability,\n            or1_token: self.or1_token,\n            or2_token: self.or2_token,\n\n            // try to lower the body to an expression - if might fail if it can't\n            body: Box::new(self.body.as_expr()?),\n        };\n\n        Ok(Expr::Closure(expr_closure))\n    }\n}\n\n/// This might look complex but it is just a ripoff of the `syn::ExprClosure` implementation. AFAIK\n/// This code is not particularly accessible from outside syn... so it lives here. sorry\nfn closure_arg(input: ParseStream) -> Result<Pat> {\n    let attrs = input.call(Attribute::parse_outer)?;\n    let mut pat = Pat::parse_single(input)?;\n\n    if input.peek(Token![:]) {\n        Ok(Pat::Type(PatType {\n            attrs,\n            pat: Box::new(pat),\n            colon_token: input.parse()?,\n            ty: input.parse()?,\n        }))\n    } else {\n        match &mut pat {\n            Pat::Const(pat) => pat.attrs = attrs,\n            Pat::Ident(pat) => pat.attrs = attrs,\n            Pat::Lit(pat) => pat.attrs = attrs,\n            Pat::Macro(pat) => pat.attrs = attrs,\n            Pat::Or(pat) => pat.attrs = attrs,\n            Pat::Paren(pat) => pat.attrs = attrs,\n            Pat::Path(pat) => pat.attrs = attrs,\n            Pat::Range(pat) => pat.attrs = attrs,\n            Pat::Reference(pat) => pat.attrs = attrs,\n            Pat::Rest(pat) => pat.attrs = attrs,\n            Pat::Slice(pat) => pat.attrs = attrs,\n            Pat::Struct(pat) => pat.attrs = attrs,\n            Pat::Tuple(pat) => pat.attrs = attrs,\n            Pat::TupleStruct(pat) => pat.attrs = attrs,\n            Pat::Wild(pat) => pat.attrs = attrs,\n            Pat::Type(_) => unreachable!(),\n            Pat::Verbatim(_) => {}\n            _ => {}\n        }\n        Ok(pat)\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use quote::quote;\n\n    #[test]\n    fn parses() {\n        let doesnt_parse: Result<ExprClosure> = syn::parse2(quote! {\n            |a, b| { method. }\n        });\n\n        // regular closures can't parse as partial closures\n        assert!(doesnt_parse.is_err());\n\n        let parses: Result<PartialClosure> = syn::parse2(quote! {\n            |a, b| { method. }\n        });\n\n        // but ours can - we just can't format it out\n        let parses = parses.unwrap();\n        dbg!(parses.to_token_stream().to_string());\n    }\n\n    #[test]\n    fn parses_real_world() {\n        let parses: Result<PartialClosure> = syn::parse2(quote! {\n            move |_| {\n                let mut sidebar = SHOW_SIDEBAR.write();\n                *sidebar = !*sidebar;\n            }\n        });\n\n        // but ours can - we just can't format it out\n        let parses = parses.unwrap();\n        dbg!(parses.to_token_stream().to_string());\n        parses.as_expr().unwrap();\n\n        let parses: Result<PartialClosure> = syn::parse2(quote! {\n            move |_| {\n                rsx! {\n                    div { class: \"max-w-lg lg:max-w-2xl mx-auto mb-16 text-center\",\n                        \"gomg\"\n                        \"hi!!\"\n                        \"womh\"\n                    }\n                };\n                println!(\"hi\")\n            }\n        });\n        parses.unwrap().as_expr().unwrap();\n    }\n\n    #[test]\n    fn partial_eqs() {\n        let a: PartialClosure = syn::parse2(quote! {\n            move |e| {\n                println!(\"clicked!\");\n            }\n        })\n        .unwrap();\n\n        let b: PartialClosure = syn::parse2(quote! {\n            move |e| {\n                println!(\"clicked!\");\n            }\n        })\n        .unwrap();\n\n        let c: PartialClosure = syn::parse2(quote! {\n            move |e| {\n                println!(\"unclicked\");\n            }\n        })\n        .unwrap();\n\n        assert_eq!(a, b);\n        assert_ne!(a, c);\n    }\n\n    /// Ensure our ToTokens impl is the same as the one in syn\n    #[test]\n    fn same_to_tokens() {\n        let a: PartialClosure = syn::parse2(quote! {\n            move |e| {\n                println!(\"clicked!\");\n            }\n        })\n        .unwrap();\n\n        let b: PartialClosure = syn::parse2(quote! {\n            move |e| {\n                println!(\"clicked!\");\n            }\n        })\n        .unwrap();\n\n        let c: ExprClosure = syn::parse2(quote! {\n            move |e| {\n                println!(\"clicked!\");\n            }\n        })\n        .unwrap();\n\n        assert_eq!(\n            a.to_token_stream().to_string(),\n            b.to_token_stream().to_string()\n        );\n\n        assert_eq!(\n            a.to_token_stream().to_string(),\n            c.to_token_stream().to_string()\n        );\n\n        let a: PartialClosure = syn::parse2(quote! {\n            move |e| println!(\"clicked!\")\n        })\n        .unwrap();\n\n        let b: ExprClosure = syn::parse2(quote! {\n            move |e| println!(\"clicked!\")\n        })\n        .unwrap();\n\n        assert_eq!(\n            a.to_token_stream().to_string(),\n            b.to_token_stream().to_string()\n        );\n    }\n}\n"
  },
  {
    "path": "packages/rsx/src/raw_expr.rs",
    "content": "use proc_macro2::{Delimiter, TokenStream as TokenStream2, TokenTree};\nuse quote::ToTokens;\nuse std::hash;\nuse syn::{parse::Parse, spanned::Spanned, token::Brace, Expr};\n\n/// A raw expression potentially wrapped in curly braces that is parsed from the input stream.\n///\n/// If there are no braces, it tries to parse as an expression without partial expansion. If there\n/// are braces, it parses the contents as a `TokenStream2` and stores it as such.\n#[derive(Clone, Debug)]\npub struct PartialExpr {\n    pub brace: Option<Brace>,\n    pub expr: TokenStream2,\n}\n\nimpl Parse for PartialExpr {\n    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {\n        // Input is a braced expression if it's a braced group\n        // followed by either of the following:\n        // - the end of the stream\n        // - a comma\n        // - another braced group\n        // - an identifier\n        // - a string literal\n        let mut is_braced = false;\n        if let Some((TokenTree::Group(group), next)) = input.fork().cursor().token_tree() {\n            let next_char_is_a_comma = next.punct().is_some_and(|(tt, _)| tt.as_char() == ',');\n            let next_is_a_braced_exp = next.group(Delimiter::Brace).is_some();\n            let next_is_an_ident = next.ident().is_some();\n            let next_is_a_string_literal = next.literal().is_some();\n\n            if group.delimiter() == Delimiter::Brace\n                && (next.eof()\n                    || next_char_is_a_comma\n                    || next_is_a_braced_exp\n                    || next_is_an_ident\n                    || next_is_a_string_literal)\n            {\n                is_braced = true\n            }\n        };\n\n        // Parse as an expression if it's not braced\n        if !is_braced {\n            let expr = input.parse::<syn::Expr>()?;\n            return Ok(Self {\n                brace: None,\n                expr: expr.to_token_stream(),\n            });\n        }\n\n        let content;\n        let brace = Some(syn::braced!(content in input));\n        let expr: TokenStream2 = content.parse()?;\n\n        Ok(Self { brace, expr })\n    }\n}\n\nimpl ToTokens for PartialExpr {\n    fn to_tokens(&self, tokens: &mut TokenStream2) {\n        match &self.brace {\n            Some(brace) => brace.surround(tokens, |tokens| self.expr.to_tokens(tokens)),\n            _ => self.expr.to_tokens(tokens),\n        }\n    }\n}\n\nimpl PartialExpr {\n    pub fn as_expr(&self) -> syn::Result<syn::Expr> {\n        // very important: make sure to include the brace in the span of the expr\n        // otherwise autofmt will freak out since it will use the inner span\n        if let Some(brace) = &self.brace {\n            let mut tokens = TokenStream2::new();\n            let f = |tokens: &mut TokenStream2| self.expr.to_tokens(tokens);\n            brace.surround(&mut tokens, f);\n            return syn::parse2(tokens);\n        }\n\n        let expr = self.expr.clone();\n        syn::parse2(expr.to_token_stream())\n    }\n\n    pub fn from_expr(expr: &Expr) -> Self {\n        Self {\n            brace: None,\n            expr: expr.to_token_stream(),\n        }\n    }\n    pub fn span(&self) -> proc_macro2::Span {\n        if let Some(brace) = &self.brace {\n            brace.span.span()\n        } else {\n            self.expr.span()\n        }\n    }\n}\n\nimpl PartialEq for PartialExpr {\n    fn eq(&self, other: &Self) -> bool {\n        self.expr.to_string() == other.expr.to_string()\n    }\n}\n\nimpl Eq for PartialExpr {}\n\nimpl hash::Hash for PartialExpr {\n    fn hash<H: hash::Hasher>(&self, state: &mut H) {\n        self.expr.to_string().hash(state);\n    }\n}\n"
  },
  {
    "path": "packages/rsx/src/rsx_block.rs",
    "content": "//! An arbitrary block parser.\n//!\n//! Is meant to parse the contents of a block that is either a component or an element.\n//! We put these together to cut down on code duplication and make the parsers a bit more resilient.\n//!\n//! This involves custom structs for name, attributes, and children, as well as a custom parser for the block itself.\n//! It also bubbles out diagnostics if it can to give better errors.\n\nuse crate::innerlude::*;\nuse proc_macro2::Span;\nuse proc_macro2_diagnostics::SpanDiagnosticExt;\nuse syn::{\n    ext::IdentExt,\n    parse::{Parse, ParseBuffer, ParseStream},\n    spanned::Spanned,\n    token::{self, Brace},\n    Expr, Ident, LitStr, Token,\n};\n\n/// An item in the form of\n///\n/// {\n///  attributes,\n///  ..spreads,\n///  children\n/// }\n///\n/// Does not make any guarantees about the contents of the block - this is meant to be verified by the\n/// element/component impls themselves.\n///\n/// The name of the block is expected to be parsed by the parent parser. It will accept items out of\n/// order if possible and then bubble up diagnostics to the parent. This lets us give better errors\n/// and autocomplete\n#[derive(PartialEq, Eq, Clone, Debug, Default)]\npub struct RsxBlock {\n    pub brace: token::Brace,\n    pub attributes: Vec<Attribute>,\n    pub spreads: Vec<Spread>,\n    pub children: Vec<BodyNode>,\n    pub diagnostics: Diagnostics,\n}\n\nimpl Parse for RsxBlock {\n    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {\n        let content: ParseBuffer;\n        let brace = syn::braced!(content in input);\n        RsxBlock::parse_inner(&content, brace)\n    }\n}\n\nimpl RsxBlock {\n    /// Only parse the children of the block - all others will be rejected\n    pub fn parse_children(content: &ParseBuffer) -> syn::Result<Self> {\n        let mut nodes = vec![];\n        let mut diagnostics = Diagnostics::new();\n        while !content.is_empty() {\n            nodes.push(Self::parse_body_node_with_comma_diagnostics(\n                content,\n                &mut diagnostics,\n            )?);\n        }\n        Ok(Self {\n            children: nodes,\n            diagnostics,\n            ..Default::default()\n        })\n    }\n\n    pub fn parse_inner(content: &ParseBuffer, brace: token::Brace) -> syn::Result<Self> {\n        let mut items = vec![];\n        let mut diagnostics = Diagnostics::new();\n\n        // If we are after attributes, we can try to provide better completions and diagnostics\n        // by parsing the following nodes as body nodes if they are ambiguous, we can parse them as body nodes\n        let mut after_attributes = false;\n\n        // Lots of manual parsing but it's important to do it all here to give the best diagnostics possible\n        // We can do things like lookaheads, peeking, etc. to give better errors and autocomplete\n        // We allow parsing in any order but complain if its done out of order.\n        // Autofmt will fortunately fix this for us in most cases\n        //\n        // We do this by parsing the unambiguous cases first and then do some clever lookahead to parse the rest\n        while !content.is_empty() {\n            // Parse spread attributes\n            if content.peek(Token![..]) {\n                let dots = content.parse::<Token![..]>()?;\n\n                // in case someone tries to do ...spread which is not valid\n                if let Ok(extra) = content.parse::<Token![.]>() {\n                    diagnostics.push(\n                        extra\n                            .span()\n                            .error(\"Spread expressions only take two dots - not 3! (..spread)\"),\n                    );\n                }\n\n                let expr = content.parse::<Expr>()?;\n                let attr = Spread {\n                    expr,\n                    dots,\n                    dyn_idx: DynIdx::default(),\n                    comma: content.parse().ok(),\n                };\n\n                if !content.is_empty() && attr.comma.is_none() {\n                    diagnostics.push(\n                        attr.span()\n                            .error(\"Attributes must be separated by commas\")\n                            .help(\"Did you forget a comma?\"),\n                    );\n                }\n                items.push(RsxItem::Spread(attr));\n                after_attributes = true;\n\n                continue;\n            }\n\n            // Parse unambiguous attributes - these can't be confused with anything\n            if (content.peek(LitStr) || content.peek(Ident::peek_any))\n                && content.peek2(Token![:])\n                && !content.peek3(Token![:])\n            {\n                let attr = content.parse::<Attribute>()?;\n\n                if !content.is_empty() && attr.comma.is_none() {\n                    diagnostics.push(\n                        attr.span()\n                            .error(\"Attributes must be separated by commas\")\n                            .help(\"Did you forget a comma?\"),\n                    );\n                }\n\n                items.push(RsxItem::Attribute(attr));\n\n                continue;\n            }\n\n            // Eagerly match on completed children, generally\n            if content.peek(LitStr)\n                | content.peek(Token![for])\n                | content.peek(Token![if])\n                | content.peek(Token![match])\n                | content.peek(token::Brace)\n                // web components\n                | (content.peek(Ident::peek_any) && content.peek2(Token![-]))\n                // elements\n                | (content.peek(Ident::peek_any) && (after_attributes || content.peek2(token::Brace)))\n                // components\n                | (content.peek(Ident::peek_any) && (after_attributes || content.peek2(token::Brace) || content.peek2(Token![::])))\n            {\n                items.push(RsxItem::Child(\n                    Self::parse_body_node_with_comma_diagnostics(content, &mut diagnostics)?,\n                ));\n                if !content.is_empty() && content.peek(Token![,]) {\n                    let comma = content.parse::<Token![,]>()?;\n                    diagnostics.push(\n                        comma.span().warning(\n                            \"Elements and text nodes do not need to be separated by commas.\",\n                        ),\n                    );\n                }\n                after_attributes = true;\n                continue;\n            }\n\n            // Parse shorthand attributes\n            // todo: this might cause complications with partial expansion... think more about the cases\n            // where we can imagine expansion and what better diagnostics we can provide\n            if Self::peek_lowercase_ident(&content)\n                    && !content.peek2(Brace)\n                    && !content.peek2(Token![:]) // regular attributes / components with generics\n                    && !content.peek2(Token![-]) // web components\n                    && !content.peek2(Token![<]) // generics on components\n                    // generics on components\n                    && !content.peek2(Token![::])\n            {\n                let attribute = content.parse::<Attribute>()?;\n\n                if !content.is_empty() && attribute.comma.is_none() {\n                    diagnostics.push(\n                        attribute\n                            .span()\n                            .error(\"Attributes must be separated by commas\")\n                            .help(\"Did you forget a comma?\"),\n                    );\n                }\n\n                items.push(RsxItem::Attribute(attribute));\n\n                continue;\n            }\n\n            // Finally just attempt a bodynode parse\n            items.push(RsxItem::Child(\n                Self::parse_body_node_with_comma_diagnostics(content, &mut diagnostics)?,\n            ))\n        }\n\n        // Validate the order of the items\n        RsxBlock::validate(&items, &mut diagnostics);\n\n        // todo: maybe make this a method such that the rsxblock is lossless\n        // Decompose into attributes, spreads, and children\n        let mut attributes = vec![];\n        let mut spreads = vec![];\n        let mut children = vec![];\n        for item in items {\n            match item {\n                RsxItem::Attribute(attr) => attributes.push(attr),\n                RsxItem::Spread(spread) => spreads.push(spread),\n                RsxItem::Child(child) => children.push(child),\n            }\n        }\n\n        Ok(Self {\n            attributes,\n            children,\n            spreads,\n            brace,\n            diagnostics,\n        })\n    }\n\n    // Parse a body node with diagnostics for unnecessary trailing commas\n    fn parse_body_node_with_comma_diagnostics(\n        content: &ParseBuffer,\n        _diagnostics: &mut Diagnostics,\n    ) -> syn::Result<BodyNode> {\n        let body_node = content.parse::<BodyNode>()?;\n        if !content.is_empty() && content.peek(Token![,]) {\n            let _comma = content.parse::<Token![,]>()?;\n\n            // todo: we would've pushed a warning here but proc-macro-2 emits them as errors, which we\n            // dont' want. There's no built-in cfg way for checking if we're on nightly, and adding\n            // that would require a build script, so for the interest of compile times, we won't throw\n            // any warning at all.\n            //\n            // Whenever the user formats their code with `dx fmt`, the comma will be removed, so active\n            // projects will implicitly be fixed.\n            //\n            // Whenever the issue is resolved or diagnostics are added, we can re-add this warning.\n            //\n            // - https://github.com/SergioBenitez/proc-macro2-diagnostics/issues/9\n            // - https://github.com/DioxusLabs/dioxus/issues/2807\n            //\n            // diagnostics.push(\n            //     comma\n            //         .span()\n            //         .warning(\"Elements and text nodes do not need to be separated by commas.\"),\n            // );\n        }\n        Ok(body_node)\n    }\n\n    fn peek_lowercase_ident(stream: &ParseStream) -> bool {\n        let Ok(ident) = stream.fork().call(Ident::parse_any) else {\n            return false;\n        };\n\n        ident\n            .to_string()\n            .chars()\n            .next()\n            .unwrap()\n            .is_ascii_lowercase()\n    }\n\n    /// Ensure the ordering of the items is correct\n    /// - Attributes must come before children\n    /// - Spreads must come before children\n    /// - Spreads must come after attributes\n    ///\n    /// div {\n    ///     key: \"value\",\n    ///     ..props,\n    ///     \"Hello, world!\"\n    /// }\n    fn validate(items: &[RsxItem], diagnostics: &mut Diagnostics) {\n        #[derive(Debug, PartialEq, Eq)]\n        enum ValidationState {\n            Attributes,\n            Spreads,\n            Children,\n        }\n        use ValidationState::*;\n        let mut state = ValidationState::Attributes;\n\n        for item in items.iter() {\n            match item {\n                RsxItem::Attribute(_) => {\n                    if state == Children || state == Spreads {\n                        diagnostics.push(\n                            item.span()\n                                .error(\"Attributes must come before children in an element\"),\n                        );\n                    }\n                    state = Attributes;\n                }\n                RsxItem::Spread(_) => {\n                    if state == Children {\n                        diagnostics.push(\n                            item.span()\n                                .error(\"Spreads must come before children in an element\"),\n                        );\n                    }\n                    state = Spreads;\n                }\n                RsxItem::Child(_) => {\n                    state = Children;\n                }\n            }\n        }\n    }\n}\n\npub enum RsxItem {\n    Attribute(Attribute),\n    Spread(Spread),\n    Child(BodyNode),\n}\n\nimpl RsxItem {\n    pub fn span(&self) -> Span {\n        match self {\n            RsxItem::Attribute(attr) => attr.span(),\n            RsxItem::Spread(spread) => spread.dots.span(),\n            RsxItem::Child(child) => child.span(),\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use quote::quote;\n\n    #[test]\n    fn basic_cases() {\n        let input = quote! {\n            { \"Hello, world!\" }\n        };\n\n        let block: RsxBlock = syn::parse2(input).unwrap();\n        assert_eq!(block.attributes.len(), 0);\n        assert_eq!(block.children.len(), 1);\n\n        let input = quote! {\n            {\n                key: \"value\",\n                onclick: move |_| {\n                    \"Hello, world!\"\n                },\n                ..spread,\n                \"Hello, world!\"\n            }\n        };\n\n        let block: RsxBlock = syn::parse2(input).unwrap();\n        dbg!(block);\n\n        let complex_element = quote! {\n            {\n                key: \"value\",\n                onclick2: move |_| {\n                    \"Hello, world!\"\n                },\n                thing: if true { \"value\" },\n                otherthing: if true { \"value\" } else { \"value\" },\n                onclick: move |_| {\n                    \"Hello, world!\"\n                },\n                ..spread,\n                ..spread1\n                ..spread2,\n                \"Hello, world!\"\n            }\n        };\n\n        let _block: RsxBlock = syn::parse2(complex_element).unwrap();\n\n        let complex_component = quote! {\n            {\n                key: \"value\",\n                onclick2: move |_| {\n                    \"Hello, world!\"\n                },\n                ..spread,\n                \"Hello, world!\"\n            }\n        };\n\n        let _block: RsxBlock = syn::parse2(complex_component).unwrap();\n    }\n\n    /// Some tests of partial expansion to give better autocomplete\n    #[test]\n    fn partial_cases() {\n        let with_handler = quote! {\n            {\n                onclick: move |_| {\n                    some.\n                }\n            }\n        };\n\n        let _block: RsxBlock = syn::parse2(with_handler).unwrap();\n    }\n\n    /// Ensure the hotreload scoring algorithm works as expected\n    #[test]\n    fn hr_score() {\n        let _block = quote! {\n            {\n                a: \"value {cool}\",\n                b: \"{cool} value\",\n                b: \"{cool} {thing} value\",\n                b: \"{thing} value\",\n            }\n        };\n\n        // loop { accumulate perfect matches }\n        // stop when all matches are equally valid\n        //\n        // Remove new attr one by one as we find its perfect match. If it doesn't have a perfect match, we\n        // score it instead.\n\n        quote! {\n            // start with\n            div {\n                div { class: \"other {abc} {def} {hij}\" } // 1, 1, 1\n                div { class: \"thing {abc} {def}\" }       // 1, 1, 1\n                // div { class: \"thing {abc}\" }             // 1, 0, 1\n            }\n\n            // end with\n            div {\n                h1 {\n                    class: \"thing {abc}\" // 1, 1, MAX\n                }\n                h1 {\n                    class: \"thing {hij}\" // 1, 1, MAX\n                }\n                // h2 {\n                //     class: \"thing {def}\" // 1, 1, 0\n                // }\n                // h3 {\n                //     class: \"thing {def}\" // 1, 1, 0\n                // }\n            }\n\n            // how about shuffling components, for, if, etc\n            Component {\n                class: \"thing {abc}\",\n                other: \"other {abc} {def}\",\n            }\n            Component {\n                class: \"thing\",\n                other: \"other\",\n            }\n\n            Component {\n                class: \"thing {abc}\",\n                other: \"other\",\n            }\n            Component {\n                class: \"thing {abc}\",\n                other: \"other {abc} {def}\",\n            }\n        };\n    }\n\n    #[test]\n    fn kitchen_sink_parse() {\n        let input = quote! {\n            // Elements\n            {\n                class: \"hello\",\n                id: \"node-{node_id}\",\n                ..props,\n\n                // Text Nodes\n                \"Hello, world!\"\n\n                // Exprs\n                {rsx! { \"hi again!\" }}\n\n\n                for item in 0..10 {\n                    // \"Second\"\n                    div { \"cool-{item}\" }\n                }\n\n                Link {\n                    to: \"/home\",\n                    class: \"link {is_ready}\",\n                    \"Home\"\n                }\n\n                if false {\n                    div { \"hi again!?\" }\n                } else if true {\n                    div { \"its cool?\" }\n                } else {\n                    div { \"not nice !\" }\n                }\n            }\n        };\n\n        let _parsed: RsxBlock = syn::parse2(input).unwrap();\n    }\n\n    #[test]\n    fn simple_comp_syntax() {\n        let input = quote! {\n            { class: \"inline-block mr-4\", icons::icon_14 {} }\n        };\n\n        let _parsed: RsxBlock = syn::parse2(input).unwrap();\n    }\n\n    #[test]\n    fn with_sutter() {\n        let input = quote! {\n            {\n                div {}\n                d\n                div {}\n            }\n        };\n\n        let _parsed: RsxBlock = syn::parse2(input).unwrap();\n    }\n\n    #[test]\n    fn looks_like_prop_but_is_expr() {\n        let input = quote! {\n            {\n                a: \"asd\".to_string(),\n                // b can be omitted, and it will be filled with its default value\n                c: \"asd\".to_string(),\n                d: Some(\"asd\".to_string()),\n                e: Some(\"asd\".to_string()),\n            }\n        };\n\n        let _parsed: RsxBlock = syn::parse2(input).unwrap();\n    }\n\n    #[test]\n    fn no_comma_diagnostics() {\n        let input = quote! {\n            { a, ..ComponentProps { a: 1, b: 2, c: 3, children: VNode::empty(), onclick: Default::default() } }\n        };\n\n        let parsed: RsxBlock = syn::parse2(input).unwrap();\n        assert!(parsed.diagnostics.is_empty());\n    }\n    #[test]\n    fn proper_attributes() {\n        let input = quote! {\n            {\n                onclick: action,\n                href,\n                onmounted: onmounted,\n                prevent_default,\n                class,\n                rel,\n                target: tag_target,\n                aria_current,\n                ..attributes,\n                {children}\n            }\n        };\n\n        let parsed: RsxBlock = syn::parse2(input).unwrap();\n        dbg!(parsed.attributes);\n    }\n\n    #[test]\n    fn reserved_attributes() {\n        let input = quote! {\n            {\n                label {\n                    for: \"blah\",\n                }\n            }\n        };\n\n        let parsed: RsxBlock = syn::parse2(input).unwrap();\n        dbg!(parsed.attributes);\n    }\n\n    #[test]\n    fn diagnostics_check() {\n        let input = quote::quote! {\n            {\n                class: \"foo bar\"\n                \"Hello world\"\n            }\n        };\n\n        let _parsed: RsxBlock = syn::parse2(input).unwrap();\n    }\n\n    #[test]\n    fn incomplete_components() {\n        let input = quote::quote! {\n            {\n                some::cool::Component\n            }\n        };\n\n        let _parsed: RsxBlock = syn::parse2(input).unwrap();\n    }\n\n    #[test]\n    fn incomplete_root_elements() {\n        use syn::parse::Parser;\n\n        let input = quote::quote! {\n            di\n        };\n\n        let parsed = RsxBlock::parse_children.parse2(input).unwrap();\n        let children = parsed.children;\n\n        assert_eq!(children.len(), 1);\n        if let BodyNode::Element(parsed) = &children[0] {\n            assert_eq!(\n                parsed.name,\n                ElementName::Ident(Ident::new(\"di\", Span::call_site()))\n            );\n        } else {\n            panic!(\"expected element, got {:?}\", children);\n        }\n        assert!(parsed.diagnostics.is_empty());\n    }\n}\n"
  },
  {
    "path": "packages/rsx/src/rsx_call.rs",
    "content": "//! The actual rsx! macro implementation.\n//!\n//! This mostly just defers to the root TemplateBody with some additional tooling to provide better errors.\n//! Currently the additional tooling doesn't do much.\n\nuse proc_macro2::{Span, TokenStream as TokenStream2};\nuse quote::ToTokens;\nuse std::{cell::Cell, fmt::Debug};\nuse syn::{\n    parse::{Parse, ParseStream},\n    Result,\n};\n\nuse crate::{BodyNode, TemplateBody};\n\n/// The Callbody is the contents of the rsx! macro\n///\n/// It is a list of BodyNodes, which are the different parts of the template.\n/// The Callbody contains no information about how the template will be rendered, only information about the parsed tokens.\n///\n/// Every callbody should be valid, so you can use it to build a template.\n/// To generate the code used to render the template, use the ToTokens impl on the Callbody, or with the `render_with_location` method.\n///\n/// Ideally we don't need the metadata here and can bake the idx-es into the templates themselves but I haven't figured out how to do that yet.\n#[derive(Debug, Clone)]\npub struct CallBody {\n    pub body: TemplateBody,\n    pub template_idx: Cell<usize>,\n    pub span: Option<Span>,\n}\n\nimpl Parse for CallBody {\n    fn parse(input: ParseStream) -> Result<Self> {\n        // Defer to the `new` method such that we can wire up hotreload information\n        let mut body = CallBody::new(input.parse()?);\n        body.span = Some(input.span());\n        Ok(body)\n    }\n}\n\nimpl ToTokens for CallBody {\n    fn to_tokens(&self, out: &mut TokenStream2) {\n        self.body.to_tokens(out)\n    }\n}\n\nimpl CallBody {\n    /// Create a new CallBody from a TemplateBody\n    ///\n    /// This will overwrite all internal metadata regarding hotreloading.\n    pub fn new(body: TemplateBody) -> Self {\n        let body = CallBody {\n            body,\n            template_idx: Cell::new(0),\n            span: None,\n        };\n\n        body.body.template_idx.set(body.next_template_idx());\n\n        body.cascade_hotreload_info(&body.body.roots);\n\n        body\n    }\n\n    /// Parse a stream into a CallBody. Return all error immediately instead of trying to partially expand the macro\n    ///\n    /// This should be preferred over `parse` if you are outside of a macro\n    pub fn parse_strict(input: ParseStream) -> Result<Self> {\n        // todo: actually throw warnings if there are any\n        Self::parse(input)\n    }\n\n    /// With the entire knowledge of the macro call, wire up location information for anything hotreloading\n    /// specific. It's a little bit simpler just to have a global id per callbody than to try and track it\n    /// relative to each template, though we could do that if we wanted to.\n    ///\n    /// For now this is just information for ifmts and templates so that when they generate, they can be\n    /// tracked back to the original location in the source code, to support formatted string hotreloading.\n    ///\n    /// Note that there are some more complex cases we could in theory support, but have bigger plans\n    /// to enable just pure rust hotreloading that would make those tricks moot. So, manage more of\n    /// the simple cases until that proper stuff ships.\n    ///\n    /// We need to make sure to wire up:\n    /// - subtemplate IDs\n    /// - ifmt IDs\n    /// - dynamic node IDs\n    /// - dynamic attribute IDs\n    /// - paths for dynamic nodes and attributes\n    ///\n    /// Lots of wiring!\n    ///\n    /// However, here, we only need to wire up template IDs since TemplateBody will handle the rest.\n    ///\n    /// This is better though since we can save the relevant data on the structures themselves.\n    fn cascade_hotreload_info(&self, nodes: &[BodyNode]) {\n        for node in nodes.iter() {\n            match node {\n                BodyNode::Element(el) => {\n                    self.cascade_hotreload_info(&el.children);\n                }\n\n                BodyNode::Component(comp) => {\n                    comp.children.template_idx.set(self.next_template_idx());\n                    self.cascade_hotreload_info(&comp.children.roots);\n                }\n\n                BodyNode::ForLoop(floop) => {\n                    floop.body.template_idx.set(self.next_template_idx());\n                    self.cascade_hotreload_info(&floop.body.roots);\n                }\n\n                BodyNode::IfChain(chain) => chain.for_each_branch(&mut |body| {\n                    body.template_idx.set(self.next_template_idx());\n                    self.cascade_hotreload_info(&body.roots)\n                }),\n\n                _ => {}\n            }\n        }\n    }\n\n    fn next_template_idx(&self) -> usize {\n        let idx = self.template_idx.get();\n        self.template_idx.set(idx + 1);\n        idx\n    }\n}\n"
  },
  {
    "path": "packages/rsx/src/template_body.rs",
    "content": "//! I'm so sorry this is so complicated. Here's my best to simplify and explain it:\n//!\n//! The `Callbody` is the contents of the rsx! macro - this contains all the information about every\n//! node that rsx! directly knows about. For loops, if statements, etc.\n//!\n//! However, there are multiple *templates* inside a callbody - due to how core clones templates and\n//! just generally rationalize the concept of a template, nested bodies like for loops and if statements\n//! and component children are all templates, contained within the same Callbody.\n//!\n//! This gets confusing fast since there's lots of IDs bouncing around.\n//!\n//! The IDs at play:\n//! - The id of the template itself so we can find it and apply it to the dom.\n//!   This is challenging since all calls to file/line/col/id are relative to the macro invocation,\n//!   so they will have to share the same base ID and we need to give each template a new ID.\n//!   The id of the template will be something like file!():line!():col!():ID where ID increases for\n//!   each nested template.\n//!\n//! - The IDs of dynamic nodes relative to the template they live in. This is somewhat easy to track\n//!   but needs to happen on a per-template basis.\n//!\n//! - The IDs of formatted strings in debug mode only. Any formatted segments like \"{x:?}\" get pulled out\n//!   into a pool so we can move them around during hot reloading on a per-template basis.\n//!\n//! - The IDs of component property literals in debug mode only. Any component property literals like\n//!   1234 get pulled into the pool so we can hot reload them with the context of the literal pool.\n//!\n//! We solve this by parsing the structure completely and then doing a second pass that fills in IDs\n//! by walking the structure.\n//!\n//! This means you can't query the ID of any node \"in a vacuum\" - these are assigned once - but at\n//! least they're stable enough for the purposes of hotreloading\n//!\n//! ```text\n//! rsx! {\n//!     div {\n//!         class: \"hello\",\n//!         id: \"node-{node_id}\",         <--- {node_id} has the formatted segment id 0 in the literal pool\n//!         ..props,                      <--- spreads are not reloadable\n//!\n//!         \"Hello, world!\"               <--- not tracked but reloadable in the template since it's just a string\n//!\n//!         for item in 0..10 {           <--- both 0 and 10 are technically reloadable, but we don't hot reload them today...\n//!             div { \"cool-{item}\" }     <--- {item} has the formatted segment id 1 in the literal pool\n//!         }\n//!\n//!         Link {\n//!             to: \"/home\",              <--- hotreloadable since its a component prop literal (with component literal id 0)\n//!             class: \"link {is_ready}\", <--- {is_ready} has the formatted segment id 2 in the literal pool and the property has the component literal id 1\n//!             \"Home\"                    <--- hotreloadable since its a component child (via template)\n//!         }\n//!     }\n//! }\n//! ```\n\nuse self::location::DynIdx;\nuse crate::innerlude::Attribute;\nuse crate::*;\nuse proc_macro2::{Span, TokenStream as TokenStream2};\nuse proc_macro2_diagnostics::SpanDiagnosticExt;\nuse syn::parse_quote;\n\ntype NodePath = Vec<u8>;\ntype AttributePath = Vec<u8>;\n\n/// A set of nodes in a template position\n///\n/// this could be:\n/// - The root of a callbody\n/// - The children of a component\n/// - The children of a for loop\n/// - The children of an if chain\n///\n/// The TemplateBody when needs to be parsed into a surrounding `Body` to be correctly re-indexed\n/// By default every body has a `0` default index\n#[derive(PartialEq, Eq, Clone, Debug)]\npub struct TemplateBody {\n    pub roots: Vec<BodyNode>,\n    pub template_idx: DynIdx,\n    pub node_paths: Vec<NodePath>,\n    pub attr_paths: Vec<(AttributePath, usize)>,\n    pub dynamic_text_segments: Vec<FormattedSegment>,\n    pub diagnostics: Diagnostics,\n}\n\nimpl Parse for TemplateBody {\n    /// Parse the nodes of the callbody as `Body`.\n    fn parse(input: ParseStream) -> Result<Self> {\n        let children = RsxBlock::parse_children(input)?;\n        let mut myself = Self::new(children.children);\n        myself\n            .diagnostics\n            .extend(children.diagnostics.into_diagnostics());\n\n        Ok(myself)\n    }\n}\n\n/// Our ToTokens impl here just defers to rendering a template out like any other `Body`.\n/// This is because the parsing phase filled in all the additional metadata we need\nimpl ToTokens for TemplateBody {\n    fn to_tokens(&self, tokens: &mut TokenStream2) {\n        // First normalize the template body for rendering\n        let node = self.normalized();\n\n        // If we have an implicit key, then we need to write its tokens\n        let key_tokens = match node.implicit_key() {\n            Some(tok) => quote! { Some( #tok.to_string() ) },\n            None => quote! { None },\n        };\n\n        let key_warnings = self.check_for_duplicate_keys();\n\n        let roots = node.quote_roots();\n\n        // Print paths is easy - just print the paths\n        let node_paths = node.node_paths.iter().map(|it| quote!(&[#(#it),*]));\n        let attr_paths = node.attr_paths.iter().map(|(it, _)| quote!(&[#(#it),*]));\n\n        // For printing dynamic nodes, we rely on the ToTokens impl\n        // Elements have a weird ToTokens - they actually are the entrypoint for Template creation\n        let dynamic_nodes: Vec<_> = node.dynamic_nodes().collect();\n        let dynamic_nodes_len = dynamic_nodes.len();\n\n        // We could add a ToTokens for Attribute but since we use that for both components and elements\n        // They actually need to be different, so we just localize that here\n        let dyn_attr_printer: Vec<_> = node\n            .dynamic_attributes()\n            .map(|attr| attr.rendered_as_dynamic_attr())\n            .collect();\n        let dynamic_attr_len = dyn_attr_printer.len();\n\n        let dynamic_text = node.dynamic_text_segments.iter();\n\n        let diagnostics = &node.diagnostics;\n        let index = node.template_idx.get();\n        let hot_reload_mapping = node.hot_reload_mapping();\n\n        tokens.append_all(quote! {\n            dioxus_core::Element::Ok({\n                #diagnostics\n\n                #key_warnings\n\n                // Components pull in the dynamic literal pool and template in debug mode, so they need to be defined before dynamic nodes\n                #[cfg(debug_assertions)]\n                fn __original_template() -> &'static dioxus_core::internal::HotReloadedTemplate {\n                    static __ORIGINAL_TEMPLATE: ::std::sync::OnceLock<dioxus_core::internal::HotReloadedTemplate> = ::std::sync::OnceLock::new();\n                    if __ORIGINAL_TEMPLATE.get().is_none() {\n                        _ = __ORIGINAL_TEMPLATE.set(#hot_reload_mapping);\n                    }\n                    __ORIGINAL_TEMPLATE.get().unwrap()\n                }\n                #[cfg(debug_assertions)]\n                let __template_read = {\n                    use dioxus_signals::ReadableExt;\n\n                    static __NORMALIZED_FILE: &'static str = {\n                        const PATH: &str = dioxus_core::const_format::str_replace!(file!(), \"\\\\\\\\\", \"/\");\n                        dioxus_core::const_format::str_replace!(PATH, '\\\\', \"/\")\n                    };\n\n                    // The key is important here - we're creating a new GlobalSignal each call to this\n                    // But the key is what's keeping it stable\n                    static __TEMPLATE: dioxus_signals::GlobalSignal<Option<dioxus_core::internal::HotReloadedTemplate>> = dioxus_signals::GlobalSignal::with_location(\n                        || None::<dioxus_core::internal::HotReloadedTemplate>,\n                        __NORMALIZED_FILE,\n                        line!(),\n                        column!(),\n                        #index\n                    );\n\n                    dioxus_core::Runtime::try_current().map(|_| __TEMPLATE.read())\n                };\n                // If the template has not been hot reloaded, we always use the original template\n                // Templates nested within macros may be merged because they have the same file-line-column-index\n                // They cannot be hot reloaded, so this prevents incorrect rendering\n                #[cfg(debug_assertions)]\n                let __template_read = match __template_read.as_ref().map(|__template_read| __template_read.as_ref()) {\n                    Some(Some(__template_read)) => &__template_read,\n                    _ => __original_template(),\n                };\n                #[cfg(debug_assertions)]\n                let mut __dynamic_literal_pool = dioxus_core::internal::DynamicLiteralPool::new(\n                    vec![ #( #dynamic_text.to_string() ),* ],\n                );\n\n                // The key needs to be created before the dynamic nodes as it might depend on a borrowed value which gets moved into the dynamic nodes\n                #[cfg(not(debug_assertions))]\n                let __key = #key_tokens;\n                // These items are used in both the debug and release expansions of rsx. Pulling them out makes the expansion\n                // slightly smaller and easier to understand. Rust analyzer also doesn't autocomplete well when it sees an ident show up twice in the expansion\n                let __dynamic_nodes: [dioxus_core::DynamicNode; #dynamic_nodes_len] = [ #( #dynamic_nodes ),* ];\n                let __dynamic_attributes: [Box<[dioxus_core::Attribute]>; #dynamic_attr_len] = [ #( #dyn_attr_printer ),* ];\n                #[doc(hidden)]\n                static __TEMPLATE_ROOTS: &[dioxus_core::TemplateNode] = &[ #( #roots ),* ];\n\n                #[cfg(debug_assertions)]\n                {\n                    let mut __dynamic_value_pool = dioxus_core::internal::DynamicValuePool::new(\n                        Vec::from(__dynamic_nodes),\n                        Vec::from(__dynamic_attributes),\n                        __dynamic_literal_pool\n                    );\n                    __dynamic_value_pool.render_with(__template_read)\n                }\n                #[cfg(not(debug_assertions))]\n                {\n                    #[doc(hidden)] // vscode please stop showing these in symbol search\n                    static ___TEMPLATE: dioxus_core::Template = dioxus_core::Template {\n                        roots: __TEMPLATE_ROOTS,\n                        node_paths: &[ #( #node_paths ),* ],\n                        attr_paths: &[ #( #attr_paths ),* ],\n                    };\n\n                    // NOTE: Allocating a temporary is important to make reads within rsx drop before the value is returned\n                    #[allow(clippy::let_and_return)]\n                    let __vnodes = dioxus_core::VNode::new(\n                        __key,\n                        ___TEMPLATE,\n                        Box::new(__dynamic_nodes),\n                        Box::new(__dynamic_attributes),\n                    );\n                    __vnodes\n                }\n            })\n        });\n    }\n}\n\nimpl TemplateBody {\n    /// Create a new TemplateBody from a set of nodes\n    ///\n    /// This will fill in all the necessary path information for the nodes in the template and will\n    /// overwrite data like dynamic indexes.\n    pub fn new(nodes: Vec<BodyNode>) -> Self {\n        let mut body = Self {\n            roots: vec![],\n            template_idx: DynIdx::default(),\n            node_paths: Vec::new(),\n            attr_paths: Vec::new(),\n            dynamic_text_segments: Vec::new(),\n            diagnostics: Diagnostics::new(),\n        };\n\n        // Assign paths to all nodes in the template\n        body.assign_paths_inner(&nodes);\n\n        // And then save the roots\n        body.roots = nodes;\n\n        // Finally, validate the key\n        body.validate_key();\n\n        body\n    }\n\n    /// Normalize the Template body for rendering. If the body is completely empty, insert a placeholder node\n    pub fn normalized(&self) -> Self {\n        // If the nodes are completely empty, insert a placeholder node\n        // Core expects at least one node in the template to make it easier to replace\n        if self.is_empty() {\n            // Create an empty template body with a placeholder and diagnostics + the template index from the original\n            let empty = Self::new(vec![BodyNode::RawExpr(parse_quote! {()})]);\n            let default = Self {\n                diagnostics: self.diagnostics.clone(),\n                template_idx: self.template_idx.clone(),\n                ..empty\n            };\n            return default;\n        }\n        self.clone()\n    }\n\n    pub fn is_empty(&self) -> bool {\n        self.roots.is_empty()\n    }\n\n    pub fn implicit_key(&self) -> Option<&AttributeValue> {\n        self.roots.first().and_then(BodyNode::key)\n    }\n\n    /// Ensure only one key and that the key is not a static str\n    ///\n    /// todo: we want to allow arbitrary exprs for keys provided they impl hash / eq\n    fn validate_key(&mut self) {\n        let key = self.implicit_key();\n\n        if let Some(attr) = key {\n            let diagnostic = match &attr {\n                AttributeValue::AttrLiteral(ifmt) => {\n                    if ifmt.is_static() {\n                        ifmt.span().error(\"Key must not be a static string. Make sure to use a formatted string like `key: \\\"{value}\\\"\")\n                    } else {\n                        return;\n                    }\n                }\n                _ => attr\n                    .span()\n                    .error(\"Key must be in the form of a formatted string like `key: \\\"{value}\\\"\"),\n            };\n\n            self.diagnostics.push(diagnostic);\n        }\n    }\n\n    fn check_for_duplicate_keys(&self) -> TokenStream2 {\n        let mut warnings = TokenStream2::new();\n\n        // Make sure there are not multiple keys or keys on nodes other than the first in the block\n        for root in self.roots.iter().skip(1) {\n            if let Some(key) = root.key() {\n                warnings.extend(new_diagnostics::warning_diagnostic(\n                    key.span(),\n                    \"Keys are only allowed on the first node in the block.\",\n                ));\n            }\n        }\n\n        warnings\n    }\n\n    pub fn get_dyn_node(&self, path: &[u8]) -> &BodyNode {\n        let mut node = self.roots.get(path[0] as usize).unwrap();\n        for idx in path.iter().skip(1) {\n            node = node.element_children().get(*idx as usize).unwrap();\n        }\n        node\n    }\n\n    pub fn get_dyn_attr(&self, path: &AttributePath, idx: usize) -> &Attribute {\n        match self.get_dyn_node(path) {\n            BodyNode::Element(el) => &el.merged_attributes[idx],\n            _ => unreachable!(),\n        }\n    }\n\n    pub fn dynamic_attributes(&self) -> impl DoubleEndedIterator<Item = &Attribute> {\n        self.attr_paths\n            .iter()\n            .map(|(path, idx)| self.get_dyn_attr(path, *idx))\n    }\n\n    pub fn dynamic_nodes(&self) -> impl DoubleEndedIterator<Item = &BodyNode> {\n        self.node_paths.iter().map(|path| self.get_dyn_node(path))\n    }\n\n    fn quote_roots(&self) -> impl Iterator<Item = TokenStream2> + '_ {\n        self.roots.iter().map(|node| match node {\n            BodyNode::Element(el) => quote! { #el },\n            BodyNode::Text(text) if text.is_static() => {\n                let text = text.input.to_static().unwrap();\n                quote! { dioxus_core::TemplateNode::Text { text: #text } }\n            }\n            _ => {\n                let id = node.get_dyn_idx();\n                quote! { dioxus_core::TemplateNode::Dynamic { id: #id } }\n            }\n        })\n    }\n\n    /// Iterate through the literal component properties of this rsx call in depth-first order\n    pub fn literal_component_properties(&self) -> impl Iterator<Item = &HotLiteral> + '_ {\n        self.dynamic_nodes()\n            .filter_map(|node| {\n                if let BodyNode::Component(component) = node {\n                    Some(component)\n                } else {\n                    None\n                }\n            })\n            .flat_map(|component| {\n                component.component_props().filter_map(|field| {\n                    if let AttributeValue::AttrLiteral(literal) = &field.value {\n                        Some(literal)\n                    } else {\n                        None\n                    }\n                })\n            })\n    }\n\n    fn hot_reload_mapping(&self) -> TokenStream2 {\n        let key = if let Some(AttributeValue::AttrLiteral(HotLiteral::Fmted(key))) =\n            self.implicit_key()\n        {\n            quote! { Some(#key) }\n        } else {\n            quote! { None }\n        };\n        let dynamic_nodes = self.dynamic_nodes().map(|node| {\n            let id = node.get_dyn_idx();\n            quote! { dioxus_core::internal::HotReloadDynamicNode::Dynamic(#id) }\n        });\n        let dyn_attr_printer = self.dynamic_attributes().map(|attr| {\n            let id = attr.get_dyn_idx();\n            quote! { dioxus_core::internal::HotReloadDynamicAttribute::Dynamic(#id) }\n        });\n        let component_values = self\n            .literal_component_properties()\n            .map(|literal| literal.quote_as_hot_reload_literal());\n        quote! {\n            dioxus_core::internal::HotReloadedTemplate::new(\n                #key,\n                vec![ #( #dynamic_nodes ),* ],\n                vec![ #( #dyn_attr_printer ),* ],\n                vec![ #( #component_values ),* ],\n                __TEMPLATE_ROOTS,\n            )\n        }\n    }\n\n    /// Get the span of the first root of this template\n    pub(crate) fn first_root_span(&self) -> Span {\n        match self.roots.first() {\n            Some(root) => root.span(),\n            _ => Span::call_site(),\n        }\n    }\n}\n"
  },
  {
    "path": "packages/rsx/src/text_node.rs",
    "content": "use crate::{literal::HotLiteral, location::DynIdx, HotReloadFormattedSegment, IfmtInput};\nuse proc_macro2::{Span, TokenStream as TokenStream2};\nuse quote::ToTokens;\nuse quote::{quote, TokenStreamExt};\nuse syn::Result;\nuse syn::{\n    parse::{Parse, ParseStream},\n    LitStr,\n};\n\n#[derive(PartialEq, Eq, Clone, Debug, Hash)]\npub struct TextNode {\n    pub input: HotReloadFormattedSegment,\n    pub dyn_idx: DynIdx,\n}\n\nimpl Parse for TextNode {\n    fn parse(input: ParseStream) -> Result<Self> {\n        Ok(Self {\n            input: input.parse()?,\n            dyn_idx: DynIdx::default(),\n        })\n    }\n}\n\nimpl ToTokens for TextNode {\n    fn to_tokens(&self, tokens: &mut TokenStream2) {\n        let txt = &self.input;\n\n        if txt.is_static() {\n            tokens.append_all(quote! {\n                dioxus_core::DynamicNode::Text(dioxus_core::VText::new(#txt.to_string()))\n            })\n        } else {\n            // todo:\n            // Use the RsxLiteral implementation to spit out a hotreloadable variant of this string\n            // This is not super efficient since we're doing a bit of cloning\n            let as_lit = HotLiteral::Fmted(txt.clone());\n\n            tokens.append_all(quote! {\n                dioxus_core::DynamicNode::Text(dioxus_core::VText::new( #as_lit ))\n            })\n        }\n    }\n}\n\nimpl TextNode {\n    pub fn from_text(text: &str) -> Self {\n        let ifmt = IfmtInput {\n            source: LitStr::new(text, Span::call_site()),\n            segments: vec![],\n        };\n        Self {\n            input: ifmt.into(),\n            dyn_idx: Default::default(),\n        }\n    }\n\n    pub fn is_static(&self) -> bool {\n        self.input.is_static()\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use prettier_please::PrettyUnparse;\n\n    #[test]\n    fn parses() {\n        let input = syn::parse2::<TextNode>(quote! { \"hello world\" }).unwrap();\n        assert_eq!(input.input.source.value(), \"hello world\");\n    }\n\n    #[test]\n    fn to_tokens_with_hr() {\n        let lit = syn::parse2::<TextNode>(quote! { \"hi {world1} {world2} {world3}\" }).unwrap();\n        println!(\"{}\", lit.to_token_stream().pretty_unparse());\n    }\n\n    #[test]\n    fn raw_str() {\n        let input = syn::parse2::<TextNode>(quote! { r#\"hello world\"# }).unwrap();\n        println!(\"{}\", input.input.source.to_token_stream());\n        assert_eq!(input.input.source.value(), \"hello world\");\n    }\n}\n"
  },
  {
    "path": "packages/rsx/src/util.rs",
    "content": "#![allow(unused)]\n\nuse proc_macro2::TokenStream as TokenStream2;\nuse std::{fmt::Debug, hash::Hash};\nuse syn::{\n    ext::IdentExt,\n    parse::{Parse, ParseBuffer},\n    Ident,\n};\n\n/// Parse a raw ident and return a new ident with the r# prefix added\npub fn parse_raw_ident(parse_buffer: &ParseBuffer) -> syn::Result<Ident> {\n    // First try to parse as a normal ident\n    if let Ok(ident) = Ident::parse(parse_buffer) {\n        return Ok(ident);\n    }\n    // If that fails, try to parse as a raw ident\n    let ident = Ident::parse_any(parse_buffer)?;\n    Ok(Ident::new_raw(&ident.to_string(), ident.span()))\n}\n"
  },
  {
    "path": "packages/rsx/tests/parsing.rs",
    "content": "use dioxus_rsx::CallBody;\nuse quote::ToTokens;\n\nuse prettier_please::PrettyUnparse;\n\n#[test]\nfn callbody_ctx() {\n    let item = quote::quote! {\n        div {\n            h1 {\n                id: \"Some cool attribute {cool}\"\n            }\n            for item in items {\n                \"Something {cool}\"\n            }\n            Component {\n                \"Something {elseish}\"\n            }\n            Component2 {\n                \"Something {Body}\"\n                Component3 {\n                    prop: \"Something {Body3}\",\n                    \"Something {Body4}\"\n                }\n            }\n            something-cool {\n                \"Something {cool}ish\"\n            }\n            if true {\n                div {\n                    \"hi! {cool}\"\n                }\n            }\n            \"hi!\"\n            \"goodbye!\"\n        }\n        {some_expr}\n    };\n\n    let cb: CallBody = syn::parse2(item).unwrap();\n\n    dbg!(cb.template_idx.get());\n}\n\n#[test]\nfn simple_case() {\n    let item = quote::quote! {\n        div {\n            something: \"cool\",\n            id: \"Some cool attribute {cool}\",\n            class: \"Some cool attribute {cool2}\",\n            \"hi!\"\n            {some_expr}\n            Component {\n                boolish: true,\n                otherish: 123,\n                otherish2: 123.0,\n                otherish3: \"dang!\",\n                otherish3: \"dang! {cool}\",\n            }\n        }\n    };\n\n    let cb: CallBody = syn::parse2(item).unwrap();\n    println!(\"{}\", cb.to_token_stream().pretty_unparse());\n}\n\n#[test]\nfn complex_kitchen_sink() {\n    let item = quote::quote! {\n        // complex_carry\n        button {\n            class: \"flex items-center pl-3 py-3 pr-2 text-gray-500 hover:bg-indigo-50 rounded\",\n            width: {\"100%\"}.to_string(),\n            onclick: move |evt| {\n                show_user_menu.set(!show_user_menu.get());\n                evt.cancel_bubble();\n            },\n            onmousedown: move |evt| show_user_menu.set(!show_user_menu.get()),\n            span { class: \"inline-block mr-4\", icons::icon_14 {} }\n            span { \"Settings\" }\n        }\n\n        // Complex nesting with handlers\n        li {\n            Link {\n                class: \"flex items-center pl-3 py-3 pr-4 {active_class} rounded\",\n                to: \"{to}\",\n                span { class: \"inline-block mr-3\", icons::icon_0 {} }\n                span { \"{name}\" }\n                {children.is_some().then(|| rsx! {\n                    span {\n                        class: \"inline-block ml-auto hover:bg-gray-500\",\n                        onclick: move |evt| {\n                            // open.set(!open.get());\n                            evt.cancel_bubble();\n                        },\n                        icons::icon_8 {}\n                    }\n                })}\n            }\n            div { class: \"px-4\", {is_current.then(|| rsx! { children })} }\n        }\n\n        // No nesting\n        Component {\n            adsasd: \"asd\",\n            onclick: move |_| {\n                let blah = 120;\n            }\n        }\n\n        // No nesting\n        Component {\n            adsasd: \"asd\",\n            onclick: {\n                let a = 1;\n                move |_| {\n                    let blah = 120;\n                }\n            }\n        }\n\n        // Component path\n        my::thing::Component {\n            adsasd: \"asd\",\n            onclick: move |_| {\n                let blah = 120;\n            }\n        }\n\n        for i in 0..10 {\n            Component { key: \"{i}\", blah: 120 }\n        }\n        for i in 0..10 {\n            Component { key: \"{i}\" }\n        }\n\n        for i in 0..10 {\n            div { key: \"{i}\", blah: 120 }\n        }\n\n        for i in 0..10 {\n            div { key: \"{i}\" }\n        }\n\n        div {\n            \"asdbascasdbasd\"\n            \"asbdasbdabsd\"\n            {asbdabsdbasdbas}\n        }\n    };\n\n    let _cb: CallBody = syn::parse2(item).unwrap();\n}\n\n#[test]\nfn key_must_be_formatted() {\n    let item = quote::quote! {\n        div {\n            key: value\n        }\n    };\n\n    let parsed = syn::parse2::<CallBody>(item).unwrap();\n    println!(\"{:?}\", parsed.body.diagnostics);\n    assert!(!parsed.body.diagnostics.is_empty());\n}\n\n#[test]\nfn key_cannot_be_static() {\n    let item = quote::quote! {\n        div {\n            key: \"hello world\"\n        }\n    };\n\n    let parsed = syn::parse2::<CallBody>(item).unwrap();\n    println!(\"{:?}\", parsed.body.diagnostics);\n    assert!(!parsed.body.diagnostics.is_empty());\n}\n\n#[test]\nfn braced_expressions() {\n    let item = quote::quote! {\n        div {\n            width: {100} - 50,\n            width: {\"100%\"}.to_string(),\n            width: {|| \"100%\"}(),\n        }\n        // Partial expressions in braces rsx should be allowed and output as-is\n        // for autocomplete\n        {partial.}\n        div {}\n        {partial.}\n        // Comments should be ignored\n        div {}\n        {partial.}\n        \"hello world\"\n        {partial.}\n        if true {}\n    };\n\n    let _cb: CallBody = syn::parse2(item).unwrap();\n}\n"
  },
  {
    "path": "packages/rsx-hotreload/Cargo.toml",
    "content": "[package]\nname = \"dioxus-rsx-hotreload\"\nedition = \"2021\"\nversion.workspace = true\nauthors = [\"Jonathan Kelley\", \"Evan Almloff\"]\nlicense = \"MIT OR Apache-2.0\"\nrepository = \"https://github.com/DioxusLabs/dioxus/\"\nhomepage = \"https://dioxuslabs.com\"\ndescription = \"CLI Configuration for dioxus-cli\"\nkeywords = [\"dom\", \"ui\", \"gui\", \"react\"]\n\n[dependencies]\ndioxus-rsx = { workspace = true }\ninternment = { workspace = true }\nproc-macro2 = { workspace = true, features = [\"span-locations\"] }\nproc-macro2-diagnostics = { workspace = true }\nquote = { workspace = true }\nsyn = { workspace = true, features = [\"full\", \"extra-traits\", \"visit\", \"visit-mut\"] }\ntracing = { workspace = true }\ndioxus-core = { workspace = true }\ndioxus-core-types = { workspace = true }\n"
  },
  {
    "path": "packages/rsx-hotreload/src/collect.rs",
    "content": "//! Compare two files and find any rsx calls that have changed\n//!\n//! This is used to determine if a hotreload is needed.\n//! We use a special syn visitor to find all the rsx! calls in the file and then compare them to see\n//! if they are the same. This visitor will actually remove the rsx! calls and replace them with a\n//! dummy rsx! call. The final file type is thus mutated in place, leaving the original file idents\n//! in place. We then compare the two files to see if they are the same. We're able to differentiate\n//! between rust code changes and rsx code changes with much less code than the previous manual diff\n//! approach.\n\nuse syn::visit_mut::VisitMut;\nuse syn::{File, Macro};\n\npub struct ChangedRsx {\n    /// The old macro - the original RSX from the original file\n    pub old: Macro,\n\n    /// The new macro\n    pub new: Macro,\n}\n\n#[derive(Debug)]\npub enum ReloadableRustCode {\n    Rsx { old: Macro, new: Macro },\n}\n\n/// Find any rsx calls in the given file and return a list of all the rsx calls that have changed.\n///\n/// Takes in the two files, clones them, removes the rsx! contents and prunes any doc comments.\n/// Then it compares the two files to see if they are different - if they are, the code changed.\n/// Otherwise, the code is the same and we can move on to handling the changed rsx\n///\n/// Returns `None` if the files are the same and `Some` if they are different\n/// If there are no rsx! calls in the files, the vec will be empty.\npub fn diff_rsx(new: &File, old: &File) -> Option<Vec<ChangedRsx>> {\n    // Make a clone of these files in place so we don't have to worry about mutating the original\n    let mut old = old.clone();\n    let mut new = new.clone();\n\n    // Collect all the rsx! macros from the old file - modifying the files in place\n    let old_macros = collect_from_file(&mut old);\n    let new_macros = collect_from_file(&mut new);\n\n    // If the number of rsx! macros is different, then it's not hotreloadable\n    if old_macros.len() != new_macros.len() {\n        return None;\n    }\n\n    // If the files are not the same, then it's not hotreloadable\n    if old != new {\n        return None;\n    }\n\n    Some(\n        old_macros\n            .into_iter()\n            .zip(new_macros)\n            .map(|(old, new)| ChangedRsx { old, new })\n            .collect(),\n    )\n}\n\npub fn collect_from_file(file: &mut File) -> Vec<Macro> {\n    struct MacroCollector(Vec<Macro>);\n    impl VisitMut for MacroCollector {\n        /// Take out the rsx! macros, leaving a default in their place\n        fn visit_macro_mut(&mut self, dest: &mut syn::Macro) {\n            let name = &dest.path.segments.last().map(|i| i.ident.to_string());\n            if let Some(\"rsx\" | \"render\") = name.as_deref() {\n                let mut default: syn::Macro = syn::parse_quote! { rsx! {} };\n                std::mem::swap(dest, &mut default);\n                self.0.push(default)\n            }\n        }\n\n        /// Ignore doc comments by swapping them out with a default\n        fn visit_attribute_mut(&mut self, i: &mut syn::Attribute) {\n            if i.path().is_ident(\"doc\") {\n                *i = syn::parse_quote! { #[doc = \"\"] };\n            }\n        }\n    }\n\n    let mut macros = MacroCollector(vec![]);\n    macros.visit_file_mut(file);\n    macros.0\n}\n\n#[test]\nfn changing_files() {\n    let old = r#\"\nuse dioxus::prelude::*;\n\n/// some comment\npub fn CoolChild() -> Element {\n    let a = 123;\n\n    rsx! {\n        div {\n            {some_expr()}\n        }\n    }\n}\"#;\n\n    let new = r#\"\nuse dioxus::prelude::*;\n\n/// some comment\npub fn CoolChild() -> Element {\n    rsx! {\n        div {\n            {some_expr()}\n        }\n    }\n}\"#;\n\n    let same = r#\"\nuse dioxus::prelude::*;\n\n/// some comment!!!!!\npub fn CoolChild() -> Element {\n    let a = 123;\n\n    rsx! {\n        div {\n            {some_expr()}\n        }\n    }\n}\"#;\n\n    let old = syn::parse_file(old).unwrap();\n    let new = syn::parse_file(new).unwrap();\n    let same = syn::parse_file(same).unwrap();\n\n    assert!(\n        diff_rsx(&old, &new).is_none(),\n        \"Files with different expressions should not be hotreloadable\"\n    );\n\n    assert!(\n        diff_rsx(&new, &new).is_some(),\n        \"The same file should be reloadable with itself\"\n    );\n\n    assert!(\n        diff_rsx(&old, &same).is_some(),\n        \"Files with changed comments should be hotreloadable\"\n    );\n}\n"
  },
  {
    "path": "packages/rsx-hotreload/src/diff.rs",
    "content": "//! This module contains the diffing logic for rsx hot reloading.\n//!\n//! There's a few details that I wish we could've gotten right but we can revisit later:\n//!\n//! - Expanding an if chain is not possible - only its contents can be hot reloaded\n//!\n//! - Components that don't start with children can't be hot reloaded - IE going from `Comp {}` to `Comp { \"foo\" }`\n//!   is not possible. We could in theory allow this by seeding all Components with a `children` field.\n//!\n//! - Cross-templates hot reloading is not possible - multiple templates don't share the dynamic pool. This would require handling aliases\n//!   in hot reload diffing.\n//!\n//! - We've proven that binary patching is feasible but has a longer path to stabilization for all platforms.\n//!   Binary patching is pretty quick, actually, and *might* remove the need to literal hot reloading.\n//!   However, you could imagine a scenario where literal hot reloading would be useful without the\n//!   compiler in the loop. Ideally we can slash most of this code once patching is stable.\n//!\n//! ## Assigning/Scoring Templates\n//!\n//! We can clone most dynamic items from the last full rebuild:\n//! - Dynamic text segments: `div { width: \"{x}%\" } -> div { width: \"{x}%\", height: \"{x}%\" }`\n//! - Dynamic attributes: `div { width: dynamic } -> div { width: dynamic, height: dynamic }`\n//! - Dynamic nodes: `div { {children} } -> div { {children} {children} }`\n//!\n//! But we cannot clone rsx bodies themselves because we cannot hot reload the new rsx body:\n//! - `div { Component { \"{text}\" } } -> div { Component { \"{text}\" } Component { \"hello\" } }` // We can't create a template for both \"{text}\" and \"hello\"\n//!\n//! In some cases, two nodes with children are ambiguous. For example:\n//! ```rust, ignore\n//! rsx! {\n//!     div {\n//!         Component { \"{text}\" }\n//!         Component { \"hello\" }\n//!     }\n//! }\n//! ```\n//!\n//! Outside of the template, both components are compatible for hot reloading.\n//!\n//! After we create a list of all components with compatible names and props, we need to find the best match for the\n//! template.\n//!\n//!\n//! Dioxus uses a greedy algorithm to find the best match. We first try to create the child template with the dynamic context from the last full rebuild.\n//! Then we use the child template that leaves the least unused dynamic items in the pool to create the new template.\n//!\n//! For the example above:\n//! - Hot reloading `Component { \"hello\" }`:\n//!   - Try to hot reload the component body `\"hello\"` with the dynamic pool from `\"{text}\"`: Success with 1 unused dynamic item\n//!   - Try to hot reload the component body `\"hello\"` with the dynamic pool from `\"hello\"`: Success with 0 unused dynamic items\n//!   - We use the template that leaves the least unused dynamic items in the pool - `\"hello\"`\n//! - Hot reloading `Component { \"{text}\" }`:\n//!   - Try to hot reload the component body `\"{text}\"` with the dynamic pool from `\"{text}\"`: Success with 0 unused dynamic items\n//!   - The `\"hello\"` template has already been hot reloaded, so we don't try to hot reload it again\n//!   - We use the template that leaves the least unused dynamic items in the pool - `\"{text}\"`\n//!\n//! Greedy algorithms are optimal when:\n//! - The step we take reduces the problem size\n//! - The subproblem is optimal\n//!\n//! In this case, hot reloading a template removes it from the pool of templates we can use to hot reload the next template which reduces the problem size.\n//!\n//! The subproblem is optimal because the alternative is leaving less dynamic items for the remaining templates to hot reload which just makes it\n//! more difficult to match future templates.\n\nuse dioxus_core::internal::{\n    FmtedSegments, HotReloadAttributeValue, HotReloadDynamicAttribute, HotReloadDynamicNode,\n    HotReloadLiteral, HotReloadedTemplate, NamedAttribute,\n};\nuse dioxus_core_types::HotReloadingContext;\nuse dioxus_rsx::*;\nuse std::collections::HashMap;\n\nuse crate::extensions::{html_tag_and_namespace, intern, to_template_node};\n\nuse super::last_build_state::LastBuildState;\n\n/// A result of hot reloading\n///\n/// This contains information about what has changed so the hotreloader can apply the right changes\n#[non_exhaustive]\n#[derive(Debug, PartialEq, Clone)]\npub struct HotReloadResult {\n    /// The child templates we have already used. As we walk through the template tree, we will run into child templates.\n    /// Each of those child templates also need to be hot reloaded. We keep track of which ones we've already hotreloaded\n    /// to avoid diffing the same template twice against different new templates.\n    ///\n    /// ```rust, ignore\n    /// rsx! {\n    ///     Component { class: \"{class}\", \"{text}\" } // The children of a Component is a new template\n    ///     for item in items {\n    ///         \"{item}\" // The children of a for loop is a new template\n    ///     }\n    ///     if true {\n    ///         \"{text}\" // The children of an if chain is a new template\n    ///     }\n    /// }\n    /// ```\n    ///\n    /// If we hotreload the component, we don't need to hotreload the for loop\n    ///\n    /// You should diff the result of this against the old template to see if you actually need to send down the result\n    pub templates: HashMap<usize, HotReloadedTemplate>,\n\n    /// The state of the last full rebuild.\n    full_rebuild_state: LastBuildState,\n\n    /// The dynamic nodes for the current node\n    dynamic_nodes: Vec<HotReloadDynamicNode>,\n\n    /// The dynamic attributes for the current node\n    dynamic_attributes: Vec<HotReloadDynamicAttribute>,\n\n    /// The literal component properties for the current node\n    literal_component_properties: Vec<HotReloadLiteral>,\n}\n\nimpl HotReloadResult {\n    /// Calculate the hot reload diff between two template bodies\n    pub fn new<Ctx: HotReloadingContext>(\n        full_rebuild_state: &TemplateBody,\n        new: &TemplateBody,\n        name: String,\n    ) -> Option<Self> {\n        // Normalize both the full rebuild state and the new state for rendering\n        let full_rebuild_state = full_rebuild_state.normalized();\n        let new = new.normalized();\n        let full_rebuild_state = LastBuildState::new(&full_rebuild_state, name);\n        let mut s = Self {\n            full_rebuild_state,\n            templates: Default::default(),\n            dynamic_nodes: Default::default(),\n            dynamic_attributes: Default::default(),\n            literal_component_properties: Default::default(),\n        };\n\n        s.hotreload_body::<Ctx>(&new)?;\n\n        Some(s)\n    }\n\n    fn extend(&mut self, other: Self) {\n        self.templates.extend(other.templates);\n    }\n\n    /// Walk the dynamic contexts and do our best to find hot reload-able changes between the two\n    /// sets of dynamic nodes/attributes. If there's a change we can't hot reload, we'll return None\n    ///\n    /// Otherwise, we pump out the list of templates that need to be updated. The templates will be\n    /// re-ordered such that the node paths will be adjusted to match the new template for every\n    /// existing dynamic node.\n    ///\n    /// ```ignore\n    /// old:\n    ///     [[0], [1], [2]]\n    ///     rsx! {\n    ///         \"{one}\"\n    ///         \"{two}\"\n    ///         \"{three}\"\n    ///     }\n    ///\n    /// new:\n    ///     [[0], [2], [1, 1]]\n    ///     rsx! {\n    ///        \"{one}\"\n    ///         div { \"{three}\" }\n    ///         \"{two}\"\n    ///    }\n    /// ```\n    ///\n    /// Generally we can't hot reload a node if:\n    /// - We add or modify a new rust expression\n    ///   - Adding a new formatted segment we haven't seen before\n    ///   - Adding a new dynamic node (loop, fragment, if chain, etc)\n    /// - We add a new component field\n    /// - We remove a component field\n    /// - We change the type of a component field\n    ///\n    /// If a dynamic node is removed, we don't necessarily need to kill hot reload - just unmounting it should be enough\n    /// If the dynamic node is re-added, we want to be able to find it again.\n    ///\n    /// This encourages the hot reloader to hot onto DynamicContexts directly instead of the CallBody since\n    /// you can preserve more information about the nodes as they've changed over time.\n    fn hotreload_body<Ctx: HotReloadingContext>(&mut self, new: &TemplateBody) -> Option<()> {\n        // Quickly run through dynamic attributes first attempting to invalidate them\n        // Move over old IDs onto the new template\n        self.hotreload_attributes::<Ctx>(new)?;\n        let new_dynamic_attributes = std::mem::take(&mut self.dynamic_attributes);\n\n        // Now we can run through the dynamic nodes and see if we can hot reload them\n        // Move over old IDs onto the new template\n        self.hotreload_dynamic_nodes::<Ctx>(new)?;\n        let new_dynamic_nodes = std::mem::take(&mut self.dynamic_nodes);\n        let literal_component_properties = std::mem::take(&mut self.literal_component_properties);\n\n        let key = self.hot_reload_key(new)?;\n\n        let roots: Vec<_> = new\n            .roots\n            .iter()\n            .map(|node| to_template_node::<Ctx>(node))\n            .collect();\n        let roots: &[dioxus_core::TemplateNode] = intern(&*roots);\n\n        let template = HotReloadedTemplate::new(\n            key,\n            new_dynamic_nodes,\n            new_dynamic_attributes,\n            literal_component_properties,\n            roots,\n        );\n\n        self.templates\n            .insert(self.full_rebuild_state.root_index.get(), template);\n\n        Some(())\n    }\n\n    fn hot_reload_key(&mut self, new: &TemplateBody) -> Option<Option<FmtedSegments>> {\n        match new.implicit_key() {\n            Some(AttributeValue::AttrLiteral(HotLiteral::Fmted(value))) => Some(Some(\n                self.full_rebuild_state\n                    .hot_reload_formatted_segments(value)?,\n            )),\n            None => Some(None),\n            _ => None,\n        }\n    }\n\n    fn hotreload_dynamic_nodes<Ctx: HotReloadingContext>(\n        &mut self,\n        new: &TemplateBody,\n    ) -> Option<()> {\n        for new_node in new.dynamic_nodes() {\n            self.hot_reload_node::<Ctx>(new_node)?\n        }\n\n        Some(())\n    }\n\n    fn hot_reload_node<Ctx: HotReloadingContext>(&mut self, node: &BodyNode) -> Option<()> {\n        match node {\n            BodyNode::Text(text) => self.hotreload_text_node(text),\n            BodyNode::Component(component) => self.hotreload_component::<Ctx>(component),\n            BodyNode::ForLoop(forloop) => self.hotreload_for_loop::<Ctx>(forloop),\n            BodyNode::IfChain(ifchain) => self.hotreload_if_chain::<Ctx>(ifchain),\n            BodyNode::RawExpr(expr) => self.hotreload_raw_expr(expr),\n            BodyNode::Element(_) => Some(()),\n        }\n    }\n\n    fn hotreload_raw_expr(&mut self, expr: &ExprNode) -> Option<()> {\n        // Try to find the raw expr in the last build\n        let expr_index = self\n            .full_rebuild_state\n            .dynamic_nodes\n            .position(|node| match &node {\n                BodyNode::RawExpr(raw_expr) => raw_expr.expr == expr.expr,\n                _ => false,\n            })?;\n\n        // If we find it, push it as a dynamic node\n        self.dynamic_nodes\n            .push(HotReloadDynamicNode::Dynamic(expr_index));\n\n        Some(())\n    }\n\n    fn hotreload_for_loop<Ctx>(&mut self, forloop: &ForLoop) -> Option<()>\n    where\n        Ctx: HotReloadingContext,\n    {\n        // Find all for loops that have the same pattern and expression\n        let candidate_for_loops = self\n            .full_rebuild_state\n            .dynamic_nodes\n            .inner\n            .iter()\n            .enumerate()\n            .filter_map(|(index, node)| {\n                if let BodyNode::ForLoop(for_loop) = &node.inner {\n                    if for_loop.pat == forloop.pat && for_loop.expr == forloop.expr {\n                        return Some((index, for_loop));\n                    }\n                }\n                None\n            })\n            .collect::<Vec<_>>();\n\n        // Then find the one that has the least wasted dynamic items when hot reloading the body\n        let (index, best_call_body) = self.diff_best_call_body::<Ctx>(\n            candidate_for_loops\n                .iter()\n                .map(|(_, for_loop)| &for_loop.body),\n            &forloop.body,\n        )?;\n\n        // Push the new for loop as a dynamic node\n        self.dynamic_nodes\n            .push(HotReloadDynamicNode::Dynamic(candidate_for_loops[index].0));\n\n        self.extend(best_call_body);\n\n        Some(())\n    }\n\n    fn hotreload_text_node(&mut self, text_node: &TextNode) -> Option<()> {\n        // If it is static, it is already included in the template and we don't need to do anything\n        if text_node.input.is_static() {\n            return Some(());\n        }\n        // Otherwise, hot reload the formatted segments and push that as a dynamic node\n        let formatted_segments = self\n            .full_rebuild_state\n            .hot_reload_formatted_segments(&text_node.input)?;\n        self.dynamic_nodes\n            .push(HotReloadDynamicNode::Formatted(formatted_segments));\n        Some(())\n    }\n\n    /// Find the call body that minimizes the number of wasted dynamic items\n    ///\n    /// Returns the index of the best call body and the state of the best call body\n    fn diff_best_call_body<'a, Ctx>(\n        &self,\n        bodies: impl Iterator<Item = &'a TemplateBody>,\n        new_call_body: &TemplateBody,\n    ) -> Option<(usize, Self)>\n    where\n        Ctx: HotReloadingContext,\n    {\n        let mut best_score = usize::MAX;\n        let mut best_output = None;\n        for (index, body) in bodies.enumerate() {\n            // Skip templates we've already hotreloaded\n            if self.templates.contains_key(&body.template_idx.get()) {\n                continue;\n            }\n            if let Some(state) =\n                Self::new::<Ctx>(body, new_call_body, self.full_rebuild_state.name.clone())\n            {\n                let score = state.full_rebuild_state.unused_dynamic_items();\n                if score < best_score {\n                    best_score = score;\n                    best_output = Some((index, state));\n                }\n            }\n        }\n\n        best_output\n    }\n\n    fn hotreload_component<Ctx>(&mut self, component: &Component) -> Option<()>\n    where\n        Ctx: HotReloadingContext,\n    {\n        // First we need to find the component that matches the best in the last build\n        // We try each build and choose the option that wastes the least dynamic items\n        let components_with_matching_attributes: Vec<_> = self\n            .full_rebuild_state\n            .dynamic_nodes\n            .inner\n            .iter()\n            .enumerate()\n            .filter_map(|(index, node)| {\n                if let BodyNode::Component(comp) = &node.inner {\n                    return Some((\n                        index,\n                        comp,\n                        self.hotreload_component_fields(comp, component)?,\n                    ));\n                }\n                None\n            })\n            .collect();\n\n        let possible_bodies = components_with_matching_attributes\n            .iter()\n            .map(|(_, comp, _)| &comp.children);\n\n        let (index, new_body) =\n            self.diff_best_call_body::<Ctx>(possible_bodies, &component.children)?;\n\n        let (index, _, literal_component_properties) = &components_with_matching_attributes[index];\n        let index = *index;\n\n        self.full_rebuild_state.dynamic_nodes.inner[index]\n            .used\n            .set(true);\n\n        self.literal_component_properties\n            .extend(literal_component_properties.iter().cloned());\n\n        self.extend(new_body);\n\n        // Push the new component as a dynamic node\n        self.dynamic_nodes\n            .push(HotReloadDynamicNode::Dynamic(index));\n\n        Some(())\n    }\n\n    fn hotreload_component_fields(\n        &self,\n        old_component: &Component,\n        new_component: &Component,\n    ) -> Option<Vec<HotReloadLiteral>> {\n        // First check if the component is the same\n        if new_component.name != old_component.name {\n            return None;\n        }\n\n        // Then check if the fields are the same\n        let new_non_key_fields: Vec<_> = new_component.component_props().collect();\n        let old_non_key_fields: Vec<_> = old_component.component_props().collect();\n        if new_non_key_fields.len() != old_non_key_fields.len() {\n            return None;\n        }\n\n        let mut new_fields = new_non_key_fields.clone();\n        new_fields.sort_by_key(|attribute| attribute.name.to_string());\n        let mut old_fields = old_non_key_fields.iter().enumerate().collect::<Vec<_>>();\n        old_fields.sort_by_key(|(_, attribute)| attribute.name.to_string());\n\n        // The literal component properties for the component in same the order as the original component property literals\n        let mut literal_component_properties = vec![None; old_fields.len()];\n\n        for (new_field, (index, old_field)) in new_fields.iter().zip(old_fields.iter()) {\n            // Verify the names match\n            if new_field.name != old_field.name {\n                return None;\n            }\n\n            // Verify the values match\n            match (&new_field.value, &old_field.value) {\n                // If the values are both literals, we can try to hotreload them\n                (\n                    AttributeValue::AttrLiteral(new_value),\n                    AttributeValue::AttrLiteral(old_value),\n                ) => {\n                    // Make sure that the types are the same\n                    if std::mem::discriminant(new_value) != std::mem::discriminant(old_value) {\n                        return None;\n                    }\n                    let literal = self.full_rebuild_state.hotreload_hot_literal(new_value)?;\n                    literal_component_properties[*index] = Some(literal);\n                }\n                _ => {\n                    if new_field.value != old_field.value {\n                        return None;\n                    }\n                }\n            }\n        }\n\n        Some(literal_component_properties.into_iter().flatten().collect())\n    }\n\n    /// Hot reload an if chain\n    fn hotreload_if_chain<Ctx: HotReloadingContext>(\n        &mut self,\n        new_if_chain: &IfChain,\n    ) -> Option<()> {\n        let mut best_if_chain = None;\n        let mut best_score = usize::MAX;\n\n        let if_chains = self\n            .full_rebuild_state\n            .dynamic_nodes\n            .inner\n            .iter()\n            .enumerate()\n            .filter_map(|(index, node)| {\n                if let BodyNode::IfChain(if_chain) = &node.inner {\n                    return Some((index, if_chain));\n                }\n                None\n            });\n\n        // Find the if chain that matches all of the conditions and wastes the least dynamic items\n        for (index, old_if_chain) in if_chains {\n            let Some(chain_templates) = Self::diff_if_chains::<Ctx>(\n                old_if_chain,\n                new_if_chain,\n                self.full_rebuild_state.name.clone(),\n            ) else {\n                continue;\n            };\n            let score = chain_templates\n                .iter()\n                .map(|t| t.full_rebuild_state.unused_dynamic_items())\n                .sum();\n            if score < best_score {\n                best_score = score;\n                best_if_chain = Some((index, chain_templates));\n            }\n        }\n\n        // If we found a hot reloadable if chain, hotreload it\n        let (index, chain_templates) = best_if_chain?;\n        // Mark the if chain as used\n        self.full_rebuild_state.dynamic_nodes.inner[index]\n            .used\n            .set(true);\n        // Merge the hot reload changes into the current state\n        for template in chain_templates {\n            self.extend(template);\n        }\n\n        // Push the new if chain as a dynamic node\n        self.dynamic_nodes\n            .push(HotReloadDynamicNode::Dynamic(index));\n\n        Some(())\n    }\n\n    /// Hot reload an if chain\n    fn diff_if_chains<Ctx: HotReloadingContext>(\n        old_if_chain: &IfChain,\n        new_if_chain: &IfChain,\n        name: String,\n    ) -> Option<Vec<Self>> {\n        // Go through each part of the if chain and find the best match\n        let mut old_chain = old_if_chain;\n        let mut new_chain = new_if_chain;\n\n        let mut chain_templates = Vec::new();\n\n        loop {\n            // Make sure the conditions are the same\n            if old_chain.cond != new_chain.cond {\n                return None;\n            }\n\n            // If the branches are the same, we can hotreload them\n            let hot_reload =\n                Self::new::<Ctx>(&old_chain.then_branch, &new_chain.then_branch, name.clone())?;\n            chain_templates.push(hot_reload);\n\n            // Make sure the if else branches match\n            match (\n                old_chain.else_if_branch.as_ref(),\n                new_chain.else_if_branch.as_ref(),\n            ) {\n                (Some(old), Some(new)) => {\n                    old_chain = old;\n                    new_chain = new;\n                }\n                (None, None) => {\n                    break;\n                }\n                _ => return None,\n            }\n        }\n        // Make sure the else branches match\n        match (&old_chain.else_branch, &new_chain.else_branch) {\n            (Some(old), Some(new)) => {\n                let template = Self::new::<Ctx>(old, new, name.clone())?;\n                chain_templates.push(template);\n            }\n            (None, None) => {}\n            _ => return None,\n        }\n\n        Some(chain_templates)\n    }\n\n    /// Take a new template body and return the attributes that can be hot reloaded from the last build\n    ///\n    /// IE if we shuffle attributes, remove attributes or add new attributes with the same dynamic segments, around we should be able to hot reload them.\n    ///\n    /// ```rust, ignore\n    /// rsx! {\n    ///     div { id: \"{id}\", class: \"{class}\", width, \"Hi\" }\n    /// }\n    ///\n    /// rsx! {\n    ///     div { width, class: \"{class}\", id: \"{id} and {class}\", \"Hi\" }\n    /// }\n    /// ```\n    fn hotreload_attributes<Ctx: HotReloadingContext>(&mut self, new: &TemplateBody) -> Option<()> {\n        // Walk through each attribute and create a new HotReloadAttribute for each one\n        for new_attr in new.dynamic_attributes() {\n            // While we're here, if it's a literal and not a perfect score, it's a mismatch and we need to\n            // hotreload the literal\n            self.hotreload_attribute::<Ctx>(new_attr)?;\n        }\n\n        Some(())\n    }\n\n    /// Try to hot reload an attribute and return the new HotReloadAttribute\n    fn hotreload_attribute<Ctx: HotReloadingContext>(\n        &mut self,\n        attribute: &Attribute,\n    ) -> Option<()> {\n        let (tag, namespace) = html_tag_and_namespace::<Ctx>(attribute);\n\n        // If the attribute is a spread, try to grab it from the last build\n        // If it wasn't in the last build with the same name, we can't hot reload it\n        if let AttributeName::Spread(_) = &attribute.name {\n            let hot_reload_attribute = self\n                .full_rebuild_state\n                .dynamic_attributes\n                .position(|a| a.name == attribute.name && a.value == attribute.value)?;\n            self.dynamic_attributes\n                .push(HotReloadDynamicAttribute::Dynamic(hot_reload_attribute));\n\n            return Some(());\n        }\n\n        // Otherwise the attribute is named, try to hot reload the value\n        let value = match &attribute.value {\n            // If the attribute is a literal, we can generally hot reload it if the formatted segments exist in the last build\n            AttributeValue::AttrLiteral(literal) => {\n                // If it is static, it is already included in the template and we don't need to do anything\n                if literal.is_static() {\n                    return Some(());\n                }\n                // Otherwise, hot reload the literal and push that as a dynamic attribute\n                let hot_reload_literal = self.full_rebuild_state.hotreload_hot_literal(literal)?;\n                HotReloadAttributeValue::Literal(hot_reload_literal)\n            }\n            // If it isn't a literal, try to find an exact match for the attribute value from the last build\n            _ => {\n                let value_index = self.full_rebuild_state.dynamic_attributes.position(|a| {\n                    // Spread attributes are not hot reloaded\n                    if matches!(a.name, AttributeName::Spread(_)) {\n                        return false;\n                    }\n                    if a.value != attribute.value {\n                        return false;\n                    }\n                    // The type of event handlers is influenced by the event name, so te cannot hot reload between different event\n                    // names\n                    if matches!(a.value, AttributeValue::EventTokens(_)) && a.name != attribute.name\n                    {\n                        return false;\n                    }\n                    true\n                })?;\n                HotReloadAttributeValue::Dynamic(value_index)\n            }\n        };\n\n        self.dynamic_attributes\n            .push(HotReloadDynamicAttribute::Named(NamedAttribute::new(\n                tag, namespace, value,\n            )));\n\n        Some(())\n    }\n}\n"
  },
  {
    "path": "packages/rsx-hotreload/src/extensions.rs",
    "content": "use dioxus_core::TemplateNode;\nuse dioxus_core_types::HotReloadingContext;\nuse dioxus_rsx::*;\nuse internment::Intern;\nuse std::hash::Hash;\n\n// interns a object into a static object, reusing the value if it already exists\npub(crate) fn intern<T: Eq + Hash + Send + Sync + ?Sized + 'static>(\n    s: impl Into<Intern<T>>,\n) -> &'static T {\n    s.into().as_ref()\n}\n\npub(crate) fn html_tag_and_namespace<Ctx: HotReloadingContext>(\n    attr: &Attribute,\n) -> (&'static str, Option<&'static str>) {\n    let attribute_name_rust = attr.name.to_string();\n    let element_name = attr.el_name.as_ref().unwrap();\n    let rust_name = match element_name {\n        ElementName::Ident(i) => i.to_string(),\n        // If this is a web component, just use the name of the elements instead of mapping the attribute\n        // through the hot reloading context\n        ElementName::Custom(_) => return (intern(attribute_name_rust.as_str()), None),\n    };\n\n    Ctx::map_attribute(&rust_name, &attribute_name_rust)\n        .unwrap_or((intern(attribute_name_rust.as_str()), None))\n}\n\npub fn to_template_attribute<Ctx: HotReloadingContext>(\n    attr: &Attribute,\n) -> dioxus_core::TemplateAttribute {\n    use dioxus_core::TemplateAttribute;\n\n    // If it's a dynamic node, just return it\n    // For dynamic attributes, we need to check the mapping to see if that mapping exists\n    // todo: one day we could generate new dynamic attributes on the fly if they're a literal,\n    // or something sufficiently serializable\n    //  (ie `checked`` being a bool and bools being interpretable)\n    //\n    // For now, just give up if that attribute doesn't exist in the mapping\n    if !attr.is_static_str_literal() {\n        let id = attr.dyn_idx.get();\n        return TemplateAttribute::Dynamic { id };\n    }\n\n    // Otherwise it's a static node and we can build it\n    let (_, value) = attr.as_static_str_literal().unwrap();\n    let (name, namespace) = html_tag_and_namespace::<Ctx>(attr);\n\n    TemplateAttribute::Static {\n        name,\n        namespace,\n        value: intern(value.to_static().unwrap().as_str()),\n    }\n}\n\n/// Convert this BodyNode into a TemplateNode.\n///\n/// dioxus-core uses this to understand templates at compiletime\npub fn to_template_node<Ctx: HotReloadingContext>(node: &BodyNode) -> dioxus_core::TemplateNode {\n    use dioxus_core::TemplateNode;\n    match node {\n        BodyNode::Element(el) => {\n            let rust_name = el.name.to_string();\n\n            let (tag, namespace) =\n                Ctx::map_element(&rust_name).unwrap_or((intern(rust_name.as_str()), None));\n\n            TemplateNode::Element {\n                tag,\n                namespace,\n                children: intern(\n                    el.children\n                        .iter()\n                        .map(|c| to_template_node::<Ctx>(c))\n                        .collect::<Vec<_>>(),\n                ),\n                attrs: intern(\n                    el.merged_attributes\n                        .iter()\n                        .map(|attr| to_template_attribute::<Ctx>(attr))\n                        .collect::<Vec<_>>(),\n                ),\n            }\n        }\n        BodyNode::Text(text) => text_to_template_node(text),\n        BodyNode::RawExpr(exp) => TemplateNode::Dynamic {\n            id: exp.dyn_idx.get(),\n        },\n        BodyNode::Component(comp) => TemplateNode::Dynamic {\n            id: comp.dyn_idx.get(),\n        },\n        BodyNode::ForLoop(floop) => TemplateNode::Dynamic {\n            id: floop.dyn_idx.get(),\n        },\n        BodyNode::IfChain(chain) => TemplateNode::Dynamic {\n            id: chain.dyn_idx.get(),\n        },\n    }\n}\npub fn text_to_template_node(node: &TextNode) -> TemplateNode {\n    match node.input.to_static() {\n        Some(text) => TemplateNode::Text {\n            text: intern(text.as_str()),\n        },\n        None => TemplateNode::Dynamic {\n            id: node.dyn_idx.get(),\n        },\n    }\n}\n"
  },
  {
    "path": "packages/rsx-hotreload/src/last_build_state.rs",
    "content": "use dioxus_core::internal::{FmtSegment, FmtedSegments, HotReloadLiteral};\nuse dioxus_rsx::*;\nuse std::cell::Cell;\n\n/// A pool of items we can grab from during hot reloading.\n/// We have three different pools we can pull from:\n/// - Dynamic text segments (eg: \"{class}\")\n/// - Dynamic nodes (eg: {children})\n/// - Dynamic attributes (eg: ..spread )\n///\n/// As we try to create a new hot reloaded template, we will pull from these pools to create the new template. We mark\n/// each item as used the first time we use it in the new template. Once the new template if fully created, we can tally\n/// up how many items are unused to determine how well the new template matches the old template.\n///\n/// The template that matches best will leave the least unused items in the pool.\n#[derive(Debug, PartialEq, Clone)]\npub(crate) struct BakedPool<T> {\n    pub inner: Vec<BakedItem<T>>,\n}\n\nimpl<T> BakedPool<T> {\n    /// Create a new baked pool from an iterator of items\n    fn new(inner: impl IntoIterator<Item = T>) -> Self {\n        Self {\n            inner: inner.into_iter().map(BakedItem::new).collect(),\n        }\n    }\n\n    /// Find the first item in the pool that matches the condition and mark it as used\n    pub fn position(&self, condition: impl Fn(&T) -> bool) -> Option<usize> {\n        for (idx, baked_item) in self.inner.iter().enumerate() {\n            if condition(&baked_item.inner) {\n                baked_item.used.set(true);\n                return Some(idx);\n            }\n        }\n        None\n    }\n\n    /// Find the number of unused items in the pool\n    fn unused_dynamic_items(&self) -> usize {\n        self.inner\n            .iter()\n            .filter(|baked_item| !baked_item.used.get())\n            .count()\n    }\n}\n\n/// A single item in the baked item pool. We keep track if which items are used for scoring how well two templates match.\n#[derive(Debug, PartialEq, Clone)]\npub(crate) struct BakedItem<T> {\n    pub inner: T,\n    pub used: Cell<bool>,\n}\n\nimpl<T> BakedItem<T> {\n    fn new(inner: T) -> Self {\n        Self {\n            inner,\n            used: Cell::new(false),\n        }\n    }\n}\n\n/// The state of the last full rebuild.\n/// This object contains the pool of compiled dynamic segments we can pull from for hot reloading\n#[derive(Debug, PartialEq, Clone)]\npub(crate) struct LastBuildState {\n    /// The formatted segments that were used in the last build. Eg: \"{class}\", \"{id}\"\n    ///\n    /// We are free to use each of these segments many times in the same build.\n    /// We just clone the result (assuming display + debug have no side effects)\n    pub dynamic_text_segments: BakedPool<FormattedSegment>,\n    /// The dynamic nodes that were used in the last build. Eg: div { {children} }\n    ///\n    /// We are also free to clone these nodes many times in the same build.\n    pub dynamic_nodes: BakedPool<BodyNode>,\n    /// The attributes that were used in the last build. Eg: div { class: \"{class}\" }\n    ///\n    /// We are also free to clone these nodes many times in the same build.\n    pub dynamic_attributes: BakedPool<Attribute>,\n    /// The component literal properties we can hot reload from the last build. Eg: Component { class: \"{class}\" }\n    ///\n    /// In the new build, we must assign each of these a value even if we no longer use the component.\n    /// The type must be the same as the last time we compiled the property\n    pub component_properties: Vec<HotLiteral>,\n    /// The root indexes of the last build\n    pub root_index: DynIdx,\n    /// The name of the original template\n    pub name: String,\n}\n\nimpl LastBuildState {\n    /// Create a new LastBuildState from the given [`TemplateBody`]\n    pub fn new(body: &TemplateBody, name: String) -> Self {\n        let dynamic_text_segments = body.dynamic_text_segments.iter().cloned();\n        let dynamic_nodes = body.dynamic_nodes().cloned();\n        let dynamic_attributes = body.dynamic_attributes().cloned();\n        let component_properties = body.literal_component_properties().cloned().collect();\n        Self {\n            dynamic_text_segments: BakedPool::new(dynamic_text_segments),\n            dynamic_nodes: BakedPool::new(dynamic_nodes),\n            dynamic_attributes: BakedPool::new(dynamic_attributes),\n            component_properties,\n            root_index: body.template_idx.clone(),\n            name,\n        }\n    }\n\n    /// Return the number of unused dynamic items in the pool\n    pub fn unused_dynamic_items(&self) -> usize {\n        self.dynamic_text_segments.unused_dynamic_items()\n            + self.dynamic_nodes.unused_dynamic_items()\n            + self.dynamic_attributes.unused_dynamic_items()\n    }\n\n    /// Hot reload a hot literal\n    pub fn hotreload_hot_literal(&self, hot_literal: &HotLiteral) -> Option<HotReloadLiteral> {\n        match hot_literal {\n            // If the literal is a formatted segment, map the segments to the new formatted segments\n            HotLiteral::Fmted(segments) => {\n                let new_segments = self.hot_reload_formatted_segments(segments)?;\n                Some(HotReloadLiteral::Fmted(new_segments))\n            }\n            // Otherwise just pass the literal through unchanged\n            HotLiteral::Bool(b) => Some(HotReloadLiteral::Bool(b.value())),\n            HotLiteral::Float(f) => Some(HotReloadLiteral::Float(f.base10_parse().ok()?)),\n            HotLiteral::Int(i) => Some(HotReloadLiteral::Int(i.base10_parse().ok()?)),\n        }\n    }\n\n    pub fn hot_reload_formatted_segments(\n        &self,\n        new: &HotReloadFormattedSegment,\n    ) -> Option<FmtedSegments> {\n        // Go through each dynamic segment and look for a match in the formatted segments pool.\n        // If we find a match, we can hot reload the segment otherwise we need to do a full rebuild\n        let mut segments = Vec::new();\n        for segment in &new.segments {\n            match segment {\n                // If it is a literal, we can always hot reload it. Just add it to the segments\n                Segment::Literal(value) => {\n                    segments.push(FmtSegment::Literal {\n                        value: Box::leak(value.clone().into_boxed_str()),\n                    });\n                } // If it is a dynamic segment, we need to check if it exists in the formatted segments pool\n                Segment::Formatted(formatted) => {\n                    let index = self.dynamic_text_segments.position(|s| s == formatted)?;\n\n                    segments.push(FmtSegment::Dynamic { id: index });\n                }\n            }\n        }\n\n        Some(FmtedSegments::new(segments))\n    }\n}\n"
  },
  {
    "path": "packages/rsx-hotreload/src/lib.rs",
    "content": "mod collect;\npub use collect::*;\n\nmod diff;\npub use diff::*;\n\nmod last_build_state;\n\nmod extensions;\n"
  },
  {
    "path": "packages/rsx-hotreload/tests/hotreload_pattern.rs",
    "content": "#![allow(unused)]\n\nuse std::collections::HashMap;\n\nuse dioxus_core::{\n    internal::{\n        FmtSegment, FmtedSegments, HotReloadAttributeValue, HotReloadDynamicAttribute,\n        HotReloadDynamicNode, HotReloadLiteral, HotReloadedTemplate, NamedAttribute,\n    },\n    Template, TemplateAttribute, TemplateNode, VNode,\n};\nuse dioxus_core_types::HotReloadingContext;\nuse dioxus_rsx::CallBody;\nuse dioxus_rsx_hotreload::{self, diff_rsx, ChangedRsx, HotReloadResult};\nuse proc_macro2::TokenStream;\nuse quote::{quote, ToTokens};\nuse syn::{parse::Parse, spanned::Spanned, token::Token, File};\n\n#[derive(Debug)]\nstruct Mock;\n\nimpl HotReloadingContext for Mock {\n    fn map_attribute(\n        element_name_rust: &str,\n        attribute_name_rust: &str,\n    ) -> Option<(&'static str, Option<&'static str>)> {\n        match element_name_rust {\n            \"svg\" => match attribute_name_rust {\n                \"width\" => Some((\"width\", Some(\"style\"))),\n                \"height\" => Some((\"height\", Some(\"style\"))),\n                _ => None,\n            },\n            _ => None,\n        }\n    }\n\n    fn map_element(element_name_rust: &str) -> Option<(&'static str, Option<&'static str>)> {\n        match element_name_rust {\n            \"svg\" => Some((\"svg\", Some(\"svg\"))),\n            _ => None,\n        }\n    }\n}\n\nfn hot_reload_from_tokens(\n    old: TokenStream,\n    new: TokenStream,\n) -> Option<HashMap<usize, HotReloadedTemplate>> {\n    let old: CallBody = syn::parse2(old).unwrap();\n    let new: CallBody = syn::parse2(new).unwrap();\n\n    hotreload_callbody::<Mock>(&old, &new)\n}\n\nfn can_hotreload(old: TokenStream, new: TokenStream) -> bool {\n    hot_reload_from_tokens(old, new).is_some()\n}\n\nfn hotreload_callbody<Ctx: HotReloadingContext>(\n    old: &CallBody,\n    new: &CallBody,\n) -> Option<HashMap<usize, HotReloadedTemplate>> {\n    let results = HotReloadResult::new::<Ctx>(&old.body, &new.body, Default::default())?;\n    Some(results.templates)\n}\n\nfn callbody_to_template<Ctx: HotReloadingContext>(\n    old: &CallBody,\n    location: &'static str,\n) -> Option<HotReloadedTemplate> {\n    let mut results = HotReloadResult::new::<Ctx>(&old.body, &old.body, Default::default())?;\n    Some(results.templates.remove(&0).unwrap())\n}\n\nfn base_stream() -> TokenStream {\n    quote! {\n        div {\n            for item in vec![1, 2, 3] {\n                div { \"asasddasdasd\" }\n            }\n            for item in vec![4, 5, 6] {\n                div { \"asasddasdasd\" }\n            }\n        }\n    }\n}\n\nfn base() -> CallBody {\n    syn::parse2(base_stream()).unwrap()\n}\n\n#[test]\nfn simple_for_loop() {\n    let old = quote! {\n        div {\n            for item in vec![1, 2, 3] {\n                div { \"asasddasdasd\" }\n            }\n        }\n    };\n\n    let new_valid = quote! {\n        div {\n            for item in vec![1, 2, 3] {\n                div { \"asasddasdasd\" }\n                div { \"123\" }\n            }\n        }\n    };\n\n    let new_invalid = quote! {\n        div {\n            for item in vec![1, 2, 3, 4] {\n                div { \"asasddasdasd\" }\n                div { \"123\" }\n            }\n        }\n    };\n\n    let location = \"file:line:col:0\";\n    let old: CallBody = syn::parse2(old).unwrap();\n    let new_valid: CallBody = syn::parse2(new_valid).unwrap();\n    let new_invalid: CallBody = syn::parse2(new_invalid).unwrap();\n\n    assert!(hotreload_callbody::<Mock>(&old, &new_valid).is_some());\n    assert!(hotreload_callbody::<Mock>(&old, &new_invalid).is_none());\n}\n\n#[test]\nfn valid_reorder() {\n    let old = base();\n    let new_valid = quote! {\n        div {\n            for item in vec![4, 5, 6] {\n                span { \"asasddasdasd\" }\n                span { \"123\" }\n            }\n            for item in vec![1, 2, 3] {\n                div { \"asasddasdasd\" }\n                div { \"123\" }\n            }\n        }\n    };\n\n    let new: CallBody = syn::parse2(new_valid).unwrap();\n\n    let valid = hotreload_callbody::<Mock>(&old, &new);\n    assert!(valid.is_some());\n    let templates = valid.unwrap();\n\n    // Currently we return all the templates, even if they didn't change\n    assert_eq!(templates.len(), 3);\n\n    let template = &templates[&0];\n\n    // It's an inversion, so we should get them in reverse\n    assert_eq!(\n        template.roots,\n        &[TemplateNode::Element {\n            tag: \"div\",\n            namespace: None,\n            attrs: &[],\n            children: &[\n                TemplateNode::Dynamic { id: 0 },\n                TemplateNode::Dynamic { id: 1 }\n            ]\n        }]\n    );\n    assert_eq!(\n        template.dynamic_nodes,\n        &[\n            HotReloadDynamicNode::Dynamic(1),\n            HotReloadDynamicNode::Dynamic(0)\n        ]\n    );\n}\n\n#[test]\nfn valid_new_node() {\n    // Adding a new dynamic node should be hot reloadable as long as the text was present in the old version\n    // of the rsx block\n    let old = quote! {\n        div {\n            for item in vec![1, 2, 3] {\n                div { \"item is {item}\" }\n            }\n        }\n    };\n    let new = quote! {\n        div {\n            for item in vec![1, 2, 3] {\n                div { \"item is {item}\" }\n                div { \"item is also {item}\" }\n            }\n        }\n    };\n\n    let templates = hot_reload_from_tokens(old, new).unwrap();\n\n    // Currently we return all the templates, even if they didn't change\n    assert_eq!(templates.len(), 2);\n\n    let template = &templates[&1];\n\n    // The new dynamic node should be created from the formatted segments pool\n    assert_eq!(\n        template.dynamic_nodes,\n        &[\n            HotReloadDynamicNode::Formatted(FmtedSegments::new(vec![\n                FmtSegment::Literal { value: \"item is \" },\n                FmtSegment::Dynamic { id: 0 }\n            ],)),\n            HotReloadDynamicNode::Formatted(FmtedSegments::new(vec![\n                FmtSegment::Literal {\n                    value: \"item is also \"\n                },\n                FmtSegment::Dynamic { id: 0 }\n            ],)),\n        ]\n    );\n}\n\n#[test]\nfn valid_new_dynamic_attribute() {\n    // Adding a new dynamic attribute should be hot reloadable as long as the text was present in the old version\n    // of the rsx block\n    let old = quote! {\n        div {\n            for item in vec![1, 2, 3] {\n                div {\n                    class: \"item is {item}\"\n                }\n            }\n        }\n    };\n    let new = quote! {\n        div {\n            for item in vec![1, 2, 3] {\n                div {\n                    class: \"item is {item}\"\n                }\n                div {\n                    class: \"item is also {item}\"\n                }\n            }\n        }\n    };\n\n    let templates = hot_reload_from_tokens(old, new).unwrap();\n\n    // Currently we return all the templates, even if they didn't change\n    assert_eq!(templates.len(), 2);\n\n    let template = &templates[&1];\n\n    // We should have a new dynamic attribute\n    assert_eq!(\n        template.roots,\n        &[\n            TemplateNode::Element {\n                tag: \"div\",\n                namespace: None,\n                attrs: &[TemplateAttribute::Dynamic { id: 0 }],\n                children: &[]\n            },\n            TemplateNode::Element {\n                tag: \"div\",\n                namespace: None,\n                attrs: &[TemplateAttribute::Dynamic { id: 1 }],\n                children: &[]\n            }\n        ]\n    );\n\n    // The new dynamic attribute should be created from the formatted segments pool\n    assert_eq!(\n        template.dynamic_attributes,\n        &[\n            HotReloadDynamicAttribute::Named(NamedAttribute::new(\n                \"class\",\n                None,\n                HotReloadAttributeValue::Literal(HotReloadLiteral::Fmted(FmtedSegments::new(\n                    vec![\n                        FmtSegment::Literal { value: \"item is \" },\n                        FmtSegment::Dynamic { id: 0 }\n                    ],\n                )))\n            )),\n            HotReloadDynamicAttribute::Named(NamedAttribute::new(\n                \"class\",\n                None,\n                HotReloadAttributeValue::Literal(HotReloadLiteral::Fmted(FmtedSegments::new(\n                    vec![\n                        FmtSegment::Literal {\n                            value: \"item is also \"\n                        },\n                        FmtSegment::Dynamic { id: 0 }\n                    ],\n                )))\n            )),\n        ]\n    );\n}\n\n#[test]\nfn valid_move_dynamic_segment_between_nodes() {\n    // Hot reloading should let you move around a dynamic formatted segment between nodes\n    let old = quote! {\n        div {\n            for item in vec![1, 2, 3] {\n                div {\n                    class: \"item is {item}\"\n                }\n            }\n        }\n    };\n    let new = quote! {\n        div {\n            for item in vec![1, 2, 3] {\n                \"item is {item}\"\n            }\n        }\n    };\n\n    let templates = hot_reload_from_tokens(old, new).unwrap();\n\n    // Currently we return all the templates, even if they didn't change\n    assert_eq!(templates.len(), 2);\n\n    let template = &templates[&1];\n\n    // We should have a new dynamic node and no attributes\n    assert_eq!(template.roots, &[TemplateNode::Dynamic { id: 0 }]);\n\n    // The new dynamic node should be created from the formatted segments pool\n    assert_eq!(\n        template.dynamic_nodes,\n        &[HotReloadDynamicNode::Formatted(FmtedSegments::new(vec![\n            FmtSegment::Literal { value: \"item is \" },\n            FmtSegment::Dynamic { id: 0 }\n        ])),]\n    );\n}\n\n#[test]\nfn valid_keys() {\n    let a = quote! {\n        div {\n            key: \"{value}\",\n        }\n    };\n\n    // we can clone dynamic nodes to hot reload them\n    let b = quote! {\n        div {\n            key: \"{value}-1234\",\n        }\n    };\n\n    let hot_reload = hot_reload_from_tokens(a, b).unwrap();\n\n    assert_eq!(hot_reload.len(), 1);\n\n    let template = &hot_reload[&0];\n\n    assert_eq!(\n        template.key,\n        Some(FmtedSegments::new(vec![\n            FmtSegment::Dynamic { id: 0 },\n            FmtSegment::Literal { value: \"-1234\" }\n        ]))\n    );\n}\n\n#[test]\nfn invalid_cases() {\n    let new_invalid = quote! {\n        div {\n            for item in vec![1, 2, 3, 4] {\n                div { \"asasddasdasd\" }\n                div { \"123\" }\n            }\n            for item in vec![4, 5, 6] {\n                span { \"asasddasdasd\" }\n                span { \"123\" }\n            }\n        }\n    };\n\n    // just remove an entire for loop\n    let new_valid_removed = quote! {\n        div {\n            for item in vec![4, 5, 6] {\n                span { \"asasddasdasd\" }\n                span { \"123\" }\n            }\n        }\n    };\n\n    let new_invalid_new_dynamic_internal = quote! {\n        div {\n            for item in vec![1, 2, 3] {\n                div { \"asasddasdasd\" }\n                div { \"123\" }\n            }\n            for item in vec![4, 5, 6] {\n                span { \"asasddasdasd\" }\n\n                // this is a new dynamic node, and thus can't be hot reloaded\n                // Eventually we might be able to do a format like this, but not right now\n                span { \"123 {item}\" }\n            }\n        }\n    };\n\n    let new_invalid_added = quote! {\n        div {\n            for item in vec![1, 2, 3] {\n                div { \"asasddasdasd\" }\n                div { \"123\" }\n            }\n            for item in vec![4, 5, 6] {\n                span { \"asasddasdasd\" }\n                span { \"123\" }\n            }\n\n            for item in vec![7, 8, 9] {\n                span { \"asasddasdasd\" }\n                span { \"123\" }\n            }\n        }\n    };\n\n    let location = \"file:line:col:0\";\n    let old = base();\n\n    let new_invalid: CallBody = syn::parse2(new_invalid).unwrap();\n    let new_valid_removed: CallBody = syn::parse2(new_valid_removed).unwrap();\n    let new_invalid_new_dynamic_internal: CallBody =\n        syn::parse2(new_invalid_new_dynamic_internal).unwrap();\n    let new_invalid_added: CallBody = syn::parse2(new_invalid_added).unwrap();\n\n    assert!(hotreload_callbody::<Mock>(&old, &new_invalid).is_none());\n    assert!(hotreload_callbody::<Mock>(&old, &new_invalid_new_dynamic_internal).is_none());\n\n    let templates = hotreload_callbody::<Mock>(&old, &new_valid_removed).unwrap();\n\n    // we don't get the removed template back\n    assert_eq!(templates.len(), 2);\n    let template = &templates.get(&0).unwrap();\n\n    // We just completely removed the dynamic node, so it should be a \"dud\" path and then the placement\n    assert_eq!(\n        template.roots,\n        &[TemplateNode::Element {\n            tag: \"div\",\n            namespace: None,\n            attrs: &[],\n            children: &[TemplateNode::Dynamic { id: 0 }]\n        }]\n    );\n    assert_eq!(template.dynamic_nodes, &[HotReloadDynamicNode::Dynamic(1)]);\n\n    // Adding a new dynamic node should not be hot reloadable\n    let added = hotreload_callbody::<Mock>(&old, &new_invalid_added);\n    assert!(added.is_none());\n}\n\n#[test]\nfn invalid_empty_rsx() {\n    let old_template = quote! {\n        div {\n            for item in vec![1, 2, 3, 4] {\n                div { \"asasddasdasd\" }\n                div { \"123\" }\n            }\n            for item in vec![4, 5, 6] {\n                span { \"asasddasdasd\" }\n                span { \"123\" }\n            }\n        }\n    };\n\n    // empty out the whole rsx block\n    let new_template = quote! {};\n\n    let location = \"file:line:col:0\";\n\n    let old_template: CallBody = syn::parse2(old_template).unwrap();\n    let new_template: CallBody = syn::parse2(new_template).unwrap();\n\n    assert!(hotreload_callbody::<Mock>(&old_template, &new_template).is_none());\n}\n\n#[test]\nfn attributes_reload() {\n    let old = quote! {\n        div {\n            class: \"{class}\",\n            id: \"{id}\",\n            name: \"name\",\n        }\n    };\n\n    // Same order, just different contents\n    let new_valid_internal = quote! {\n        div {\n            id: \"{id}\",\n            name: \"name\",\n            class: \"{class}\"\n        }\n    };\n\n    let templates = hot_reload_from_tokens(old, new_valid_internal).unwrap();\n\n    dbg!(templates);\n}\n\n#[test]\nfn template_generates() {\n    let old = quote! {\n        svg {\n            width: 100,\n            height: \"100px\",\n            \"width2\": 100,\n            \"height2\": \"100px\",\n            p { \"hello world\" }\n            {(0..10).map(|i| rsx! {\"{i}\"})}\n        }\n        div {\n            width: 120,\n            div {\n                height: \"100px\",\n                \"width2\": 130,\n                \"height2\": \"100px\",\n                for i in 0..10 {\n                    div {\n                        \"asdasd\"\n                    }\n                }\n            }\n        }\n    };\n\n    let old: CallBody = syn::parse2(old).unwrap();\n    let template = callbody_to_template::<Mock>(&old, \"file:line:col:0\");\n}\n\n#[test]\nfn diffs_complex() {\n    #[allow(unused, non_snake_case)]\n    fn Comp() -> dioxus_core::Element {\n        VNode::empty()\n    }\n\n    let old = quote! {\n        svg {\n            width: 100,\n            height: \"100px\",\n            \"width2\": 100,\n            \"height2\": \"100px\",\n            p { \"hello world\" }\n            {(0..10).map(|i| rsx! {\"{i}\"})},\n            {(0..10).map(|i| rsx! {\"{i}\"})},\n            {(0..11).map(|i| rsx! {\"{i}\"})},\n            Comp {}\n        }\n    };\n\n    // scrambling the attributes should not cause a full rebuild\n    let new = quote! {\n        div {\n            width: 100,\n            height: \"100px\",\n            \"width2\": 100,\n            \"height2\": \"100px\",\n            p { \"hello world\" }\n            Comp {}\n            {(0..10).map(|i| rsx! {\"{i}\"})},\n            {(0..10).map(|i| rsx! {\"{i}\"})},\n            {(0..11).map(|i| rsx! {\"{i}\"})},\n        }\n    };\n\n    let old: CallBody = syn::parse2(old).unwrap();\n    let new: CallBody = syn::parse2(new).unwrap();\n\n    let templates = hotreload_callbody::<Mock>(&old, &new).unwrap();\n}\n\n#[test]\nfn remove_node() {\n    let valid = hot_reload_from_tokens(\n        quote! {\n            svg {\n                Comp {}\n                {(0..10).map(|i| rsx! {\"{i}\"})},\n            }\n        },\n        quote! {\n            div {\n                {(0..10).map(|i| rsx! {\"{i}\"})},\n            }\n        },\n    )\n    .unwrap();\n\n    dbg!(valid);\n}\n\n#[test]\nfn if_chains() {\n    let valid = hot_reload_from_tokens(\n        quote! {\n            if cond {\n                \"foo\"\n            }\n        },\n        quote! {\n            if cond {\n                \"baz\"\n            }\n        },\n    )\n    .unwrap();\n\n    let very_complex_chain = hot_reload_from_tokens(\n        quote! {\n            if cond {\n                if second_cond {\n                    \"foo\"\n                }\n            } else if othercond {\n                \"bar\"\n            } else {\n                \"baz\"\n            }\n        },\n        quote! {\n            if cond {\n                if second_cond {\n                    span { \"asasddasdasd 789\" }\n                }\n            } else if othercond {\n                span { \"asasddasdasd 123\" }\n            } else {\n                span { \"asasddasdas 456\" }\n            }\n        },\n    )\n    .unwrap();\n\n    dbg!(very_complex_chain);\n}\n\n#[test]\nfn component_bodies() {\n    let valid = can_hotreload(\n        quote! {\n            Comp {\n                \"foo\"\n            }\n        },\n        quote! {\n            Comp {\n                \"baz\"\n            }\n        },\n    );\n\n    assert!(valid);\n}\n\n// We currently don't track aliasing which means we can't allow dynamic nodes/formatted segments to be moved between scopes\n#[test]\nfn moving_between_scopes() {\n    let valid = can_hotreload(\n        quote! {\n            for x in 0..10 {\n                for y in 0..10 {\n                    div { \"x is {x}\" }\n                }\n            }\n        },\n        quote! {\n            for x in 0..10 {\n                div { \"x is {x}\" }\n            }\n        },\n    );\n\n    assert!(!valid);\n}\n\n/// Everything reloads!\n#[test]\nfn kitch_sink_of_reloadability() {\n    let valid = hot_reload_from_tokens(\n        quote! {\n            div {\n                for i in 0..10 {\n                    div { \"123\" }\n                    Comp {\n                        \"foo\"\n                    }\n                    if cond {\n                        \"foo\"\n                    }\n                }\n            }\n        },\n        quote! {\n            div {\n                \"hi!\"\n                for i in 0..10 {\n                    div { \"456\" }\n                    Comp { \"bar\" }\n                    if cond {\n                        \"baz\"\n                    }\n                }\n            }\n        },\n    )\n    .unwrap();\n\n    dbg!(valid);\n}\n\n/// Moving nodes inbetween multiple rsx! calls currently doesn't work\n/// Sad. Needs changes to core to work, and is technically flawed?\n#[test]\nfn entire_kitchen_sink() {\n    let valid = hot_reload_from_tokens(\n        quote! {\n            div {\n                for i in 0..10 {\n                    div { \"123\" }\n                }\n                Comp {\n                    \"foo\"\n                }\n                if cond {\n                    \"foo\"\n                }\n            }\n        },\n        quote! {\n            div {\n                \"hi!\"\n                Comp {\n                    for i in 0..10 {\n                        div { \"456\" }\n                    }\n                    \"bar\"\n                    if cond {\n                        \"baz\"\n                    }\n                }\n            }\n        },\n    );\n\n    assert!(valid.is_none());\n}\n\n#[test]\nfn tokenstreams_and_locations() {\n    let valid = hot_reload_from_tokens(\n        quote! {\n            div { \"hhi\" }\n            div {\n                {rsx! { \"hi again!\" }},\n                for i in 0..2 {\n                    \"first\"\n                    div { \"hi {i}\" }\n                }\n\n                for i in 0..3 {\n                    \"Second\"\n                    div { \"hi {i}\" }\n                }\n\n                if false {\n                    div { \"hi again!?\" }\n                } else if true {\n                    div { \"its cool?\" }\n                } else {\n                    div { \"not nice !\" }\n                }\n            }\n        },\n        quote! {\n            div { \"hhi\" }\n            div {\n                {rsx! { \"hi again!\" }},\n                for i in 0..2 {\n                    \"first\"\n                    div { \"hi {i}\" }\n                }\n\n                for i in 0..3 {\n                    \"Second\"\n                    div { \"hi {i}\" }\n                }\n\n                if false {\n                    div { \"hi again?\" }\n                } else if true {\n                    div { \"cool?\" }\n                } else {\n                    div { \"nice !\" }\n                }\n            }\n\n        },\n    );\n\n    dbg!(valid);\n}\n\n#[test]\nfn ide_testcase() {\n    let valid = hot_reload_from_tokens(\n        quote! {\n            div {\n                div { \"hi!!!123 in!stant relo123a1123dasasdasdasdasd\" }\n                for x in 0..5 {\n                    h3 { \"For loop contents\" }\n                }\n            }\n        },\n        quote! {\n            div {\n                div { \"hi!!!123 in!stant relo123a1123dasasdasdasdasd\" }\n                for x in 0..5 {\n                    h3 { \"For loop contents\" }\n                }\n            }\n        },\n    );\n\n    dbg!(valid);\n}\n\n#[test]\nfn assigns_ids() {\n    let toks = quote! {\n        div {\n            div { \"hi!!!123 in!stant relo123a1123dasasdasdasdasd\" }\n            for x in 0..5 {\n                h3 { \"For loop contents\" }\n            }\n        }\n    };\n\n    let parsed = syn::parse2::<CallBody>(toks).unwrap();\n\n    let node = parsed.body.get_dyn_node(&[0, 1]);\n    dbg!(node);\n}\n\n#[test]\nfn simple_start() {\n    let valid = can_hotreload(\n        //\n        quote! {\n            div {\n                class: \"Some {one}\",\n                id: \"Something {two}\",\n                \"One\"\n            }\n        },\n        quote! {\n            div {\n                id: \"Something {two}\",\n                class: \"Some {one}\",\n                \"One\"\n            }\n        },\n    );\n\n    assert!(valid);\n}\n\n#[test]\nfn complex_cases() {\n    let valid = can_hotreload(\n        quote! {\n            div {\n                class: \"Some {one}\",\n                id: \"Something {two}\",\n                \"One\"\n            }\n        },\n        quote! {\n            div {\n                class: \"Some {one}\",\n                id: \"Something else {two}\",\n                \"One\"\n            }\n        },\n    );\n\n    assert!(valid);\n}\n\n#[test]\nfn attribute_cases() {\n    let valid = can_hotreload(\n        quote! {\n            div {\n                class: \"Some {one}\",\n                id: \"Something {two}\",\n                \"One\"\n            }\n        },\n        quote! {\n            div {\n                id: \"Something {two}\",\n                \"One\"\n            }\n        },\n    );\n    assert!(valid);\n\n    let valid = can_hotreload(\n        //\n        quote! { div { class: 123 } },\n        quote! { div { class: 456 } },\n    );\n    assert!(valid);\n\n    let valid = can_hotreload(\n        //\n        quote! { div { class: 123.0 } },\n        quote! { div { class: 456.0 } },\n    );\n    assert!(valid);\n\n    let valid = can_hotreload(\n        //\n        quote! { div { class: \"asd {123}\", } },\n        quote! { div { class: \"def\", } },\n    );\n    assert!(valid);\n}\n\n#[test]\nfn text_node_cases() {\n    let valid = can_hotreload(\n        //\n        quote! { div { \"hello {world}\" } },\n        quote! { div { \"world {world}\" } },\n    );\n    assert!(valid);\n\n    let valid = can_hotreload(\n        //\n        quote! { div { \"hello {world}\" } },\n        quote! { div { \"world\" } },\n    );\n    assert!(valid);\n\n    let valid = can_hotreload(\n        //\n        quote! { div { \"hello {world}\" } },\n        quote! { div { \"world {world} {world}\" } },\n    );\n    assert!(valid);\n\n    let valid = can_hotreload(\n        //\n        quote! { div { \"hello\" } },\n        quote! { div { \"world {world}\" } },\n    );\n    assert!(!valid);\n}\n\n#[test]\nfn simple_carry() {\n    let a = quote! {\n        // start with\n        \"thing {abc} {def}\"       // 1, 1, 1\n        \"thing {def}\"             // 1, 0, 1\n        \"other {hij}\" // 1, 1, 1\n    };\n\n    let b = quote! {\n        // end with\n        \"thing {def}\"\n        \"thing {abc}\"\n        \"thing {hij}\"\n    };\n\n    let valid = can_hotreload(a, b);\n    assert!(valid);\n}\n\n#[test]\nfn complex_carry_text() {\n    let a = quote! {\n        // start with\n        \"thing {abc} {def}\"       // 1, 1, 1\n        \"thing {abc}\"             // 1, 0, 1\n        \"other {abc} {def} {hij}\" // 1, 1, 1\n    };\n\n    let b = quote! {\n        // end with\n        \"thing {abc}\"\n        \"thing {hij}\"\n    };\n\n    let valid = can_hotreload(a, b);\n    assert!(valid);\n}\n\n#[test]\nfn complex_carry() {\n    let a = quote! {\n        Component {\n            class: \"thing {abc}\",\n            other: \"other {abc} {def}\",\n        }\n        Component {\n            class: \"thing {abc}\",\n            other: \"other\",\n        }\n    };\n\n    let b = quote! {\n        // how about shuffling components, for, if, etc\n        Component {\n            class: \"thing {abc}\",\n            other: \"other {abc} {def}\",\n        }\n        Component {\n            class: \"thing\",\n            other: \"other\",\n        }\n    };\n\n    let valid = can_hotreload(a, b);\n    assert!(valid);\n}\n\n#[test]\nfn component_with_lits() {\n    let a = quote! {\n        Component {\n            class: 123,\n            id: 456.789,\n            other: true,\n            blah: \"hello {world}\",\n        }\n    };\n\n    // changing lit values\n    let b = quote! {\n        Component {\n            class: 456,\n            id: 789.456,\n            other: false,\n            blah: \"goodbye {world}\",\n        }\n    };\n\n    let valid = can_hotreload(a, b);\n    assert!(valid);\n}\n\n#[test]\nfn component_with_handlers() {\n    let a = quote! {\n        Component {\n            class: 123,\n            id: 456.789,\n            other: true,\n            blah: \"hello {world}\",\n            onclick: |e| { println!(\"clicked\") },\n        }\n    };\n\n    // changing lit values\n    let b = quote! {\n        Component {\n            class: 456,\n            id: 789.456,\n            other: false,\n            blah: \"goodbye {world}\",\n            onclick: |e| { println!(\"clicked\") },\n        }\n    };\n\n    let hot_reload = hot_reload_from_tokens(a, b).unwrap();\n    let template = hot_reload.get(&0).unwrap();\n    assert_eq!(\n        template.component_values,\n        &[\n            HotReloadLiteral::Int(456),\n            HotReloadLiteral::Float(789.456),\n            HotReloadLiteral::Bool(false),\n            HotReloadLiteral::Fmted(FmtedSegments::new(vec![\n                FmtSegment::Literal { value: \"goodbye \" },\n                FmtSegment::Dynamic { id: 0 }\n            ])),\n        ]\n    );\n}\n\n#[test]\nfn component_remove_key() {\n    let a = quote! {\n        Component {\n            key: \"{key}\",\n            class: 123,\n            id: 456.789,\n            other: true,\n            dynamic1,\n            dynamic2,\n            blah: \"hello {world}\",\n            onclick: |e| { println!(\"clicked\") },\n        }\n    };\n\n    // changing lit values\n    let b = quote! {\n        Component {\n            class: 456,\n            id: 789.456,\n            other: false,\n            dynamic1,\n            dynamic2,\n            blah: \"goodbye {world}\",\n            onclick: |e| { println!(\"clicked\") },\n        }\n    };\n\n    let hot_reload = hot_reload_from_tokens(a, b).unwrap();\n    let template = hot_reload.get(&0).unwrap();\n    assert_eq!(\n        template.component_values,\n        &[\n            HotReloadLiteral::Int(456),\n            HotReloadLiteral::Float(789.456),\n            HotReloadLiteral::Bool(false),\n            HotReloadLiteral::Fmted(FmtedSegments::new(vec![\n                FmtSegment::Literal { value: \"goodbye \" },\n                FmtSegment::Dynamic { id: 1 }\n            ]))\n        ]\n    );\n}\n\n#[test]\nfn component_modify_key() {\n    let a = quote! {\n        Component {\n            key: \"{key}\",\n            class: 123,\n            id: 456.789,\n            other: true,\n            dynamic1,\n            dynamic2,\n            blah1: \"hello {world123}\",\n            blah2: \"hello {world}\",\n            onclick: |e| { println!(\"clicked\") },\n        }\n    };\n\n    // changing lit values\n    let b = quote! {\n        Component {\n            key: \"{key}-{world}\",\n            class: 456,\n            id: 789.456,\n            other: false,\n            dynamic1,\n            dynamic2,\n            blah1: \"hello {world123}\",\n            blah2: \"hello {world}\",\n            onclick: |e| { println!(\"clicked\") },\n        }\n    };\n\n    let hot_reload = hot_reload_from_tokens(a, b).unwrap();\n    let template = hot_reload.get(&0).unwrap();\n    assert_eq!(\n        template.key,\n        Some(FmtedSegments::new(vec![\n            FmtSegment::Dynamic { id: 0 },\n            FmtSegment::Literal { value: \"-\" },\n            FmtSegment::Dynamic { id: 2 },\n        ]))\n    );\n    assert_eq!(\n        template.component_values,\n        &[\n            HotReloadLiteral::Int(456),\n            HotReloadLiteral::Float(789.456),\n            HotReloadLiteral::Bool(false),\n            HotReloadLiteral::Fmted(FmtedSegments::new(vec![\n                FmtSegment::Literal { value: \"hello \" },\n                FmtSegment::Dynamic { id: 1 }\n            ])),\n            HotReloadLiteral::Fmted(FmtedSegments::new(vec![\n                FmtSegment::Literal { value: \"hello \" },\n                FmtSegment::Dynamic { id: 2 }\n            ]))\n        ]\n    );\n}\n\n#[test]\nfn duplicating_dynamic_nodes() {\n    let a = quote! {\n        div {\n            {some_expr}\n        }\n    };\n\n    // we can clone dynamic nodes to hot reload them\n    let b = quote! {\n        div {\n            {some_expr}\n            {some_expr}\n        }\n    };\n\n    let valid = can_hotreload(a, b);\n    assert!(valid);\n}\n\n#[test]\nfn duplicating_dynamic_attributes() {\n    let a = quote! {\n        div {\n            width: value,\n        }\n    };\n\n    // we can clone dynamic nodes to hot reload them\n    let b = quote! {\n        div {\n            width: value,\n            height: value,\n        }\n    };\n\n    let valid = can_hotreload(a, b);\n    assert!(valid);\n}\n\n// We should be able to fill in empty nodes\n#[test]\nfn valid_fill_empty() {\n    let valid = can_hotreload(\n        quote! {},\n        quote! {\n            div { \"x is 123\" }\n        },\n    );\n\n    assert!(valid);\n}\n\n// We should be able to hot reload spreads\n#[test]\nfn valid_spread() {\n    let valid = can_hotreload(\n        quote! {\n            div {\n                ..spread\n            }\n        },\n        quote! {\n            div {\n                \"hello world\"\n            }\n            h1 {\n                ..spread\n            }\n        },\n    );\n\n    assert!(valid);\n}\n"
  },
  {
    "path": "packages/rsx-hotreload/tests/hotreloads.rs",
    "content": "use dioxus_rsx_hotreload::diff_rsx;\nuse syn::File;\n\nmacro_rules! assert_rsx_changed {\n    (\n        $( #[doc = $doc:expr] )*\n        $name:ident\n    ) => {\n        $( #[doc = $doc] )*\n        #[test]\n        fn $name() {\n            let old = include_str!(concat!(\"./valid/\", stringify!($name), \".old.rsx\"));\n            let new = include_str!(concat!(\"./valid/\", stringify!($name), \".new.rsx\"));\n            let (old, new) = load_files(old, new);\n            assert!(diff_rsx(&new, &old).is_some());\n        }\n    };\n}\n\nfn load_files(old: &str, new: &str) -> (File, File) {\n    let old = syn::parse_file(old).unwrap();\n    let new = syn::parse_file(new).unwrap();\n    (old, new)\n}\n\nassert_rsx_changed![combo];\nassert_rsx_changed![expr];\nassert_rsx_changed![for_];\nassert_rsx_changed![if_];\nassert_rsx_changed![let_];\nassert_rsx_changed![nested];\n"
  },
  {
    "path": "packages/rsx-hotreload/tests/valid/combo.new.rsx",
    "content": "// This test is used by playwright configured in the root of the repo\n\nuse dioxus::prelude::*;\n\nfn app() -> Element {\n    let mut num = use_signal(|| 0);\n    let mut eval_result = use_signal(String::new);\n    let a = 123;\n\n    rsx! {\n        div {\n            \"hello axum! {num}\"\n            button { class: \"increment-button\", onclick: move |_| num += 1, \"Increment\" }\n        }\n        svg {\n            circle {\n                cx: 50,\n                cy: 50,\n                r: 40,\n                stroke: \"green\",\n                fill: \"yellow\"\n            }\n        }\n        div {\n            class: \"raw-attribute-div\",\n            \"raw-attribute\": \"raw-attribute-value\"\n        }\n        div { class: \"hidden-attribute-div\", hidden: true }\n        div {\n            class: \"dangerous-inner-html-div\",\n            dangerous_inner_html: \"<p>hello dangerous inner html</p>\"\n        }\n        input { value: \"hello input\" }\n        div { class: \"style-div\", color: \"red\", \"colored text\" }\n        div { class: \"style-div\", color: \"red\", \"colored text\" }\n        button {\n            class: \"eval-button\",\n            onclick: move |_| async move {\n                let mut eval = eval(\n                    r#\"\n                                window.document.title = 'Hello from Dioxus Eval!';\n                                dioxus.send(\"returned eval value\");\n                            \"#,\n                );\n                let result = eval.recv().await;\n                if let Ok(serde_json::Value::String(string)) = result {\n                    eval_result.set(string);\n                }\n            },\n            \"Eval!!!!\"\n            \"Eval!!!!\"\n            \"Eval!!!!!\"\n            \"Eval!!!!\"\n            \"Eval!!!!\"\n            \"Eval!!!!\"\n        }\n        div { class: \"eval-result\", \"{eval_result}\" }\n    }\n}\n\nfn main() {\n    // tracing_wasm::set_as_global_default_with_config(\n    //     tracing_wasm::WASMLayerConfigBuilder::default()\n    //         .set_max_level(tracing::Level::TRACE)\n    //         .build(),\n    // );\n    dioxus::launch(app);\n}\n"
  },
  {
    "path": "packages/rsx-hotreload/tests/valid/combo.old.rsx",
    "content": "// This test is used by playwright configured in the root of the repo\n\nuse dioxus::prelude::*;\n\nfn app() -> Element {\n    let mut num = use_signal(|| 0);\n    let mut eval_result = use_signal(String::new);\n    let a = 123;\n\n    rsx! {\n        div {\n            \"hello axum! {num}\"\n            button { class: \"increment-button\", onclick: move |_| num += 1, \"Increment\" }\n        }\n        svg { circle { cx: 50, cy: 50, r: 40, stroke: \"green\", fill: \"yellow\" } }\n        div { class: \"raw-attribute-div\", \"raw-attribute\": \"raw-attribute-value\" }\n        div { class: \"hidden-attribute-div\", hidden: true }\n        div {\n            class: \"dangerous-inner-html-div\",\n            dangerous_inner_html: \"<p>hello dangerous inner html</p>\"\n        }\n        input { value: \"hello input\" }\n        div { class: \"style-div\", color: \"red\", \"colored text\" }\n        div { class: \"style-div\", color: \"red\", \"colored text\" }\n        button {\n            class: \"eval-button\",\n            onclick: move |_| async move {\n                let mut eval = eval(\n                    r#\"\n                        window.document.title = 'Hello from Dioxus Eval!';\n                        dioxus.send(\"returned eval value\");\n                    \"#,\n                );\n\n                let result = eval.recv().await;\n                if let Ok(serde_json::Value::String(string)) = result {\n                    eval_result.set(string);\n                }\n            },\n            \"Eval!!!!\"\n            \"Eval!!!!\"\n            \"Eval!!!!!\"\n            \"Eval!!!!\"\n            \"Eval!!!!\"\n        }\n        div { class: \"eval-result\", \"{eval_result}\" }\n    }\n}\n\nfn main() {\n    // tracing_wasm::set_as_global_default_with_config(\n    //     tracing_wasm::WASMLayerConfigBuilder::default()\n    //         .set_max_level(tracing::Level::TRACE)\n    //         .build(),\n    // );\n    dioxus::launch(app);\n}\n"
  },
  {
    "path": "packages/rsx-hotreload/tests/valid/expr.new.rsx",
    "content": "use dioxus::prelude::*;\n\npub fn CoolChild() -> Element {\n    let head_ = rsx! {\n        div {\n            div { \"asasddasdasd\" }\n            div { \"asasdd1asaassdd23asasddasd\" }\n            div { \"aasdsdsaasdsddasd\" }\n        }\n    };\n\n    rsx! {\n        div {\n            {head_}\n        }\n    }\n}\n"
  },
  {
    "path": "packages/rsx-hotreload/tests/valid/expr.old.rsx",
    "content": "use dioxus::prelude::*;\n\npub fn CoolChild() -> Element {\n    let head_ = rsx! {\n        div {\n            div { \"asasddasdasd\" }\n            div { \"asasdd1asaassdd23asasddasd\" }\n            // div { \"aasdsdsaasdsddasd\" }\n        }\n    };\n\n    rsx! {\n        div {\n            {head_}\n        }\n    }\n}\n"
  },
  {
    "path": "packages/rsx-hotreload/tests/valid/for_.new.rsx",
    "content": "use dioxus::prelude::*;\n\npub fn CoolChild() -> Element {\n    rsx! {\n        for items in vec![1, 2, 3] {\n            div { \"asasddasdasd\" }\n        }\n    }\n}\n"
  },
  {
    "path": "packages/rsx-hotreload/tests/valid/for_.old.rsx",
    "content": "use dioxus::prelude::*;\n\npub fn CoolChild() -> Element {\n    rsx! {\n        for items in vec![1, 2, 3] {\n            div { \"123\" }\n            div { \"asasddasdasd\" }\n        }\n    }\n}\n"
  },
  {
    "path": "packages/rsx-hotreload/tests/valid/if_.new.rsx",
    "content": "use dioxus::prelude::*;\n\npub fn CoolChild() -> Element {\n    rsx! {\n        if cond() {\n            div { \"123\" }\n            div { \"asasddasdasd\" }\n        }\n    }\n}\n"
  },
  {
    "path": "packages/rsx-hotreload/tests/valid/if_.old.rsx",
    "content": "use dioxus::prelude::*;\n\npub fn CoolChild() -> Element {\n    rsx! {\n        if cond() {\n            div { \"asasddasdasd\" }\n        }\n    }\n}\n"
  },
  {
    "path": "packages/rsx-hotreload/tests/valid/let_.new.rsx",
    "content": "use dioxus::prelude::*;\n\npub fn CoolChild() -> Element {\n    let head_ = rsx! {\n        div {\n            div { \"asasddasdasd\" }\n            div { \"asasdd1asaassdd23asasddasd\" }\n        }\n    };\n\n    head_\n}\n"
  },
  {
    "path": "packages/rsx-hotreload/tests/valid/let_.old.rsx",
    "content": "use dioxus::prelude::*;\n\npub fn CoolChild() -> Element {\n    let head_ = rsx! {\n        div {\n            div { \"asasddasdasd\" }\n            div { \"asasdd1asaassdd23asasddasdasd\" }\n        }\n    };\n\n    head_\n}\n"
  },
  {
    "path": "packages/rsx-hotreload/tests/valid/nested.new.rsx",
    "content": "use dioxus::prelude::*;\n\npub fn CoolChild() -> Element {\n    rsx! {\n        ForLoop {\n            div { \"123\" }\n            div { \"asasddasdasd\" }\n        }\n    }\n}\n"
  },
  {
    "path": "packages/rsx-hotreload/tests/valid/nested.old.rsx",
    "content": "use dioxus::prelude::*;\n\npub fn CoolChild() -> Element {\n    rsx! {\n        ForLoop {\n            div { \"asasddasdasd\" }\n        }\n    }\n}\n"
  },
  {
    "path": "packages/rsx-rosetta/Cargo.toml",
    "content": "[package]\nname = \"dioxus-rsx-rosetta\"\nversion = { workspace = true }\nedition = \"2021\"\nauthors = [\"Jonathan Kelley\"]\ndescription = \"Autofomatter for Dioxus RSX\"\nlicense = \"MIT OR Apache-2.0\"\nrepository = \"https://github.com/DioxusLabs/dioxus/\"\nhomepage = \"https://dioxuslabs.com\"\ndocumentation = \"https://dioxuslabs.com\"\nkeywords = [\"dom\", \"ui\", \"gui\", \"react\"]\n\n# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html\n\n[dependencies]\ndioxus-autofmt = { workspace = true }\ndioxus-rsx = { workspace = true }\ndioxus-html = { workspace = true, features = [\"html-to-rsx\"]}\nhtml_parser = { workspace = true }\nproc-macro2 = { workspace = true }\nquote = { workspace = true }\nsyn = { workspace = true, features = [\"full\"] }\nconvert_case = { workspace = true }\nhtmlentity = \"1.3.2\"\n\n[dev-dependencies]\npretty_assertions = { workspace = true }\n\n[package.metadata.docs.rs]\ncargo-args = [\"-Zunstable-options\", \"-Zrustdoc-scrape-examples\"]\n"
  },
  {
    "path": "packages/rsx-rosetta/README.md",
    "content": "# `rsx-rosetta`\n\n[![Crates.io][crates-badge]][crates-url]\n[![MIT licensed][mit-badge]][mit-url]\n[![Build Status][actions-badge]][actions-url]\n[![Discord chat][discord-badge]][discord-url]\n\n[crates-badge]: https://img.shields.io/crates/v/rsx-rosetta.svg\n[crates-url]: https://crates.io/crates/rsx-rosetta\n[mit-badge]: https://img.shields.io/badge/license-MIT-blue.svg\n[mit-url]: https://github.com/dioxuslabs/dioxus/blob/main/LICENSE-MIT\n[actions-badge]: https://github.com/dioxuslabs/dioxus/actions/workflows/main.yml/badge.svg\n[actions-url]: https://github.com/dioxuslabs/dioxus/actions?query=workflow%3ACI+branch%3Amaster\n[discord-badge]: https://img.shields.io/discord/899851952891002890.svg?logo=discord&style=flat-square\n[discord-url]: https://discord.gg/XgGxMSkvUM\n\n[Website](https://dioxuslabs.com) |\n[Guides](https://dioxuslabs.com/learn/0.7/) |\n[API Docs](https://docs.rs/rsx-rosetta/latest/rsx_rosetta) |\n[Chat](https://discord.gg/XgGxMSkvUM)\n\n## Overview\n\nDioxus sports its own templating language inspired by C#/Kotlin/RTMP, etc. It's pretty straightforward.\n\nHowever, it's NOT HTML. This is done since HTML is verbose and you'd need a dedicated LSP or IDE integration to get a good DX in .rs files.\n\nRSX is simple... It's similar enough to regular Rust code to trick most IDEs into automatically providing support for things like block selections, folding, highlighting, etc.\n\nTo accommodate the transition from HTML to RSX, you might need to translate some existing code.\n\nThis library provides a central AST that can accept a number of inputs:\n\n- HTML\n- Syn (todo)\n- Akama (todo)\n- Jinja (todo)\n\nFrom there, you can convert directly to a string or into some other AST.\n\n## Contributing\n\n- Report issues on our [issue tracker](https://github.com/dioxuslabs/dioxus/issues).\n- Join the discord and ask questions!\n\n## License\n\nThis project is licensed under the [MIT license].\n\n[mit license]: https://github.com/dioxuslabs/dioxus/blob/main/LICENSE-MIT\n\nUnless you explicitly state otherwise, any contribution intentionally submitted\nfor inclusion in Dioxus by you, shall be licensed as MIT, without any additional\nterms or conditions.\n"
  },
  {
    "path": "packages/rsx-rosetta/examples/html.rs",
    "content": "use html_parser::Dom;\n\nfn main() {\n    let html = r#\"\n    <div>\n        <div class=\"asd\">hello world!</div>\n        <div id=\"asd\">hello world!</div>\n        <div id=\"asd\">hello world!</div>\n        <div for=\"asd\">hello world!</div>\n        <div async=\"asd\">hello world!</div>\n        <div LargeThing=\"asd\">hello world!</div>\n        <ai-is-awesome>hello world!</ai-is-awesome>\n    </div>\n    \"#\n    .trim();\n\n    let dom = Dom::parse(html).unwrap();\n\n    let body = dioxus_rsx_rosetta::rsx_from_html(&dom);\n\n    let out = dioxus_autofmt::write_block_out(&body).unwrap();\n\n    println!(\"{out}\");\n}\n"
  },
  {
    "path": "packages/rsx-rosetta/src/lib.rs",
    "content": "#![doc = include_str!(\"../README.md\")]\n#![doc(html_logo_url = \"https://avatars.githubusercontent.com/u/79236386\")]\n#![doc(html_favicon_url = \"https://avatars.githubusercontent.com/u/79236386\")]\n\nuse convert_case::{Case, Casing};\nuse dioxus_html::{map_html_attribute_to_rsx, map_html_element_to_rsx};\nuse dioxus_rsx::{\n    Attribute, AttributeName, AttributeValue, BodyNode, CallBody, Component, Element, ElementName,\n    HotLiteral, TemplateBody, TextNode,\n};\npub use html_parser::{Dom, Node};\nuse htmlentity::entity::ICodedDataTrait;\nuse proc_macro2::{Ident, Span};\nuse syn::{punctuated::Punctuated, LitStr};\n\n/// Convert an HTML DOM tree into an RSX CallBody\npub fn rsx_from_html(dom: &Dom) -> CallBody {\n    let nodes = dom\n        .children\n        .iter()\n        .filter_map(rsx_node_from_html)\n        .collect::<Vec<_>>();\n\n    let template = TemplateBody::new(nodes);\n\n    CallBody::new(template)\n}\n\n/// Convert an HTML Node into an RSX BodyNode\n///\n/// If the node is a comment, it will be ignored since RSX doesn't support comments\npub fn rsx_node_from_html(node: &Node) -> Option<BodyNode> {\n    use AttributeName::*;\n    use AttributeValue::*;\n\n    match node {\n        Node::Text(text) => Some(BodyNode::Text(TextNode::from_text(\n            &htmlentity::entity::decode(text.as_bytes())\n                .to_string()\n                .ok()?,\n        ))),\n\n        Node::Element(el) => {\n            let el_name = if let Some(name) = map_html_element_to_rsx(&el.name) {\n                ElementName::Ident(Ident::new(name, Span::call_site()))\n            } else {\n                // if we don't recognize it and it has a dash, we assume it's a web component\n                if el.name.contains('-') {\n                    ElementName::Custom(LitStr::new(&el.name, Span::call_site()))\n                } else {\n                    // otherwise, it might be an element that isn't supported yet\n                    ElementName::Ident(Ident::new(&el.name.to_case(Case::Snake), Span::call_site()))\n                }\n            };\n\n            let mut attributes: Vec<_> = el\n                .attributes\n                .iter()\n                .map(|(name, value)| {\n                    // xlink attributes are deprecated and technically we can't handle them.\n                    // todo(jon): apply the namespaces to the attributes\n                    let (_namespace, name) = name.split_once(':').unwrap_or((\"\", name));\n\n                    let value = HotLiteral::from_raw_text(value.as_deref().unwrap_or(\"false\"));\n                    let attr = if let Some(name) = map_html_attribute_to_rsx(name) {\n                        let name = if let Some(name) = name.strip_prefix(\"r#\") {\n                            Ident::new_raw(name, Span::call_site())\n                        } else {\n                            Ident::new(name, Span::call_site())\n                        };\n                        BuiltIn(name)\n                    } else {\n                        // If we don't recognize the attribute, we assume it's a custom attribute\n                        Custom(LitStr::new(name, Span::call_site()))\n                    };\n\n                    Attribute::from_raw(attr, AttrLiteral(value))\n                })\n                .collect();\n\n            let class = el.classes.join(\" \");\n            if !class.is_empty() {\n                attributes.push(Attribute::from_raw(\n                    BuiltIn(Ident::new(\"class\", Span::call_site())),\n                    AttrLiteral(HotLiteral::from_raw_text(&class)),\n                ));\n            }\n\n            if let Some(id) = &el.id {\n                attributes.push(Attribute::from_raw(\n                    BuiltIn(Ident::new(\"id\", Span::call_site())),\n                    AttrLiteral(HotLiteral::from_raw_text(id)),\n                ));\n            }\n\n            // the html-parser crate we use uses a HashMap for attributes. This leads to a\n            // non-deterministic order of attributes.\n            // Sort them here\n            attributes.sort_by(|a, b| a.name.to_string().cmp(&b.name.to_string()));\n\n            let children = el.children.iter().filter_map(rsx_node_from_html).collect();\n\n            Some(BodyNode::Element(Element {\n                name: el_name,\n                children,\n                raw_attributes: attributes,\n                merged_attributes: Default::default(),\n                diagnostics: Default::default(),\n                spreads: Default::default(),\n                brace: Default::default(),\n            }))\n        }\n\n        // We ignore comments\n        Node::Comment(_) => None,\n    }\n}\n\n/// Pull out all the svgs from the body and replace them with components of the same name\npub fn collect_svgs(children: &mut [BodyNode], out: &mut Vec<BodyNode>) {\n    for child in children {\n        match child {\n            BodyNode::Component(comp) => collect_svgs(&mut comp.children.roots, out),\n\n            BodyNode::Element(el) if el.name == \"svg\" => {\n                // we want to replace this instance with a component\n                let mut segments = Punctuated::new();\n\n                segments.push(Ident::new(\"icons\", Span::call_site()).into());\n\n                let new_name: Ident = Ident::new(&format!(\"icon_{}\", out.len()), Span::call_site());\n\n                segments.push(new_name.clone().into());\n\n                // Replace this instance with a component\n                let mut new_comp = BodyNode::Component(Component {\n                    name: syn::Path {\n                        leading_colon: None,\n                        segments,\n                    },\n                    generics: None,\n                    spreads: Default::default(),\n                    diagnostics: Default::default(),\n                    fields: vec![],\n                    children: TemplateBody::new(vec![]),\n                    brace: Some(Default::default()),\n                    dyn_idx: Default::default(),\n                    component_literal_dyn_idx: vec![],\n                });\n\n                std::mem::swap(child, &mut new_comp);\n\n                // And push the original svg into the svg list\n                out.push(new_comp);\n            }\n\n            BodyNode::Element(el) => collect_svgs(&mut el.children, out),\n\n            _ => {}\n        }\n    }\n}\n"
  },
  {
    "path": "packages/rsx-rosetta/tests/escape.rs",
    "content": "use html_parser::Dom;\n\n// Regression test for https://github.com/DioxusLabs/dioxus/issues/3037\n// We need to escape html entities as we translate html because rsx doesn't support them\n#[test]\nfn escaped_text() {\n    let html = r#\"<div>&lt;div&gt;&#x231b;&#x231b;&#x231b;&#x231b;</div>\"#.trim();\n\n    let dom = Dom::parse(html).unwrap();\n\n    let body = dioxus_rsx_rosetta::rsx_from_html(&dom);\n\n    let out = dioxus_autofmt::write_block_out(&body).unwrap();\n\n    let expected = r#\"\n    div { \"<div>⌛⌛⌛⌛\" }\"#;\n    pretty_assertions::assert_eq!(&out, &expected);\n}\n"
  },
  {
    "path": "packages/rsx-rosetta/tests/h-tags.rs",
    "content": "use html_parser::Dom;\n\n#[test]\nfn h_tags_translate() {\n    let html = r#\"\n    <div>\n        <h1>hello world!</h1>\n        <h2>hello world!</h2>\n        <h3>hello world!</h3>\n        <h4>hello world!</h4>\n        <h5>hello world!</h5>\n        <h6>hello world!</h6>\n    </div>\n    \"#\n    .trim();\n\n    let dom = Dom::parse(html).unwrap();\n\n    let body = dioxus_rsx_rosetta::rsx_from_html(&dom);\n\n    let out = dioxus_autofmt::write_block_out(&body).unwrap();\n\n    let expected = r#\"\n    div {\n        h1 { \"hello world!\" }\n        h2 { \"hello world!\" }\n        h3 { \"hello world!\" }\n        h4 { \"hello world!\" }\n        h5 { \"hello world!\" }\n        h6 { \"hello world!\" }\n    }\"#;\n    pretty_assertions::assert_eq!(&out, &expected);\n}\n"
  },
  {
    "path": "packages/rsx-rosetta/tests/raw.rs",
    "content": "use html_parser::Dom;\n\n#[test]\nfn raw_attribute() {\n    let html = r#\"\n    <div>\n        <div unrecognizedattribute=\"asd\">hello world!</div>\n    </div>\n    \"#\n    .trim();\n\n    let dom = Dom::parse(html).unwrap();\n\n    let body = dioxus_rsx_rosetta::rsx_from_html(&dom);\n\n    let out = dioxus_autofmt::write_block_out(&body).unwrap();\n\n    let expected = r#\"\n    div {\n        div { \"unrecognizedattribute\": \"asd\", \"hello world!\" }\n    }\"#;\n    pretty_assertions::assert_eq!(&out, &expected);\n}\n"
  },
  {
    "path": "packages/rsx-rosetta/tests/simple.rs",
    "content": "use html_parser::Dom;\n\n#[test]\nfn simple_elements() {\n    let html = r#\"\n    <div>\n        <div class=\"asd\">hello world!</div>\n        <div id=\"asd\">hello world!</div>\n        <div id=\"asd\">hello world!</div>\n        <div for=\"asd\">hello world!</div>\n        <div async=\"asd\">hello world!</div>\n    </div>\n    \"#\n    .trim();\n\n    let dom = Dom::parse(html).unwrap();\n\n    let body = dioxus_rsx_rosetta::rsx_from_html(&dom);\n\n    let out = dioxus_autofmt::write_block_out(&body).unwrap();\n\n    let expected = r#\"\n    div {\n        div { class: \"asd\", \"hello world!\" }\n        div { id: \"asd\", \"hello world!\" }\n        div { id: \"asd\", \"hello world!\" }\n        div { r#for: \"asd\", \"hello world!\" }\n        div { r#async: \"asd\", \"hello world!\" }\n    }\"#;\n    pretty_assertions::assert_eq!(&out, &expected);\n}\n\n#[test]\nfn deeply_nested() {\n    let html = r#\"\n    <div>\n        <div class=\"asd\">\n            <div class=\"asd\">\n                <div class=\"asd\">\n                    <div class=\"asd\">\n                    </div>\n                </div>\n            </div>\n        </div>\n    </div>\n    \"#\n    .trim();\n\n    let dom = Dom::parse(html).unwrap();\n\n    let body = dioxus_rsx_rosetta::rsx_from_html(&dom);\n\n    let out = dioxus_autofmt::write_block_out(&body).unwrap();\n\n    let expected = r#\"\n    div {\n        div { class: \"asd\",\n            div { class: \"asd\",\n                div { class: \"asd\",\n                    div { class: \"asd\" }\n                }\n            }\n        }\n    }\"#;\n    pretty_assertions::assert_eq!(&out, &expected);\n}\n"
  },
  {
    "path": "packages/rsx-rosetta/tests/svgs.rs",
    "content": "use html_parser::Dom;\n\n#[test]\nfn svgs() {\n    let viewbox = dioxus_html::map_html_attribute_to_rsx(\"viewBox\");\n    assert_eq!(viewbox, Some(\"view_box\"));\n\n    let html = r###\"\n<svg xmlns=\"http://www.w3.org/2000/svg\" id=\"flag-icons-fr\" viewBox=\"0 0 640 480\">\n  <path fill=\"#fff\" d=\"M0 0h640v480H0z\"/>\n  <path fill=\"#000091\" d=\"M0 0h213.3v480H0z\"/>\n  <path fill=\"#e1000f\" d=\"M426.7 0H640v480H426.7z\"/>\n</svg>\n\"###;\n\n    let dom = Dom::parse(html).unwrap();\n\n    let body = dioxus_rsx_rosetta::rsx_from_html(&dom);\n\n    let out = dioxus_autofmt::write_block_out(&body).unwrap();\n    pretty_assertions::assert_eq!(\n        &out,\n        r##\"\n    svg {\n        id: \"flag-icons-fr\",\n        view_box: \"0 0 640 480\",\n        xmlns: \"http://www.w3.org/2000/svg\",\n        path { d: \"M0 0h640v480H0z\", fill: \"#fff\" }\n        path { d: \"M0 0h213.3v480H0z\", fill: \"#000091\" }\n        path { d: \"M426.7 0H640v480H426.7z\", fill: \"#e1000f\" }\n    }\"##\n    );\n\n    let html = r###\"\n<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" id=\"flag-icons-cn\" viewBox=\"0 0 640 480\">\n  <defs>\n    <path id=\"cn-a\" fill=\"#ff0\" d=\"M-.6.8 0-1 .6.8-1-.3h2z\"/>\n  </defs>\n  <path fill=\"#ee1c25\" d=\"M0 0h640v480H0z\"/>\n  <use xlink:href=\"#cn-a\" width=\"30\" height=\"20\" transform=\"matrix(71.9991 0 0 72 120 120)\"/>\n  <use xlink:href=\"#cn-a\" width=\"30\" height=\"20\" transform=\"matrix(-12.33562 -20.5871 20.58684 -12.33577 240.3 48)\"/>\n  <use xlink:href=\"#cn-a\" width=\"30\" height=\"20\" transform=\"matrix(-3.38573 -23.75998 23.75968 -3.38578 288 95.8)\"/>\n  <use xlink:href=\"#cn-a\" width=\"30\" height=\"20\" transform=\"matrix(6.5991 -23.0749 23.0746 6.59919 288 168)\"/>\n  <use xlink:href=\"#cn-a\" width=\"30\" height=\"20\" transform=\"matrix(14.9991 -18.73557 18.73533 14.99929 240 216)\"/>\n</svg>\n    \"###;\n\n    let dom = Dom::parse(html).unwrap();\n\n    let body = dioxus_rsx_rosetta::rsx_from_html(&dom);\n\n    let out = dioxus_autofmt::write_block_out(&body).unwrap();\n    pretty_assertions::assert_eq!(\n        &out,\n        r##\"\n    svg {\n        id: \"flag-icons-cn\",\n        view_box: \"0 0 640 480\",\n        \"xlink\": \"http://www.w3.org/1999/xlink\",\n        xmlns: \"http://www.w3.org/2000/svg\",\n        defs {\n            path { d: \"M-.6.8 0-1 .6.8-1-.3h2z\", fill: \"#ff0\", id: \"cn-a\" }\n        }\n        path { d: \"M0 0h640v480H0z\", fill: \"#ee1c25\" }\n        use {\n            height: \"20\",\n            href: \"#cn-a\",\n            transform: \"matrix(71.9991 0 0 72 120 120)\",\n            width: \"30\",\n        }\n        use {\n            height: \"20\",\n            href: \"#cn-a\",\n            transform: \"matrix(-12.33562 -20.5871 20.58684 -12.33577 240.3 48)\",\n            width: \"30\",\n        }\n        use {\n            height: \"20\",\n            href: \"#cn-a\",\n            transform: \"matrix(-3.38573 -23.75998 23.75968 -3.38578 288 95.8)\",\n            width: \"30\",\n        }\n        use {\n            height: \"20\",\n            href: \"#cn-a\",\n            transform: \"matrix(6.5991 -23.0749 23.0746 6.59919 288 168)\",\n            width: \"30\",\n        }\n        use {\n            height: \"20\",\n            href: \"#cn-a\",\n            transform: \"matrix(14.9991 -18.73557 18.73533 14.99929 240 216)\",\n            width: \"30\",\n        }\n    }\"##\n    );\n}\n"
  },
  {
    "path": "packages/rsx-rosetta/tests/web-component.rs",
    "content": "use html_parser::Dom;\n\n#[test]\nfn web_components_translate() {\n    let html = r#\"\n    <div>\n       <my-component></my-component>\n    </div>\n    \"#\n    .trim();\n\n    let dom = Dom::parse(html).unwrap();\n\n    let body = dioxus_rsx_rosetta::rsx_from_html(&dom);\n\n    let out = dioxus_autofmt::write_block_out(&body).unwrap();\n\n    let expected = r#\"\n    div {\n        my-component {}\n    }\"#;\n    pretty_assertions::assert_eq!(&out, &expected);\n}\n"
  },
  {
    "path": "packages/signals/Cargo.toml",
    "content": "[package]\nname = \"dioxus-signals\"\nauthors = [\"Jonathan Kelley\", \"Evan Almloff\"]\nversion = { workspace = true }\nedition = \"2021\"\ndescription = \"Reactivie signals for Dioxus: Build fullstack web, desktop, and mobile apps with a single codebase.\"\nlicense = \"MIT OR Apache-2.0\"\nrepository = \"https://github.com/DioxusLabs/dioxus/\"\nhomepage = \"https://dioxuslabs.com\"\nkeywords = [\"dom\", \"ui\", \"gui\", \"react\", \"wasm\"]\nrust-version = \"1.79.0\"\n\n# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html\n\n[dependencies]\ndioxus-core = { workspace = true }\ngenerational-box = { workspace = true }\ntracing = { workspace = true }\nserde = { workspace = true, features = [\"derive\"], optional = true }\nparking_lot = { workspace = true}\nrustc-hash = { workspace = true }\nfutures-channel = { workspace = true }\nfutures-util = { workspace = true }\nwarnings = { workspace = true }\n\n[dev-dependencies]\ndioxus = { workspace = true }\ntokio = { workspace = true, features = [\"full\"] }\ntracing-subscriber = { workspace = true, default-features = true }\nreqwest = { workspace = true }\nrand = { workspace = true }\n\n[features]\ndefault = []\nserialize = [\"dep:serde\"]\n\n[package.metadata.docs.rs]\ncargo-args = [\"-Zunstable-options\", \"-Zrustdoc-scrape-examples\"]\n"
  },
  {
    "path": "packages/signals/README.md",
    "content": "# Dioxus Signals\n\nDioxus Signals is an ergonomic Copy runtime for data with local subscriptions.\n\n## Copy Data\n\nAll signals implement Copy, even if the inner value does not implement copy. This makes it easy to move any data into futures or children.\n\n```rust\nuse dioxus::prelude::*;\nuse dioxus_signals::*;\n\n#[component]\nfn App() -> Element {\n    let signal = use_signal(|| \"hello world\".to_string());\n\n    spawn(async move {\n        // signal is Copy even though String is not copy\n        print!(\"{signal}\");\n    });\n\n    rsx! {\n        \"{signal}\"\n    }\n}\n```\n\n## Local Subscriptions\n\nSignals will only subscribe to components when you read from the signal in that component. It will never subscribe to a component when reading data in a future or event handler.\n\n```rust\nuse dioxus::prelude::*;\nuse dioxus_signals::*;\n\n#[component]\nfn App() -> Element {\n    // Because signal is never read in this component, this component will not rerun when the signal changes\n    let mut signal = use_signal(|| 0);\n\n    rsx! {\n        button {\n            onclick: move |_| {\n                signal += 1;\n            },\n            \"Increase\"\n        }\n        for id in 0..10 {\n            Child {\n                signal,\n            }\n        }\n    }\n}\n\n#[derive(Props, Clone, PartialEq)]\nstruct ChildProps {\n    signal: Signal<usize>,\n}\n\nfn Child(props: ChildProps) -> Element {\n    // This component does read from the signal, so when the signal changes it will rerun\n    rsx! {\n        \"{props.signal}\"\n    }\n}\n```\n\nBecause subscriptions happen when you read from (not create) the data, you can provide signals through the normal context API:\n\n```rust\nuse dioxus::prelude::*;\nuse dioxus_signals::*;\n\n#[component]\nfn App() -> Element {\n    // Because signal is never read in this component, this component will not rerun when the signal changes\n    use_context_provider(|| Signal::new(0));\n\n    rsx! {\n        Child {}\n    }\n}\n\n#[component]\nfn Child() -> Element {\n    let signal: Signal<i32> = use_context();\n    // This component does read from the signal, so when the signal changes it will rerun\n    rsx! {\n        \"{signal}\"\n    }\n}\n```\n\n## Computed Data\n\nIn addition to local subscriptions in components, `dioxus-signals` provides a way to derive data with local subscriptions.\n\nThe use_memo hook will only rerun when any signals inside the hook change:\n\n```rust\nuse dioxus::prelude::*;\nuse dioxus_signals::*;\n\n#[component]\nfn App() -> Element {\n    let mut signal = use_signal(|| 0);\n    let doubled = use_memo(move || signal * 2);\n\n    rsx! {\n        button {\n            onclick: move |_| signal += 1,\n            \"Increase\"\n        }\n        Child {\n            signal: doubled\n        }\n    }\n}\n\n#[component]\nfn Child(signal: ReadSignal<usize>) -> Element {\n    rsx! {\n        \"{signal}\"\n    }\n}\n```\n"
  },
  {
    "path": "packages/signals/docs/hoist/error.rs",
    "content": "#[component]\nfn Counters() -> Element {\n    let counts = use_signal(Vec::new);\n    let mut children = use_signal(|| 0);\n\n    rsx! {\n        button { onclick: move |_| children += 1, \"Add child\" }\n        button { onclick: move |_| children -= 1, \"Remove child\" }\n        // A signal from a child is read or written to in a parent scope\n        \"{counts:?}\"\n        for _ in 0..children() {\n            Counter {\n                counts\n            }\n        }\n    }\n}\n\n#[component]\nfn Counter(mut counts: Signal<Vec<Signal<i32>>>) -> Element {\n    let mut signal_owned_by_child = use_signal(|| 0);\n    // Moving the signal up to the parent may cause issues if you read the signal after the child scope is dropped\n    use_hook(|| counts.push(signal_owned_by_child));\n\n    rsx! {\n        button {\n            onclick: move |_| signal_owned_by_child += 1,\n            \"{signal_owned_by_child}\"\n        }\n    }\n}\n"
  },
  {
    "path": "packages/signals/docs/hoist/fixed_list.rs",
    "content": "#[component]\nfn Counters() -> Element {\n    let mut counts = use_signal(Vec::new);\n\n    rsx! {\n        button { onclick: move |_| counts.write().push(0), \"Add child\" }\n        button {\n            onclick: move |_| {\n                counts.write().pop();\n            },\n            \"Remove child\"\n        }\n        \"{counts:?}\"\n        // Instead of passing up a signal, we can just write to the signal that lives in the parent\n        for index in 0..counts.len() {\n            Counter {\n                index,\n                counts\n            }\n        }\n    }\n}\n\n#[component]\nfn Counter(index: usize, mut counts: Signal<Vec<i32>>) -> Element {\n    rsx! {\n        button {\n            onclick: move |_| counts.write()[index] += 1,\n            \"{counts.read()[index]}\"\n        }\n    }\n}\n"
  },
  {
    "path": "packages/signals/docs/memo.md",
    "content": "Memos are the result of computing a value from `use_memo`.\n\nYou may have noticed that this struct doesn't have many methods. Most methods for `Memo` are defined on the [`ReadableExt`] trait.\n\n# Reading a Memo\n\nYou can use the methods on the `ReadableExt` trait to read a memo:\n\n```rust, no_run\n# use dioxus::prelude::*;\n\nfn app() -> Element {\n    let mut count = use_signal(|| 0);\n    // The memo will rerun any time we write to the count signal\n    let halved = use_memo(move || count() / 2);\n\n    rsx! {\n        // When we read the value of memo, the current component will subscribe to the result of the memo. It will only rerun when the result of the memo changes.\n        \"{halved}\"\n        button {\n            onclick: move |_| {\n                count += 1;\n            },\n            \"Increment\"\n        }\n    }\n}\n```\n\nMemo also includes helper methods just like [`Signal`]s to make it easier to use. Calling a memo like a function will clone the inner value:\n\n```rust, no_run\n# use dioxus::prelude::*;\n\nfn app() -> Element {\n    let mut count = use_signal(|| 0);\n    // The memo will rerun any time we write to the count signal\n    let halved = use_memo(move || count() / 2);\n    // This will rerun any time the halved value changes\n    let doubled = use_memo(move || 2 * halved());\n\n    rsx! {\n        \"{doubled}\"\n        button {\n            onclick: move |_| {\n                count += 1;\n            },\n            \"Increment\"\n        }\n    }\n}\n```\n\nFor a full list of all the helpers available, check out the [`Readable`], [`ReadableVecExt`](crate::ReadableVecExt), and [`ReadableOptionExt`](crate::ReadableOptionExt) traits.\n\n# Memos with Async\n\nBecause Memos check borrows at runtime, you need to be careful when reading memos inside of async code. If you hold a read of a memo over an await point, that read may still be open when the memo reruns which will cause a panic:\n\n```rust, no_run\n# use dioxus::prelude::*;\n# async fn sleep(delay: u32) {}\nasync fn double_me_async(value: &u32) -> u32 {\n    sleep(100).await;\n    *value * 2\n}\nlet mut signal = use_signal(|| 0);\nlet halved = use_memo(move || signal() / 2);\n\nlet doubled = use_resource(move || async move {\n    // Don't hold reads over await points\n    let halved = halved.read();\n    // While the future is waiting for the async work to finish, the read will be open\n    double_me_async(&halved).await\n});\n\nrsx!{\n    \"{doubled:?}\"\n    button {\n        onclick: move |_| {\n            // When you write to signal, it will cause the memo to rerun which may panic because you are holding a read of the memo over an await point\n            signal += 1;\n        },\n        \"Increment\"\n    }\n};\n```\n\nInstead of holding a read over an await point, you can clone whatever values you need out of your memo:\n\n```rust, no_run\n# use dioxus::prelude::*;\n# async fn sleep(delay: u32) {}\nasync fn double_me_async(value: u32) -> u32 {\n    sleep(100).await;\n    value * 2\n}\nlet mut signal = use_signal(|| 0);\nlet halved = use_memo(move || signal() / 2);\n\nlet doubled = use_resource(move || async move {\n    // Calling the memo will clone the inner value\n    let halved = halved();\n    double_me_async(halved).await;\n});\n\nrsx!{\n    \"{doubled:?}\"\n    button {\n        onclick: move |_| {\n            signal += 1;\n        },\n        \"Increment\"\n    }\n};\n```\n\n# Memo lifecycle\n\nMemos are implemented with [generational-box](https://crates.io/crates/generational-box) which makes all values Copy even if the inner value is not Copy.\n\nThis is incredibly convenient for UI development, but it does come with some tradeoffs. The lifetime of the memo is tied to the lifetime of the component it was created in. If you drop the component that created the memo, the memo will be dropped as well. You might run into this if you try to pass a memo from a child component to a parent component and drop the child component. To avoid this you can create your memo higher up in your component tree, or use global memos.\n\nTLDR **Don't pass memos up in the component tree**. It will cause issues:\n\n```rust\n# use dioxus::prelude::*;\nfn MyComponent() -> Element {\n    let child_signal = use_signal(|| None);\n\n    rsx! {\n        IncrementButton {\n            child_signal\n        }\n    }\n}\n\n#[component]\nfn IncrementButton(mut child_signal: Signal<Option<Memo<i32>>>) -> Element {\n    let signal_owned_by_child = use_signal(|| 0);\n    let memo_owned_by_child = use_memo(move || signal_owned_by_child() * 2);\n    // Don't do this: it may cause issues if you drop the child component\n    child_signal.set(Some(memo_owned_by_child));\n\n    todo!()\n}\n```\n"
  },
  {
    "path": "packages/signals/docs/signals.md",
    "content": "Signals are a Copy state management solution with automatic dependency tracking.\n\nYou may have noticed that this struct doesn't have many methods. Most methods for Signal are defined on the [`Readable`] and [`Writable`] traits.\n\n# Reading and Writing to a Signal\n\nSignals are similar to a copy version of `Rc<RefCell<T>>` built for UIs. You can read and write to a signal like a RefCell:\n\n```rust, no_run\n# use dioxus::prelude::*;\nlet mut signal = use_signal(|| 0);\n\n{\n    // This will read the value (we use a block to make sure the read is dropped before the write. You can read more about this in the next section)\n    let read = signal.read();\n    // Just like refcell, read you can deref the read to get the inner &T reference\n    match &*read {\n        &0 => println!(\"read is 0\"),\n        &1 => println!(\"read is 1\"),\n        _ => println!(\"read is something else ({read})\"),\n    }\n}\n\n// This will write to the value\nlet mut write = signal.write();\n// Again, we can deref the write to get the inner &mut T reference\n*write += 1;\n```\n\nSignals also have a bunch of helper methods to make it easier to use. Calling it like a function will clone the inner value. You can also call a few traits like AddAssign on it directly without writing to it manually:\n\n```rust, no_run\n# use dioxus::prelude::*;\nlet mut signal = use_signal(|| 0);\n// This will clone the value\nlet clone: i32 = signal();\n\n// You can directly display the signal\nprintln!(\"{}\", signal);\n\nlet signal_vec = use_signal(|| vec![1, 2, 3]);\n// And use vec methods like .get and .len without reading the signal explicitly\nlet first = signal_vec.get(0);\nlet last = signal_vec.last();\nlet len = signal_vec.len();\n\n// You can also iterate over signals directly\nfor i in signal_vec.iter() {\n    println!(\"{}\", i);\n}\n```\n\nFor a full list of all the helpers available, check out the [`Readable`], [`ReadableVecExt`], [`ReadableOptionExt`], [`Writable`], [`WritableVecExt`], and [`WritableOptionExt`] traits.\n\nJust like `RefCell<T>`, Signal checks borrows at runtime. If you read and write to the signal at the same time, it will panic:\n\n```rust, no_run\n# use dioxus::prelude::*;\nlet mut signal = use_signal(|| 0);\n// If you create a read and hold it while you write to the signal, it will panic\nlet read = signal.read_unchecked();\n// This will panic\nsignal += 1;\nprintln!(\"{}\", read);\n```\n\nTo avoid issues with overlapping reads and writes, you can use the `with_*` variants of methods to read and write to the signal in a single scope or wrap your reads and writes inside a block:\n\n```rust, no_run\n# use dioxus::prelude::*;\nlet mut signal = use_signal(|| 0);\n{\n    // Since this read is inside a block that ends before we write to the signal, the signal will be dropped before the write and it will not panic\n    let read = signal.read();\n    println!(\"{}\", read);\n}\nsignal += 1;\n\n// Or you can use the with and with_write methods which only read or write to the signal inside the closure\nsignal.with(|read| println!(\"{}\", read));\n// Since the read only lasts as long as the closure, this will not panic\nsignal.with_mut(|write| *write += 1);\n```\n\n# Signals with Async\n\nBecause signals check borrows at runtime, you need to be careful when reading and writing to signals inside of async code. If you hold a read or write to a signal over an await point, that read or write may still be open while you run other parts of your app:\n\n```rust, no_run\n# use dioxus::prelude::*;\n# async fn sleep(delay: u32) {}\nasync fn double_me_async(value: &mut u32) {\n    sleep(100).await;\n    *value *= 2;\n}\nlet mut signal = use_signal(|| 0);\n\nuse_future(move || async move {\n    // Don't hold reads or writes over await points\n    let mut write = signal.write();\n    // While the future is waiting for the async work to finish, the write will be open\n    double_me_async(&mut write).await;\n});\n\nrsx!{\n    // This read may panic because the write is still active while the future is waiting for the async work to finish\n    \"{signal}\"\n};\n```\n\nInstead of holding a read or write over an await point, you can clone whatever values you need out of your signal and then set the signal to the result once the async work is done:\n\n```rust, no_run\n# use dioxus::prelude::*;\n# async fn sleep(delay: u32) {}\nasync fn double_me_async(value: u32) -> u32 {\n    sleep(100).await;\n    value * 2\n}\nlet mut signal = use_signal(|| 0);\n\nuse_future(move || async move {\n    // Clone the value out of the signal\n    let current_value = signal();\n    // Run the async work\n    let new_value = double_me_async(current_value).await;\n    // Set the signal to the new value\n    signal.set(new_value);\n});\n\nrsx! {\n    // This read will not panic because the write is never held over an await point\n    \"{signal}\"\n};\n```\n\n# Signals lifecycle\n\nSignals are implemented with [generational-box](https://crates.io/crates/generational-box) which makes all values Copy even if the inner value is not Copy.\n\nThis is incredibly convenient for UI development, but it does come with some tradeoffs. The lifetime of the signal is tied to the lifetime of the component it was created in. If you drop the component that created the signal, the signal will be dropped as well. You might run into this if you try to pass a signal from a child component to a parent component and drop the child component. To avoid this you can create your signal higher up in your component tree, use global signals, or create a signal in a specific scope (like the `ScopeId::ROOT`) with [`Signal::new_in_scope`](https://docs.rs/dioxus/latest/dioxus/prelude/struct.Signal.html#method.new_in_scope)\n\nTLDR **Don't pass signals up in the component tree**. It will cause issues:\n\n```rust\n# use dioxus::prelude::*;\nfn MyComponent() -> Element {\n    let child_signal = use_signal(|| None);\n\n    rsx! {\n        IncrementButton {\n            child_signal\n        }\n    }\n}\n\n#[component]\nfn IncrementButton(mut child_signal: Signal<Option<Signal<i32>>>) -> Element {\n    let signal_owned_by_child = use_signal(|| 0);\n    // Don't do this: it may cause issues if you drop the child component\n    child_signal.set(Some(signal_owned_by_child));\n\n    todo!()\n}\n```\n"
  },
  {
    "path": "packages/signals/examples/context.rs",
    "content": "#![allow(non_snake_case)]\n\nuse dioxus::prelude::*;\n\nfn main() {\n    dioxus::launch(app)\n}\n\n// Because signal is never read in this component, this component will not rerun when the signal changes\nfn app() -> Element {\n    use_context_provider(|| Signal::new(0));\n    rsx! { Child {} }\n}\n\n// This component does read from the signal, so when the signal changes it will rerun\n#[component]\nfn Child() -> Element {\n    let signal: Signal<i32> = use_context();\n    rsx! { \"{signal}\" }\n}\n"
  },
  {
    "path": "packages/signals/examples/dependencies.rs",
    "content": "use dioxus::prelude::*;\n\nfn main() {\n    dioxus::launch(app);\n}\n\nfn app() -> Element {\n    let mut signal = use_signal(|| 0);\n\n    use_future(move || async move {\n        loop {\n            tokio::time::sleep(std::time::Duration::from_secs(1)).await;\n            signal += 1;\n        }\n    });\n\n    rsx! {\n        \"Parent count: {signal}\"\n        Child {\n            non_reactive_prop: signal()\n        }\n    }\n}\n\n#[component]\nfn Child(non_reactive_prop: i32) -> Element {\n    let mut signal = use_signal(|| 0);\n\n    // You can manually specify the dependencies with `use_reactive` for values that are not reactive like props\n    let computed = use_memo(use_reactive!(\n        |(non_reactive_prop,)| non_reactive_prop + signal()\n    ));\n    use_effect(use_reactive!(|(non_reactive_prop,)| println!(\n        \"{}\",\n        non_reactive_prop + signal()\n    )));\n    let fut = use_resource(use_reactive!(|(non_reactive_prop,)| async move {\n        tokio::time::sleep(std::time::Duration::from_secs(1)).await;\n        non_reactive_prop + signal()\n    }));\n\n    rsx! {\n        button {\n            onclick: move |_| signal += 1,\n            \"Child count: {signal}\"\n        }\n\n        \"Sum: {computed}\"\n\n        \"{fut():?}\"\n    }\n}\n"
  },
  {
    "path": "packages/signals/examples/map_signal.rs",
    "content": "#![allow(non_snake_case)]\n\nuse dioxus::prelude::*;\n\nfn main() {\n    dioxus::launch(app);\n}\n\nfn app() -> Element {\n    let mut vec = use_signal(|| vec![0]);\n\n    rsx! {\n        button {\n            onclick: move |_| {\n                let mut write = vec.write();\n                let len = write.len() as i32;\n                write.push(len);\n            },\n            \"Create\"\n        }\n\n        button {\n            onclick: move |_| {\n                vec.write().pop();\n            },\n            \"Destroy\"\n        }\n\n        for i in 0..vec.len() {\n            Child { count: vec.map_mut(move |v| &v[i], move |v| &mut v[i]) }\n        }\n    }\n}\n\n#[component]\nfn Child(count: WriteSignal<i32>) -> Element {\n    rsx! {\n        div {\n            \"{count}\"\n        }\n    }\n}\n"
  },
  {
    "path": "packages/signals/examples/read_only_degrade.rs",
    "content": "//! Signals can degrade into a Read variant automatically\n//! This is done thanks to a conversion by the #[component] macro\n\nuse dioxus::prelude::*;\n\nfn main() {\n    dioxus::launch(app);\n}\n\nfn app() -> Element {\n    let mut count = use_signal(|| 0);\n\n    rsx! {\n        h1 { \"High-Five counter: {count}\" }\n        button { onclick: move |_| count += 1, \"Up high!\" }\n        button { onclick: move |_| count -= 1, \"Down low!\" }\n        Child {\n            count,\n            \"hiiii\"\n        }\n    }\n}\n\n#[component]\nfn Child(count: ReadSignal<i32>, children: Element) -> Element {\n    rsx! {\n        div { \"Count: {count}\" }\n        {children}\n    }\n}\n"
  },
  {
    "path": "packages/signals/examples/selector.rs",
    "content": "use dioxus::prelude::*;\n\nfn main() {\n    dioxus::launch(app)\n}\n\nfn app() -> Element {\n    let mut signal = use_signal(|| 0);\n    let doubled = use_memo(move || signal * 2);\n\n    rsx! {\n        button {\n            onclick: move |_| signal += 1,\n            \"Increase\"\n        }\n        Child { signal: doubled }\n    }\n}\n\n#[component]\nfn Child(signal: ReadSignal<usize>) -> Element {\n    rsx! { \"{signal}\" }\n}\n"
  },
  {
    "path": "packages/signals/examples/send.rs",
    "content": "use dioxus::prelude::*;\n\nfn main() {\n    dioxus::launch(app);\n}\n\nfn app() -> Element {\n    let mut signal = use_signal_sync(|| 0);\n\n    use_hook(|| {\n        std::thread::spawn(move || loop {\n            std::thread::sleep(std::time::Duration::from_secs(1));\n            signal += 1;\n        });\n    });\n\n    rsx! {\n        button { onclick: move |_| signal += 1, \"Increase\" }\n        \"{signal}\"\n    }\n}\n"
  },
  {
    "path": "packages/signals/examples/send_store.rs",
    "content": "use dioxus::prelude::*;\n\nfn main() {\n    dioxus::launch(app);\n}\n\nfn app() -> Element {\n    let mut store = use_hook(|| Store::new_maybe_sync(0u32));\n\n    rsx! {\n        button { onclick: move |_| store += 1, \"Increase\" }\n        \"{store}\"\n        Child { store }\n    }\n}\n\n#[component]\nfn Child(store: WriteStore<u32, SyncStorage>) -> Element {\n    // A `WriteStore<T, SyncStorage>` can be sent to another thread!\n    // Make sure to clean up your threads when the component unmounts.\n    use_hook(|| {\n        std::thread::spawn(move || loop {\n            std::thread::sleep(std::time::Duration::from_secs(1));\n            store += 1;\n\n            if store() >= 10 {\n                break;\n            }\n        });\n    });\n\n    rsx! {}\n}\n"
  },
  {
    "path": "packages/signals/examples/split_subscriptions.rs",
    "content": "#![allow(non_snake_case)]\n\nuse dioxus::prelude::*;\nuse dioxus_signals::Signal;\n\nfn main() {\n    dioxus::launch(app);\n}\n\n#[derive(Clone, Copy, Default)]\nstruct ApplicationData {\n    first_data: Signal<i32>,\n    second_data: Signal<i32>,\n    many_signals: Signal<Vec<Signal<i32>>>,\n}\n\nfn app() -> Element {\n    use_context_provider(ApplicationData::default);\n\n    rsx! {\n        div { ReadsFirst {} }\n        div { ReadsSecond {} }\n        div { ReadsManySignals {} }\n    }\n}\n\n#[component]\nfn ReadsFirst() -> Element {\n    println!(\"running first\");\n    let mut data = use_context::<ApplicationData>();\n\n    rsx! {\n        button {\n            onclick: move |_| {\n                *data.first_data.write() += 1;\n            },\n            \"Increase\"\n        }\n        button {\n            onclick: move |_| {\n                *data.first_data.write() -= 1;\n            },\n            \"Decrease\"\n        }\n        button {\n            onclick: move |_| {\n                *data.first_data.write() = 0;\n            },\n            \"Reset\"\n        }\n        \"{data.first_data}\"\n    }\n}\n\n#[component]\nfn ReadsSecond() -> Element {\n    println!(\"running second\");\n    let mut data = use_context::<ApplicationData>();\n\n    rsx! {\n        button { onclick: move |_| data.second_data += 1, \"Increase\" }\n        button { onclick: move |_| data.second_data -= 1, \"Decrease\" }\n        button { onclick: move |_| data.second_data.set(0), \"Reset\" }\n        \"{data.second_data}\"\n    }\n}\n\n#[component]\nfn ReadsManySignals() -> Element {\n    println!(\"running many signals\");\n    let mut data = use_context::<ApplicationData>();\n\n    rsx! {\n        button {\n            onclick: move |_| data.many_signals.write().push(Signal::new(0)),\n            \"Create\"\n        }\n        button {\n            onclick: move |_| { data.many_signals.write().pop(); },\n            \"Destroy\"\n        }\n        button {\n            onclick: move |_| {\n                if let Some(mut first) = data.many_signals.read().first().cloned() {\n                    first += 1;\n                }\n            },\n            \"Increase First Item\"\n        }\n        for signal in data.many_signals.iter() {\n            Child { count: *signal }\n        }\n    }\n}\n\n#[component]\nfn Child(mut count: Signal<i32>) -> Element {\n    println!(\"running child\");\n\n    rsx! {\n        div {\n            \"Child: {count}\"\n            button { onclick: move |_| count += 1, \"Increase\" }\n            button { onclick: move |_| count -= 1, \"Decrease\" }\n        }\n    }\n}\n"
  },
  {
    "path": "packages/signals/src/boxed.rs",
    "content": "use std::{any::Any, ops::Deref};\n\nuse dioxus_core::{IntoAttributeValue, IntoDynNode, Subscribers};\nuse generational_box::{BorrowResult, Storage, SyncStorage, UnsyncStorage};\n\nuse crate::{\n    read_impls, write_impls, CopyValue, Global, InitializeFromFunction, MappedMutSignal,\n    MappedSignal, Memo, Readable, ReadableExt, ReadableRef, Signal, SignalData, Writable,\n    WritableExt,\n};\n\n/// A signal that can only be read from.\n#[deprecated(\n    since = \"0.7.0\",\n    note = \"Use `ReadSignal` instead. Will be removed in 0.8\"\n)]\npub type ReadOnlySignal<T, S = UnsyncStorage> = ReadSignal<T, S>;\n\n/// A boxed version of [Readable] that can be used to store any readable type.\npub struct ReadSignal<T: ?Sized, S: BoxedSignalStorage<T> = UnsyncStorage> {\n    value: CopyValue<Box<S::DynReadable<sealed::SealedToken>>, S>,\n}\n\nimpl<T: ?Sized + 'static> ReadSignal<T> {\n    /// Create a new boxed readable value.\n    pub fn new(value: impl Readable<Target = T, Storage = UnsyncStorage> + 'static) -> Self {\n        Self::new_maybe_sync(value)\n    }\n}\n\nimpl<T: ?Sized + 'static, S: BoxedSignalStorage<T>> ReadSignal<T, S> {\n    /// Create a new boxed readable value which may be sync\n    pub fn new_maybe_sync<R>(value: R) -> Self\n    where\n        S: CreateBoxedSignalStorage<R>,\n        R: Readable<Target = T>,\n    {\n        Self {\n            value: CopyValue::new_maybe_sync(S::new_readable(value, sealed::SealedToken)),\n        }\n    }\n\n    /// Point to another [ReadSignal]. This will subscribe the other [ReadSignal] to all subscribers of this [ReadSignal].\n    pub fn point_to(&self, other: Self) -> BorrowResult {\n        let this_subscribers = self.subscribers();\n        let mut this_subscribers_vec = Vec::new();\n        // Note we don't subscribe directly in the visit closure to avoid a deadlock when pointing to self\n        this_subscribers.visit(|subscriber| this_subscribers_vec.push(*subscriber));\n        let other_subscribers = other.subscribers();\n        for subscriber in this_subscribers_vec {\n            subscriber.subscribe(other_subscribers.clone());\n        }\n        self.value.point_to(other.value)?;\n        Ok(())\n    }\n\n    #[doc(hidden)]\n    /// This is only used by the `props` macro.\n    /// Mark any readers of the signal as dirty\n    pub fn mark_dirty(&mut self) {\n        let subscribers = self.subscribers();\n        let mut this_subscribers_vec = Vec::new();\n        subscribers.visit(|subscriber| this_subscribers_vec.push(*subscriber));\n        for subscriber in this_subscribers_vec {\n            subscribers.remove(&subscriber);\n            subscriber.mark_dirty();\n        }\n    }\n}\n\nimpl<T: ?Sized, S: BoxedSignalStorage<T>> Clone for ReadSignal<T, S> {\n    fn clone(&self) -> Self {\n        *self\n    }\n}\n\nimpl<T: ?Sized, S: BoxedSignalStorage<T>> Copy for ReadSignal<T, S> {}\n\nimpl<T: ?Sized, S: BoxedSignalStorage<T>> PartialEq for ReadSignal<T, S> {\n    fn eq(&self, other: &Self) -> bool {\n        self.value == other.value\n    }\n}\n\nimpl<\n        T: Default + 'static,\n        S: CreateBoxedSignalStorage<Signal<T, S>> + BoxedSignalStorage<T> + Storage<SignalData<T>>,\n    > Default for ReadSignal<T, S>\n{\n    fn default() -> Self {\n        Self::new_maybe_sync(Signal::new_maybe_sync(T::default()))\n    }\n}\n\nread_impls!(ReadSignal<T, S: BoxedSignalStorage<T>>);\n\nimpl<T, S: BoxedSignalStorage<T>> IntoAttributeValue for ReadSignal<T, S>\nwhere\n    T: Clone + IntoAttributeValue + 'static,\n{\n    fn into_value(self) -> dioxus_core::AttributeValue {\n        self.with(|f| f.clone().into_value())\n    }\n}\n\nimpl<T, S> IntoDynNode for ReadSignal<T, S>\nwhere\n    T: Clone + IntoDynNode + 'static,\n    S: BoxedSignalStorage<T>,\n{\n    fn into_dyn_node(self) -> dioxus_core::DynamicNode {\n        self.with(|f| f.clone().into_dyn_node())\n    }\n}\n\nimpl<T: Clone + 'static, S: BoxedSignalStorage<T>> Deref for ReadSignal<T, S> {\n    type Target = dyn Fn() -> T;\n\n    fn deref(&self) -> &Self::Target {\n        unsafe { ReadableExt::deref_impl(self) }\n    }\n}\n\nimpl<T: ?Sized, S: BoxedSignalStorage<T>> Readable for ReadSignal<T, S> {\n    type Target = T;\n    type Storage = S;\n\n    #[track_caller]\n    fn try_read_unchecked(\n        &self,\n    ) -> Result<ReadableRef<'static, Self>, generational_box::BorrowError>\n    where\n        T: 'static,\n    {\n        self.value\n            .try_peek_unchecked()\n            .unwrap()\n            .try_read_unchecked()\n    }\n\n    #[track_caller]\n    fn try_peek_unchecked(&self) -> BorrowResult<ReadableRef<'static, Self>>\n    where\n        T: 'static,\n    {\n        self.value\n            .try_peek_unchecked()\n            .unwrap()\n            .try_peek_unchecked()\n    }\n\n    fn subscribers(&self) -> Subscribers\n    where\n        T: 'static,\n    {\n        self.value.try_peek_unchecked().unwrap().subscribers()\n    }\n}\n\n// We can't implement From<impl Readable<Target = T, Storage = S> > for ReadSignal<T, S>\n// because it would conflict with the From<T> for T implementation, but we can implement it for\n// all specific readable types\nimpl<\n        T: 'static,\n        S: CreateBoxedSignalStorage<Signal<T, S>> + BoxedSignalStorage<T> + Storage<SignalData<T>>,\n    > From<Signal<T, S>> for ReadSignal<T, S>\n{\n    fn from(value: Signal<T, S>) -> Self {\n        Self::new_maybe_sync(value)\n    }\n}\nimpl<T: PartialEq + 'static> From<Memo<T>> for ReadSignal<T> {\n    fn from(value: Memo<T>) -> Self {\n        Self::new(value)\n    }\n}\nimpl<\n        T: 'static,\n        S: CreateBoxedSignalStorage<CopyValue<T, S>> + BoxedSignalStorage<T> + Storage<T>,\n    > From<CopyValue<T, S>> for ReadSignal<T, S>\n{\n    fn from(value: CopyValue<T, S>) -> Self {\n        Self::new_maybe_sync(value)\n    }\n}\nimpl<T, R> From<Global<T, R>> for ReadSignal<R>\nwhere\n    T: Readable<Target = R, Storage = UnsyncStorage> + InitializeFromFunction<R> + Clone + 'static,\n    R: 'static,\n{\n    fn from(value: Global<T, R>) -> Self {\n        Self::new(value)\n    }\n}\nimpl<V, O, F, S> From<MappedSignal<O, V, F>> for ReadSignal<O, S>\nwhere\n    O: ?Sized + 'static,\n    V: Readable<Storage = S> + 'static,\n    F: Fn(&V::Target) -> &O + 'static,\n    S: BoxedSignalStorage<O> + CreateBoxedSignalStorage<MappedSignal<O, V, F>>,\n{\n    fn from(value: MappedSignal<O, V, F>) -> Self {\n        Self::new_maybe_sync(value)\n    }\n}\nimpl<V, O, F, FMut, S> From<MappedMutSignal<O, V, F, FMut>> for ReadSignal<O, S>\nwhere\n    O: ?Sized + 'static,\n    V: Readable<Storage = S> + 'static,\n    F: Fn(&V::Target) -> &O + 'static,\n    FMut: 'static,\n    S: BoxedSignalStorage<O> + CreateBoxedSignalStorage<MappedMutSignal<O, V, F, FMut>>,\n{\n    fn from(value: MappedMutSignal<O, V, F, FMut>) -> Self {\n        Self::new_maybe_sync(value)\n    }\n}\nimpl<T: ?Sized + 'static, S> From<WriteSignal<T, S>> for ReadSignal<T, S>\nwhere\n    S: BoxedSignalStorage<T> + CreateBoxedSignalStorage<WriteSignal<T, S>>,\n{\n    fn from(value: WriteSignal<T, S>) -> Self {\n        Self::new_maybe_sync(value)\n    }\n}\n\n/// A boxed version of [Writable] that can be used to store any writable type.\npub struct WriteSignal<T: ?Sized, S: BoxedSignalStorage<T> = UnsyncStorage> {\n    value: CopyValue<Box<S::DynWritable<sealed::SealedToken>>, S>,\n}\n\nimpl<T: ?Sized + 'static> WriteSignal<T> {\n    /// Create a new boxed writable value.\n    pub fn new(\n        value: impl Writable<Target = T, Storage = UnsyncStorage, WriteMetadata: 'static> + 'static,\n    ) -> Self {\n        Self::new_maybe_sync(value)\n    }\n}\n\nimpl<T: ?Sized + 'static, S: BoxedSignalStorage<T>> WriteSignal<T, S> {\n    /// Create a new boxed writable value which may be sync\n    pub fn new_maybe_sync<R>(value: R) -> Self\n    where\n        R: Writable<Target = T, WriteMetadata: 'static>,\n        S: CreateBoxedSignalStorage<R>,\n    {\n        Self {\n            value: CopyValue::new_maybe_sync(S::new_writable(value, sealed::SealedToken)),\n        }\n    }\n}\n\nstruct BoxWriteMetadata<W> {\n    value: W,\n}\n\nimpl<W: Writable> BoxWriteMetadata<W> {\n    fn new(value: W) -> Self {\n        Self { value }\n    }\n}\n\nimpl<W: Readable> Readable for BoxWriteMetadata<W> {\n    type Target = W::Target;\n\n    type Storage = W::Storage;\n\n    fn try_read_unchecked(\n        &self,\n    ) -> Result<ReadableRef<'static, Self>, generational_box::BorrowError>\n    where\n        W::Target: 'static,\n    {\n        self.value.try_read_unchecked()\n    }\n\n    fn try_peek_unchecked(\n        &self,\n    ) -> Result<ReadableRef<'static, Self>, generational_box::BorrowError>\n    where\n        W::Target: 'static,\n    {\n        self.value.try_peek_unchecked()\n    }\n\n    fn subscribers(&self) -> Subscribers\n    where\n        W::Target: 'static,\n    {\n        self.value.subscribers()\n    }\n}\n\nimpl<W> Writable for BoxWriteMetadata<W>\nwhere\n    W: Writable,\n    W::WriteMetadata: 'static,\n{\n    type WriteMetadata = Box<dyn Any>;\n\n    fn try_write_unchecked(\n        &self,\n    ) -> Result<crate::WritableRef<'static, Self>, generational_box::BorrowMutError>\n    where\n        W::Target: 'static,\n    {\n        self.value\n            .try_write_unchecked()\n            .map(|w| w.map_metadata(|data| Box::new(data) as Box<dyn Any>))\n    }\n}\n\nimpl<T: ?Sized, S: BoxedSignalStorage<T>> Clone for WriteSignal<T, S> {\n    fn clone(&self) -> Self {\n        *self\n    }\n}\n\nimpl<T: ?Sized, S: BoxedSignalStorage<T>> Copy for WriteSignal<T, S> {}\n\nimpl<T: ?Sized, S: BoxedSignalStorage<T>> PartialEq for WriteSignal<T, S> {\n    fn eq(&self, other: &Self) -> bool {\n        self.value == other.value\n    }\n}\n\nread_impls!(WriteSignal<T, S: BoxedSignalStorage<T>>);\nwrite_impls!(WriteSignal<T, S: BoxedSignalStorage<T>>);\n\nimpl<T, S> IntoAttributeValue for WriteSignal<T, S>\nwhere\n    T: Clone + IntoAttributeValue + 'static,\n    S: BoxedSignalStorage<T>,\n{\n    fn into_value(self) -> dioxus_core::AttributeValue {\n        self.with(|f| f.clone().into_value())\n    }\n}\n\nimpl<T, S> IntoDynNode for WriteSignal<T, S>\nwhere\n    T: Clone + IntoDynNode + 'static,\n    S: BoxedSignalStorage<T>,\n{\n    fn into_dyn_node(self) -> dioxus_core::DynamicNode {\n        self.with(|f| f.clone().into_dyn_node())\n    }\n}\n\nimpl<T: Clone + 'static, S: BoxedSignalStorage<T>> Deref for WriteSignal<T, S> {\n    type Target = dyn Fn() -> T;\n\n    fn deref(&self) -> &Self::Target {\n        unsafe { ReadableExt::deref_impl(self) }\n    }\n}\n\nimpl<T: ?Sized, S: BoxedSignalStorage<T>> Readable for WriteSignal<T, S> {\n    type Target = T;\n    type Storage = S;\n\n    #[track_caller]\n    fn try_read_unchecked(\n        &self,\n    ) -> Result<ReadableRef<'static, Self>, generational_box::BorrowError>\n    where\n        T: 'static,\n    {\n        self.value\n            .try_peek_unchecked()\n            .unwrap()\n            .try_read_unchecked()\n    }\n\n    #[track_caller]\n    fn try_peek_unchecked(&self) -> BorrowResult<ReadableRef<'static, Self>>\n    where\n        T: 'static,\n    {\n        self.value\n            .try_peek_unchecked()\n            .unwrap()\n            .try_peek_unchecked()\n    }\n\n    fn subscribers(&self) -> Subscribers\n    where\n        T: 'static,\n    {\n        self.value.try_peek_unchecked().unwrap().subscribers()\n    }\n}\n\nimpl<T: ?Sized, S: BoxedSignalStorage<T>> Writable for WriteSignal<T, S> {\n    type WriteMetadata = Box<dyn Any>;\n\n    fn try_write_unchecked(\n        &self,\n    ) -> Result<crate::WritableRef<'static, Self>, generational_box::BorrowMutError>\n    where\n        T: 'static,\n    {\n        self.value\n            .try_peek_unchecked()\n            .unwrap()\n            .try_write_unchecked()\n    }\n}\n\n// We can't implement From<impl Writable<Target = T, Storage = S>> for Write<T, S>\n// because it would conflict with the From<T> for T implementation, but we can implement it for\n// all specific readable types\nimpl<\n        T: 'static,\n        S: CreateBoxedSignalStorage<Signal<T, S>> + BoxedSignalStorage<T> + Storage<SignalData<T>>,\n    > From<Signal<T, S>> for WriteSignal<T, S>\n{\n    fn from(value: Signal<T, S>) -> Self {\n        Self::new_maybe_sync(value)\n    }\n}\nimpl<\n        T: 'static,\n        S: CreateBoxedSignalStorage<CopyValue<T, S>> + BoxedSignalStorage<T> + Storage<T>,\n    > From<CopyValue<T, S>> for WriteSignal<T, S>\n{\n    fn from(value: CopyValue<T, S>) -> Self {\n        Self::new_maybe_sync(value)\n    }\n}\nimpl<T, R> From<Global<T, R>> for WriteSignal<R>\nwhere\n    T: Writable<Target = R, Storage = UnsyncStorage> + InitializeFromFunction<R> + Clone + 'static,\n    R: 'static,\n{\n    fn from(value: Global<T, R>) -> Self {\n        Self::new(value)\n    }\n}\nimpl<V, O, F, FMut, S> From<MappedMutSignal<O, V, F, FMut>> for WriteSignal<O, S>\nwhere\n    O: ?Sized + 'static,\n    V: Writable<Storage = S> + 'static,\n    F: Fn(&V::Target) -> &O + 'static,\n    FMut: Fn(&mut V::Target) -> &mut O + 'static,\n    S: CreateBoxedSignalStorage<MappedMutSignal<O, V, F, FMut>> + BoxedSignalStorage<O>,\n{\n    fn from(value: MappedMutSignal<O, V, F, FMut>) -> Self {\n        Self::new_maybe_sync(value)\n    }\n}\n\n/// A trait for creating boxed readable and writable signals. This is implemented for\n/// [UnsyncStorage] and [SyncStorage].\n///\n/// You may need to add this trait as a bound when you use [ReadSignal] or [WriteSignal] while\n/// remaining generic over syncness.\npub trait BoxedSignalStorage<T: ?Sized>:\n    Storage<Box<Self::DynReadable<sealed::SealedToken>>>\n    + Storage<Box<Self::DynWritable<sealed::SealedToken>>>\n    + sealed::Sealed\n    + 'static\n{\n    // This is not a public api, and is sealed to prevent external usage and implementations\n    #[doc(hidden)]\n    type DynReadable<Seal: sealed::SealedTokenTrait>: Readable<Target = T, Storage = Self> + ?Sized;\n    // This is not a public api, and is sealed to prevent external usage and implementations\n    #[doc(hidden)]\n    type DynWritable<Seal: sealed::SealedTokenTrait>: Writable<Target = T, Storage = Self, WriteMetadata = Box<dyn Any>>\n        + ?Sized;\n}\n\n/// A trait for creating boxed readable and writable signals. This is implemented for\n/// [UnsyncStorage] and [SyncStorage].\n///\n/// The storage type must implement `CreateReadOnlySignalStorage<T>` for every readable `T` type\n/// to be used with `ReadSignal` and `WriteSignal`.\n///\n/// You may need to add this trait as a bound when you call [ReadSignal::new_maybe_sync] or\n/// [WriteSignal::new_maybe_sync] while remaining generic over syncness.\npub trait CreateBoxedSignalStorage<T: Readable + ?Sized>:\n    BoxedSignalStorage<T::Target> + 'static\n{\n    // This is not a public api, and is sealed to prevent external usage and implementations\n    #[doc(hidden)]\n    fn new_readable(\n        value: T,\n        _: sealed::SealedToken,\n    ) -> Box<Self::DynReadable<sealed::SealedToken>>\n    where\n        T: Sized;\n\n    // This is not a public api, and is sealed to prevent external usage and implementations\n    #[doc(hidden)]\n    fn new_writable(\n        value: T,\n        _: sealed::SealedToken,\n    ) -> Box<Self::DynWritable<sealed::SealedToken>>\n    where\n        T: Writable + Sized;\n}\n\nimpl<T: ?Sized + 'static> BoxedSignalStorage<T> for UnsyncStorage {\n    type DynReadable<Seal: sealed::SealedTokenTrait> = dyn Readable<Target = T, Storage = Self>;\n    type DynWritable<Seal: sealed::SealedTokenTrait> =\n        dyn Writable<Target = T, Storage = Self, WriteMetadata = Box<dyn Any>>;\n}\n\nimpl<T: Readable<Storage = UnsyncStorage> + ?Sized + 'static> CreateBoxedSignalStorage<T>\n    for UnsyncStorage\n{\n    fn new_readable(value: T, _: sealed::SealedToken) -> Box<Self::DynReadable<sealed::SealedToken>>\n    where\n        T: Sized,\n    {\n        Box::new(value)\n    }\n\n    fn new_writable(value: T, _: sealed::SealedToken) -> Box<Self::DynWritable<sealed::SealedToken>>\n    where\n        T: Writable + Sized,\n    {\n        Box::new(BoxWriteMetadata::new(value))\n    }\n}\n\nimpl<T: ?Sized + 'static> BoxedSignalStorage<T> for SyncStorage {\n    type DynReadable<Seal: sealed::SealedTokenTrait> =\n        dyn Readable<Target = T, Storage = Self> + Send + Sync;\n    type DynWritable<Seal: sealed::SealedTokenTrait> =\n        dyn Writable<Target = T, Storage = Self, WriteMetadata = Box<dyn Any>> + Send + Sync;\n}\n\nimpl<T: Readable<Storage = SyncStorage> + Sync + Send + ?Sized + 'static>\n    CreateBoxedSignalStorage<T> for SyncStorage\n{\n    fn new_readable(value: T, _: sealed::SealedToken) -> Box<Self::DynReadable<sealed::SealedToken>>\n    where\n        T: Sized,\n    {\n        Box::new(value)\n    }\n\n    fn new_writable(value: T, _: sealed::SealedToken) -> Box<Self::DynWritable<sealed::SealedToken>>\n    where\n        T: Writable + Sized,\n    {\n        Box::new(BoxWriteMetadata::new(value))\n    }\n}\n\nmod sealed {\n    use generational_box::{SyncStorage, UnsyncStorage};\n\n    pub trait Sealed {}\n    impl Sealed for UnsyncStorage {}\n    impl Sealed for SyncStorage {}\n\n    pub struct SealedToken;\n\n    pub trait SealedTokenTrait {}\n    impl SealedTokenTrait for SealedToken {}\n}\n"
  },
  {
    "path": "packages/signals/src/copy_value.rs",
    "content": "#![allow(clippy::unnecessary_operation)]\n#![allow(clippy::no_effect)]\n\nuse dioxus_core::{current_owner, current_scope_id, ScopeId};\nuse dioxus_core::{Runtime, Subscribers};\nuse generational_box::{\n    AnyStorage, BorrowResult, GenerationalBox, GenerationalBoxId, Storage, UnsyncStorage,\n};\nuse std::ops::Deref;\n\nuse crate::read_impls;\nuse crate::Readable;\nuse crate::ReadableExt;\nuse crate::ReadableRef;\nuse crate::Writable;\nuse crate::WritableRef;\nuse crate::WriteLock;\nuse crate::{default_impl, write_impls, WritableExt};\n\n/// CopyValue is a wrapper around a value to make the value mutable and Copy.\n///\n/// It is internally backed by [`generational_box::GenerationalBox`].\npub struct CopyValue<T, S: 'static = UnsyncStorage> {\n    pub(crate) value: GenerationalBox<T, S>,\n    pub(crate) origin_scope: ScopeId,\n}\n\n#[cfg(feature = \"serialize\")]\nimpl<T, Store: Storage<T>> serde::Serialize for CopyValue<T, Store>\nwhere\n    T: serde::Serialize + 'static,\n{\n    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {\n        self.value.read().serialize(serializer)\n    }\n}\n\n#[cfg(feature = \"serialize\")]\nimpl<'de, T, Store: Storage<T>> serde::Deserialize<'de> for CopyValue<T, Store>\nwhere\n    T: serde::Deserialize<'de> + 'static,\n{\n    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {\n        let value = T::deserialize(deserializer)?;\n\n        Ok(Self::new_maybe_sync(value))\n    }\n}\n\nimpl<T: 'static> CopyValue<T> {\n    /// Create a new CopyValue. The value will be stored in the current component.\n    ///\n    /// Once the component this value is created in is dropped, the value will be dropped.\n    #[track_caller]\n    pub fn new(value: T) -> Self {\n        Self::new_maybe_sync(value)\n    }\n\n    /// Create a new CopyValue. The value will be stored in the given scope. When the specified scope is dropped, the value will be dropped.\n    #[track_caller]\n    pub fn new_in_scope(value: T, scope: ScopeId) -> Self {\n        Self::new_maybe_sync_in_scope(value, scope)\n    }\n}\n\nimpl<T, S: Storage<T>> CopyValue<T, S> {\n    /// Create a new CopyValue. The value will be stored in the current component.\n    ///\n    /// Once the component this value is created in is dropped, the value will be dropped.\n    #[track_caller]\n    pub fn new_maybe_sync(value: T) -> Self\n    where\n        T: 'static,\n    {\n        Self::new_with_caller(value, std::panic::Location::caller())\n    }\n\n    /// Create a new CopyValue without an owner. This will leak memory if you don't manually drop it.\n    pub fn leak_with_caller(value: T, caller: &'static std::panic::Location<'static>) -> Self\n    where\n        T: 'static,\n    {\n        Self {\n            value: GenerationalBox::leak(value, caller),\n            origin_scope: current_scope_id(),\n        }\n    }\n\n    /// Point to another copy value\n    pub fn point_to(&self, other: Self) -> BorrowResult {\n        self.value.point_to(other.value)\n    }\n\n    pub(crate) fn new_with_caller(\n        value: T,\n        caller: &'static std::panic::Location<'static>,\n    ) -> Self {\n        let owner = current_owner();\n\n        Self {\n            value: owner.insert_rc_with_caller(value, caller),\n            origin_scope: current_scope_id(),\n        }\n    }\n\n    /// Create a new CopyValue. The value will be stored in the given scope. When the specified scope is dropped, the value will be dropped.\n    #[track_caller]\n    pub fn new_maybe_sync_in_scope(value: T, scope: ScopeId) -> Self {\n        Self::new_maybe_sync_in_scope_with_caller(value, scope, std::panic::Location::caller())\n    }\n\n    /// Create a new CopyValue with a custom caller. The value will be stored in the given scope. When the specified scope is dropped, the value will be dropped.\n    #[track_caller]\n    pub fn new_maybe_sync_in_scope_with_caller(\n        value: T,\n        scope: ScopeId,\n        caller: &'static std::panic::Location<'static>,\n    ) -> Self {\n        let owner = Runtime::current().scope_owner(scope);\n        Self {\n            value: owner.insert_rc_with_caller(value, caller),\n            origin_scope: scope,\n        }\n    }\n\n    /// Manually drop the value in the CopyValue, invalidating the value in the process.\n    pub fn manually_drop(&self)\n    where\n        T: 'static,\n    {\n        self.value.manually_drop()\n    }\n\n    /// Get the scope this value was created in.\n    pub fn origin_scope(&self) -> ScopeId {\n        self.origin_scope\n    }\n\n    /// Get the generational id of the value.\n    pub fn id(&self) -> GenerationalBoxId {\n        self.value.id()\n    }\n\n    /// Get the underlying [`GenerationalBox`] value.\n    pub fn value(&self) -> GenerationalBox<T, S> {\n        self.value\n    }\n}\n\nimpl<T, S: Storage<T>> Readable for CopyValue<T, S> {\n    type Target = T;\n    type Storage = S;\n\n    #[track_caller]\n    fn try_read_unchecked(\n        &self,\n    ) -> Result<ReadableRef<'static, Self>, generational_box::BorrowError> {\n        crate::warnings::copy_value_hoisted(self, std::panic::Location::caller());\n        self.value.try_read()\n    }\n\n    #[track_caller]\n    fn try_peek_unchecked(&self) -> BorrowResult<ReadableRef<'static, Self>> {\n        crate::warnings::copy_value_hoisted(self, std::panic::Location::caller());\n        self.value.try_read()\n    }\n\n    fn subscribers(&self) -> Subscribers {\n        Subscribers::new_noop()\n    }\n}\n\nimpl<T, S: Storage<T>> Writable for CopyValue<T, S> {\n    type WriteMetadata = ();\n\n    #[track_caller]\n    fn try_write_unchecked(\n        &self,\n    ) -> Result<WritableRef<'static, Self>, generational_box::BorrowMutError> {\n        crate::warnings::copy_value_hoisted(self, std::panic::Location::caller());\n        self.value.try_write().map(WriteLock::new)\n    }\n}\n\nimpl<T, S: AnyStorage> PartialEq for CopyValue<T, S> {\n    fn eq(&self, other: &Self) -> bool {\n        self.value.ptr_eq(&other.value)\n    }\n}\nimpl<T, S: AnyStorage> Eq for CopyValue<T, S> {}\n\nimpl<T: Copy + 'static, S: Storage<T>> Deref for CopyValue<T, S> {\n    type Target = dyn Fn() -> T;\n\n    fn deref(&self) -> &Self::Target {\n        unsafe { ReadableExt::deref_impl(self) }\n    }\n}\n\nimpl<T, S> Clone for CopyValue<T, S> {\n    fn clone(&self) -> Self {\n        *self\n    }\n}\n\nimpl<T, S> Copy for CopyValue<T, S> {}\n\nread_impls!(CopyValue<T, S: Storage<T>>);\ndefault_impl!(CopyValue<T, S: Storage<T>>);\nwrite_impls!(CopyValue<T, S: Storage<T>>);\n"
  },
  {
    "path": "packages/signals/src/global/memo.rs",
    "content": "use super::{Global, InitializeFromFunction};\nuse crate::read::ReadableExt;\nuse crate::read_impls;\nuse crate::Memo;\n\nimpl<T: PartialEq + 'static> InitializeFromFunction<T> for Memo<T> {\n    fn initialize_from_function(f: fn() -> T) -> Self {\n        Memo::new(f)\n    }\n}\n\n/// A memo that can be accessed from anywhere in the application and created in a static\npub type GlobalMemo<T> = Global<Memo<T>, T>;\n\nimpl<T: PartialEq + 'static> GlobalMemo<T> {\n    /// Get the generational id of the signal.\n    pub fn id(&self) -> generational_box::GenerationalBoxId {\n        self.resolve().id()\n    }\n\n    /// Resolve the global memo. This will try to get the existing value from the current virtual dom, and if it doesn't exist, it will create a new one.\n    pub fn memo(&self) -> Memo<T> {\n        self.resolve()\n    }\n}\n\nread_impls!(GlobalMemo<T> where T: PartialEq);\n"
  },
  {
    "path": "packages/signals/src/global/mod.rs",
    "content": "use dioxus_core::{Runtime, ScopeId, Subscribers};\nuse generational_box::BorrowResult;\nuse std::{any::Any, cell::RefCell, collections::HashMap, ops::Deref, panic::Location, rc::Rc};\n\nmod memo;\npub use memo::*;\n\nmod signal;\npub use signal::*;\n\nuse crate::{Readable, ReadableExt, ReadableRef, Signal, Writable, WritableExt, WritableRef};\n\n/// A trait for an item that can be constructed from an initialization function\npub trait InitializeFromFunction<T> {\n    /// Create an instance of this type from an initialization function\n    fn initialize_from_function(f: fn() -> T) -> Self;\n}\n\nimpl<T> InitializeFromFunction<T> for T {\n    fn initialize_from_function(f: fn() -> T) -> Self {\n        f()\n    }\n}\n\n/// A lazy value that is created once per application and can be accessed from anywhere in that application\npub struct Global<T, R = T> {\n    constructor: fn() -> R,\n    key: GlobalKey<'static>,\n    phantom: std::marker::PhantomData<fn() -> T>,\n}\n\n/// Allow calling a signal with signal() syntax\n///\n/// Currently only limited to copy types, though could probably specialize for string/arc/rc\nimpl<T: Clone, R: Clone> Deref for Global<T, R>\nwhere\n    T: Readable<Target = R> + InitializeFromFunction<R> + 'static,\n    T::Target: 'static,\n{\n    type Target = dyn Fn() -> R;\n\n    fn deref(&self) -> &Self::Target {\n        unsafe { ReadableExt::deref_impl(self) }\n    }\n}\n\nimpl<T, R> Readable for Global<T, R>\nwhere\n    T: Readable<Target = R> + InitializeFromFunction<R> + Clone + 'static,\n{\n    type Target = R;\n    type Storage = T::Storage;\n\n    #[track_caller]\n    fn try_read_unchecked(\n        &self,\n    ) -> Result<ReadableRef<'static, Self>, generational_box::BorrowError>\n    where\n        R: 'static,\n    {\n        self.resolve().try_read_unchecked()\n    }\n\n    #[track_caller]\n    fn try_peek_unchecked(&self) -> BorrowResult<ReadableRef<'static, Self>>\n    where\n        R: 'static,\n    {\n        self.resolve().try_peek_unchecked()\n    }\n\n    fn subscribers(&self) -> Subscribers\n    where\n        R: 'static,\n    {\n        self.resolve().subscribers()\n    }\n}\n\nimpl<T: Clone, R> Writable for Global<T, R>\nwhere\n    T: Writable<Target = R> + InitializeFromFunction<R> + 'static,\n{\n    type WriteMetadata = T::WriteMetadata;\n\n    #[track_caller]\n    fn try_write_unchecked(\n        &self,\n    ) -> Result<WritableRef<'static, Self>, generational_box::BorrowMutError> {\n        self.resolve().try_write_unchecked()\n    }\n}\n\nimpl<T: Clone, R> Global<T, R>\nwhere\n    T: Writable<Target = R> + InitializeFromFunction<R> + 'static,\n{\n    /// Write this value\n    pub fn write(&self) -> WritableRef<'static, T, R> {\n        self.resolve().try_write_unchecked().unwrap()\n    }\n\n    /// Run a closure with a mutable reference to the signal's value.\n    /// If the signal has been dropped, this will panic.\n    #[track_caller]\n    pub fn with_mut<O>(&self, f: impl FnOnce(&mut R) -> O) -> O\n    where\n        T::Target: 'static,\n    {\n        self.resolve().with_mut(f)\n    }\n}\n\nimpl<T: Clone, R> Global<T, R>\nwhere\n    T: InitializeFromFunction<R>,\n{\n    #[track_caller]\n    /// Create a new global value\n    pub const fn new(constructor: fn() -> R) -> Self {\n        let key = std::panic::Location::caller();\n        Self {\n            constructor,\n            key: GlobalKey::new(key),\n            phantom: std::marker::PhantomData,\n        }\n    }\n\n    /// Create this global signal with a specific key.\n    /// This is useful for ensuring that the signal is unique across the application and accessible from\n    /// outside the application too.\n    #[track_caller]\n    pub const fn with_name(constructor: fn() -> R, key: &'static str) -> Self {\n        Self {\n            constructor,\n            key: GlobalKey::File {\n                file: key,\n                line: 0,\n                column: 0,\n                index: 0,\n            },\n            phantom: std::marker::PhantomData,\n        }\n    }\n\n    /// Create this global signal with a specific key.\n    /// This is useful for ensuring that the signal is unique across the application and accessible from\n    /// outside the application too.\n    #[track_caller]\n    pub const fn with_location(\n        constructor: fn() -> R,\n        file: &'static str,\n        line: u32,\n        column: u32,\n        index: usize,\n    ) -> Self {\n        Self {\n            constructor,\n            key: GlobalKey::File {\n                file,\n                line: line as _,\n                column: column as _,\n                index: index as _,\n            },\n            phantom: std::marker::PhantomData,\n        }\n    }\n\n    /// Get the key for this global\n    pub fn key(&self) -> GlobalKey<'static> {\n        self.key.clone()\n    }\n\n    /// Resolve the global value. This will try to get the existing value from the current virtual dom, and if it doesn't exist, it will create a new one.\n    // NOTE: This is not called \"get\" or \"value\" because those methods overlap with Readable and Writable\n    pub fn resolve(&self) -> T\n    where\n        T: 'static,\n    {\n        let key = self.key();\n\n        let context = get_global_context();\n\n        // Get the entry if it already exists\n        let mut evicted_stale_entry = false;\n        {\n            let read = context.map.borrow();\n            if let Some(signal) = read.get(&key) {\n                if let Some(signal) = signal.downcast_ref::<T>() {\n                    return signal.clone();\n                }\n                evicted_stale_entry = true;\n            }\n        }\n\n        if evicted_stale_entry {\n            context.map.borrow_mut().remove(&key);\n        }\n        // Otherwise, create it\n        // Constructors are always run in the root scope\n        let signal = dioxus_core::Runtime::current().in_scope(ScopeId::ROOT, || {\n            T::initialize_from_function(self.constructor)\n        });\n        context\n            .map\n            .borrow_mut()\n            .insert(key, Box::new(signal.clone()));\n        signal\n    }\n\n    /// Get the scope the signal was created in.\n    pub fn origin_scope(&self) -> ScopeId {\n        ScopeId::ROOT\n    }\n}\n\n/// The context for global signals\n#[derive(Clone, Default)]\npub struct GlobalLazyContext {\n    map: Rc<RefCell<HashMap<GlobalKey<'static>, Box<dyn Any>>>>,\n}\n\n/// A key used to identify a signal in the global signal context\n#[derive(Clone, Debug, PartialEq, Eq, Hash)]\npub enum GlobalKey<'a> {\n    /// A key derived from a `std::panic::Location` type\n    File {\n        /// The file name\n        file: &'a str,\n\n        /// The line number\n        line: u32,\n\n        /// The column number\n        column: u32,\n\n        /// The index of the signal in the file - used to disambiguate macro calls\n        index: u32,\n    },\n\n    /// A raw key derived just from a string\n    Raw(&'a str),\n}\n\nimpl<'a> GlobalKey<'a> {\n    /// Create a new key from a location\n    pub const fn new(key: &'a Location<'a>) -> Self {\n        GlobalKey::File {\n            file: key.file(),\n            line: key.line(),\n            column: key.column(),\n            index: 0,\n        }\n    }\n}\n\nimpl From<&'static Location<'static>> for GlobalKey<'static> {\n    fn from(key: &'static Location<'static>) -> Self {\n        Self::new(key)\n    }\n}\n\nimpl GlobalLazyContext {\n    /// Get a signal with the given string key\n    /// The key will be converted to a UUID with the appropriate internal namespace\n    pub fn get_signal_with_key<T: 'static>(&self, key: GlobalKey) -> Option<Signal<T>> {\n        self.map.borrow().get(&key).map(|f| {\n            *f.downcast_ref::<Signal<T>>().unwrap_or_else(|| {\n                panic!(\n                    \"Global signal with key {:?} is not of the expected type. Keys are {:?}\",\n                    key,\n                    self.map.borrow().keys()\n                )\n            })\n        })\n    }\n\n    #[doc(hidden)]\n    /// Clear all global signals of a given type.\n    pub fn clear<T: 'static>(&self) {\n        self.map.borrow_mut().retain(|_k, v| !v.is::<T>());\n    }\n}\n\n/// Get the global context for signals\npub fn get_global_context() -> GlobalLazyContext {\n    let rt = Runtime::current();\n    match rt.has_context(ScopeId::ROOT) {\n        Some(context) => context,\n        None => rt.provide_context(ScopeId::ROOT, Default::default()),\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    /// Test that keys of global signals are correctly generated and different from one another.\n    /// We don't want signals to merge, but we also want them to use both string IDs and memory addresses.\n    #[test]\n    fn test_global_keys() {\n        // we're using consts since it's harder than statics due to merging - these won't be merged\n        const MYSIGNAL: GlobalSignal<i32> = GlobalSignal::new(|| 42);\n        const MYSIGNAL2: GlobalSignal<i32> = GlobalSignal::new(|| 42);\n        const MYSIGNAL3: GlobalSignal<i32> = GlobalSignal::with_name(|| 42, \"custom-keyed\");\n\n        let a = MYSIGNAL.key();\n        let b = MYSIGNAL.key();\n        let c = MYSIGNAL.key();\n        assert_eq!(a, b);\n        assert_eq!(b, c);\n\n        let d = MYSIGNAL2.key();\n        assert_ne!(a, d);\n\n        let e = MYSIGNAL3.key();\n        assert_ne!(a, e);\n    }\n}\n"
  },
  {
    "path": "packages/signals/src/global/signal.rs",
    "content": "use super::{Global, InitializeFromFunction};\nuse crate::read::ReadableExt;\nuse crate::read_impls;\nuse crate::Signal;\n\nimpl<T: 'static> InitializeFromFunction<T> for Signal<T> {\n    fn initialize_from_function(f: fn() -> T) -> Self {\n        Signal::new(f())\n    }\n}\n\n/// A signal that can be accessed from anywhere in the application and created in a static\npub type GlobalSignal<T> = Global<Signal<T>, T>;\n\nimpl<T: 'static> GlobalSignal<T> {\n    /// Get the generational id of the signal.\n    pub fn id(&self) -> generational_box::GenerationalBoxId {\n        self.resolve().id()\n    }\n\n    /// Resolve the global signal. This will try to get the existing value from the current virtual dom, and if it doesn't exist, it will create a new one.\n    pub fn signal(&self) -> Signal<T> {\n        self.resolve()\n    }\n}\n\nread_impls!(GlobalSignal<T>);\n"
  },
  {
    "path": "packages/signals/src/impls.rs",
    "content": "/// This macro is used to generate a `impl Default` block for any type with the function new_maybe_sync that takes a generic `T`\n///\n/// # Example\n/// ```rust\n/// use generational_box::*;\n/// use dioxus::prelude::*;\n/// use dioxus_core::Subscribers;\n///\n/// struct MyCopyValue<T, S: 'static> {\n///     value: CopyValue<T, S>,\n/// }\n///\n/// impl<T: 'static, S: Storage<T> + 'static> MyCopyValue<T, S> {\n///     fn new_maybe_sync(value: T) -> Self {\n///         Self { value: CopyValue::new_maybe_sync(value) }\n///     }\n/// }\n///\n/// impl<T, S: Storage<T> + 'static> Readable for MyCopyValue<T, S> {\n///     type Target = T;\n///     type Storage = S;\n///\n///     fn try_read_unchecked(\n///         &self,\n///     ) -> Result<ReadableRef<'static, Self>, generational_box::BorrowError> where T: 'static {\n///         self.value.try_read_unchecked()\n///     }\n///\n///     fn try_peek_unchecked(\n///         &self,\n///     ) -> Result<ReadableRef<'static, Self>, generational_box::BorrowError> where T: 'static {\n///         self.value.try_read_unchecked()\n///     }\n///\n///     fn subscribers(&self) -> Subscribers where T: 'static {\n///         self.value.subscribers()\n///     }\n/// }\n///\n/// default_impl!(MyCopyValue<T, S: Storage<T>>);\n/// ```\n#[macro_export]\nmacro_rules! default_impl {\n    (\n        $ty:ident\n        // Accept generics\n        < T $(, $gen:ident $(: $gen_bound:path)?)* $(,)?>\n        // Accept extra bounds\n        $(\n            where\n                $(\n                    $extra_bound_ty:ident: $extra_bound:path\n                ),+\n        )?\n    ) => {\n        impl<T: Default + 'static\n            $(, $gen $(: $gen_bound)?)*\n        > Default for $ty <T $(, $gen)*>\n        $(\n            where\n                $(\n                    $extra_bound_ty: $extra_bound\n                ),+\n        )?\n        {\n            #[track_caller]\n            fn default() -> Self {\n                Self::new_maybe_sync(Default::default())\n            }\n        }\n    }\n}\n\n/// This macro is used to generate `impl Display`, `impl Debug`, `impl PartialEq`, and `impl Eq` blocks for any Readable type that takes a generic `T`\n///\n/// # Example\n/// ```rust\n/// use generational_box::*;\n/// use dioxus::prelude::*;\n/// use dioxus_core::Subscribers;\n///\n/// struct MyCopyValue<T, S: 'static> {\n///     value: CopyValue<T, S>,\n/// }\n///\n/// impl<T: 'static, S: Storage<T> + 'static> Readable for MyCopyValue<T, S> where T: 'static {\n///     type Target = T;\n///     type Storage = S;\n///\n///     fn try_read_unchecked(\n///         &self,\n///     ) -> Result<ReadableRef<'static, Self>, generational_box::BorrowError> where T: 'static {\n///         self.value.try_read_unchecked()\n///     }\n///\n///     fn try_peek_unchecked(\n///         &self,\n///     ) -> Result<ReadableRef<'static, Self>, generational_box::BorrowError> where T: 'static {\n///         self.value.try_read_unchecked()\n///     }\n///\n///     fn subscribers(&self) -> Subscribers where T: 'static {\n///         self.value.subscribers()\n///     }\n/// }\n///\n/// read_impls!(MyCopyValue<T, S: Storage<T>>);\n/// ```\n#[macro_export]\nmacro_rules! read_impls {\n    (\n        $ty:ident\n        // Accept generics\n        < T $(, $gen:ident $(: $gen_bound:path)?)* $(,)?>\n        // Accept extra bounds\n        $(\n            where\n                $(\n                    $extra_bound_ty:ident: $extra_bound:path\n                ),+\n        )?\n    ) => {\n        $crate::fmt_impls!{\n            $ty<\n                T\n                $(\n                    , $gen\n                    $(: $gen_bound)?\n                )*\n            >\n            $(\n                where\n                    $($extra_bound_ty: $extra_bound),*\n            )?\n        }\n        $crate::eq_impls!{\n            $ty<\n                T\n                $(\n                    , $gen\n                    $(: $gen_bound)?\n                )*\n            >\n            $(\n                where\n                    $($extra_bound_ty: $extra_bound),*\n            )?\n        }\n    };\n}\n\n/// This macro is used to generate `impl Display`, and `impl Debug` blocks for any Readable type that takes a generic `T`\n///\n/// # Example\n/// ```rust\n/// use generational_box::*;\n/// use dioxus::prelude::*;\n/// use dioxus_core::Subscribers;\n///\n/// struct MyCopyValue<T, S: 'static> {\n///     value: CopyValue<T, S>,\n/// }\n///\n/// impl<T: 'static, S: Storage<T> + 'static> Readable for MyCopyValue<T, S> {\n///     type Target = T;\n///     type Storage = S;\n///\n///     fn try_read_unchecked(\n///         &self,\n///     ) -> Result<ReadableRef<'static, Self>, generational_box::BorrowError> where T: 'static {\n///         self.value.try_read_unchecked()\n///     }\n///\n///     fn try_peek_unchecked(\n///         &self,\n///     ) -> Result<ReadableRef<'static, Self>, generational_box::BorrowError> where T: 'static {\n///         self.value.try_read_unchecked()\n///     }\n///\n///     fn subscribers(&self) -> Subscribers where T: 'static {\n///         self.value.subscribers()\n///     }\n/// }\n///\n/// fmt_impls!(MyCopyValue<T, S: Storage<T>>);\n/// ```\n#[macro_export]\nmacro_rules! fmt_impls {\n    (\n        $ty:ident\n        // Accept generics\n        < T $(, $gen:ident $(: $gen_bound:path)?)* $(,)?>\n        // Accept extra bounds\n        $(\n            where\n                $(\n                    $extra_bound_ty:ident: $extra_bound:path\n                ),+\n        )?\n    ) => {\n    impl<\n        T: std::fmt::Display + 'static\n        $(, $gen $(: $gen_bound)?)*\n    > std::fmt::Display for $ty<T $(, $gen)*>\n        $(\n            where\n                $($extra_bound_ty: $extra_bound,)*\n        )?\n    {\n        #[track_caller]\n        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n            self.with(|v| std::fmt::Display::fmt(v, f))\n        }\n    }\n\n    impl<\n        T: std::fmt::Debug + 'static\n        $(, $gen $(: $gen_bound)?)*\n    > std::fmt::Debug for $ty<T $(, $gen)*>\n        $(\n            where\n                $($extra_bound_ty: $extra_bound,)*\n        )?\n    {\n        #[track_caller]\n        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n            self.with(|v| std::fmt::Debug::fmt(v, f))\n        }\n    }\n};\n    }\n\n/// This macro is used to generate `impl PartialEq` blocks for any Readable type that takes a generic `T`\n///\n/// # Example\n/// ```rust\n/// use generational_box::*;\n/// use dioxus::prelude::*;\n/// use dioxus_core::Subscribers;\n///\n/// struct MyCopyValue<T, S: 'static> {\n///     value: CopyValue<T, S>,\n/// }\n///\n/// impl<T: 'static, S: Storage<T> + 'static> Readable for MyCopyValue<T, S> {\n///     type Target = T;\n///     type Storage = S;\n///\n///     fn try_read_unchecked(\n///         &self,\n///     ) -> Result<ReadableRef<'static, Self>, generational_box::BorrowError> where T: 'static {\n///         self.value.try_read_unchecked()\n///     }\n///\n///     fn try_peek_unchecked(\n///         &self,\n///     ) -> Result<ReadableRef<'static, Self>, generational_box::BorrowError> where T: 'static {\n///         self.value.try_read_unchecked()\n///     }\n///\n///     fn subscribers(&self) -> Subscribers where T: 'static {\n///         self.value.subscribers()\n///     }\n/// }\n///\n/// eq_impls!(MyCopyValue<T, S: Storage<T>>);\n/// ```\n#[macro_export]\nmacro_rules! eq_impls {\n    (\n        $ty:ident\n        // Accept generics\n        < T $(, $gen:ident $(: $gen_bound:path)?)* $(,)?>\n        // Accept extra bounds\n        $(\n            where\n                $(\n                    $extra_bound_ty:ident: $extra_bound:path\n                ),+\n        )?\n    ) => {\n        impl<\n            T: PartialEq + 'static\n            $(, $gen $(: $gen_bound)?)*\n        > PartialEq<T> for $ty<T $(, $gen)*>\n            $(\n                where\n                    $($extra_bound_ty: $extra_bound,)*\n            )?\n        {\n            #[track_caller]\n            fn eq(&self, other: &T) -> bool {\n                self.with(|v| *v == *other)\n            }\n        }\n    };\n}\n\n/// This macro is used to generate `impl Add`, `impl AddAssign`, `impl Sub`, `impl SubAssign`, `impl Mul`, `impl MulAssign`, `impl Div`, and `impl DivAssign` blocks for any Writable type that takes a generic `T`\n///\n/// # Example\n/// ```rust, ignore\n/// use generational_box::*;\n/// use dioxus::prelude::*;\n///\n/// struct MyCopyValue<T, S: 'static> {\n///     value: CopyValue<T, S>,\n/// }\n///\n/// impl<T: 'static, S: Storage<T> + 'static> Readable for MyCopyValue<T, S> {\n///     type Target = T;\n///     type Storage = S;\n///\n///     fn try_read_unchecked(\n///         &self,\n///     ) -> Result<ReadableRef<'static, Self>, generational_box::BorrowError> where T: 'static {\n///         self.value.try_read_unchecked()\n///     }\n///\n///     fn peek_unchecked(&self) -> ReadableRef<'static, Self> where T: 'static {\n///         self.value.read_unchecked()\n///     }\n/// }\n///\n/// impl<T: 'static, S: Storage<T> + 'static> Writable for MyCopyValue<T, S> {\n///     fn try_write_unchecked(\n///         &self,\n///     ) -> Result<WritableRef<'static, Self>, generational_box::BorrowMutError> where T: 'static {\n///         self.value.try_write_unchecked()\n///     }\n///\n///     //...\n/// }\n///\n/// write_impls!(MyCopyValue<T, S: Storage<T>>);\n/// ```\n#[macro_export]\nmacro_rules! write_impls {\n    (\n        $ty:ident\n        // Accept generics\n        < T $(, $gen:ident $(: $gen_bound:path)?)* $(,)?>\n        // Accept extra bounds\n        $(\n            where\n                $(\n                    $extra_bound_ty:ident: $extra_bound:path\n                ),+\n        )?) => {\n        impl<T: std::ops::Add<Output = T> + Copy + 'static\n        $(, $gen $(: $gen_bound)?)*\n        > std::ops::Add<T>\n            for $ty<T $(, $gen)*>\n            $(\n                where\n                    $($extra_bound_ty: $extra_bound,)*\n            )?\n        {\n            type Output = T;\n\n            #[track_caller]\n            fn add(self, rhs: T) -> Self::Output {\n                self.with(|v| *v + rhs)\n            }\n        }\n\n        impl<T: std::ops::Add<Output = T> + Copy + 'static\n        $(, $gen $(: $gen_bound)?)*\n        > std::ops::AddAssign<T>\n            for $ty<T $(, $gen)*>\n            $(\n                where\n                    $($extra_bound_ty: $extra_bound,)*\n            )?\n        {\n            #[track_caller]\n            fn add_assign(&mut self, rhs: T) {\n                self.with_mut(|v| *v = *v + rhs)\n            }\n        }\n\n        impl<T: std::ops::Sub<Output = T> + Copy + 'static\n        $(, $gen $(: $gen_bound)?)*\n        > std::ops::SubAssign<T>\n            for $ty<T $(, $gen)*>\n            $(\n                where\n                    $($extra_bound_ty: $extra_bound,)*\n            )?\n        {\n            #[track_caller]\n            fn sub_assign(&mut self, rhs: T) {\n                self.with_mut(|v| *v = *v - rhs)\n            }\n        }\n\n        impl<T: std::ops::Sub<Output = T> + Copy + 'static\n        $(, $gen $(: $gen_bound)?)*\n        > std::ops::Sub<T>\n            for $ty<T $(, $gen)*>\n            $(\n                where\n                    $($extra_bound_ty: $extra_bound,)*\n            )?\n        {\n            type Output = T;\n\n            #[track_caller]\n            fn sub(self, rhs: T) -> Self::Output {\n                self.with(|v| *v - rhs)\n            }\n        }\n\n        impl<T: std::ops::Mul<Output = T> + Copy + 'static\n        $(, $gen $(: $gen_bound)?)*\n        > std::ops::MulAssign<T>\n            for $ty<T $(, $gen)*>\n            $(\n                where\n                    $($extra_bound_ty: $extra_bound,)*\n            )?\n        {\n            #[track_caller]\n            fn mul_assign(&mut self, rhs: T) {\n                self.with_mut(|v| *v = *v * rhs)\n            }\n        }\n\n        impl<T: std::ops::Mul<Output = T> + Copy + 'static\n        $(, $gen $(: $gen_bound)?)*\n        > std::ops::Mul<T>\n            for $ty<T $(, $gen)*>\n            $(\n                where\n                    $($extra_bound_ty: $extra_bound,)*\n            )?\n        {\n            type Output = T;\n\n            #[track_caller]\n            fn mul(self, rhs: T) -> Self::Output {\n                self.with(|v| *v * rhs)\n            }\n        }\n\n        impl<T: std::ops::Div<Output = T> + Copy + 'static\n        $(, $gen $(: $gen_bound)?)*\n        > std::ops::DivAssign<T>\n            for $ty<T $(, $gen)*>\n            $(\n                where\n                    $($extra_bound_ty: $extra_bound,)*\n            )?\n        {\n            #[track_caller]\n            fn div_assign(&mut self, rhs: T) {\n                self.with_mut(|v| *v = *v / rhs)\n            }\n        }\n\n        impl<T: std::ops::Div<Output = T> + Copy + 'static\n        $(, $gen $(: $gen_bound)?)*\n        > std::ops::Div<T>\n            for $ty<T $(, $gen)*>\n            $(\n                where\n                    $($extra_bound_ty: $extra_bound,)*\n            )?\n        {\n            type Output = T;\n\n            #[track_caller]\n            fn div(self, rhs: T) -> Self::Output {\n                self.with(|v| *v / rhs)\n            }\n        }\n    };\n}\n"
  },
  {
    "path": "packages/signals/src/lib.rs",
    "content": "#![doc = include_str!(\"../README.md\")]\n#![doc(html_logo_url = \"https://avatars.githubusercontent.com/u/79236386\")]\n#![doc(html_favicon_url = \"https://avatars.githubusercontent.com/u/79236386\")]\n#![warn(missing_docs)]\n#![allow(clippy::type_complexity)]\n\nmod copy_value;\npub use copy_value::*;\n\npub(crate) mod signal;\npub use signal::*;\n\nmod map;\npub use map::*;\n\nmod map_mut;\npub use map_mut::*;\n\nmod set_compare;\npub use set_compare::*;\n\nmod memo;\npub use memo::*;\n\nmod global;\npub use global::*;\n\nmod impls;\n\npub use generational_box::{\n    AnyStorage, BorrowError, BorrowMutError, Owner, Storage, SyncStorage, UnsyncStorage,\n};\n\nmod read;\npub use read::*;\n\nmod write;\npub use write::*;\n\nmod props;\npub use props::*;\n\npub mod warnings;\n\nmod boxed;\npub use boxed::*;\n\n/// A macro to define extension methods for signal types that call the method with either `with` or `with_mut` depending on the mutability of self.\nmacro_rules! ext_methods {\n    (\n        $(\n            $(#[$meta:meta])*\n            fn $name:ident $(<$($gen:tt),*>)? (&$($self:ident)+ $(, $arg_name:ident: $arg_type:ty )* ) $(-> $ret:ty)? = $expr:expr;\n        )*\n    ) => {\n        $(\n            $(#[$meta])*\n            #[track_caller]\n            fn $name$(<$($gen),*>)? (& $($self)+ $(, $arg_name: $arg_type )* ) $(-> $ret)?\n            {\n                ext_methods!(@with $($self)+, $($arg_name),*; $expr)\n            }\n        )*\n    };\n\n    (@with mut $self:ident, $($arg_name:ident),*; $expr:expr) => {\n        $self.with_mut(|_self| ($expr)(_self, $($arg_name),*))\n    };\n\n    (@with $self:ident, $($arg_name:ident),*; $expr:expr) => {\n        $self.with(|_self| ($expr)(_self, $($arg_name),*))\n    };\n}\n\npub(crate) use ext_methods;\n"
  },
  {
    "path": "packages/signals/src/map.rs",
    "content": "use crate::{read::Readable, read_impls, ReadableExt, ReadableRef};\nuse dioxus_core::{IntoAttributeValue, Subscribers};\nuse generational_box::{AnyStorage, BorrowResult};\nuse std::ops::Deref;\n\n/// A read only signal that has been mapped to a new type.\npub struct MappedSignal<O: ?Sized, V, F = fn(&<V as Readable>::Target) -> &O> {\n    value: V,\n    map_fn: F,\n    _marker: std::marker::PhantomData<O>,\n}\n\nimpl<V, O, F> Clone for MappedSignal<O, V, F>\nwhere\n    V: Readable + Clone,\n    F: Clone,\n{\n    fn clone(&self) -> Self {\n        MappedSignal {\n            value: self.value.clone(),\n            map_fn: self.map_fn.clone(),\n            _marker: std::marker::PhantomData,\n        }\n    }\n}\n\nimpl<V, O, F> Copy for MappedSignal<O, V, F>\nwhere\n    V: Readable + Copy,\n    F: Copy,\n{\n}\n\nimpl<V, O, F> MappedSignal<O, V, F>\nwhere\n    O: ?Sized,\n{\n    /// Create a new mapped signal.\n    pub fn new(value: V, map_fn: F) -> Self {\n        MappedSignal {\n            value,\n            map_fn,\n            _marker: std::marker::PhantomData,\n        }\n    }\n}\n\nimpl<V, O, F> Readable for MappedSignal<O, V, F>\nwhere\n    O: ?Sized,\n    V: Readable,\n    V::Target: 'static,\n    F: Fn(&V::Target) -> &O,\n{\n    type Target = O;\n    type Storage = V::Storage;\n\n    fn try_read_unchecked(\n        &self,\n    ) -> Result<ReadableRef<'static, Self>, generational_box::BorrowError> {\n        let value = self.value.try_read_unchecked()?;\n        Ok(V::Storage::map(value, |v| (self.map_fn)(v)))\n    }\n\n    fn try_peek_unchecked(&self) -> BorrowResult<ReadableRef<'static, Self>> {\n        let value = self.value.try_peek_unchecked()?;\n        Ok(V::Storage::map(value, |v| (self.map_fn)(v)))\n    }\n\n    fn subscribers(&self) -> Subscribers {\n        self.value.subscribers()\n    }\n}\n\nimpl<V, O, F> IntoAttributeValue for MappedSignal<O, V, F>\nwhere\n    O: Clone + IntoAttributeValue + 'static,\n    V: Readable,\n    V::Target: 'static,\n    F: Fn(&V::Target) -> &O,\n{\n    fn into_value(self) -> dioxus_core::AttributeValue {\n        self.with(|f| f.clone().into_value())\n    }\n}\n\nimpl<V, O, F> PartialEq for MappedSignal<O, V, F>\nwhere\n    O: ?Sized,\n    V: Readable + PartialEq,\n    F: PartialEq,\n{\n    fn eq(&self, other: &Self) -> bool {\n        self.value == other.value && self.map_fn == other.map_fn\n    }\n}\n\n/// Allow calling a signal with signal() syntax\n///\n/// Currently only limited to clone types, though could probably specialize for string/arc/rc\nimpl<V, O, F> Deref for MappedSignal<O, V, F>\nwhere\n    O: Clone + 'static,\n    V: Readable + 'static,\n    F: Fn(&V::Target) -> &O + 'static,\n{\n    type Target = dyn Fn() -> O;\n\n    fn deref(&self) -> &Self::Target {\n        unsafe { ReadableExt::deref_impl(self) }\n    }\n}\n\nread_impls!(MappedSignal<T, V, F> where V: Readable<Target: 'static>, F: Fn(&V::Target) -> &T);\n"
  },
  {
    "path": "packages/signals/src/map_mut.rs",
    "content": "use std::ops::Deref;\n\nuse crate::{\n    read_impls, write_impls, Readable, ReadableExt, ReadableRef, Writable, WritableExt,\n    WritableRef, WriteLock,\n};\nuse dioxus_core::{IntoAttributeValue, Subscribers};\nuse generational_box::{AnyStorage, BorrowResult};\n\n/// A read only signal that has been mapped to a new type.\npub struct MappedMutSignal<\n    O: ?Sized,\n    V,\n    F = fn(&<V as Readable>::Target) -> &O,\n    FMut = fn(&mut <V as Readable>::Target) -> &mut O,\n> {\n    value: V,\n    map_fn: F,\n    map_fn_mut: FMut,\n    _marker: std::marker::PhantomData<O>,\n}\n\nimpl<V, O, F, FMut> Clone for MappedMutSignal<O, V, F, FMut>\nwhere\n    V: Clone,\n    F: Clone,\n    FMut: Clone,\n{\n    fn clone(&self) -> Self {\n        MappedMutSignal {\n            value: self.value.clone(),\n            map_fn: self.map_fn.clone(),\n            map_fn_mut: self.map_fn_mut.clone(),\n            _marker: std::marker::PhantomData,\n        }\n    }\n}\n\nimpl<V, O, F, FMut> Copy for MappedMutSignal<O, V, F, FMut>\nwhere\n    V: Copy,\n    F: Copy,\n    FMut: Copy,\n{\n}\n\nimpl<V, O, F, FMut> MappedMutSignal<O, V, F, FMut>\nwhere\n    O: ?Sized,\n{\n    /// Create a new mapped signal.\n    pub fn new(value: V, map_fn: F, map_fn_mut: FMut) -> Self {\n        MappedMutSignal {\n            value,\n            map_fn,\n            map_fn_mut,\n            _marker: std::marker::PhantomData,\n        }\n    }\n}\n\nimpl<V, O, F, FMut> Readable for MappedMutSignal<O, V, F, FMut>\nwhere\n    O: ?Sized,\n    V: Readable,\n    V::Target: 'static,\n    F: Fn(&V::Target) -> &O,\n{\n    type Target = O;\n    type Storage = V::Storage;\n\n    fn try_read_unchecked(\n        &self,\n    ) -> Result<ReadableRef<'static, Self>, generational_box::BorrowError>\n    where\n        O: 'static,\n    {\n        let value = self.value.try_read_unchecked()?;\n        Ok(V::Storage::map(value, |v| (self.map_fn)(v)))\n    }\n\n    fn try_peek_unchecked(&self) -> BorrowResult<ReadableRef<'static, Self>>\n    where\n        O: 'static,\n    {\n        let value = self.value.try_peek_unchecked()?;\n        Ok(V::Storage::map(value, |v| (self.map_fn)(v)))\n    }\n\n    fn subscribers(&self) -> Subscribers\n    where\n        O: 'static,\n    {\n        self.value.subscribers()\n    }\n}\n\nimpl<V, O, F, FMut> Writable for MappedMutSignal<O, V, F, FMut>\nwhere\n    O: ?Sized,\n    V: Writable,\n    V::Target: 'static,\n    F: Fn(&V::Target) -> &O,\n    FMut: Fn(&mut V::Target) -> &mut O,\n{\n    type WriteMetadata = V::WriteMetadata;\n\n    fn try_write_unchecked(\n        &self,\n    ) -> Result<WritableRef<'static, Self>, generational_box::BorrowMutError> {\n        let value = self.value.try_write_unchecked()?;\n        Ok(WriteLock::map(value, |v| (self.map_fn_mut)(v)))\n    }\n}\n\nimpl<V, O, F, FMut> IntoAttributeValue for MappedMutSignal<O, V, F, FMut>\nwhere\n    O: Clone + IntoAttributeValue + 'static,\n    V: Readable,\n    V::Target: 'static,\n    F: Fn(&V::Target) -> &O,\n{\n    fn into_value(self) -> dioxus_core::AttributeValue {\n        self.with(|f| f.clone().into_value())\n    }\n}\n\nimpl<V, O, F, FMut> PartialEq for MappedMutSignal<O, V, F, FMut>\nwhere\n    O: ?Sized,\n    V: Readable + PartialEq,\n    F: PartialEq,\n    FMut: PartialEq,\n{\n    fn eq(&self, other: &Self) -> bool {\n        self.value == other.value\n            && self.map_fn == other.map_fn\n            && self.map_fn_mut == other.map_fn_mut\n    }\n}\n\n/// Allow calling a signal with signal() syntax\n///\n/// Currently only limited to clone types, though could probably specialize for string/arc/rc\nimpl<V, O, F, FMut> Deref for MappedMutSignal<O, V, F, FMut>\nwhere\n    O: Clone + 'static,\n    V: Readable + 'static,\n    V::Target: 'static,\n    F: Fn(&V::Target) -> &O + 'static,\n    FMut: 'static,\n{\n    type Target = dyn Fn() -> O;\n\n    fn deref(&self) -> &Self::Target {\n        unsafe { ReadableExt::deref_impl(self) }\n    }\n}\n\nread_impls!(MappedMutSignal<T, V, F, FMut> where V: Readable<Target: 'static>, F: Fn(&V::Target) -> &T);\nwrite_impls!(MappedMutSignal<T, V, F, FMut> where V: Writable<Target: 'static>, F: Fn(&V::Target) -> &T, FMut: Fn(&mut V::Target) -> &mut T);\n"
  },
  {
    "path": "packages/signals/src/memo.rs",
    "content": "use crate::{read::Readable, write_impls, ReadableRef, Signal};\nuse crate::{read_impls, GlobalMemo, ReadableExt, WritableExt};\nuse crate::{CopyValue, Writable};\nuse std::{\n    cell::RefCell,\n    ops::Deref,\n    sync::{atomic::AtomicBool, Arc},\n};\n\nuse dioxus_core::{\n    current_scope_id, spawn_isomorphic, IntoAttributeValue, IntoDynNode, ReactiveContext, ScopeId,\n    Subscribers,\n};\nuse futures_util::StreamExt;\nuse generational_box::{AnyStorage, BorrowResult, UnsyncStorage};\n\nstruct UpdateInformation<T> {\n    dirty: Arc<AtomicBool>,\n    callback: RefCell<Box<dyn FnMut() -> T>>,\n}\n\n#[doc = include_str!(\"../docs/memo.md\")]\n#[doc(alias = \"Selector\")]\n#[doc(alias = \"UseMemo\")]\n#[doc(alias = \"Memorize\")]\npub struct Memo<T> {\n    inner: Signal<T>,\n    update: CopyValue<UpdateInformation<T>>,\n}\n\nimpl<T> Memo<T> {\n    /// Create a new memo\n    #[track_caller]\n    pub fn new(f: impl FnMut() -> T + 'static) -> Self\n    where\n        T: PartialEq + 'static,\n    {\n        Self::new_with_location(f, std::panic::Location::caller())\n    }\n\n    /// Create a new memo with an explicit location\n    pub fn new_with_location(\n        mut f: impl FnMut() -> T + 'static,\n        location: &'static std::panic::Location<'static>,\n    ) -> Self\n    where\n        T: PartialEq + 'static,\n    {\n        let dirty = Arc::new(AtomicBool::new(false));\n        let (tx, mut rx) = futures_channel::mpsc::unbounded();\n\n        let callback = {\n            let dirty = dirty.clone();\n            move || {\n                dirty.store(true, std::sync::atomic::Ordering::Relaxed);\n                let _ = tx.unbounded_send(());\n            }\n        };\n        let rc = ReactiveContext::new_with_callback(callback, current_scope_id(), location);\n\n        // Create a new signal in that context, wiring up its dependencies and subscribers\n        let mut recompute = move || rc.reset_and_run_in(&mut f);\n        let value = recompute();\n        let recompute = RefCell::new(Box::new(recompute) as Box<dyn FnMut() -> T>);\n        let update = CopyValue::new(UpdateInformation {\n            dirty,\n            callback: recompute,\n        });\n        let state: Signal<T> = Signal::new_with_caller(value, location);\n\n        let memo = Memo {\n            inner: state,\n            update,\n        };\n\n        spawn_isomorphic(async move {\n            while rx.next().await.is_some() {\n                // Remove any pending updates\n                while rx.try_next().is_ok() {}\n                memo.recompute();\n            }\n        });\n\n        memo\n    }\n\n    /// Creates a new [`GlobalMemo`] that can be used anywhere inside your dioxus app. This memo will automatically be created once per app the first time you use it.\n    ///\n    /// # Example\n    /// ```rust, no_run\n    /// # use dioxus::prelude::*;\n    /// static SIGNAL: GlobalSignal<i32> = Signal::global(|| 0);\n    /// // Create a new global memo that can be used anywhere in your app\n    /// static DOUBLED: GlobalMemo<i32> = Memo::global(|| SIGNAL() * 2);\n    ///\n    /// fn App() -> Element {\n    ///     rsx! {\n    ///         button {\n    ///             // When SIGNAL changes, the memo will update because the SIGNAL is read inside DOUBLED\n    ///             onclick: move |_| *SIGNAL.write() += 1,\n    ///             \"{DOUBLED}\"\n    ///         }\n    ///     }\n    /// }\n    /// ```\n    ///\n    /// <div class=\"warning\">\n    ///\n    /// Global memos are generally not recommended for use in libraries because it makes it more difficult to allow multiple instances of components you define in your library.\n    ///\n    /// </div>\n    #[track_caller]\n    pub const fn global(constructor: fn() -> T) -> GlobalMemo<T>\n    where\n        T: PartialEq + 'static,\n    {\n        GlobalMemo::new(constructor)\n    }\n\n    /// Rerun the computation and update the value of the memo if the result has changed.\n    #[tracing::instrument(skip(self))]\n    fn recompute(&self)\n    where\n        T: PartialEq + 'static,\n    {\n        let mut update_copy = self.update;\n        let update_write = update_copy.write();\n        let peak = self.inner.peek();\n        let new_value = (update_write.callback.borrow_mut())();\n        if new_value != *peak {\n            drop(peak);\n            let mut copy = self.inner;\n            copy.set(new_value);\n        }\n        // Always mark the memo as no longer dirty even if the value didn't change\n        update_write\n            .dirty\n            .store(false, std::sync::atomic::Ordering::Relaxed);\n    }\n\n    /// Get the scope that the signal was created in.\n    pub fn origin_scope(&self) -> ScopeId\n    where\n        T: 'static,\n    {\n        self.inner.origin_scope()\n    }\n\n    /// Get the id of the signal.\n    pub fn id(&self) -> generational_box::GenerationalBoxId\n    where\n        T: 'static,\n    {\n        self.inner.id()\n    }\n}\n\nimpl<T> Readable for Memo<T>\nwhere\n    T: PartialEq,\n{\n    type Target = T;\n    type Storage = UnsyncStorage;\n\n    #[track_caller]\n    fn try_read_unchecked(\n        &self,\n    ) -> Result<ReadableRef<'static, Self>, generational_box::BorrowError>\n    where\n        T: 'static,\n    {\n        // Read the inner generational box instead of the signal so we have more fine grained control over exactly when the subscription happens\n        let read = self.inner.inner.try_read_unchecked()?;\n\n        let needs_update = self\n            .update\n            .read()\n            .dirty\n            .swap(false, std::sync::atomic::Ordering::Relaxed);\n        let result = if needs_update {\n            drop(read);\n            // We shouldn't be subscribed to the value here so we don't trigger the scope we are currently in to rerun even though that scope got the latest value because we synchronously update the value: https://github.com/DioxusLabs/dioxus/issues/2416\n            self.recompute();\n            self.inner.inner.try_read_unchecked()\n        } else {\n            Ok(read)\n        };\n        // Subscribe to the current scope before returning the value\n        if let Ok(read) = &result {\n            if let Some(reactive_context) = ReactiveContext::current() {\n                tracing::trace!(\"Subscribing to the reactive context {}\", reactive_context);\n                reactive_context.subscribe(read.subscribers.clone());\n            }\n        }\n        result.map(|read| <UnsyncStorage as AnyStorage>::map(read, |v| &v.value))\n    }\n\n    /// Get the current value of the signal. **Unlike read, this will not subscribe the current scope to the signal which can cause parts of your UI to not update.**\n    ///\n    /// If the signal has been dropped, this will panic.\n    #[track_caller]\n    fn try_peek_unchecked(&self) -> BorrowResult<ReadableRef<'static, Self>>\n    where\n        T: 'static,\n    {\n        self.inner.try_peek_unchecked()\n    }\n\n    fn subscribers(&self) -> Subscribers\n    where\n        T: 'static,\n    {\n        self.inner.subscribers()\n    }\n}\n\nimpl<T: 'static + PartialEq> Writable for Memo<T> {\n    type WriteMetadata = <Signal<T> as Writable>::WriteMetadata;\n\n    fn try_write_unchecked(\n        &self,\n    ) -> Result<crate::WritableRef<'static, Self>, generational_box::BorrowMutError>\n    where\n        Self::Target: 'static,\n    {\n        self.inner.try_write_unchecked()\n    }\n}\n\nimpl<T> IntoAttributeValue for Memo<T>\nwhere\n    T: Clone + IntoAttributeValue + PartialEq + 'static,\n{\n    fn into_value(self) -> dioxus_core::AttributeValue {\n        self.with(|f| f.clone().into_value())\n    }\n}\n\nimpl<T> IntoDynNode for Memo<T>\nwhere\n    T: Clone + IntoDynNode + PartialEq + 'static,\n{\n    fn into_dyn_node(self) -> dioxus_core::DynamicNode {\n        self().into_dyn_node()\n    }\n}\n\nimpl<T: 'static> PartialEq for Memo<T> {\n    fn eq(&self, other: &Self) -> bool {\n        self.inner == other.inner\n    }\n}\n\nimpl<T: Clone> Deref for Memo<T>\nwhere\n    T: PartialEq + 'static,\n{\n    type Target = dyn Fn() -> T;\n\n    fn deref(&self) -> &Self::Target {\n        unsafe { ReadableExt::deref_impl(self) }\n    }\n}\n\nread_impls!(Memo<T> where T: PartialEq);\nwrite_impls!(Memo<T> where T: PartialEq);\n\nimpl<T> Clone for Memo<T> {\n    fn clone(&self) -> Self {\n        *self\n    }\n}\n\nimpl<T> Copy for Memo<T> {}\n"
  },
  {
    "path": "packages/signals/src/props.rs",
    "content": "use crate::{ReadSignal, Signal};\nuse dioxus_core::SuperFrom;\n\n#[doc(hidden)]\npub struct ReadFromMarker<M>(std::marker::PhantomData<M>);\n\nimpl<T, O, M> SuperFrom<T, ReadFromMarker<M>> for ReadSignal<O>\nwhere\n    O: SuperFrom<T, M> + 'static,\n    T: 'static,\n{\n    fn super_from(input: T) -> Self {\n        ReadSignal::new(Signal::new(O::super_from(input)))\n    }\n}\n\n#[test]\n#[allow(unused)]\nfn into_signal_compiles() {\n    use dioxus_core::SuperInto;\n    fn takes_signal_string<M>(_: impl SuperInto<ReadSignal<String>, M>) {}\n\n    fn takes_option_signal_string<M>(_: impl SuperInto<ReadSignal<Option<String>>, M>) {}\n\n    fn don_t_run() {\n        takes_signal_string(\"hello world\");\n        takes_signal_string(Signal::new(String::from(\"hello world\")));\n        takes_option_signal_string(\"hello world\");\n    }\n}\n"
  },
  {
    "path": "packages/signals/src/read.rs",
    "content": "use std::collections::{HashMap, HashSet};\nuse std::{\n    mem::MaybeUninit,\n    ops::{Deref, Index},\n};\n\nuse crate::{ext_methods, MappedSignal, ReadSignal};\nuse dioxus_core::Subscribers;\nuse generational_box::{AnyStorage, UnsyncStorage};\n\n/// A reference to a value that can be read from.\n#[allow(type_alias_bounds)]\npub type ReadableRef<'a, T: Readable, O = <T as Readable>::Target> =\n    <T::Storage as AnyStorage>::Ref<'a, O>;\n\n/// A trait for states that can be read from like [`crate::Signal`], [`crate::GlobalSignal`], or [`crate::ReadSignal`]. You may choose to accept this trait as a parameter instead of the concrete type to allow for more flexibility in your API. For example, instead of creating two functions, one that accepts a [`crate::Signal`] and one that accepts a [`crate::GlobalSignal`], you can create one function that accepts a [`Readable`] type.\n///\n/// # Example\n/// ```rust\n/// # use dioxus::prelude::*;\n/// fn double(something_readable: &impl Readable<Target = i32>) -> i32 {\n///     something_readable.cloned() * 2\n/// }\n///\n/// static COUNT: GlobalSignal<i32> = Signal::global(|| 0);\n///\n/// fn MyComponent(count: Signal<i32>) -> Element {\n///     // Since we defined the function in terms of the readable trait, we can use it with any readable type (Signal, GlobalSignal, ReadSignal, etc)\n///     let doubled = use_memo(move || double(&count));\n///     let global_count_doubled = use_memo(|| double(&COUNT));\n///     rsx! {\n///         div {\n///             \"Count local: {count}\"\n///             \"Doubled local: {doubled}\"\n///             \"Count global: {COUNT}\"\n///             \"Doubled global: {global_count_doubled}\"\n///         }\n///     }\n/// }\n/// ```\npub trait Readable {\n    /// The target type of the reference.\n    type Target: ?Sized;\n\n    /// The type of the storage this readable uses.\n    type Storage: AnyStorage;\n\n    /// Try to get a reference to the value without checking the lifetime. This will subscribe the current scope to the signal.\n    ///\n    /// NOTE: This method is completely safe because borrow checking is done at runtime.\n    fn try_read_unchecked(\n        &self,\n    ) -> Result<ReadableRef<'static, Self>, generational_box::BorrowError>\n    where\n        Self::Target: 'static;\n\n    /// Try to peek the current value of the signal without subscribing to updates. If the value has\n    /// been dropped, this will return an error.\n    ///\n    /// NOTE: This method is completely safe because borrow checking is done at runtime.\n    fn try_peek_unchecked(\n        &self,\n    ) -> Result<ReadableRef<'static, Self>, generational_box::BorrowError>\n    where\n        Self::Target: 'static;\n\n    /// Get the underlying subscriber list for this readable. This is used to track when the value changes and notify subscribers.\n    fn subscribers(&self) -> Subscribers\n    where\n        Self::Target: 'static;\n}\n\n/// An extension trait for `Readable` types that provides some convenience methods.\npub trait ReadableExt: Readable {\n    /// Get the current value of the state. If this is a signal, this will subscribe the current scope to the signal.\n    /// If the value has been dropped, this will panic. Calling this on a Signal is the same as\n    /// using the signal() syntax to read and subscribe to its value\n    #[track_caller]\n    fn read(&self) -> ReadableRef<'_, Self>\n    where\n        Self::Target: 'static,\n    {\n        self.try_read().unwrap()\n    }\n\n    /// Try to get the current value of the state. If this is a signal, this will subscribe the current scope to the signal.\n    #[track_caller]\n    fn try_read(&self) -> Result<ReadableRef<'_, Self>, generational_box::BorrowError>\n    where\n        Self::Target: 'static,\n    {\n        self.try_read_unchecked()\n            .map(Self::Storage::downcast_lifetime_ref)\n    }\n\n    /// Get a reference to the value without checking the lifetime. This will subscribe the current scope to the signal.\n    ///\n    /// NOTE: This method is completely safe because borrow checking is done at runtime.\n    #[track_caller]\n    fn read_unchecked(&self) -> ReadableRef<'static, Self>\n    where\n        Self::Target: 'static,\n    {\n        self.try_read_unchecked().unwrap()\n    }\n\n    /// Get the current value of the state without subscribing to updates. If the value has been dropped, this will panic.\n    ///\n    /// # Example\n    /// ```rust\n    /// # use dioxus::prelude::*;\n    /// fn MyComponent(mut count: Signal<i32>) -> Element {\n    ///     let mut event_source = use_signal(|| None);\n    ///     let doubled = use_memo(move || {\n    ///         // We want to log the value of the event_source, but we don't need to rerun the doubled value if the event_source changes (because the value of doubled doesn't depend on the event_source)\n    ///         // We can read the value with peek without subscribing to updates\n    ///         let source = event_source.peek();\n    ///         tracing::info!(\"Clicked: {source:?}\");\n    ///         count() * 2\n    ///     });\n    ///     rsx! {\n    ///         div { \"Count: {count}\" }\n    ///         div { \"Doubled: {doubled}\" }\n    ///         button {\n    ///             onclick: move |_| {\n    ///                 event_source.set(Some(\"Click me button\"));\n    ///                 count += 1;\n    ///             },\n    ///             \"Click me\"\n    ///         }\n    ///         button {\n    ///             onclick: move |_| {\n    ///                 event_source.set(Some(\"Double me button\"));\n    ///                 count += 1;\n    ///             },\n    ///             \"Double me\"\n    ///         }\n    ///     }\n    /// }\n    /// ```\n    #[track_caller]\n    fn peek(&self) -> ReadableRef<'_, Self>\n    where\n        Self::Target: 'static,\n    {\n        Self::Storage::downcast_lifetime_ref(self.peek_unchecked())\n    }\n\n    /// Try to peek the current value of the signal without subscribing to updates. If the value has\n    /// been dropped, this will return an error.\n    #[track_caller]\n    fn try_peek(&self) -> Result<ReadableRef<'_, Self>, generational_box::BorrowError>\n    where\n        Self::Target: 'static,\n    {\n        self.try_peek_unchecked()\n            .map(Self::Storage::downcast_lifetime_ref)\n    }\n\n    /// Get the current value of the signal without checking the lifetime. **Unlike read, this will not subscribe the current scope to the signal which can cause parts of your UI to not update.**\n    ///\n    /// If the signal has been dropped, this will panic.\n    #[track_caller]\n    fn peek_unchecked(&self) -> ReadableRef<'static, Self>\n    where\n        Self::Target: 'static,\n    {\n        self.try_peek_unchecked().unwrap()\n    }\n\n    /// Map the references of the readable value to a new type. This lets you provide a view\n    /// into the readable value without creating a new signal or cloning the value.\n    ///\n    /// Anything that subscribes to the readable value will be rerun whenever the original value changes, even if the view does not\n    /// change. If you want to memorize the view, you can use a [`crate::Memo`] instead. For fine grained scoped updates, use\n    /// stores instead\n    ///\n    /// # Example\n    /// ```rust\n    /// # use dioxus::prelude::*;\n    /// fn List(list: Signal<Vec<i32>>) -> Element {\n    ///     rsx! {\n    ///         for index in 0..list.len() {\n    ///             // We can use the `map` method to provide a view into the single item in the list that the child component will render\n    ///             Item { item: list.map(move |v| &v[index]) }\n    ///         }\n    ///     }\n    /// }\n    ///\n    /// // The child component doesn't need to know that the mapped value is coming from a list\n    /// #[component]\n    /// fn Item(item: ReadSignal<i32>) -> Element {\n    ///     rsx! {\n    ///         div { \"Item: {item}\" }\n    ///     }\n    /// }\n    /// ```\n    fn map<F, O>(self, f: F) -> MappedSignal<O, Self, F>\n    where\n        Self: Clone + Sized,\n        F: Fn(&Self::Target) -> &O,\n    {\n        MappedSignal::new(self, f)\n    }\n\n    /// Clone the inner value and return it. If the value has been dropped, this will panic.\n    #[track_caller]\n    fn cloned(&self) -> Self::Target\n    where\n        Self::Target: Clone + 'static,\n    {\n        self.read().clone()\n    }\n\n    /// Run a function with a reference to the value. If the value has been dropped, this will panic.\n    #[track_caller]\n    fn with<O>(&self, f: impl FnOnce(&Self::Target) -> O) -> O\n    where\n        Self::Target: 'static,\n    {\n        f(&*self.read())\n    }\n\n    /// Run a function with a reference to the value. If the value has been dropped, this will panic.\n    #[track_caller]\n    fn with_peek<O>(&self, f: impl FnOnce(&Self::Target) -> O) -> O\n    where\n        Self::Target: 'static,\n    {\n        f(&*self.peek())\n    }\n\n    /// Index into the inner value and return a reference to the result. If the value has been dropped or the index is invalid, this will panic.\n    #[track_caller]\n    fn index<I>(\n        &self,\n        index: I,\n    ) -> ReadableRef<'_, Self, <Self::Target as std::ops::Index<I>>::Output>\n    where\n        Self::Target: std::ops::Index<I> + 'static,\n    {\n        <Self::Storage as AnyStorage>::map(self.read(), |v| v.index(index))\n    }\n\n    /// SAFETY: You must call this function directly with `self` as the argument.\n    /// This function relies on the size of the object you return from the deref\n    /// being the same as the object you pass in\n    #[doc(hidden)]\n    unsafe fn deref_impl<'a>(&self) -> &'a dyn Fn() -> Self::Target\n    where\n        Self: Sized + 'a,\n        Self::Target: Clone + 'static,\n    {\n        // https://github.com/dtolnay/case-studies/tree/master/callable-types\n\n        // First we create a closure that captures something with the Same in memory layout as Self (MaybeUninit<Self>).\n        let uninit_callable = MaybeUninit::<Self>::uninit();\n        // Then move that value into the closure. We assume that the closure now has a in memory layout of Self.\n        let uninit_closure = move || Self::read(unsafe { &*uninit_callable.as_ptr() }).clone();\n\n        // Check that the size of the closure is the same as the size of Self in case the compiler changed the layout of the closure.\n        let size_of_closure = std::mem::size_of_val(&uninit_closure);\n        assert_eq!(size_of_closure, std::mem::size_of::<Self>());\n\n        // Then cast the lifetime of the closure to the lifetime of &self.\n        fn cast_lifetime<'a, T>(_a: &T, b: &'a T) -> &'a T {\n            b\n        }\n        let reference_to_closure = cast_lifetime(\n            {\n                // The real closure that we will never use.\n                &uninit_closure\n            },\n            #[allow(clippy::missing_transmute_annotations)]\n            // We transmute self into a reference to the closure. This is safe because we know that the closure has the same memory layout as Self so &Closure == &Self.\n            unsafe {\n                std::mem::transmute(self)\n            },\n        );\n\n        // Cast the closure to a trait object.\n        reference_to_closure as &_\n    }\n}\n\nimpl<R: Readable + ?Sized> ReadableExt for R {}\n\n/// An extension trait for `Readable` types that can be boxed into a trait object.\npub trait ReadableBoxExt: Readable<Storage = UnsyncStorage> {\n    /// Box the readable value into a trait object. This is useful for passing around readable values without knowing their concrete type.\n    fn boxed(self) -> ReadSignal<Self::Target>\n    where\n        Self: Sized + 'static,\n    {\n        ReadSignal::new(self)\n    }\n}\nimpl<R: Readable<Storage = UnsyncStorage> + ?Sized> ReadableBoxExt for R {}\n\n/// An extension trait for `Readable<Vec<T>>` that provides some convenience methods.\npub trait ReadableVecExt<T>: Readable<Target = Vec<T>> {\n    /// Returns the length of the inner vector.\n    #[track_caller]\n    fn len(&self) -> usize\n    where\n        T: 'static,\n    {\n        self.with(|v| v.len())\n    }\n\n    /// Returns true if the inner vector is empty.\n    #[track_caller]\n    fn is_empty(&self) -> bool\n    where\n        T: 'static,\n    {\n        self.with(|v| v.is_empty())\n    }\n\n    /// Get the first element of the inner vector.\n    #[track_caller]\n    fn first(&self) -> Option<ReadableRef<'_, Self, T>>\n    where\n        T: 'static,\n    {\n        <Self::Storage as AnyStorage>::try_map(self.read(), |v| v.first())\n    }\n\n    /// Get the last element of the inner vector.\n    #[track_caller]\n    fn last(&self) -> Option<ReadableRef<'_, Self, T>>\n    where\n        T: 'static,\n    {\n        <Self::Storage as AnyStorage>::try_map(self.read(), |v| v.last())\n    }\n\n    /// Get the element at the given index of the inner vector.\n    #[track_caller]\n    fn get(&self, index: usize) -> Option<ReadableRef<'_, Self, T>>\n    where\n        T: 'static,\n    {\n        <Self::Storage as AnyStorage>::try_map(self.read(), |v| v.get(index))\n    }\n\n    /// Get an iterator over the values of the inner vector.\n    #[track_caller]\n    fn iter(&self) -> ReadableValueIterator<'_, Self>\n    where\n        Self: Sized,\n    {\n        ReadableValueIterator {\n            index: 0,\n            value: self,\n        }\n    }\n}\n\n/// An iterator over the values of a `Readable<Vec<T>>`.\npub struct ReadableValueIterator<'a, R> {\n    index: usize,\n    value: &'a R,\n}\n\nimpl<'a, T: 'static, R: Readable<Target = Vec<T>>> Iterator for ReadableValueIterator<'a, R> {\n    type Item = ReadableRef<'a, R, T>;\n\n    fn next(&mut self) -> Option<Self::Item> {\n        let index = self.index;\n        self.index += 1;\n        self.value.get(index)\n    }\n}\n\nimpl<T, R> ReadableVecExt<T> for R where R: Readable<Target = Vec<T>> {}\n\n/// An extension trait for `Readable<Option<T>>` that provides some convenience methods.\npub trait ReadableOptionExt<T>: Readable<Target = Option<T>> {\n    /// Unwraps the inner value and clones it.\n    #[track_caller]\n    fn unwrap(&self) -> T\n    where\n        T: Clone + 'static,\n    {\n        self.as_ref().unwrap().clone()\n    }\n\n    /// Attempts to read the inner value of the Option.\n    #[track_caller]\n    fn as_ref(&self) -> Option<ReadableRef<'_, Self, T>>\n    where\n        T: 'static,\n    {\n        <Self::Storage as AnyStorage>::try_map(self.read(), |v| v.as_ref())\n    }\n}\n\nimpl<T, R> ReadableOptionExt<T> for R where R: Readable<Target = Option<T>> {}\n\n/// An extension trait for `Readable<Option<T>>` that provides some convenience methods.\npub trait ReadableResultExt<T, E>: Readable<Target = Result<T, E>> {\n    /// Unwraps the inner value and clones it.\n    #[track_caller]\n    fn unwrap(&self) -> T\n    where\n        T: Clone + 'static,\n        E: 'static,\n    {\n        self.as_ref()\n            .unwrap_or_else(|_| panic!(\"Tried to unwrap a Result that was an error\"))\n            .clone()\n    }\n\n    /// Attempts to read the inner value of the Option.\n    #[track_caller]\n    fn as_ref(&self) -> Result<ReadableRef<'_, Self, T>, ReadableRef<'_, Self, E>>\n    where\n        T: 'static,\n        E: 'static,\n    {\n        let read = self.read();\n        match read.deref() {\n            Ok(_) => Ok(<Self::Storage as AnyStorage>::map(read, |v| {\n                v.as_ref()\n                    .ok()\n                    .expect(\"Result variant changed between read and map\")\n            })),\n            Err(_) => Err(<Self::Storage as AnyStorage>::map(read, |v| {\n                v.as_ref()\n                    .err()\n                    .expect(\"Result variant changed between read and map\")\n            })),\n        }\n    }\n}\n\nimpl<T, E, R> ReadableResultExt<T, E> for R where R: Readable<Target = Result<T, E>> {}\n\n/// An extension trait for [`Readable<String>`] that provides some convenience methods.\npub trait ReadableStringExt: Readable<Target = String> {\n    ext_methods! {\n        /// Check the capacity of the string.\n        fn capacity(&self) -> usize = String::capacity;\n    }\n}\n\nimpl<W> ReadableStringExt for W where W: Readable<Target = String> {}\n\n/// An extension trait for [`Readable<String>`] and [`Readable<str>`] that provides some convenience methods.\npub trait ReadableStrExt: Readable<Target: Deref<Target = str> + 'static> {\n    ext_methods! {\n        /// Check if the string is empty.\n        fn is_empty(&self) -> bool = |s: &Self::Target| s.deref().is_empty();\n\n        /// Get the length of the string.\n        fn len(&self) -> usize = |s: &Self::Target| s.deref().len();\n\n        /// Check if the string contains the given pattern.\n        fn contains(&self, pat: &str) -> bool = |s: &Self::Target, pat| s.deref().contains(pat);\n    }\n}\n\nimpl<W> ReadableStrExt for W where W: Readable<Target: Deref<Target = str> + 'static> {}\n\n/// An extension trait for [`Readable<HashMap<K, V, H>>`] that provides some convenience methods.\npub trait ReadableHashMapExt<K: 'static, V: 'static, H: 'static>:\n    Readable<Target = HashMap<K, V, H>>\n{\n    ext_methods! {\n        /// Check if the hashmap is empty.\n        fn is_empty(&self) -> bool = HashMap::is_empty;\n\n        /// Get the length of the hashmap.\n        fn len(&self) -> usize = HashMap::len;\n\n        /// Get the capacity of the hashmap.\n        fn capacity(&self) -> usize = HashMap::capacity;\n    }\n\n    /// Get the value for the given key.\n    #[track_caller]\n    fn get(&self, key: &K) -> Option<ReadableRef<'_, Self, V>>\n    where\n        K: std::hash::Hash + Eq,\n        H: std::hash::BuildHasher,\n    {\n        <Self::Storage as AnyStorage>::try_map(self.read(), |v| v.get(key))\n    }\n\n    /// Check if the hashmap contains the given key.\n    #[track_caller]\n    fn contains_key(&self, key: &K) -> bool\n    where\n        K: std::hash::Hash + Eq,\n        H: std::hash::BuildHasher,\n    {\n        self.with(|v| v.contains_key(key))\n    }\n}\n\nimpl<K: 'static, V: 'static, H: 'static, R> ReadableHashMapExt<K, V, H> for R where\n    R: Readable<Target = HashMap<K, V, H>>\n{\n}\n\n/// An extension trait for [`Readable<HashSet<V, H>>`] that provides some convenience methods.\npub trait ReadableHashSetExt<V: 'static, H: 'static>: Readable<Target = HashSet<V, H>> {\n    ext_methods! {\n        /// Check if the hashset is empty.\n        fn is_empty(&self) -> bool = HashSet::is_empty;\n\n        /// Get the length of the hashset.\n        fn len(&self) -> usize = HashSet::len;\n\n        /// Get the capacity of the hashset.\n        fn capacity(&self) -> usize = HashSet::capacity;\n    }\n\n    /// Check if the hashset contains the given value.\n    #[track_caller]\n    fn contains(&self, value: &V) -> bool\n    where\n        V: std::hash::Hash + Eq,\n        H: std::hash::BuildHasher,\n    {\n        self.with(|v| v.contains(value))\n    }\n}\n\nimpl<V: 'static, H: 'static, R> ReadableHashSetExt<V, H> for R where\n    R: Readable<Target = HashSet<V, H>>\n{\n}\n"
  },
  {
    "path": "packages/signals/src/set_compare.rs",
    "content": "use crate::{write::WritableExt, ReadableExt};\nuse std::hash::Hash;\n\nuse dioxus_core::ReactiveContext;\nuse futures_util::StreamExt;\nuse generational_box::{Storage, UnsyncStorage};\n\nuse crate::{CopyValue, ReadSignal, Signal, SignalData};\nuse rustc_hash::FxHashMap;\n\n/// An object that can efficiently compare a value to a set of values.\npub struct SetCompare<R, S: 'static = UnsyncStorage> {\n    subscribers: CopyValue<FxHashMap<R, Signal<bool, S>>>,\n}\n\nimpl<R: Eq + Hash + 'static> SetCompare<R> {\n    /// Creates a new [`SetCompare`] which efficiently tracks when a value changes to check if it is equal to a set of values.\n    ///\n    /// Generally, you shouldn't need to use this hook. Instead you can use [`crate::Memo`]. If you have many values that you need to compare to a single value, this hook will change updates from O(n) to O(1) where n is the number of values you are comparing to.\n    #[track_caller]\n    pub fn new(f: impl FnMut() -> R + 'static) -> SetCompare<R> {\n        Self::new_maybe_sync(f)\n    }\n}\n\nimpl<R: Eq + Hash + 'static, S: Storage<SignalData<bool>> + 'static> SetCompare<R, S> {\n    /// Creates a new [`SetCompare`] that may be `Sync + Send` which efficiently tracks when a value changes to check if it is equal to a set of values.\n    ///\n    /// Generally, you shouldn't need to use this hook. Instead you can use [`crate::Memo`]. If you have many values that you need to compare to a single value, this hook will change updates from O(n) to O(1) where n is the number of values you are comparing to.\n    #[track_caller]\n    pub fn new_maybe_sync(mut f: impl FnMut() -> R + 'static) -> SetCompare<R, S> {\n        let subscribers: CopyValue<FxHashMap<R, Signal<bool, S>>> =\n            CopyValue::new(FxHashMap::default());\n        let mut previous = CopyValue::new(None);\n\n        let mut recompute = move || {\n            let subscribers = subscribers.read();\n            let mut previous = previous.write();\n\n            if let Some(previous) = previous.take() {\n                if let Some(mut value) = subscribers.get(&previous).cloned() {\n                    *value.write() = false;\n                }\n            }\n\n            let current = f();\n\n            if let Some(mut value) = subscribers.get(&current).cloned() {\n                *value.write() = true;\n            }\n\n            *previous = Some(current);\n        };\n        let (rc, mut changed) = ReactiveContext::new();\n        dioxus_core::spawn(async move {\n            loop {\n                // Recompute the value\n                rc.reset_and_run_in(&mut recompute);\n\n                // Wait for context to change\n                let _ = changed.next().await;\n            }\n        });\n\n        SetCompare { subscribers }\n    }\n}\n\nimpl<R: Eq + Hash + 'static> SetCompare<R> {\n    /// Returns a signal which is true when the value is equal to the value passed to this function.\n    pub fn equal(&mut self, value: R) -> ReadSignal<bool> {\n        let subscribers = self.subscribers.write();\n\n        match subscribers.get(&value) {\n            Some(&signal) => signal.into(),\n            None => {\n                drop(subscribers);\n                let mut subscribers = self.subscribers.write();\n                let signal = Signal::new_maybe_sync(false);\n                subscribers.insert(value, signal);\n                signal.into()\n            }\n        }\n    }\n}\n\nimpl<R, S: Storage<SignalData<bool>>> PartialEq for SetCompare<R, S> {\n    fn eq(&self, other: &Self) -> bool {\n        self.subscribers == other.subscribers\n    }\n}\n\nimpl<R, S: Storage<SignalData<bool>>> Clone for SetCompare<R, S> {\n    fn clone(&self) -> Self {\n        *self\n    }\n}\n\nimpl<R, S: Storage<SignalData<bool>>> Copy for SetCompare<R, S> {}\n"
  },
  {
    "path": "packages/signals/src/signal.rs",
    "content": "use crate::{\n    default_impl, fmt_impls, read::*, write::*, write_impls, CopyValue, Global, GlobalMemo,\n    GlobalSignal, Memo, ReadableRef, WritableRef,\n};\nuse dioxus_core::{IntoAttributeValue, IntoDynNode, ReactiveContext, ScopeId, Subscribers};\nuse generational_box::{BorrowResult, Storage, SyncStorage, UnsyncStorage};\nuse std::{collections::HashSet, ops::Deref, sync::Arc, sync::Mutex};\n\n#[doc = include_str!(\"../docs/signals.md\")]\n#[doc(alias = \"State\")]\n#[doc(alias = \"UseState\")]\n#[doc(alias = \"UseRef\")]\npub struct Signal<T, S: 'static = UnsyncStorage> {\n    pub(crate) inner: CopyValue<SignalData<T>, S>,\n}\n\n/// A signal that can safely shared between threads.\n#[doc(alias = \"SendSignal\")]\n#[doc(alias = \"UseRwLock\")]\n#[doc(alias = \"UseRw\")]\n#[doc(alias = \"UseMutex\")]\npub type SyncSignal<T> = Signal<T, SyncStorage>;\n\n/// The data stored for tracking in a signal.\npub struct SignalData<T> {\n    pub(crate) subscribers: Arc<Mutex<HashSet<ReactiveContext>>>,\n    pub(crate) value: T,\n}\n\nimpl<T: 'static> Signal<T> {\n    /// Creates a new [`Signal`]. Signals are a Copy state management solution with automatic dependency tracking.\n    ///\n    /// <div class=\"warning\">\n    ///\n    /// This function should generally only be called inside hooks. The signal that this function creates is owned by the current component and will only be dropped when the component is dropped. If you call this function outside of a hook many times, you will leak memory until the component is dropped.\n    ///\n    /// ```rust\n    /// # use dioxus::prelude::*;\n    /// fn MyComponent() {\n    ///     // ❌ Every time MyComponent runs, it will create a new signal that is only dropped when MyComponent is dropped\n    ///     let signal = Signal::new(0);\n    ///     use_context_provider(|| signal);\n    ///     // ✅ Since the use_context_provider hook only runs when the component is created, the signal will only be created once and it will be dropped when MyComponent is dropped\n    ///     let signal = use_context_provider(|| Signal::new(0));\n    /// }\n    /// ```\n    ///\n    /// </div>\n    #[track_caller]\n    pub fn new(value: T) -> Self {\n        Self::new_maybe_sync(value)\n    }\n\n    /// Create a new signal with a custom owner scope. The signal will be dropped when the owner scope is dropped instead of the current scope.\n    #[track_caller]\n    pub fn new_in_scope(value: T, owner: ScopeId) -> Self {\n        Self::new_maybe_sync_in_scope(value, owner)\n    }\n\n    /// Creates a new [`GlobalSignal`] that can be used anywhere inside your dioxus app. This signal will automatically be created once per app the first time you use it.\n    ///\n    /// # Example\n    /// ```rust, no_run\n    /// # use dioxus::prelude::*;\n    /// // Create a new global signal that can be used anywhere in your app\n    /// static SIGNAL: GlobalSignal<i32> = Signal::global(|| 0);\n    ///\n    /// fn App() -> Element {\n    ///     rsx! {\n    ///         button {\n    ///             onclick: move |_| *SIGNAL.write() += 1,\n    ///             \"{SIGNAL}\"\n    ///         }\n    ///     }\n    /// }\n    /// ```\n    ///\n    /// <div class=\"warning\">\n    ///\n    /// Global signals are generally not recommended for use in libraries because it makes it more difficult to allow multiple instances of components you define in your library.\n    ///\n    /// </div>\n    #[track_caller]\n    pub const fn global(constructor: fn() -> T) -> GlobalSignal<T> {\n        Global::new(constructor)\n    }\n}\n\nimpl<T: PartialEq + 'static> Signal<T> {\n    /// Creates a new [`GlobalMemo`] that can be used anywhere inside your dioxus app. This memo will automatically be created once per app the first time you use it.\n    ///\n    /// # Example\n    /// ```rust, no_run\n    /// # use dioxus::prelude::*;\n    /// static SIGNAL: GlobalSignal<i32> = Signal::global(|| 0);\n    /// // Create a new global memo that can be used anywhere in your app\n    /// static DOUBLED: GlobalMemo<i32> = Signal::global_memo(|| SIGNAL() * 2);\n    ///\n    /// fn App() -> Element {\n    ///     rsx! {\n    ///         button {\n    ///             // When SIGNAL changes, the memo will update because the SIGNAL is read inside DOUBLED\n    ///             onclick: move |_| *SIGNAL.write() += 1,\n    ///             \"{DOUBLED}\"\n    ///         }\n    ///     }\n    /// }\n    /// ```\n    ///\n    /// <div class=\"warning\">\n    ///\n    /// Global memos are generally not recommended for use in libraries because it makes it more difficult to allow multiple instances of components you define in your library.\n    ///\n    /// </div>\n    #[track_caller]\n    pub const fn global_memo(constructor: fn() -> T) -> GlobalMemo<T>\n    where\n        T: PartialEq,\n    {\n        GlobalMemo::new(constructor)\n    }\n\n    /// Creates a new unsync Selector. The selector will be run immediately and whenever any signal it reads changes.\n    ///\n    /// Selectors can be used to efficiently compute derived data from signals.\n    #[track_caller]\n    pub fn memo(f: impl FnMut() -> T + 'static) -> Memo<T> {\n        Memo::new(f)\n    }\n\n    /// Creates a new unsync Selector with an explicit location. The selector will be run immediately and whenever any signal it reads changes.\n    ///\n    /// Selectors can be used to efficiently compute derived data from signals.\n    pub fn memo_with_location(\n        f: impl FnMut() -> T + 'static,\n        location: &'static std::panic::Location<'static>,\n    ) -> Memo<T> {\n        Memo::new_with_location(f, location)\n    }\n}\n\nimpl<T, S: Storage<SignalData<T>>> Signal<T, S> {\n    /// Creates a new Signal. Signals are a Copy state management solution with automatic dependency tracking.\n    #[track_caller]\n    #[tracing::instrument(skip(value))]\n    pub fn new_maybe_sync(value: T) -> Self\n    where\n        T: 'static,\n    {\n        Self {\n            inner: CopyValue::<SignalData<T>, S>::new_maybe_sync(SignalData {\n                subscribers: Default::default(),\n                value,\n            }),\n        }\n    }\n\n    /// Creates a new Signal with an explicit caller. Signals are a Copy state management solution with automatic dependency tracking.\n    ///\n    /// This method can be used to provide the correct caller information for signals that are created in closures:\n    ///\n    /// ```rust\n    /// # use dioxus::prelude::*;\n    /// #[track_caller]\n    /// fn use_my_signal(function: impl FnOnce() -> i32) -> Signal<i32> {\n    ///     // We capture the caller information outside of the closure so that it points to the caller of use_my_custom_hook instead of the closure\n    ///     let caller = std::panic::Location::caller();\n    ///     use_hook(move || Signal::new_with_caller(function(), caller))\n    /// }\n    /// ```\n    pub fn new_with_caller(value: T, caller: &'static std::panic::Location<'static>) -> Self\n    where\n        T: 'static,\n    {\n        Self {\n            inner: CopyValue::new_with_caller(\n                SignalData {\n                    subscribers: Default::default(),\n                    value,\n                },\n                caller,\n            ),\n        }\n    }\n\n    /// Create a new Signal without an owner. This will leak memory if you don't manually drop it.\n    pub fn leak_with_caller(value: T, caller: &'static std::panic::Location<'static>) -> Self\n    where\n        T: 'static,\n    {\n        Self {\n            inner: CopyValue::leak_with_caller(\n                SignalData {\n                    subscribers: Default::default(),\n                    value,\n                },\n                caller,\n            ),\n        }\n    }\n\n    /// Create a new signal with a custom owner scope. The signal will be dropped when the owner scope is dropped instead of the current scope.\n    #[track_caller]\n    #[tracing::instrument(skip(value))]\n    pub fn new_maybe_sync_in_scope(value: T, owner: ScopeId) -> Self {\n        Self::new_maybe_sync_in_scope_with_caller(value, owner, std::panic::Location::caller())\n    }\n\n    /// Create a new signal with a custom owner scope and a custom caller. The signal will be dropped when the owner scope is dropped instead of the current scope.\n    #[tracing::instrument(skip(value))]\n    pub fn new_maybe_sync_in_scope_with_caller(\n        value: T,\n        owner: ScopeId,\n        caller: &'static std::panic::Location<'static>,\n    ) -> Self {\n        Self {\n            inner: CopyValue::<SignalData<T>, S>::new_maybe_sync_in_scope_with_caller(\n                SignalData {\n                    subscribers: Default::default(),\n                    value,\n                },\n                owner,\n                caller,\n            ),\n        }\n    }\n\n    /// Point to another signal. This will subscribe the other signal to all subscribers of this signal.\n    pub fn point_to(&self, other: Self) -> BorrowResult\n    where\n        T: 'static,\n    {\n        #[allow(clippy::mutable_key_type)]\n        let this_subscribers = self.inner.value.read().subscribers.lock().unwrap().clone();\n        let other_read = other.inner.value.read();\n        for subscriber in this_subscribers.iter() {\n            subscriber.subscribe(other_read.subscribers.clone());\n        }\n        self.inner.point_to(other.inner)\n    }\n\n    /// Drop the value out of the signal, invalidating the signal in the process.\n    pub fn manually_drop(&self)\n    where\n        T: 'static,\n    {\n        self.inner.manually_drop()\n    }\n\n    /// Get the scope the signal was created in.\n    pub fn origin_scope(&self) -> ScopeId {\n        self.inner.origin_scope()\n    }\n\n    fn update_subscribers(&self)\n    where\n        T: 'static,\n    {\n        {\n            let inner = self.inner.read();\n\n            // We cannot hold the subscribers lock while calling mark_dirty, because mark_dirty can run user code which may cause a new subscriber to be added. If we hold the lock, we will deadlock.\n            #[allow(clippy::mutable_key_type)]\n            let mut subscribers = std::mem::take(&mut *inner.subscribers.lock().unwrap());\n            subscribers.retain(|reactive_context| reactive_context.mark_dirty());\n            // Extend the subscribers list instead of overwriting it in case a subscriber is added while reactive contexts are marked dirty\n            inner.subscribers.lock().unwrap().extend(subscribers);\n        }\n    }\n\n    /// Get the generational id of the signal.\n    pub fn id(&self) -> generational_box::GenerationalBoxId {\n        self.inner.id()\n    }\n\n    /// **This pattern is no longer recommended. Prefer [`peek`](ReadableExt::peek) or creating new signals instead.**\n    ///\n    /// This function is the equivalent of the [write_silent](https://docs.rs/dioxus/latest/dioxus/prelude/struct.UseRef.html#method.write_silent) method on use_ref.\n    ///\n    /// ## What you should use instead\n    ///\n    /// ### Reading and Writing to data in the same scope\n    ///\n    /// Reading and writing to the same signal in the same scope will cause that scope to rerun forever:\n    /// ```rust, no_run\n    /// # use dioxus::prelude::*;\n    /// let mut signal = use_signal(|| 0);\n    /// // This makes the scope rerun whenever we write to the signal\n    /// println!(\"{}\", *signal.read());\n    /// // This will rerun the scope because we read the signal earlier in the same scope\n    /// *signal.write() += 1;\n    /// ```\n    ///\n    /// You may have used the write_silent method to avoid this infinite loop with use_ref like this:\n    /// ```rust, no_run\n    /// # use dioxus::prelude::*;\n    /// let signal = use_signal(|| 0);\n    /// // This makes the scope rerun whenever we write to the signal\n    /// println!(\"{}\", *signal.read());\n    /// // Write silent will not rerun any subscribers\n    /// *signal.write_silent() += 1;\n    /// ```\n    ///\n    /// Instead you can use the [`peek`](ReadableExt::peek) and [`write`](WritableExt::write) methods instead. The peek method will not subscribe to the current scope which will avoid an infinite loop if you are reading and writing to the same signal in the same scope.\n    /// ```rust, no_run\n    /// # use dioxus::prelude::*;\n    /// let mut signal = use_signal(|| 0);\n    /// // Peek will read the value but not subscribe to the current scope\n    /// println!(\"{}\", *signal.peek());\n    /// // Write will update any subscribers which does not include the current scope\n    /// *signal.write() += 1;\n    /// ```\n    ///\n    /// ### Reading and Writing to different data\n    ///\n    ///\n    ///\n    /// ## Why is this pattern no longer recommended?\n    ///\n    /// This pattern is no longer recommended because it is very easy to allow your state and UI to grow out of sync. `write_silent` globally opts out of automatic state updates which can be difficult to reason about.\n    ///\n    ///\n    /// Lets take a look at an example:\n    /// main.rs:\n    /// ```rust, no_run\n    /// # use dioxus::prelude::*;\n    /// # fn Child() -> Element { unimplemented!() }\n    /// fn app() -> Element {\n    ///     let signal = use_context_provider(|| Signal::new(0));\n    ///\n    ///     // We want to log the value of the signal whenever the app component reruns\n    ///     println!(\"{}\", *signal.read());\n    ///\n    ///     rsx! {\n    ///         button {\n    ///             // If we don't want to rerun the app component when the button is clicked, we can use write_silent\n    ///             onclick: move |_| *signal.write_silent() += 1,\n    ///             \"Increment\"\n    ///         }\n    ///         Child {}\n    ///     }\n    /// }\n    /// ```\n    /// child.rs:\n    /// ```rust, no_run\n    /// # use dioxus::prelude::*;\n    /// fn Child() -> Element {\n    ///     let signal: Signal<i32> = use_context();\n    ///\n    ///     // It is difficult to tell that changing the button to use write_silent in the main.rs file will cause UI to be out of sync in a completely different file\n    ///     rsx! {\n    ///         \"{signal}\"\n    ///     }\n    /// }\n    /// ```\n    ///\n    /// Instead [`peek`](ReadableExt::peek) locally opts out of automatic state updates explicitly for a specific read which is easier to reason about.\n    ///\n    /// Here is the same example using peek:\n    /// main.rs:\n    /// ```rust, no_run\n    /// # use dioxus::prelude::*;\n    /// # fn Child() -> Element { unimplemented!() }\n    /// fn app() -> Element {\n    ///     let mut signal = use_context_provider(|| Signal::new(0));\n    ///\n    ///     // We want to log the value of the signal whenever the app component reruns, but we don't want to rerun the app component when the signal is updated so we use peek instead of read\n    ///     println!(\"{}\", *signal.peek());\n    ///\n    ///     rsx! {\n    ///         button {\n    ///             // We can use write like normal and update the child component automatically\n    ///             onclick: move |_| *signal.write() += 1,\n    ///             \"Increment\"\n    ///         }\n    ///         Child {}\n    ///     }\n    /// }\n    /// ```\n    /// child.rs:\n    /// ```rust, no_run\n    /// # use dioxus::prelude::*;\n    /// fn Child() -> Element {\n    ///     let signal: Signal<i32> = use_context();\n    ///\n    ///     rsx! {\n    ///         \"{signal}\"\n    ///     }\n    /// }\n    /// ```\n    #[track_caller]\n    #[deprecated = \"This pattern is no longer recommended. Prefer `peek` or creating new signals instead.\"]\n    pub fn write_silent(&self) -> WriteLock<'static, T, S> {\n        WriteLock::map(self.inner.write_unchecked(), |inner: &mut SignalData<T>| {\n            &mut inner.value\n        })\n    }\n}\n\nimpl<T, S: Storage<SignalData<T>>> Readable for Signal<T, S> {\n    type Target = T;\n    type Storage = S;\n\n    #[track_caller]\n    fn try_read_unchecked(&self) -> BorrowResult<ReadableRef<'static, Self>>\n    where\n        T: 'static,\n    {\n        let inner = self.inner.try_read_unchecked()?;\n\n        if let Some(reactive_context) = ReactiveContext::current() {\n            tracing::trace!(\"Subscribing to the reactive context {}\", reactive_context);\n            reactive_context.subscribe(inner.subscribers.clone());\n        }\n\n        Ok(S::map(inner, |v| &v.value))\n    }\n\n    /// Get the current value of the signal. **Unlike read, this will not subscribe the current scope to the signal which can cause parts of your UI to not update.**\n    ///\n    /// If the signal has been dropped, this will panic.\n    #[track_caller]\n    fn try_peek_unchecked(&self) -> BorrowResult<ReadableRef<'static, Self>>\n    where\n        T: 'static,\n    {\n        self.inner\n            .try_read_unchecked()\n            .map(|inner| S::map(inner, |v| &v.value))\n    }\n\n    fn subscribers(&self) -> Subscribers\n    where\n        T: 'static,\n    {\n        self.inner.read().subscribers.clone().into()\n    }\n}\n\nimpl<T: 'static, S: Storage<SignalData<T>>> Writable for Signal<T, S> {\n    type WriteMetadata = SignalSubscriberDrop<T, S>;\n\n    #[track_caller]\n    fn try_write_unchecked(\n        &self,\n    ) -> Result<WritableRef<'static, Self>, generational_box::BorrowMutError> {\n        #[cfg(debug_assertions)]\n        let origin = std::panic::Location::caller();\n        self.inner.try_write_unchecked().map(|inner| {\n            let borrow = S::map_mut(inner.into_inner(), |v| &mut v.value);\n            WriteLock::new_with_metadata(\n                borrow,\n                SignalSubscriberDrop {\n                    signal: *self,\n                    #[cfg(debug_assertions)]\n                    origin,\n                },\n            )\n        })\n    }\n}\n\nimpl<T> IntoAttributeValue for Signal<T>\nwhere\n    T: Clone + IntoAttributeValue + 'static,\n{\n    fn into_value(self) -> dioxus_core::AttributeValue {\n        self.with(|f| f.clone().into_value())\n    }\n}\n\nimpl<T> IntoDynNode for Signal<T>\nwhere\n    T: Clone + IntoDynNode + 'static,\n{\n    fn into_dyn_node(self) -> dioxus_core::DynamicNode {\n        self().into_dyn_node()\n    }\n}\n\nimpl<T, S: Storage<SignalData<T>>> PartialEq for Signal<T, S> {\n    fn eq(&self, other: &Self) -> bool {\n        self.inner == other.inner\n    }\n}\n\nimpl<T, S: Storage<SignalData<T>>> Eq for Signal<T, S> {}\n\n/// Allow calling a signal with signal() syntax\n///\n/// Currently only limited to copy types, though could probably specialize for string/arc/rc\nimpl<T: Clone + 'static, S: Storage<SignalData<T>> + 'static> Deref for Signal<T, S> {\n    type Target = dyn Fn() -> T;\n\n    fn deref(&self) -> &Self::Target {\n        unsafe { ReadableExt::deref_impl(self) }\n    }\n}\n\n#[cfg(feature = \"serialize\")]\nimpl<T: serde::Serialize + 'static, Store: Storage<SignalData<T>> + 'static> serde::Serialize\n    for Signal<T, Store>\n{\n    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {\n        self.read().serialize(serializer)\n    }\n}\n\n#[cfg(feature = \"serialize\")]\nimpl<'de, T: serde::Deserialize<'de> + 'static, Store: Storage<SignalData<T>> + 'static>\n    serde::Deserialize<'de> for Signal<T, Store>\n{\n    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {\n        Ok(Self::new_maybe_sync(T::deserialize(deserializer)?))\n    }\n}\n\n#[doc(hidden)]\n/// A drop guard that will update the subscribers of the signal when it is dropped.\npub struct SignalSubscriberDrop<T: 'static, S: Storage<SignalData<T>> + 'static> {\n    signal: Signal<T, S>,\n    #[cfg(debug_assertions)]\n    origin: &'static std::panic::Location<'static>,\n}\n\n#[allow(clippy::no_effect)]\nimpl<T: 'static, S: Storage<SignalData<T>> + 'static> Drop for SignalSubscriberDrop<T, S> {\n    fn drop(&mut self) {\n        #[cfg(debug_assertions)]\n        {\n            tracing::trace!(\n                \"Write on signal at {} finished, updating subscribers\",\n                self.origin\n            );\n        }\n        self.signal.update_subscribers();\n    }\n}\n\nfmt_impls!(Signal<T, S: Storage<SignalData<T>>>);\ndefault_impl!(Signal<T, S: Storage<SignalData<T>>>);\nwrite_impls!(Signal<T, S: Storage<SignalData<T>>>);\n\nimpl<T, S> Clone for Signal<T, S> {\n    fn clone(&self) -> Self {\n        *self\n    }\n}\n\nimpl<T, S> Copy for Signal<T, S> {}\n"
  },
  {
    "path": "packages/signals/src/warnings.rs",
    "content": "//! Warnings that can be triggered by suspicious usage of signals\n\nuse warnings::warning;\n\n/// A warning that is triggered when a copy value is used in a higher scope that it is owned by\n#[warning]\npub fn copy_value_hoisted<T: 'static, S: generational_box::Storage<T> + 'static>(\n    value: &crate::CopyValue<T, S>,\n    caller: &'static std::panic::Location<'static>,\n) {\n    let origin_scope = value.origin_scope;\n    let Some(rt) = dioxus_core::Runtime::try_current() else {\n        return;\n    };\n\n    if let Some(current_scope) = rt.try_current_scope_id() {\n        // If the current scope is a descendant of the origin scope or is the same scope, we don't need to warn\n        if origin_scope == current_scope || rt.is_descendant_of(current_scope, origin_scope) {\n            return;\n        }\n        let create_location = value.value.created_at().unwrap();\n        let broken_example = include_str!(\"../docs/hoist/error.rs\");\n        let fixed_example = include_str!(\"../docs/hoist/fixed_list.rs\");\n\n        // Otherwise, warn that the value is being used in a higher scope and may be dropped\n        tracing::warn!(\n            r#\"A Copy Value created in {origin_scope:?} (at {create_location}). It will be dropped when that scope is dropped, but it was used in {current_scope:?} (at {caller}) which is not a descendant of the owning scope.\nThis may cause reads or writes to fail because the value is dropped while it still held.\n\nHelp:\nCopy values (like CopyValue, Signal, Memo, and Resource) are owned by the scope they are created in. If you use the value in a scope that may be dropped after the origin scope,\nit is very easy to use the value after it has been dropped. To fix this, you can move the value to the parent of all of the scopes that it is used in.\n\nBroken example ❌:\n```rust\n{broken_example}\n```\n\nFixed example ✅:\n```rust\n{fixed_example}\n```\"#\n        );\n    }\n}\n\n// Include the examples from the warning to make sure they compile\n#[test]\n#[allow(unused)]\nfn hoist() {\n    mod broken {\n        use dioxus::prelude::*;\n        include!(\"../docs/hoist/error.rs\");\n    }\n    mod fixed {\n        use dioxus::prelude::*;\n        include!(\"../docs/hoist/fixed_list.rs\");\n    }\n}\n"
  },
  {
    "path": "packages/signals/src/write.rs",
    "content": "use std::{\n    collections::{HashMap, HashSet},\n    ops::{Deref, DerefMut, IndexMut},\n};\n\nuse generational_box::{AnyStorage, UnsyncStorage};\n\nuse crate::{ext_methods, read::Readable, read::ReadableExt, MappedMutSignal, WriteSignal};\n\n/// A reference to a value that can be written to.\n#[allow(type_alias_bounds)]\npub type WritableRef<'a, T: Writable, O = <T as Readable>::Target> =\n    WriteLock<'a, O, <T as Readable>::Storage, <T as Writable>::WriteMetadata>;\n\n/// A trait for states that can be written to like [`crate::Signal`]. You may choose to accept this trait as a parameter instead of the concrete type to allow for more flexibility in your API.\n///\n/// # Example\n/// ```rust\n/// # use dioxus::prelude::*;\n/// enum MyEnum {\n///     String(String),\n///     Number(i32),\n/// }\n///\n/// fn MyComponent(mut count: Signal<MyEnum>) -> Element {\n///     rsx! {\n///         button {\n///             onclick: move |_| {\n///                 // You can use any methods from the Writable trait on Signals\n///                 match &mut *count.write() {\n///                     MyEnum::String(s) => s.push('a'),\n///                     MyEnum::Number(n) => *n += 1,\n///                 }\n///             },\n///             \"Add value\"\n///         }\n///     }\n/// }\n/// ```\npub trait Writable: Readable {\n    /// Additional data associated with the write reference.\n    type WriteMetadata;\n\n    /// Try to get a mutable reference to the value without checking the lifetime. This will update any subscribers.\n    ///\n    /// NOTE: This method is completely safe because borrow checking is done at runtime.\n    fn try_write_unchecked(\n        &self,\n    ) -> Result<WritableRef<'static, Self>, generational_box::BorrowMutError>\n    where\n        Self::Target: 'static;\n}\n\n/// A mutable reference to a writable value. This reference acts similarly to [`std::cell::RefMut`], but it has extra debug information\n/// and integrates with the reactive system to automatically update dependents.\n///\n/// [`WriteLock`] implements [`DerefMut`] which means you can call methods on the inner value just like you would on a mutable reference\n/// to the inner value. If you need to get the inner reference directly, you can call [`WriteLock::deref_mut`].\n///\n/// # Example\n/// ```rust\n/// # use dioxus::prelude::*;\n/// fn app() -> Element {\n///     let mut value = use_signal(|| String::from(\"hello\"));\n///\n///     rsx! {\n///         button {\n///             onclick: move |_| {\n///                 let mut mutable_reference = value.write();\n///\n///                 // You call methods like `push_str` on the reference just like you would with the inner String\n///                 mutable_reference.push_str(\"world\");\n///             },\n///             \"Click to add world to the string\"\n///         }\n///         div { \"{value}\" }\n///     }\n/// }\n/// ```\n///\n/// ## Matching on WriteLock\n///\n/// You need to get the inner mutable reference with [`WriteLock::deref_mut`] before you match the inner value. If you try to match\n/// without calling [`WriteLock::deref_mut`], you will get an error like this:\n///\n/// ```compile_fail\n/// # use dioxus::prelude::*;\n/// #[derive(Debug)]\n/// enum Colors {\n///     Red(u32),\n///     Green\n/// }\n/// fn app() -> Element {\n///     let mut value = use_signal(|| Colors::Red(0));\n///\n///     rsx! {\n///         button {\n///             onclick: move |_| {\n///                 let mut mutable_reference = value.write();\n///\n///                 match mutable_reference {\n///                     // Since we are matching on the `Write` type instead of &mut Colors, we can't match on the enum directly\n///                     Colors::Red(brightness) => *brightness += 1,\n///                     Colors::Green => {}\n///                 }\n///             },\n///             \"Click to add brightness to the red color\"\n///         }\n///         div { \"{value:?}\" }\n///     }\n/// }\n/// ```\n///\n/// ```text\n/// error[E0308]: mismatched types\n///   --> src/main.rs:18:21\n///    |\n/// 16 |                 match mutable_reference {\n///    |                       ----------------- this expression has type `dioxus::prelude::Write<'_, Colors>`\n/// 17 |                     // Since we are matching on the `Write` t...\n/// 18 |                     Colors::Red(brightness) => *brightness += 1,\n///    |                     ^^^^^^^^^^^^^^^^^^^^^^^ expected `Write<'_, Colors>`, found `Colors`\n///    |\n///    = note: expected struct `dioxus::prelude::Write<'_, Colors, >`\n///                found enum `Colors`\n/// ```\n///\n/// Instead, you need to call deref mut on the reference to get the inner value **before** you match on it:\n///\n/// ```rust\n/// use std::ops::DerefMut;\n/// # use dioxus::prelude::*;\n/// #[derive(Debug)]\n/// enum Colors {\n///     Red(u32),\n///     Green\n/// }\n/// fn app() -> Element {\n///     let mut value = use_signal(|| Colors::Red(0));\n///\n///     rsx! {\n///         button {\n///             onclick: move |_| {\n///                 let mut mutable_reference = value.write();\n///\n///                 // DerefMut converts the `Write` into a `&mut Colors`\n///                 match mutable_reference.deref_mut() {\n///                     // Now we can match on the inner value\n///                     Colors::Red(brightness) => *brightness += 1,\n///                     Colors::Green => {}\n///                 }\n///             },\n///             \"Click to add brightness to the red color\"\n///         }\n///         div { \"{value:?}\" }\n///     }\n/// }\n/// ```\n///\n/// ## Generics\n/// - T is the current type of the write\n/// - S is the storage type of the signal. This type determines if the signal is local to the current thread, or it can be shared across threads.\n/// - D is the additional data associated with the write reference. This is used by signals to track when the write is dropped\npub struct WriteLock<'a, T: ?Sized + 'a, S: AnyStorage = UnsyncStorage, D = ()> {\n    write: S::Mut<'a, T>,\n    data: D,\n}\n\nimpl<'a, T: ?Sized, S: AnyStorage> WriteLock<'a, T, S> {\n    /// Create a new write reference\n    pub fn new(write: S::Mut<'a, T>) -> Self {\n        Self { write, data: () }\n    }\n}\n\nimpl<'a, T: ?Sized, S: AnyStorage, D> WriteLock<'a, T, S, D> {\n    /// Create a new write reference with additional data.\n    pub fn new_with_metadata(write: S::Mut<'a, T>, data: D) -> Self {\n        Self { write, data }\n    }\n\n    /// Get the inner value of the write reference.\n    pub fn into_inner(self) -> S::Mut<'a, T> {\n        self.write\n    }\n\n    /// Get the additional data associated with the write reference.\n    pub fn data(&self) -> &D {\n        &self.data\n    }\n\n    /// Split into the inner value and the additional data.\n    pub fn into_parts(self) -> (S::Mut<'a, T>, D) {\n        (self.write, self.data)\n    }\n\n    /// Map the metadata of the write reference to a new type.\n    pub fn map_metadata<O>(self, f: impl FnOnce(D) -> O) -> WriteLock<'a, T, S, O> {\n        WriteLock {\n            write: self.write,\n            data: f(self.data),\n        }\n    }\n\n    /// Map the mutable reference to the signal's value to a new type.\n    pub fn map<O: ?Sized>(\n        myself: Self,\n        f: impl FnOnce(&mut T) -> &mut O,\n    ) -> WriteLock<'a, O, S, D> {\n        let Self { write, data, .. } = myself;\n        WriteLock {\n            write: S::map_mut(write, f),\n            data,\n        }\n    }\n\n    /// Try to map the mutable reference to the signal's value to a new type\n    pub fn filter_map<O: ?Sized>(\n        myself: Self,\n        f: impl FnOnce(&mut T) -> Option<&mut O>,\n    ) -> Option<WriteLock<'a, O, S, D>> {\n        let Self { write, data, .. } = myself;\n        let write = S::try_map_mut(write, f);\n        write.map(|write| WriteLock { write, data })\n    }\n\n    /// Downcast the lifetime of the mutable reference to the signal's value.\n    ///\n    /// This function enforces the variance of the lifetime parameter `'a` in Mut.  Rust will typically infer this cast with a concrete type, but it cannot with a generic type.\n    pub fn downcast_lifetime<'b>(mut_: Self) -> WriteLock<'b, T, S, D>\n    where\n        'a: 'b,\n    {\n        WriteLock {\n            write: S::downcast_lifetime_mut(mut_.write),\n            data: mut_.data,\n        }\n    }\n}\n\nimpl<T, S, D> Deref for WriteLock<'_, T, S, D>\nwhere\n    S: AnyStorage,\n    T: ?Sized,\n{\n    type Target = T;\n\n    fn deref(&self) -> &Self::Target {\n        &self.write\n    }\n}\n\nimpl<T, S, D> DerefMut for WriteLock<'_, T, S, D>\nwhere\n    S: AnyStorage,\n    T: ?Sized,\n{\n    fn deref_mut(&mut self) -> &mut Self::Target {\n        &mut self.write\n    }\n}\n\n/// An extension trait for [`Writable`] that provides some convenience methods.\npub trait WritableExt: Writable {\n    /// Get a mutable reference to the value. If the value has been dropped, this will panic.\n    #[track_caller]\n    fn write(&mut self) -> WritableRef<'_, Self>\n    where\n        Self::Target: 'static,\n    {\n        self.try_write().unwrap()\n    }\n\n    /// Try to get a mutable reference to the value.\n    #[track_caller]\n    fn try_write(&mut self) -> Result<WritableRef<'_, Self>, generational_box::BorrowMutError>\n    where\n        Self::Target: 'static,\n    {\n        self.try_write_unchecked().map(WriteLock::downcast_lifetime)\n    }\n\n    /// Get a mutable reference to the value without checking the lifetime. This will update any subscribers.\n    ///\n    /// NOTE: This method is completely safe because borrow checking is done at runtime.\n    #[track_caller]\n    fn write_unchecked(&self) -> WritableRef<'static, Self>\n    where\n        Self::Target: 'static,\n    {\n        self.try_write_unchecked().unwrap()\n    }\n\n    /// Map the references and mutable references of the writable value to a new type. This lets you provide a view\n    /// into the writable value without creating a new signal or cloning the value.\n    ///\n    /// Anything that subscribes to the writable value will be rerun whenever the original value changes or you write to this\n    /// scoped value, even if the view does not change. If you want to memorize the view, you can use a [`crate::Memo`] instead.\n    /// For fine grained scoped updates, use stores instead\n    ///\n    /// # Example\n    /// ```rust\n    /// # use dioxus::prelude::*;\n    /// fn List(list: Signal<Vec<i32>>) -> Element {\n    ///     rsx! {\n    ///         for index in 0..list.len() {\n    ///             // We can use the `map` method to provide a view into the single item in the list that the child component will render\n    ///             Item { item: list.map_mut(move |v| &v[index], move |v| &mut v[index]) }\n    ///         }\n    ///     }\n    /// }\n    ///\n    /// // The child component doesn't need to know that the mapped value is coming from a list\n    /// #[component]\n    /// fn Item(item: WriteSignal<i32>) -> Element {\n    ///     rsx! {\n    ///         button {\n    ///             onclick: move |_| *item.write() += 1,\n    ///             \"{item}\"\n    ///         }\n    ///     }\n    /// }\n    /// ```\n    fn map_mut<O, F, FMut>(self, f: F, f_mut: FMut) -> MappedMutSignal<O, Self, F, FMut>\n    where\n        Self: Sized,\n        O: ?Sized,\n        F: Fn(&Self::Target) -> &O,\n        FMut: Fn(&mut Self::Target) -> &mut O,\n    {\n        MappedMutSignal::new(self, f, f_mut)\n    }\n\n    /// Run a function with a mutable reference to the value. If the value has been dropped, this will panic.\n    #[track_caller]\n    fn with_mut<O>(&mut self, f: impl FnOnce(&mut Self::Target) -> O) -> O\n    where\n        Self::Target: 'static,\n    {\n        f(&mut *self.write())\n    }\n\n    /// Set the value of the signal. This will trigger an update on all subscribers.\n    #[track_caller]\n    fn set(&mut self, value: Self::Target)\n    where\n        Self::Target: Sized + 'static,\n    {\n        *self.write() = value;\n    }\n\n    /// Invert the boolean value of the signal. This will trigger an update on all subscribers.\n    #[track_caller]\n    fn toggle(&mut self)\n    where\n        Self::Target: std::ops::Not<Output = Self::Target> + Clone + 'static,\n    {\n        let inverted = !(*self.peek()).clone();\n        self.set(inverted);\n    }\n\n    /// Index into the inner value and return a reference to the result.\n    #[track_caller]\n    fn index_mut<I>(\n        &mut self,\n        index: I,\n    ) -> WritableRef<'_, Self, <Self::Target as std::ops::Index<I>>::Output>\n    where\n        Self::Target: std::ops::IndexMut<I> + 'static,\n    {\n        WriteLock::map(self.write(), |v| v.index_mut(index))\n    }\n\n    /// Takes the value out of the Signal, leaving a Default in its place.\n    #[track_caller]\n    fn take(&mut self) -> Self::Target\n    where\n        Self::Target: Default + 'static,\n    {\n        self.with_mut(std::mem::take)\n    }\n\n    /// Replace the value in the Signal, returning the old value.\n    #[track_caller]\n    fn replace(&mut self, value: Self::Target) -> Self::Target\n    where\n        Self::Target: Sized + 'static,\n    {\n        self.with_mut(|v| std::mem::replace(v, value))\n    }\n}\n\nimpl<W: Writable + ?Sized> WritableExt for W {}\n\n/// An extension trait for [`Writable`] values that can be boxed into a trait object.\npub trait WritableBoxedExt: Writable<Storage = UnsyncStorage> {\n    /// Box the writable value into a trait object. This is useful for passing around writable values without knowing their concrete type.\n    fn boxed_mut(self) -> WriteSignal<Self::Target>\n    where\n        Self: Sized + 'static,\n    {\n        WriteSignal::new(self)\n    }\n}\n\nimpl<T: Writable<Storage = UnsyncStorage> + 'static> WritableBoxedExt for T {\n    fn boxed_mut(self) -> WriteSignal<Self::Target> {\n        WriteSignal::new(self)\n    }\n}\n\n/// An extension trait for [`Writable<Option<T>>`]` that provides some convenience methods.\npub trait WritableOptionExt<T>: Writable<Target = Option<T>> {\n    /// Gets the value out of the Option, or inserts the given value if the Option is empty.\n    #[track_caller]\n    fn get_or_insert(&mut self, default: T) -> WritableRef<'_, Self, T>\n    where\n        T: 'static,\n    {\n        self.get_or_insert_with(|| default)\n    }\n\n    /// Gets the value out of the Option, or inserts the value returned by the given function if the Option is empty.\n    #[track_caller]\n    fn get_or_insert_with(&mut self, default: impl FnOnce() -> T) -> WritableRef<'_, Self, T>\n    where\n        T: 'static,\n    {\n        let is_none = self.read().is_none();\n        if is_none {\n            self.with_mut(|v| *v = Some(default()));\n            WriteLock::map(self.write(), |v| v.as_mut().unwrap())\n        } else {\n            WriteLock::map(self.write(), |v| v.as_mut().unwrap())\n        }\n    }\n\n    /// Attempts to write the inner value of the Option.\n    #[track_caller]\n    fn as_mut(&mut self) -> Option<WritableRef<'_, Self, T>>\n    where\n        T: 'static,\n    {\n        WriteLock::filter_map(self.write(), |v: &mut Option<T>| v.as_mut())\n    }\n}\n\nimpl<T, W> WritableOptionExt<T> for W where W: Writable<Target = Option<T>> {}\n\n/// An extension trait for [`Writable<Result<T, E>>`] that provides some convenience methods.\npub trait WritableResultExt<T, E>: Writable<Target = Result<T, E>> {\n    /// Unwraps the inner value mutably, panicking if the Result is an error.\n    #[track_caller]\n    fn unwrap_mut(&mut self) -> WritableRef<'_, Self, T>\n    where\n        T: 'static,\n        E: 'static,\n    {\n        WriteLock::filter_map(self.write(), |v| v.as_mut().ok())\n            .expect(\"Tried to unwrap a Result that was an error\")\n    }\n\n    /// Attempts to mutably access the inner value of the Result.\n    #[track_caller]\n    fn as_mut(&mut self) -> Result<WritableRef<'_, Self, T>, WritableRef<'_, Self, E>>\n    where\n        T: 'static,\n        E: 'static,\n    {\n        let write = self.write();\n        match write.as_ref() {\n            Ok(_) => Ok(WriteLock::map(write, |v| {\n                v.as_mut()\n                    .ok()\n                    .expect(\"Result variant changed between read and write\")\n            })),\n            Err(_) => Err(WriteLock::map(write, |v| {\n                v.as_mut()\n                    .err()\n                    .expect(\"Result variant changed between read and write\")\n            })),\n        }\n    }\n}\n\nimpl<T, E, W> WritableResultExt<T, E> for W where W: Writable<Target = Result<T, E>> {}\n\n/// An extension trait for [`Writable<Vec<T>>`] that provides some convenience methods.\npub trait WritableVecExt<T>: Writable<Target = Vec<T>> {\n    /// Pushes a new value to the end of the vector.\n    #[track_caller]\n    fn push(&mut self, value: T)\n    where\n        T: 'static,\n    {\n        self.with_mut(|v| v.push(value))\n    }\n\n    /// Pops the last value from the vector.\n    #[track_caller]\n    fn pop(&mut self) -> Option<T>\n    where\n        T: 'static,\n    {\n        self.with_mut(|v| v.pop())\n    }\n\n    /// Inserts a new value at the given index.\n    #[track_caller]\n    fn insert(&mut self, index: usize, value: T)\n    where\n        T: 'static,\n    {\n        self.with_mut(|v| v.insert(index, value))\n    }\n\n    /// Removes the value at the given index.\n    #[track_caller]\n    fn remove(&mut self, index: usize) -> T\n    where\n        T: 'static,\n    {\n        self.with_mut(|v| v.remove(index))\n    }\n\n    /// Clears the vector, removing all values.\n    #[track_caller]\n    fn clear(&mut self)\n    where\n        T: 'static,\n    {\n        self.with_mut(|v| v.clear())\n    }\n\n    /// Extends the vector with the given iterator.\n    #[track_caller]\n    fn extend(&mut self, iter: impl IntoIterator<Item = T>)\n    where\n        T: 'static,\n    {\n        self.with_mut(|v| v.extend(iter))\n    }\n\n    /// Truncates the vector to the given length.\n    #[track_caller]\n    fn truncate(&mut self, len: usize)\n    where\n        T: 'static,\n    {\n        self.with_mut(|v| v.truncate(len))\n    }\n\n    /// Swaps two values in the vector.\n    #[track_caller]\n    fn swap_remove(&mut self, index: usize) -> T\n    where\n        T: 'static,\n    {\n        self.with_mut(|v| v.swap_remove(index))\n    }\n\n    /// Retains only the values that match the given predicate.\n    #[track_caller]\n    fn retain(&mut self, f: impl FnMut(&T) -> bool)\n    where\n        T: 'static,\n    {\n        self.with_mut(|v| v.retain(f))\n    }\n\n    /// Splits the vector into two at the given index.\n    #[track_caller]\n    fn split_off(&mut self, at: usize) -> Vec<T>\n    where\n        T: 'static,\n    {\n        self.with_mut(|v| v.split_off(at))\n    }\n\n    /// Try to mutably get an element from the vector.\n    #[track_caller]\n    fn get_mut(&mut self, index: usize) -> Option<WritableRef<'_, Self, T>>\n    where\n        T: 'static,\n    {\n        WriteLock::filter_map(self.write(), |v: &mut Vec<T>| v.get_mut(index))\n    }\n\n    /// Gets an iterator over the values of the vector.\n    #[track_caller]\n    fn iter_mut(&mut self) -> WritableValueIterator<'_, Self>\n    where\n        Self: Sized + Clone,\n    {\n        WritableValueIterator {\n            index: 0,\n            value: self,\n        }\n    }\n}\n\n/// An iterator over the values of a [`Writable<Vec<T>>`].\npub struct WritableValueIterator<'a, R> {\n    index: usize,\n    value: &'a mut R,\n}\n\nimpl<'a, T: 'static, R: Writable<Target = Vec<T>>> Iterator for WritableValueIterator<'a, R> {\n    type Item = WritableRef<'a, R, T>;\n\n    fn next(&mut self) -> Option<Self::Item> {\n        let index = self.index;\n        self.index += 1;\n        WriteLock::filter_map(\n            self.value.try_write_unchecked().unwrap(),\n            |v: &mut Vec<T>| v.get_mut(index),\n        )\n        .map(WriteLock::downcast_lifetime)\n    }\n}\n\nimpl<W, T> WritableVecExt<T> for W where W: Writable<Target = Vec<T>> {}\n\n/// An extension trait for [`Writable<String>`] that provides some convenience methods.\npub trait WritableStringExt: Writable<Target = String> {\n    ext_methods! {\n        /// Pushes a character to the end of the string.\n        fn push_str(&mut self, s: &str) = String::push_str;\n\n        /// Pushes a character to the end of the string.\n        fn push(&mut self, c: char) = String::push;\n\n        /// Pops a character from the end of the string.\n        fn pop(&mut self) -> Option<char> = String::pop;\n\n        /// Inserts a string at the given index.\n        fn insert_str(&mut self, idx: usize, s: &str) = String::insert_str;\n\n        /// Inserts a character at the given index.\n        fn insert(&mut self, idx: usize, c: char) = String::insert;\n\n        /// Remove a character at the given index\n        fn remove(&mut self, idx: usize) -> char = String::remove;\n\n        /// Replace a range of the string with the given string.\n        fn replace_range(&mut self, range: impl std::ops::RangeBounds<usize>, replace_with: &str) = String::replace_range;\n\n        /// Clears the string, removing all characters.\n        fn clear(&mut self) = String::clear;\n\n        /// Extends the string with the given iterator of characters.\n        fn extend(&mut self, iter: impl IntoIterator<Item = char>) = String::extend;\n\n        /// Truncates the string to the given length.\n        fn truncate(&mut self, len: usize) = String::truncate;\n\n        /// Splits the string off at the given index, returning the tail as a new string.\n        fn split_off(&mut self, at: usize) -> String = String::split_off;\n    }\n}\n\nimpl<W> WritableStringExt for W where W: Writable<Target = String> {}\n\n/// An extension trait for [`Writable<HashMap<K, V, H>>`] that provides some convenience methods.\npub trait WritableHashMapExt<K: 'static, V: 'static, H: 'static>:\n    Writable<Target = HashMap<K, V, H>>\n{\n    ext_methods! {\n        /// Clears the map, removing all key-value pairs.\n        fn clear(&mut self) = HashMap::clear;\n\n        /// Retains only the key-value pairs that match the given predicate.\n        fn retain(&mut self, f: impl FnMut(&K, &mut V) -> bool) = HashMap::retain;\n    }\n\n    /// Inserts a key-value pair into the map. If the key was already present, the old value is returned.\n    #[track_caller]\n    fn insert(&mut self, k: K, v: V) -> Option<V>\n    where\n        K: std::cmp::Eq + std::hash::Hash,\n        H: std::hash::BuildHasher,\n    {\n        self.with_mut(|map: &mut HashMap<K, V, H>| map.insert(k, v))\n    }\n\n    /// Extends the map with the key-value pairs from the given iterator.\n    #[track_caller]\n    fn extend(&mut self, iter: impl IntoIterator<Item = (K, V)>)\n    where\n        K: std::cmp::Eq + std::hash::Hash,\n        H: std::hash::BuildHasher,\n    {\n        self.with_mut(|map: &mut HashMap<K, V, H>| map.extend(iter))\n    }\n\n    /// Removes a key from the map, returning the value at the key if the key was previously in the map.\n    #[track_caller]\n    fn remove(&mut self, k: &K) -> Option<V>\n    where\n        K: std::cmp::Eq + std::hash::Hash,\n        H: std::hash::BuildHasher,\n    {\n        self.with_mut(|map: &mut HashMap<K, V, H>| map.remove(k))\n    }\n\n    /// Get a mutable reference to the value at the given key.\n    #[track_caller]\n    fn get_mut(&mut self, k: &K) -> Option<WritableRef<'_, Self, V>>\n    where\n        K: std::cmp::Eq + std::hash::Hash,\n        H: std::hash::BuildHasher,\n    {\n        WriteLock::filter_map(self.write(), |map: &mut HashMap<K, V, H>| map.get_mut(k))\n    }\n}\n\nimpl<K: 'static, V: 'static, H: 'static, R> WritableHashMapExt<K, V, H> for R where\n    R: Writable<Target = HashMap<K, V, H>>\n{\n}\n\n/// An extension trait for [`Writable<HashSet<V, H>>`] that provides some convenience methods.\npub trait WritableHashSetExt<V: 'static, H: 'static>: Writable<Target = HashSet<V, H>> {\n    ext_methods! {\n        /// Clear the hash set.\n        fn clear(&mut self) = HashSet::clear;\n\n        /// Retain only the elements specified by the predicate.\n        fn retain(&mut self, f: impl FnMut(&V) -> bool) = HashSet::retain;\n    }\n\n    /// Inserts a value into the set. Returns true if the value was not already present.\n    #[track_caller]\n    fn insert(&mut self, k: V) -> bool\n    where\n        V: std::cmp::Eq + std::hash::Hash,\n        H: std::hash::BuildHasher,\n    {\n        self.with_mut(|set| set.insert(k))\n    }\n\n    /// Extends the set with the values from the given iterator.\n    #[track_caller]\n    fn extend(&mut self, iter: impl IntoIterator<Item = V>)\n    where\n        V: std::cmp::Eq + std::hash::Hash,\n        H: std::hash::BuildHasher,\n    {\n        self.with_mut(|set| set.extend(iter))\n    }\n\n    /// Removes a value from the set. Returns true if the value was present.\n    #[track_caller]\n    fn remove(&mut self, k: &V) -> bool\n    where\n        V: std::cmp::Eq + std::hash::Hash,\n        H: std::hash::BuildHasher,\n    {\n        self.with_mut(|set| set.remove(k))\n    }\n}\n\nimpl<V: 'static, H: 'static, R> WritableHashSetExt<V, H> for R where\n    R: Writable<Target = HashSet<V, H>>\n{\n}\n"
  },
  {
    "path": "packages/signals/tests/create.rs",
    "content": "#![allow(unused, non_upper_case_globals, non_snake_case)]\n\nuse dioxus::prelude::*;\nuse dioxus_core::{generation, ElementId, NoOpMutations};\nuse dioxus_signals::*;\n\n#[test]\nfn create_signals_global() {\n    let mut dom = VirtualDom::new(|| {\n        rsx! {\n            for _ in 0..10 {\n                Child {}\n            }\n        }\n    });\n\n    fn Child() -> Element {\n        let signal = create_without_cx();\n\n        rsx! {\n            \"{signal}\"\n        }\n    }\n\n    dom.rebuild_in_place();\n\n    fn create_without_cx() -> Signal<String> {\n        Signal::new(\"hello world\".to_string())\n    }\n}\n\n#[test]\nfn deref_signal() {\n    let mut dom = VirtualDom::new(|| {\n        rsx! {\n            for _ in 0..10 {\n                Child {}\n            }\n        }\n    });\n\n    fn Child() -> Element {\n        let signal = Signal::new(\"hello world\".to_string());\n\n        // You can call signals like functions to get a Ref of their value.\n        assert_eq!(&*signal(), \"hello world\");\n\n        rsx! {\n            \"hello world\"\n        }\n    }\n\n    dom.rebuild_in_place();\n}\n\n#[test]\nfn drop_signals() {\n    use std::sync::atomic::AtomicUsize;\n    use std::sync::atomic::Ordering;\n\n    static SIGNAL_DROP_COUNT: AtomicUsize = AtomicUsize::new(0);\n\n    let mut dom = VirtualDom::new(|| {\n        let generation = generation();\n\n        let count = if generation % 2 == 0 { 10 } else { 0 };\n        rsx! {\n            for _ in 0..count {\n                Child {}\n            }\n        }\n    });\n\n    fn Child() -> Element {\n        struct TracksDrops;\n\n        impl Drop for TracksDrops {\n            fn drop(&mut self) {\n                SIGNAL_DROP_COUNT.fetch_add(1, Ordering::Relaxed);\n            }\n        }\n\n        use_signal(|| TracksDrops);\n\n        rsx! {\n            \"\"\n        }\n    }\n\n    dom.rebuild_in_place();\n    dom.mark_dirty(ScopeId::APP);\n    dom.render_immediate(&mut NoOpMutations);\n\n    assert_eq!(SIGNAL_DROP_COUNT.load(Ordering::Relaxed), 10);\n}\n"
  },
  {
    "path": "packages/signals/tests/memo.rs",
    "content": "#![allow(unused, non_upper_case_globals, non_snake_case)]\nuse dioxus::html::p;\nuse dioxus::prelude::*;\nuse dioxus_core::NoOpMutations;\nuse dioxus_core::{generation, ElementId};\nuse dioxus_signals::*;\nuse std::cell::RefCell;\nuse std::collections::HashMap;\nuse std::rc::Rc;\nuse std::sync::atomic::{AtomicBool, Ordering};\n\n#[test]\nfn memos_rerun() {\n    tracing_subscriber::fmt::init();\n\n    #[derive(Default)]\n    struct RunCounter {\n        component: usize,\n        effect: usize,\n    }\n\n    let counter = Rc::new(RefCell::new(RunCounter::default()));\n    let mut dom = VirtualDom::new_with_props(\n        |counter: Rc<RefCell<RunCounter>>| {\n            counter.borrow_mut().component += 1;\n\n            let mut signal = use_signal(|| 0);\n            let memo = use_memo({\n                to_owned![counter];\n                move || {\n                    counter.borrow_mut().effect += 1;\n                    println!(\"Signal: {:?}\", signal);\n                    signal()\n                }\n            });\n            assert_eq!(memo(), 0);\n            signal += 1;\n            assert_eq!(memo(), 1);\n\n            rsx! {\n                div {}\n            }\n        },\n        counter.clone(),\n    );\n\n    dom.rebuild_in_place();\n\n    let current_counter = counter.borrow();\n    assert_eq!(current_counter.component, 1);\n    assert_eq!(current_counter.effect, 2);\n}\n\n#[test]\nfn memos_prevents_component_rerun() {\n    #[derive(Default)]\n    struct RunCounter {\n        component: usize,\n        memo: usize,\n    }\n\n    let counter = Rc::new(RefCell::new(RunCounter::default()));\n    let mut dom = VirtualDom::new_with_props(\n        |props: Rc<RefCell<RunCounter>>| {\n            let mut signal = use_signal(|| 0);\n\n            if generation() == 1 {\n                *signal.write() = 0;\n            }\n            if generation() == 2 {\n                println!(\"Writing to signal\");\n                *signal.write() = 1;\n            }\n\n            rsx! {\n                Child {\n                    signal: signal,\n                    counter: props.clone(),\n                }\n            }\n        },\n        counter.clone(),\n    );\n\n    #[derive(Default, Props, Clone)]\n    struct ChildProps {\n        signal: Signal<usize>,\n        counter: Rc<RefCell<RunCounter>>,\n    }\n\n    impl PartialEq for ChildProps {\n        fn eq(&self, other: &Self) -> bool {\n            self.signal == other.signal\n        }\n    }\n\n    fn Child(props: ChildProps) -> Element {\n        let counter = &props.counter;\n        let signal = props.signal;\n        counter.borrow_mut().component += 1;\n\n        let memo = use_memo({\n            to_owned![counter];\n            move || {\n                counter.borrow_mut().memo += 1;\n                println!(\"Signal: {:?}\", signal);\n                signal()\n            }\n        });\n        match generation() {\n            0 => {\n                assert_eq!(memo(), 0);\n            }\n            1 => {\n                assert_eq!(memo(), 1);\n            }\n            _ => panic!(\"Unexpected generation\"),\n        }\n\n        rsx! {\n            div {}\n        }\n    }\n\n    dom.rebuild_in_place();\n    dom.mark_dirty(ScopeId::APP);\n    dom.render_immediate(&mut NoOpMutations);\n\n    {\n        let current_counter = counter.borrow();\n        assert_eq!(current_counter.component, 1);\n        assert_eq!(current_counter.memo, 2);\n    }\n\n    dom.mark_dirty(ScopeId::APP);\n    dom.render_immediate(&mut NoOpMutations);\n    dom.render_immediate(&mut NoOpMutations);\n\n    {\n        let current_counter = counter.borrow();\n        assert_eq!(current_counter.component, 2);\n        assert_eq!(current_counter.memo, 3);\n    }\n}\n\n// Regression test for https://github.com/DioxusLabs/dioxus/issues/2990\n#[test]\nfn memos_sync_rerun_after_unrelated_write() {\n    static PASSED: AtomicBool = AtomicBool::new(false);\n    let mut dom = VirtualDom::new(|| {\n        let mut signal = use_signal(|| 0);\n        let memo = use_memo(move || dbg!(signal() < 2));\n        if generation() == 0 {\n            assert!(memo());\n            signal += 1;\n        } else {\n            // It should be fine to hold the write and read the memo at the same time\n            let mut write = signal.write();\n            println!(\"Memo: {:?}\", memo());\n            assert!(memo());\n            *write = 2;\n            drop(write);\n            assert!(!memo());\n            PASSED.store(true, std::sync::atomic::Ordering::SeqCst);\n        }\n\n        rsx! {\n            div {}\n        }\n    });\n\n    dom.rebuild_in_place();\n    dom.mark_dirty(ScopeId::APP);\n    dom.render_immediate(&mut NoOpMutations);\n    assert!(PASSED.load(Ordering::SeqCst));\n}\n"
  },
  {
    "path": "packages/signals/tests/subscribe.rs",
    "content": "#![allow(unused, non_upper_case_globals, non_snake_case)]\n\nuse dioxus_core::{current_scope_id, generation, NoOpMutations};\nuse std::collections::HashMap;\nuse std::rc::Rc;\n\nuse dioxus::prelude::*;\nuse dioxus_core::ElementId;\nuse dioxus_signals::*;\nuse std::cell::RefCell;\n\n#[test]\nfn reading_subscribes() {\n    tracing_subscriber::fmt::init();\n\n    #[derive(Default)]\n    struct RunCounter {\n        parent: usize,\n        children: HashMap<ScopeId, usize>,\n    }\n\n    let counter = Rc::new(RefCell::new(RunCounter::default()));\n    let mut dom = VirtualDom::new_with_props(\n        |props: Rc<RefCell<RunCounter>>| {\n            let mut signal = use_signal(|| 0);\n\n            println!(\"Parent: {:?}\", current_scope_id());\n            if generation() == 1 {\n                signal += 1;\n            }\n\n            props.borrow_mut().parent += 1;\n\n            rsx! {\n                for id in 0..10 {\n                    Child {\n                        signal: signal,\n                        counter: props.clone()\n                    }\n                }\n            }\n        },\n        counter.clone(),\n    );\n\n    #[derive(Props, Clone)]\n    struct ChildProps {\n        signal: Signal<usize>,\n        counter: Rc<RefCell<RunCounter>>,\n    }\n\n    impl PartialEq for ChildProps {\n        fn eq(&self, other: &Self) -> bool {\n            self.signal == other.signal\n        }\n    }\n\n    fn Child(props: ChildProps) -> Element {\n        println!(\"Child: {:?}\", current_scope_id());\n        *props\n            .counter\n            .borrow_mut()\n            .children\n            .entry(current_scope_id())\n            .or_default() += 1;\n\n        rsx! {\n            \"{props.signal}\"\n        }\n    }\n\n    dom.rebuild_in_place();\n\n    {\n        let current_counter = counter.borrow();\n        assert_eq!(current_counter.parent, 1);\n\n        for (scope_id, rerun_count) in current_counter.children.iter() {\n            assert_eq!(rerun_count, &1);\n        }\n    }\n\n    dom.mark_dirty(ScopeId::APP);\n    dom.render_immediate(&mut NoOpMutations);\n    dom.render_immediate(&mut NoOpMutations);\n\n    {\n        let current_counter = counter.borrow();\n        assert_eq!(current_counter.parent, 2);\n\n        for (scope_id, rerun_count) in current_counter.children.iter() {\n            assert_eq!(rerun_count, &2);\n        }\n    }\n}\n"
  },
  {
    "path": "packages/ssr/Cargo.toml",
    "content": "[package]\nname = \"dioxus-ssr\"\nversion = { workspace = true }\nauthors = [\"Jonathan Kelley\"]\nedition = \"2021\"\ndescription = \"Dioxus render-to-string\"\nlicense = \"MIT OR Apache-2.0\"\nrepository = \"https://github.com/DioxusLabs/dioxus/\"\nkeywords = [\"dom\", \"ui\", \"gui\", \"react\", \"ssr\"]\n\n[dependencies]\ndioxus-core = { workspace = true, features = [\"serialize\"] }\ndioxus-core-types = { workspace = true }\naskama_escape = { workspace = true }\nrustc-hash = { workspace = true }\n\n[dev-dependencies]\ndioxus = { workspace = true }\n\n[package.metadata.docs.rs]\ncargo-args = [\"-Zunstable-options\", \"-Zrustdoc-scrape-examples\"]\n"
  },
  {
    "path": "packages/ssr/README.md",
    "content": "<div align=\"center\">\n  <h1>Dioxus Server-Side Rendering (SSR)</h1>\n  <p>\n    <strong>Render Dioxus to valid html.</strong>\n  </p>\n</div>\n\n## Resources\n\nThis crate is a part of the broader Dioxus ecosystem. For more resources about Dioxus, check out:\n\n- [Getting Started](https://dioxuslabs.com/learn/0.7/getting_started)\n- [Book](https://dioxuslabs.com/learn/0.7/)\n- [Examples](https://github.com/DioxusLabs/dioxus/tree/main/examples)\n\n## Overview\n\nDioxus SSR provides utilities to render Dioxus components to valid HTML. Once rendered, the HTML can be rehydrated client-side or served from your web server of choice.\n\n```rust\n# use dioxus::prelude::*;\nfn app() -> Element {\n  rsx! {\n    div {\"hello world!\"}\n  }\n}\n\nlet mut vdom = VirtualDom::new(app);\nvdom.rebuild_in_place();\n\nlet text = dioxus_ssr::render(&vdom);\nassert_eq!(text, \"<div>hello world!</div>\")\n```\n\n## Basic Usage\n\nThe simplest example is to simply render some `rsx!` nodes to HTML. This can be done with the [`render_element`] API.\n\n```rust, no_run\n# use dioxus::prelude::*;\nlet content = dioxus_ssr::render_element(rsx! {\n    div {\n        for i in 0..5 {\n            \"Number: {i}\"\n        }\n    }\n});\n```\n\n## Rendering a VirtualDom\n\n```rust, no_run\n# use dioxus::prelude::*;\n# fn app() -> Element { todo!() }\nlet mut vdom = VirtualDom::new(app);\nvdom.rebuild_in_place();\n\nlet content = dioxus_ssr::render(&vdom);\n```\n\n## Usage in pre-rendering\n\nThis crate is particularly useful in pre-generating pages server-side and then selectively loading Dioxus client-side to pick up the reactive elements.\n\nThis crate supports hydration out of the box. However, both the client and server must generate the _exact_ same VirtualDOMs - the client picks up its VirtualDOM assuming that the pre-rendered page output is the same. To do this, you need to make sure that your VirtualDOM implementation is deterministic! This could involve either serializing our app state and sending it to the client, hydrating only parts of the page, or building tests to ensure what's rendered on the server is the same as the client.\n\nWith pre-rendering enabled, this crate will generate element nodes with Element IDs pre-associated. During hydration, the Dioxus-WebSys renderer will attach the Virtual nodes to these real nodes after a page query.\n\nTo enable pre-rendering, simply set the pre-rendering flag to true.\n\n```rust, no_run\n# use dioxus::prelude::*;\n# fn App() -> Element { todo!() }\nlet mut vdom = VirtualDom::new(App);\n\nvdom.rebuild_in_place();\n\nlet mut renderer = dioxus_ssr::Renderer::new();\nrenderer.pre_render = true;\n\nlet text = renderer.render(&vdom);\n```\n\n## Usage in server-side rendering\n\nDioxus SSR can also be used to render on the server. You can just render the VirtualDOM to a string and send that to the client.\n\n```rust\n# use dioxus::prelude::*;\nfn App() -> Element {\n  rsx! { div { \"hello world!\" } }\n}\nlet mut vdom = VirtualDom::new(App);\nvdom.rebuild_in_place();\nlet text = dioxus_ssr::render(&vdom);\nassert_eq!(text, \"<div>hello world!</div>\")\n```\n\nThe rest of the space - IE doing this more efficiently, caching the VirtualDom, etc, will all need to be a custom implementation for now.\n\n## Usage in static site generation\n\nDioxus SSR is a powerful tool to generate static sites. Using Dioxus for static site generation _is_ a bit overkill, however. The new documentation generation library, Doxie, is essentially Dioxus SSR on steroids designed for static site generation with client-side hydration.\n\nAgain, simply render the VirtualDOM to a string using `render` or any of the other render methods.\n"
  },
  {
    "path": "packages/ssr/src/cache.rs",
    "content": "//! Dioxus SSR uses the design of templates to cache as much as possible about the HTML a block of rsx can render.\n//!\n//! The structure of templates can tell us what segments are rendered where and lets us cache segments in the output string.\n//!\n//! For example, in this code, we can cache the whole render:\n//! ```rust, no_run\n//! use dioxus::prelude::*;\n//! rsx! {\n//!     div {\n//!         \"Hello world\"\n//!     }\n//! };\n//! ```\n//! Because everything exists in the template, we can calculate the whole HTML for the template once and then reuse it.\n//! ```html\n//! <div>Hello world</div>\n//! ```\n//!\n//! If the template is more complex, we can only cache the parts that are static. In this case, we can cache `<div width=\"100px\">` and `</div>`, but not the child text.\n//!\n//! ```rust, no_run\n//! use dioxus::prelude::*;\n//! let dynamic = 123;\n//! rsx! {\n//!     div {\n//!         width: \"100px\",\n//!         \"{dynamic}\"\n//!     }\n//! };\n//!```\n\nuse crate::renderer::{str_truthy, BOOL_ATTRS};\nuse dioxus_core::{TemplateAttribute, TemplateNode, VNode};\nuse std::{fmt::Write, ops::AddAssign};\n\n#[derive(Debug)]\npub(crate) struct StringCache {\n    pub segments: Vec<Segment>,\n}\n\n#[derive(Default)]\npub struct StringChain {\n    // If we should add new static text to the last segment\n    // This will be true if the last segment is a static text and the last text isn't part of a hydration only boundary\n    add_text_to_last_segment: bool,\n    segments: Vec<Segment>,\n}\n\nimpl StringChain {\n    /// Add segments but only when hydration is enabled\n    fn if_hydration_enabled<O>(\n        &mut self,\n        during_prerender: impl FnOnce(&mut StringChain) -> O,\n    ) -> O {\n        // Insert a placeholder jump to the end of the hydration only segments\n        let jump_index = self.segments.len();\n        *self += Segment::HydrationOnlySection(0);\n        let out = during_prerender(self);\n        // Go back and fill in where the placeholder jump should skip to\n        let after_hydration_only_section = self.segments.len();\n        // Don't add any text to static text in the hydration only section. This would cause the text to be skipped during non-hydration renders\n        self.add_text_to_last_segment = false;\n        self.segments[jump_index] = Segment::HydrationOnlySection(after_hydration_only_section);\n        out\n    }\n\n    /// Add a new segment\n    pub fn push(&mut self, segment: Segment) {\n        self.add_text_to_last_segment = matches!(segment, Segment::PreRendered(_));\n        self.segments.push(segment);\n    }\n}\n\nimpl AddAssign<Segment> for StringChain {\n    fn add_assign(&mut self, rhs: Segment) {\n        self.push(rhs)\n    }\n}\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]\n/// The escape text enum is used to mark segments that should be escaped\n/// when rendering. This is used to prevent XSS attacks by escaping user input.\npub(crate) enum EscapeText {\n    /// Always escape the text. This will be assigned if the text node is under\n    /// a normal tag like a div in the template\n    Escape,\n    /// Don't escape the text. This will be assigned if the text node is under\n    /// a script or style tag in the template\n    NoEscape,\n    /// Only escape the tag if this is rendered under a script or style tag in\n    /// the parent template. This will be assigned if the text node is a root\n    /// node in the template\n    ParentEscape,\n}\n\nimpl EscapeText {\n    /// Check if the text should be escaped based on the parent's resolved\n    /// escape text value\n    pub fn should_escape(&self, parent_escaped: bool) -> bool {\n        match self {\n            EscapeText::Escape => true,\n            EscapeText::NoEscape => false,\n            EscapeText::ParentEscape => parent_escaped,\n        }\n    }\n}\n\n#[derive(Debug, Clone, PartialEq, Eq, Hash)]\npub(crate) enum Segment {\n    /// A marker for where to insert an attribute with a given index\n    Attr(usize),\n    /// A marker for where to insert a node with a given index\n    Node {\n        index: usize,\n        escape_text: EscapeText,\n    },\n    /// Text that we know is static in the template that is pre-rendered\n    PreRendered(String),\n    /// Text we know is static in the template that is pre-rendered that may or may not be escaped\n    PreRenderedMaybeEscaped {\n        /// The text to render\n        value: String,\n        /// Only render this text if the escaped value is this\n        renderer_if_escaped: bool,\n    },\n    /// Anything between this and the segments at the index is only required for hydration. If you don't need to hydrate, you can safely skip to the section at the given index\n    HydrationOnlySection(usize),\n    /// A marker for where to insert a dynamic styles\n    StyleMarker {\n        // If the marker is inside a style tag or not\n        // This will be true if there are static styles\n        inside_style_tag: bool,\n    },\n    /// A marker for where to insert a dynamic inner html\n    InnerHtmlMarker,\n    /// A marker for where to insert a node id for an attribute\n    AttributeNodeMarker,\n    /// A marker for where to insert a node id for a root node\n    RootNodeMarker,\n}\n\nimpl std::fmt::Write for StringChain {\n    fn write_str(&mut self, s: &str) -> std::fmt::Result {\n        if self.add_text_to_last_segment {\n            match self.segments.last_mut() {\n                Some(Segment::PreRendered(s2)) => s2.push_str(s),\n                _ => unreachable!(),\n            }\n        } else {\n            self.segments.push(Segment::PreRendered(s.to_string()))\n        }\n\n        self.add_text_to_last_segment = true;\n\n        Ok(())\n    }\n}\n\nimpl StringCache {\n    /// Create a new string cache from a template. This intentionally does not include any settings about the render mode (hydration or not) so that we can reuse the cache for both hydration and non-hydration renders.\n    pub fn from_template(template: &VNode) -> Result<Self, std::fmt::Error> {\n        let mut chain = StringChain::default();\n\n        let mut cur_path = vec![];\n\n        for (root_idx, root) in template.template.roots.iter().enumerate() {\n            from_template_recursive(\n                root,\n                &mut cur_path,\n                root_idx,\n                true,\n                EscapeText::ParentEscape,\n                &mut chain,\n            )?;\n        }\n\n        Ok(Self {\n            segments: chain.segments,\n        })\n    }\n}\n\nfn from_template_recursive(\n    root: &TemplateNode,\n    cur_path: &mut Vec<usize>,\n    root_idx: usize,\n    is_root: bool,\n    escape_text: EscapeText,\n    chain: &mut StringChain,\n) -> Result<(), std::fmt::Error> {\n    match root {\n        TemplateNode::Element {\n            tag,\n            attrs,\n            children,\n            ..\n        } => {\n            cur_path.push(root_idx);\n            write!(chain, \"<{tag}\")?;\n            // we need to collect the styles and write them at the end\n            let mut styles = Vec::new();\n            // we need to collect the inner html and write it at the end\n            let mut inner_html = None;\n            // we need to keep track of if we have dynamic attrs to know if we need to insert a style and inner_html marker\n            let mut has_dyn_attrs = false;\n            for attr in *attrs {\n                match attr {\n                    TemplateAttribute::Static {\n                        name,\n                        value,\n                        namespace,\n                    } => {\n                        if *name == \"dangerous_inner_html\" {\n                            inner_html = Some(value);\n                        } else if let Some(\"style\") = namespace {\n                            styles.push((name, value));\n                        } else if BOOL_ATTRS.contains(name) {\n                            if str_truthy(value) {\n                                write!(\n                                    chain,\n                                    \" {name}=\\\"{}\\\"\",\n                                    askama_escape::escape(value, askama_escape::Html)\n                                )?;\n                            }\n                        } else {\n                            write!(\n                                chain,\n                                \" {name}=\\\"{}\\\"\",\n                                askama_escape::escape(value, askama_escape::Html)\n                            )?;\n                        }\n                    }\n                    TemplateAttribute::Dynamic { id: index } => {\n                        let index = *index;\n                        *chain += Segment::Attr(index);\n                        has_dyn_attrs = true\n                    }\n                }\n            }\n\n            // write the styles\n            if !styles.is_empty() {\n                write!(chain, \" style=\\\"\")?;\n                for (name, value) in styles {\n                    write!(\n                        chain,\n                        \"{name}:{};\",\n                        askama_escape::escape(value, askama_escape::Html)\n                    )?;\n                }\n                *chain += Segment::StyleMarker {\n                    inside_style_tag: true,\n                };\n                write!(chain, \"\\\"\")?;\n            } else if has_dyn_attrs {\n                *chain += Segment::StyleMarker {\n                    inside_style_tag: false,\n                };\n            }\n\n            // write the id if we are prerendering and this is either a root node or a node with a dynamic attribute\n            if has_dyn_attrs || is_root {\n                chain.if_hydration_enabled(|chain| {\n                    write!(chain, \" data-node-hydration=\\\"\")?;\n                    if has_dyn_attrs {\n                        *chain += Segment::AttributeNodeMarker;\n                    } else if is_root {\n                        *chain += Segment::RootNodeMarker;\n                    }\n                    write!(chain, \"\\\"\")?;\n                    std::fmt::Result::Ok(())\n                })?;\n            }\n\n            if children.is_empty() && tag_is_self_closing(tag) {\n                write!(chain, \"/>\")?;\n            } else {\n                write!(chain, \">\")?;\n                // Write the static inner html, or insert a marker if dynamic inner html is possible\n                if let Some(inner_html) = inner_html {\n                    chain.write_str(inner_html)?;\n                } else if has_dyn_attrs {\n                    *chain += Segment::InnerHtmlMarker;\n                }\n\n                // Escape the text in children if this is not a style or script tag. If it is a style\n                // or script tag, we want to allow the user to write code inside the tag\n                let escape_text = match *tag {\n                    \"style\" | \"script\" => EscapeText::NoEscape,\n                    _ => EscapeText::Escape,\n                };\n\n                for child in *children {\n                    from_template_recursive(child, cur_path, root_idx, false, escape_text, chain)?;\n                }\n                write!(chain, \"</{tag}>\")?;\n            }\n            cur_path.pop();\n        }\n        TemplateNode::Text { text } => {\n            // write the id if we are prerendering and this is a root node that may need to be removed in the future\n            if is_root {\n                chain.if_hydration_enabled(|chain| {\n                    write!(chain, \"<!--node-id\")?;\n                    *chain += Segment::RootNodeMarker;\n                    write!(chain, \"-->\")?;\n                    std::fmt::Result::Ok(())\n                })?;\n            }\n            match escape_text {\n                // If we know this is statically escaped we can just write it out\n                // rsx! { div { \"hello\" } }\n                EscapeText::Escape => {\n                    write!(\n                        chain,\n                        \"{}\",\n                        askama_escape::escape(text, askama_escape::Html)\n                    )?;\n                }\n                // If we know this is statically not escaped we can just write it out\n                // rsx! { script { \"console.log('hello')\" } }\n                EscapeText::NoEscape => {\n                    write!(chain, \"{}\", text)?;\n                }\n                // Otherwise, write out both versions and let the renderer decide which one to use\n                // at runtime\n                // rsx! { \"console.log('hello')\" }\n                EscapeText::ParentEscape => {\n                    *chain += Segment::PreRenderedMaybeEscaped {\n                        value: text.to_string(),\n                        renderer_if_escaped: false,\n                    };\n                    *chain += Segment::PreRenderedMaybeEscaped {\n                        value: askama_escape::escape(text, askama_escape::Html).to_string(),\n                        renderer_if_escaped: true,\n                    };\n                }\n            }\n            if is_root {\n                chain.if_hydration_enabled(|chain| write!(chain, \"<!--#-->\"))?;\n            }\n        }\n        TemplateNode::Dynamic { id: idx } => {\n            *chain += Segment::Node {\n                index: *idx,\n                escape_text,\n            }\n        }\n    }\n\n    Ok(())\n}\n\nfn tag_is_self_closing(tag: &str) -> bool {\n    matches!(\n        tag,\n        \"area\"\n            | \"base\"\n            | \"br\"\n            | \"col\"\n            | \"embed\"\n            | \"hr\"\n            | \"img\"\n            | \"input\"\n            | \"link\"\n            | \"meta\"\n            | \"param\"\n            | \"source\"\n            | \"track\"\n            | \"wbr\"\n    )\n}\n"
  },
  {
    "path": "packages/ssr/src/config.rs",
    "content": "\n"
  },
  {
    "path": "packages/ssr/src/lib.rs",
    "content": "#![doc = include_str!(\"../README.md\")]\n#![doc(html_logo_url = \"https://avatars.githubusercontent.com/u/79236386\")]\n#![doc(html_favicon_url = \"https://avatars.githubusercontent.com/u/79236386\")]\n\nmod cache;\npub mod config;\npub mod renderer;\npub mod template;\n\nuse dioxus_core::{Element, VirtualDom};\n\npub use crate::renderer::Renderer;\n\n/// A convenience function to render an `rsx!` call to a string\n///\n/// For advanced rendering, create a new [`Renderer`].\npub fn render_element(element: Element) -> String {\n    Renderer::new().render_element(element)\n}\n\n/// A convenience function to render an existing VirtualDom to a string\n///\n/// We generally recommend creating a new `Renderer` to take advantage of template caching.\npub fn render(dom: &VirtualDom) -> String {\n    Renderer::new().render(dom)\n}\n\n/// A convenience function to pre-render an existing VirtualDom to a string\n///\n/// We generally recommend creating a new `Renderer` to take advantage of template caching.\npub fn pre_render(dom: &VirtualDom) -> String {\n    let mut renderer = Renderer::new();\n    renderer.pre_render = true;\n    renderer.render(dom)\n}\n"
  },
  {
    "path": "packages/ssr/src/renderer.rs",
    "content": "use super::cache::Segment;\nuse crate::cache::StringCache;\n\nuse dioxus_core::{\n    Attribute, AttributeValue, DynamicNode, Element, ScopeId, Template, VNode, VirtualDom,\n};\nuse rustc_hash::FxHashMap;\nuse std::fmt::Write;\nuse std::sync::Arc;\n\ntype ComponentRenderCallback = Arc<\n    dyn Fn(&mut Renderer, &mut dyn Write, &VirtualDom, ScopeId) -> std::fmt::Result + Send + Sync,\n>;\n\n/// A virtualdom renderer that caches the templates it has seen for faster rendering\n#[derive(Default)]\npub struct Renderer {\n    /// Choose to write ElementIDs into elements so the page can be re-hydrated later on\n    pub pre_render: bool,\n\n    /// A callback used to render components. You can set this callback to control what components are rendered and add wrappers around components that are not present in CSR\n    render_components: Option<ComponentRenderCallback>,\n\n    /// A cache of templates that have been rendered\n    template_cache: FxHashMap<Template, Arc<StringCache>>,\n\n    /// The current dynamic node id for hydration\n    dynamic_node_id: usize,\n}\n\nimpl Renderer {\n    pub fn new() -> Self {\n        Self::default()\n    }\n\n    /// Set the callback that the renderer uses to render components\n    pub fn set_render_components(\n        &mut self,\n        callback: impl Fn(&mut Renderer, &mut dyn Write, &VirtualDom, ScopeId) -> std::fmt::Result\n            + Send\n            + Sync\n            + 'static,\n    ) {\n        self.render_components = Some(Arc::new(callback));\n    }\n\n    /// Completely clear the renderer cache and reset the dynamic node id\n    pub fn clear(&mut self) {\n        self.template_cache.clear();\n        self.dynamic_node_id = 0;\n        self.render_components = None;\n    }\n\n    /// Reset the callback that the renderer uses to render components\n    pub fn reset_render_components(&mut self) {\n        self.render_components = None;\n    }\n\n    pub fn render(&mut self, dom: &VirtualDom) -> String {\n        let mut buf = String::new();\n        self.render_to(&mut buf, dom).unwrap();\n        buf\n    }\n\n    pub fn render_to<W: Write + ?Sized>(\n        &mut self,\n        buf: &mut W,\n        dom: &VirtualDom,\n    ) -> std::fmt::Result {\n        self.reset_hydration();\n        self.render_scope(buf, dom, ScopeId::ROOT)\n    }\n\n    /// Render an element to a string\n    pub fn render_element(&mut self, element: Element) -> String {\n        let mut buf = String::new();\n        self.render_element_to(&mut buf, element).unwrap();\n        buf\n    }\n\n    /// Render an element to the buffer\n    pub fn render_element_to<W: Write + ?Sized>(\n        &mut self,\n        buf: &mut W,\n        element: Element,\n    ) -> std::fmt::Result {\n        fn lazy_app(props: Element) -> Element {\n            props\n        }\n        let mut dom = VirtualDom::new_with_props(lazy_app, element);\n        dom.rebuild_in_place();\n        self.render_to(buf, &dom)\n    }\n\n    /// Reset the renderer hydration state\n    pub fn reset_hydration(&mut self) {\n        self.dynamic_node_id = 0;\n    }\n\n    pub fn render_scope<W: Write + ?Sized>(\n        &mut self,\n        buf: &mut W,\n        dom: &VirtualDom,\n        scope: ScopeId,\n    ) -> std::fmt::Result {\n        let node = dom.get_scope(scope).unwrap().root_node();\n        self.render_template(buf, dom, node, true)?;\n\n        Ok(())\n    }\n\n    fn render_template<W: Write + ?Sized>(\n        &mut self,\n        mut buf: &mut W,\n        dom: &VirtualDom,\n        template: &VNode,\n        parent_escaped: bool,\n    ) -> std::fmt::Result {\n        let entry = self\n            .template_cache\n            .entry(template.template)\n            .or_insert_with(move || Arc::new(StringCache::from_template(template).unwrap()))\n            .clone();\n\n        let mut inner_html = None;\n\n        // We need to keep track of the dynamic styles so we can insert them into the right place\n        let mut accumulated_dynamic_styles = Vec::new();\n\n        // We need to keep track of the listeners so we can insert them into the right place\n        let mut accumulated_listeners = Vec::new();\n\n        // We keep track of the index we are on manually so that we can jump forward to a new section quickly without iterating every item\n        let mut index = 0;\n\n        while let Some(segment) = entry.segments.get(index) {\n            match segment {\n                Segment::HydrationOnlySection(jump_to) => {\n                    // If we are not prerendering, we don't need to write the content of the hydration only section\n                    // Instead we can jump to the next section\n                    if !self.pre_render {\n                        index = *jump_to;\n                        continue;\n                    }\n                }\n                Segment::Attr(idx) => {\n                    let attrs = &*template.dynamic_attrs[*idx];\n                    for attr in attrs {\n                        if attr.name == \"dangerous_inner_html\" {\n                            inner_html = Some(attr);\n                        } else if attr.namespace == Some(\"style\") {\n                            accumulated_dynamic_styles.push(attr);\n                        } else if BOOL_ATTRS.contains(&attr.name) {\n                            if truthy(&attr.value) {\n                                write_attribute(buf, attr)?;\n                            }\n                        } else {\n                            write_attribute(buf, attr)?;\n                        }\n\n                        if self.pre_render {\n                            if let AttributeValue::Listener(_) = &attr.value {\n                                // The onmounted event doesn't need a DOM listener\n                                if attr.name != \"onmounted\" {\n                                    accumulated_listeners.push(attr.name);\n                                }\n                            }\n                        }\n                    }\n                }\n                Segment::Node { index, escape_text } => {\n                    let escaped = escape_text.should_escape(parent_escaped);\n                    match &template.dynamic_nodes[*index] {\n                        DynamicNode::Component(node) => {\n                            if let Some(render_components) = self.render_components.clone() {\n                                let scope_id =\n                                    node.mounted_scope_id(*index, template, dom).unwrap();\n\n                                render_components(self, &mut buf, dom, scope_id)?;\n                            } else {\n                                let scope = node.mounted_scope(*index, template, dom).unwrap();\n                                let node = scope.root_node();\n                                self.render_template(buf, dom, node, escaped)?\n                            }\n                        }\n                        DynamicNode::Text(text) => {\n                            // in SSR, we are concerned that we can't hunt down the right text node since they might get merged\n                            if self.pre_render {\n                                write!(buf, \"<!--node-id{}-->\", self.dynamic_node_id)?;\n                                self.dynamic_node_id += 1;\n                            }\n\n                            if escaped {\n                                write!(\n                                    buf,\n                                    \"{}\",\n                                    askama_escape::escape(&text.value, askama_escape::Html)\n                                )?;\n                            } else {\n                                write!(buf, \"{}\", text.value)?;\n                            }\n\n                            if self.pre_render {\n                                write!(buf, \"<!--#-->\")?;\n                            }\n                        }\n                        DynamicNode::Fragment(nodes) => {\n                            for child in nodes {\n                                self.render_template(buf, dom, child, escaped)?;\n                            }\n                        }\n\n                        DynamicNode::Placeholder(_) => {\n                            if self.pre_render {\n                                write!(buf, \"<!--placeholder{}-->\", self.dynamic_node_id)?;\n                                self.dynamic_node_id += 1;\n                            }\n                        }\n                    }\n                }\n\n                Segment::PreRendered(contents) => write!(buf, \"{contents}\")?,\n                Segment::PreRenderedMaybeEscaped {\n                    value,\n                    renderer_if_escaped,\n                } => {\n                    if *renderer_if_escaped == parent_escaped {\n                        write!(buf, \"{value}\")?;\n                    }\n                }\n\n                Segment::StyleMarker { inside_style_tag } => {\n                    if !accumulated_dynamic_styles.is_empty() {\n                        // if we are inside a style tag, we don't need to write the style attribute\n                        if !*inside_style_tag {\n                            write!(buf, \" style=\\\"\")?;\n                        }\n                        for attr in &accumulated_dynamic_styles {\n                            write!(buf, \"{}:\", attr.name)?;\n                            write_value_unquoted(buf, &attr.value)?;\n                            write!(buf, \";\")?;\n                        }\n                        if !*inside_style_tag {\n                            write!(buf, \"\\\"\")?;\n                        }\n\n                        // clear the accumulated styles\n                        accumulated_dynamic_styles.clear();\n                    }\n                }\n\n                Segment::InnerHtmlMarker => {\n                    if let Some(inner_html) = inner_html.take() {\n                        let inner_html = &inner_html.value;\n                        match inner_html {\n                            AttributeValue::Text(value) => write!(buf, \"{}\", value)?,\n                            AttributeValue::Bool(value) => write!(buf, \"{}\", value)?,\n                            AttributeValue::Float(f) => write!(buf, \"{}\", f)?,\n                            AttributeValue::Int(i) => write!(buf, \"{}\", i)?,\n                            _ => {}\n                        }\n                    }\n                }\n\n                Segment::AttributeNodeMarker => {\n                    // first write the id\n                    write!(buf, \"{}\", self.dynamic_node_id)?;\n                    self.dynamic_node_id += 1;\n                    // then write any listeners\n                    for name in accumulated_listeners.drain(..) {\n                        write!(buf, \",{}:\", &name[2..])?;\n                        write!(\n                            buf,\n                            \"{}\",\n                            dioxus_core_types::event_bubbles(&name[2..]) as u8\n                        )?;\n                    }\n                }\n\n                Segment::RootNodeMarker => {\n                    write!(buf, \"{}\", self.dynamic_node_id)?;\n                    self.dynamic_node_id += 1\n                }\n            }\n\n            index += 1;\n        }\n\n        Ok(())\n    }\n}\n\n#[test]\nfn to_string_works() {\n    use crate::cache::EscapeText;\n    use dioxus::prelude::*;\n\n    fn app() -> Element {\n        let dynamic = 123;\n        let dyn2 = \"</diiiiiiiiv>\"; // this should be escaped\n\n        rsx! {\n            div { class: \"asdasdasd\", class: \"asdasdasd\", id: \"id-{dynamic}\",\n                \"Hello world 1 -->\"\n                \"{dynamic}\"\n                \"<-- Hello world 2\"\n                div { \"nest 1\" }\n                div {}\n                div { \"nest 2\" }\n                \"{dyn2}\"\n                for i in (0..5) {\n                    div { \"finalize {i}\" }\n                }\n            }\n        }\n    }\n\n    let mut dom = VirtualDom::new(app);\n    dom.rebuild(&mut dioxus_core::NoOpMutations);\n\n    let mut renderer = Renderer::new();\n    let out = renderer.render(&dom);\n\n    for item in renderer.template_cache.iter() {\n        if item.1.segments.len() > 10 {\n            assert_eq!(\n                item.1.segments,\n                vec![\n                    PreRendered(\"<div class=\\\"asdasdasd asdasdasd\\\"\".to_string()),\n                    Attr(0),\n                    StyleMarker {\n                        inside_style_tag: false\n                    },\n                    HydrationOnlySection(7), // jump to `>` if we don't need to hydrate\n                    PreRendered(\" data-node-hydration=\\\"\".to_string()),\n                    AttributeNodeMarker,\n                    PreRendered(\"\\\"\".to_string()),\n                    PreRendered(\">\".to_string()),\n                    InnerHtmlMarker,\n                    PreRendered(\"Hello world 1 --&#62;\".to_string()),\n                    Node {\n                        index: 0,\n                        escape_text: EscapeText::Escape\n                    },\n                    PreRendered(\n                        \"&#60;-- Hello world 2<div>nest 1</div><div></div><div>nest 2</div>\"\n                            .to_string()\n                    ),\n                    Node {\n                        index: 1,\n                        escape_text: EscapeText::Escape\n                    },\n                    Node {\n                        index: 2,\n                        escape_text: EscapeText::Escape\n                    },\n                    PreRendered(\"</div>\".to_string())\n                ]\n            );\n        }\n    }\n\n    use Segment::*;\n\n    assert_eq!(out, \"<div class=\\\"asdasdasd asdasdasd\\\" id=\\\"id-123\\\">Hello world 1 --&#62;123&#60;-- Hello world 2<div>nest 1</div><div></div><div>nest 2</div>&#60;/diiiiiiiiv&#62;<div>finalize 0</div><div>finalize 1</div><div>finalize 2</div><div>finalize 3</div><div>finalize 4</div></div>\");\n}\n\n#[test]\nfn empty_for_loop_works() {\n    use crate::cache::EscapeText;\n    use dioxus::prelude::*;\n\n    fn app() -> Element {\n        rsx! {\n            div { class: \"asdasdasd\",\n                for _ in (0..5) {\n\n                }\n            }\n        }\n    }\n\n    let mut dom = VirtualDom::new(app);\n    dom.rebuild(&mut dioxus_core::NoOpMutations);\n\n    let mut renderer = Renderer::new();\n    let out = renderer.render(&dom);\n\n    for item in renderer.template_cache.iter() {\n        if item.1.segments.len() > 5 {\n            assert_eq!(\n                item.1.segments,\n                vec![\n                    PreRendered(\"<div class=\\\"asdasdasd\\\"\".to_string()),\n                    HydrationOnlySection(5), // jump to `>` if we don't need to hydrate\n                    PreRendered(\" data-node-hydration=\\\"\".to_string()),\n                    RootNodeMarker,\n                    PreRendered(\"\\\"\".to_string()),\n                    PreRendered(\">\".to_string()),\n                    Node {\n                        index: 0,\n                        escape_text: EscapeText::Escape\n                    },\n                    PreRendered(\"</div>\".to_string())\n                ]\n            );\n        }\n    }\n\n    use Segment::*;\n\n    assert_eq!(out, \"<div class=\\\"asdasdasd\\\"></div>\");\n}\n\n#[test]\nfn empty_render_works() {\n    use dioxus::prelude::*;\n\n    fn app() -> Element {\n        rsx! {}\n    }\n\n    let mut dom = VirtualDom::new(app);\n    dom.rebuild(&mut dioxus_core::NoOpMutations);\n\n    let mut renderer = Renderer::new();\n    let out = renderer.render(&dom);\n\n    for item in renderer.template_cache.iter() {\n        if item.1.segments.len() > 5 {\n            assert_eq!(item.1.segments, vec![]);\n        }\n    }\n    assert_eq!(out, \"\");\n}\n\npub(crate) const BOOL_ATTRS: &[&str] = &[\n    \"allowfullscreen\",\n    \"allowpaymentrequest\",\n    \"async\",\n    \"autofocus\",\n    \"autoplay\",\n    \"checked\",\n    \"controls\",\n    \"default\",\n    \"defer\",\n    \"disabled\",\n    \"formnovalidate\",\n    \"hidden\",\n    \"ismap\",\n    \"itemscope\",\n    \"loop\",\n    \"multiple\",\n    \"muted\",\n    \"nomodule\",\n    \"novalidate\",\n    \"open\",\n    \"playsinline\",\n    \"readonly\",\n    \"required\",\n    \"reversed\",\n    \"selected\",\n    \"truespeed\",\n    \"webkitdirectory\",\n];\n\npub(crate) fn str_truthy(value: &str) -> bool {\n    !value.is_empty() && value != \"0\" && value.to_lowercase() != \"false\"\n}\n\npub(crate) fn truthy(value: &AttributeValue) -> bool {\n    match value {\n        AttributeValue::Text(value) => str_truthy(value),\n        AttributeValue::Bool(value) => *value,\n        AttributeValue::Int(value) => *value != 0,\n        AttributeValue::Float(value) => *value != 0.0,\n        _ => false,\n    }\n}\n\npub(crate) fn write_attribute<W: Write + ?Sized>(\n    buf: &mut W,\n    attr: &Attribute,\n) -> std::fmt::Result {\n    let name = &attr.name;\n    match &attr.value {\n        AttributeValue::Text(value) => write!(\n            buf,\n            \" {name}=\\\"{}\\\"\",\n            askama_escape::escape(value, askama_escape::Html)\n        ),\n        AttributeValue::Bool(value) => write!(buf, \" {name}={value}\"),\n        AttributeValue::Int(value) => write!(buf, \" {name}={value}\"),\n        AttributeValue::Float(value) => write!(buf, \" {name}={value}\"),\n        _ => Ok(()),\n    }\n}\n\npub(crate) fn write_value_unquoted<W: Write + ?Sized>(\n    buf: &mut W,\n    value: &AttributeValue,\n) -> std::fmt::Result {\n    match value {\n        AttributeValue::Text(value) => {\n            write!(buf, \"{}\", askama_escape::escape(value, askama_escape::Html))\n        }\n        AttributeValue::Bool(value) => write!(buf, \"{}\", value),\n        AttributeValue::Int(value) => write!(buf, \"{}\", value),\n        AttributeValue::Float(value) => write!(buf, \"{}\", value),\n        _ => Ok(()),\n    }\n}\n"
  },
  {
    "path": "packages/ssr/src/template.rs",
    "content": "\n"
  },
  {
    "path": "packages/ssr/tests/bool_attr.rs",
    "content": "use dioxus::prelude::*;\n\n#[test]\nfn static_boolean_attributes() {\n    fn app() -> Element {\n        rsx! {\n            div { hidden: \"false\" }\n            div { hidden: \"true\" }\n        }\n    }\n\n    let mut dom = VirtualDom::new(app);\n    dom.rebuild(&mut dioxus_core::NoOpMutations);\n\n    assert_eq!(\n        dioxus_ssr::render(&dom),\n        r#\"<div></div><div hidden=\"true\"></div>\"#\n    );\n}\n\n#[test]\nfn dynamic_boolean_attributes() {\n    fn app() -> Element {\n        rsx! {\n            div { hidden: false }\n            div { hidden: true }\n        }\n    }\n\n    let mut dom = VirtualDom::new(app);\n    dom.rebuild(&mut dioxus_core::NoOpMutations);\n\n    assert_eq!(\n        dioxus_ssr::render(&dom),\n        r#\"<div></div><div hidden=true></div>\"#\n    );\n}\n"
  },
  {
    "path": "packages/ssr/tests/escape.rs",
    "content": "use dioxus::prelude::*;\n\n#[test]\nfn escape_static_values() {\n    fn app() -> Element {\n        rsx! { input { disabled: \"\\\"><div>\" } }\n    }\n\n    let mut dom = VirtualDom::new(app);\n    dom.rebuild(&mut dioxus_core::NoOpMutations);\n\n    assert_eq!(\n        dioxus_ssr::pre_render(&dom),\n        \"<input disabled=\\\"&#34;&#62;&#60;div&#62;\\\" data-node-hydration=\\\"0\\\"/>\"\n    );\n}\n\n#[test]\nfn escape_dynamic_values() {\n    fn app() -> Element {\n        let disabled = \"\\\"><div>\";\n        rsx! { input { disabled } }\n    }\n\n    let mut dom = VirtualDom::new(app);\n    dom.rebuild(&mut dioxus_core::NoOpMutations);\n\n    assert_eq!(\n        dioxus_ssr::pre_render(&dom),\n        \"<input disabled=\\\"&#34;&#62;&#60;div&#62;\\\" data-node-hydration=\\\"0\\\"/>\"\n    );\n}\n\n#[test]\nfn escape_static_style() {\n    fn app() -> Element {\n        rsx! { div { width: \"\\\"><div>\" } }\n    }\n\n    let mut dom = VirtualDom::new(app);\n    dom.rebuild(&mut dioxus_core::NoOpMutations);\n\n    assert_eq!(\n        dioxus_ssr::pre_render(&dom),\n        \"<div style=\\\"width:&#34;&#62;&#60;div&#62;;\\\" data-node-hydration=\\\"0\\\"></div>\"\n    );\n}\n\n#[test]\nfn escape_dynamic_style() {\n    fn app() -> Element {\n        let width = \"\\\"><div>\";\n        rsx! { div { width } }\n    }\n\n    let mut dom = VirtualDom::new(app);\n    dom.rebuild(&mut dioxus_core::NoOpMutations);\n\n    assert_eq!(\n        dioxus_ssr::pre_render(&dom),\n        \"<div style=\\\"width:&#34;&#62;&#60;div&#62;;\\\" data-node-hydration=\\\"0\\\"></div>\"\n    );\n}\n\n#[test]\nfn escape_static_text() {\n    fn app() -> Element {\n        rsx! {\n            div {\n                \"\\\"><div>\"\n            }\n        }\n    }\n\n    let mut dom = VirtualDom::new(app);\n    dom.rebuild(&mut dioxus_core::NoOpMutations);\n\n    assert_eq!(\n        dioxus_ssr::pre_render(&dom),\n        \"<div data-node-hydration=\\\"0\\\">&#34;&#62;&#60;div&#62;</div>\"\n    );\n}\n\n#[test]\nfn escape_dynamic_text() {\n    fn app() -> Element {\n        let text = \"\\\"><div>\";\n        rsx! {\n            div {\n                {text}\n            }\n        }\n    }\n\n    let mut dom = VirtualDom::new(app);\n    dom.rebuild(&mut dioxus_core::NoOpMutations);\n\n    assert_eq!(\n        dioxus_ssr::pre_render(&dom),\n        \"<div data-node-hydration=\\\"0\\\"><!--node-id1-->&#34;&#62;&#60;div&#62;<!--#--></div>\"\n    );\n}\n\n#[test]\nfn don_t_escape_static_scripts() {\n    fn app() -> Element {\n        rsx! {\n            script {\n                \"console.log('hello world');\"\n            }\n        }\n    }\n\n    let mut dom = VirtualDom::new(app);\n    dom.rebuild(&mut dioxus_core::NoOpMutations);\n\n    assert_eq!(\n        dioxus_ssr::pre_render(&dom),\n        \"<script data-node-hydration=\\\"0\\\">console.log('hello world');</script>\"\n    );\n}\n\n#[test]\nfn don_t_escape_dynamic_scripts() {\n    fn app() -> Element {\n        let script = \"console.log('hello world');\";\n        rsx! {\n            script {\n                {script}\n            }\n        }\n    }\n\n    let mut dom = VirtualDom::new(app);\n    dom.rebuild(&mut dioxus_core::NoOpMutations);\n\n    assert_eq!(\n        dioxus_ssr::pre_render(&dom),\n        \"<script data-node-hydration=\\\"0\\\"><!--node-id1-->console.log('hello world');<!--#--></script>\"\n    );\n}\n\n#[test]\nfn don_t_escape_static_styles() {\n    fn app() -> Element {\n        rsx! {\n            style {\n                \"body {{ background-color: red; }}\"\n            }\n        }\n    }\n\n    let mut dom = VirtualDom::new(app);\n    dom.rebuild(&mut dioxus_core::NoOpMutations);\n\n    assert_eq!(\n        dioxus_ssr::pre_render(&dom),\n        \"<style data-node-hydration=\\\"0\\\">body { background-color: red; }</style>\"\n    );\n}\n\n#[test]\nfn don_t_escape_dynamic_styles() {\n    fn app() -> Element {\n        let style = \"body { font-family: \\\"sans-serif\\\"; }\";\n        rsx! {\n            style {\n                {style}\n            }\n        }\n    }\n\n    let mut dom = VirtualDom::new(app);\n    dom.rebuild(&mut dioxus_core::NoOpMutations);\n\n    assert_eq!(\n        dioxus_ssr::pre_render(&dom),\n        \"<style data-node-hydration=\\\"0\\\"><!--node-id1-->body { font-family: \\\"sans-serif\\\"; }<!--#--></style>\"\n    );\n}\n\n#[test]\nfn don_t_escape_static_fragment_styles() {\n    fn app() -> Element {\n        let style_element = rsx! { \"body {{ font-family: \\\"sans-serif\\\"; }}\" };\n        rsx! {\n            style {\n                {style_element}\n            }\n        }\n    }\n\n    let mut dom = VirtualDom::new(app);\n    dom.rebuild(&mut dioxus_core::NoOpMutations);\n\n    assert_eq!(\n        dioxus_ssr::pre_render(&dom),\n        \"<style data-node-hydration=\\\"0\\\"><!--node-id1-->body { font-family: \\\"sans-serif\\\"; }<!--#--></style>\"\n    );\n}\n\n#[test]\nfn escape_static_component_fragment_div() {\n    #[component]\n    fn StyleContents() -> Element {\n        rsx! { \"body {{ font-family: \\\"sans-serif\\\"; }}\" }\n    }\n\n    fn app() -> Element {\n        rsx! {\n            div {\n                StyleContents {}\n            }\n        }\n    }\n\n    let mut dom = VirtualDom::new(app);\n    dom.rebuild(&mut dioxus_core::NoOpMutations);\n\n    assert_eq!(\n        dioxus_ssr::pre_render(&dom),\n        \"<div data-node-hydration=\\\"0\\\"><!--node-id1-->body { font-family: &#34;sans-serif&#34;; }<!--#--></div>\"\n    );\n}\n\n#[test]\nfn escape_dynamic_component_fragment_div() {\n    #[component]\n    fn StyleContents() -> Element {\n        let dynamic = \"body { font-family: \\\"sans-serif\\\"; }\";\n        rsx! { \"{dynamic}\" }\n    }\n\n    fn app() -> Element {\n        rsx! {\n            div {\n                StyleContents {}\n            }\n        }\n    }\n\n    let mut dom = VirtualDom::new(app);\n    dom.rebuild(&mut dioxus_core::NoOpMutations);\n\n    assert_eq!(\n        dioxus_ssr::pre_render(&dom),\n        \"<div data-node-hydration=\\\"0\\\"><!--node-id1-->body { font-family: &#34;sans-serif&#34;; }<!--#--></div>\"\n    );\n}\n"
  },
  {
    "path": "packages/ssr/tests/forward_spreads.rs",
    "content": "use dioxus::prelude::*;\n\n// Regression test for https://github.com/DioxusLabs/dioxus/issues/3844\n#[test]\nfn forward_spreads() {\n    #[derive(Props, Clone, PartialEq)]\n    struct Comp1Props {\n        #[props(extends = GlobalAttributes)]\n        attributes: Vec<Attribute>,\n    }\n\n    #[component]\n    fn Comp1(props: Comp1Props) -> Element {\n        rsx! {\n            Comp2 {\n                attributes: props.attributes.clone(),\n                height: \"100%\",\n            }\n            Comp2 {\n                height: \"100%\",\n                attributes: props.attributes.clone(),\n            }\n        }\n    }\n\n    #[derive(Props, Clone, PartialEq)]\n    struct CompProps2 {\n        #[props(extends = GlobalAttributes)]\n        attributes: Vec<Attribute>,\n    }\n\n    #[component]\n    fn Comp2(props: CompProps2) -> Element {\n        let attributes = props.attributes;\n        rsx! {\n            div {\n                ..attributes\n            }\n        }\n    }\n\n    let merged = || {\n        rsx! {\n            Comp1 {\n                width: \"100%\"\n            }\n        }\n    };\n    let dom = VirtualDom::prebuilt(merged);\n    let html = dioxus_ssr::render(&dom);\n    assert_eq!(\n        html,\n        r#\"<div style=\"width:100%;height:100%;\"></div><div style=\"width:100%;height:100%;\"></div>\"#\n    );\n}\n"
  },
  {
    "path": "packages/ssr/tests/hydration.rs",
    "content": "use dioxus::prelude::*;\n\n#[test]\nfn root_ids() {\n    fn app() -> Element {\n        rsx! { div { width: \"100px\" } }\n    }\n\n    let mut dom = VirtualDom::new(app);\n    dom.rebuild(&mut dioxus_core::NoOpMutations);\n\n    assert_eq!(\n        dioxus_ssr::pre_render(&dom),\n        r#\"<div style=\"width:100px;\" data-node-hydration=\"0\"></div>\"#\n    );\n}\n\n#[test]\nfn dynamic_attributes() {\n    fn app() -> Element {\n        let dynamic = 123;\n        rsx! {\n            div { width: \"100px\", div { width: \"{dynamic}px\" } }\n        }\n    }\n\n    let mut dom = VirtualDom::new(app);\n    dom.rebuild(&mut dioxus_core::NoOpMutations);\n\n    assert_eq!(\n        dioxus_ssr::pre_render(&dom),\n        r#\"<div style=\"width:100px;\" data-node-hydration=\"0\"><div style=\"width:123px;\" data-node-hydration=\"1\"></div></div>\"#\n    );\n}\n\n#[test]\nfn listeners() {\n    fn app() -> Element {\n        rsx! {\n            div { width: \"100px\", div { onclick: |_| {} } }\n        }\n    }\n\n    let mut dom = VirtualDom::new(app);\n    dom.rebuild(&mut dioxus_core::NoOpMutations);\n\n    assert_eq!(\n        dioxus_ssr::pre_render(&dom),\n        r#\"<div style=\"width:100px;\" data-node-hydration=\"0\"><div data-node-hydration=\"1,click:1\"></div></div>\"#\n    );\n\n    fn app2() -> Element {\n        let dynamic = 123;\n        rsx! {\n            div { width: \"100px\", div { width: \"{dynamic}px\", onclick: |_| {} } }\n        }\n    }\n\n    let mut dom = VirtualDom::new(app2);\n    dom.rebuild(&mut dioxus_core::NoOpMutations);\n\n    assert_eq!(\n        dioxus_ssr::pre_render(&dom),\n        r#\"<div style=\"width:100px;\" data-node-hydration=\"0\"><div style=\"width:123px;\" data-node-hydration=\"1,click:1\"></div></div>\"#\n    );\n}\n\n#[test]\nfn text_nodes() {\n    fn app() -> Element {\n        let dynamic_text = \"hello\";\n        rsx! {\n            div { {dynamic_text} }\n        }\n    }\n\n    let mut dom = VirtualDom::new(app);\n    dom.rebuild(&mut dioxus_core::NoOpMutations);\n\n    assert_eq!(\n        dioxus_ssr::pre_render(&dom),\n        r#\"<div data-node-hydration=\"0\"><!--node-id1-->hello<!--#--></div>\"#\n    );\n\n    fn app2() -> Element {\n        let dynamic = 123;\n        rsx! {\n            div { \"{dynamic}\" \"{1234}\" }\n        }\n    }\n\n    let mut dom = VirtualDom::new(app2);\n    dom.rebuild(&mut dioxus_core::NoOpMutations);\n\n    assert_eq!(\n        dioxus_ssr::pre_render(&dom),\n        r#\"<div data-node-hydration=\"0\"><!--node-id1-->123<!--#--><!--node-id2-->1234<!--#--></div>\"#\n    );\n}\n\n#[allow(non_snake_case)]\n#[test]\nfn components_hydrate() {\n    fn app() -> Element {\n        rsx! { Child {} }\n    }\n\n    fn Child() -> Element {\n        rsx! { div { \"hello\" } }\n    }\n\n    let mut dom = VirtualDom::new(app);\n    dom.rebuild(&mut dioxus_core::NoOpMutations);\n\n    assert_eq!(\n        dioxus_ssr::pre_render(&dom),\n        r#\"<div data-node-hydration=\"0\">hello</div>\"#\n    );\n\n    fn app2() -> Element {\n        rsx! { Child2 {} }\n    }\n\n    fn Child2() -> Element {\n        let dyn_text = \"hello\";\n        rsx! {\n            div { {dyn_text} }\n        }\n    }\n\n    let mut dom = VirtualDom::new(app2);\n    dom.rebuild(&mut dioxus_core::NoOpMutations);\n\n    assert_eq!(\n        dioxus_ssr::pre_render(&dom),\n        r#\"<div data-node-hydration=\"0\"><!--node-id1-->hello<!--#--></div>\"#\n    );\n\n    fn app3() -> Element {\n        rsx! { Child3 {} }\n    }\n\n    fn Child3() -> Element {\n        rsx! { div { width: \"{1}\" } }\n    }\n\n    let mut dom = VirtualDom::new(app3);\n    dom.rebuild(&mut dioxus_core::NoOpMutations);\n\n    assert_eq!(\n        dioxus_ssr::pre_render(&dom),\n        r#\"<div style=\"width:1;\" data-node-hydration=\"0\"></div>\"#\n    );\n\n    fn app4() -> Element {\n        rsx! { Child4 {} }\n    }\n\n    fn Child4() -> Element {\n        rsx! {\n            for _ in 0..2 {\n                {rsx! { \"{1}\" }}\n            }\n        }\n    }\n\n    let mut dom = VirtualDom::new(app4);\n    dom.rebuild(&mut dioxus_core::NoOpMutations);\n\n    assert_eq!(\n        dioxus_ssr::pre_render(&dom),\n        r#\"<!--node-id0-->1<!--#--><!--node-id1-->1<!--#-->\"#\n    );\n}\n\n#[test]\nfn hello_world_hydrates() {\n    use dioxus::hooks::use_signal;\n\n    fn app() -> Element {\n        let mut count = use_signal(|| 0);\n\n        rsx! {\n            h1 { \"High-Five counter: {count}\" }\n            button { onclick: move |_| count += 1, \"Up high!\" }\n            button { onclick: move |_| count -= 1, \"Down low!\" }\n        }\n    }\n\n    let mut dom = VirtualDom::new(app);\n    dom.rebuild(&mut dioxus_core::NoOpMutations);\n\n    assert_eq!(\n        dioxus_ssr::pre_render(&dom),\n        r#\"<h1 data-node-hydration=\"0\"><!--node-id1-->High-Five counter: 0<!--#--></h1><button data-node-hydration=\"2,click:1\">Up high!</button><button data-node-hydration=\"3,click:1\">Down low!</button>\"#\n    );\n}\n"
  },
  {
    "path": "packages/ssr/tests/inner_html.rs",
    "content": "use dioxus::prelude::*;\n\n#[test]\nfn static_inner_html() {\n    fn app() -> Element {\n        rsx! { div { dangerous_inner_html: \"<div>1234</div>\" } }\n    }\n\n    let mut dom = VirtualDom::new(app);\n    dom.rebuild(&mut dioxus_core::NoOpMutations);\n\n    assert_eq!(dioxus_ssr::render(&dom), r#\"<div><div>1234</div></div>\"#);\n}\n\n#[test]\nfn dynamic_inner_html() {\n    fn app() -> Element {\n        let inner_html = \"<div>1234</div>\";\n        rsx! { div { dangerous_inner_html: \"{inner_html}\" } }\n    }\n\n    let mut dom = VirtualDom::new(app);\n    dom.rebuild(&mut dioxus_core::NoOpMutations);\n\n    assert_eq!(dioxus_ssr::render(&dom), r#\"<div><div>1234</div></div>\"#);\n}\n"
  },
  {
    "path": "packages/ssr/tests/simple.rs",
    "content": "#![allow(non_snake_case)]\n\nuse dioxus::prelude::*;\n\n#[test]\nfn simple() {\n    fn App() -> Element {\n        rsx! { div { \"hello!\" } }\n    }\n\n    let mut dom = VirtualDom::new(App);\n    dom.rebuild(&mut dioxus_core::NoOpMutations);\n\n    assert_eq!(dioxus_ssr::render(&dom), \"<div>hello!</div>\");\n\n    assert_eq!(\n        dioxus_ssr::render_element(rsx!( div {\"hello!\"} )),\n        \"<div>hello!</div>\"\n    );\n}\n\n#[test]\nfn lists() {\n    assert_eq!(\n        dioxus_ssr::render_element(rsx! {\n            ul {\n                {\n                    (0..5).map(|i| rsx! {\n                        li { \"item {i}\" }\n                    })\n                }\n            }\n        }),\n        \"<ul><li>item 0</li><li>item 1</li><li>item 2</li><li>item 3</li><li>item 4</li></ul>\"\n    );\n}\n\n#[test]\nfn dynamic() {\n    let dynamic = 123;\n    assert_eq!(\n        dioxus_ssr::render_element(rsx! {\n            div { \"Hello world 1 -->\" \"{dynamic}\" \"<-- Hello world 2\" }\n        }),\n        \"<div>Hello world 1 --&#62;123&#60;-- Hello world 2</div>\"\n    );\n}\n\n#[test]\nfn components() {\n    #[derive(Props, Clone, PartialEq)]\n    struct MyComponentProps {\n        name: i32,\n    }\n\n    fn MyComponent(MyComponentProps { name }: MyComponentProps) -> Element {\n        rsx! { div { \"component {name}\" } }\n    }\n\n    assert_eq!(\n        dioxus_ssr::render_element(rsx! {\n            div {\n                {\n                    (0..5).map(|name| rsx! {\n                        MyComponent { name: name }\n                    })\n                }\n            }\n        }),\n        \"<div><div>component 0</div><div>component 1</div><div>component 2</div><div>component 3</div><div>component 4</div></div>\"\n    );\n}\n\n#[test]\nfn fragments() {\n    assert_eq!(\n        dioxus_ssr::render_element(rsx! {\n            div {\n                {\n                    (0..5).map(|_| rsx! ({}))\n                }\n            }\n        }),\n        \"<div></div>\"\n    );\n}\n"
  },
  {
    "path": "packages/ssr/tests/spread.rs",
    "content": "use dioxus::prelude::*;\n\n#[test]\nfn spread() {\n    let dom = VirtualDom::prebuilt(app);\n    let html = dioxus_ssr::render(&dom);\n\n    assert_eq!(\n        html,\n        r#\"<audio data-custom-attribute=\"value\" style=\"width:10px;height:10px;left:1;\">1: hello1\n2: hello2</audio>\"#\n    );\n}\n\nfn app() -> Element {\n    rsx! {\n        SpreadableComponent {\n            width: \"10px\",\n            extra_data: \"hello{1}\",\n            extra_data2: \"hello{2}\",\n            height: \"10px\",\n            left: 1,\n            \"data-custom-attribute\": \"value\",\n        }\n    }\n}\n\n#[derive(Props, PartialEq, Clone)]\nstruct Props {\n    #[props(extends = GlobalAttributes)]\n    attributes: Vec<Attribute>,\n\n    extra_data: String,\n\n    extra_data2: String,\n}\n\n#[component]\nfn SpreadableComponent(props: Props) -> Element {\n    rsx! {\n        audio { ..props.attributes, \"1: {props.extra_data}\\n2: {props.extra_data2}\" }\n    }\n}\n"
  },
  {
    "path": "packages/ssr/tests/styles.rs",
    "content": "use dioxus::prelude::*;\n\n#[test]\nfn static_styles() {\n    fn app() -> Element {\n        rsx! { div { width: \"100px\" } }\n    }\n\n    let mut dom = VirtualDom::new(app);\n    dom.rebuild(&mut dioxus_core::NoOpMutations);\n\n    assert_eq!(\n        dioxus_ssr::render(&dom),\n        r#\"<div style=\"width:100px;\"></div>\"#\n    );\n}\n\n#[test]\nfn partially_dynamic_styles() {\n    let dynamic = 123;\n\n    assert_eq!(\n        dioxus_ssr::render_element(rsx! {\n            div { width: \"100px\", height: \"{dynamic}px\" }\n        }),\n        r#\"<div style=\"width:100px;height:123px;\"></div>\"#\n    );\n}\n\n#[test]\nfn dynamic_styles() {\n    let dynamic = 123;\n\n    assert_eq!(\n        dioxus_ssr::render_element(rsx! {\n            div { width: \"{dynamic}px\" }\n        }),\n        r#\"<div style=\"width:123px;\"></div>\"#\n    );\n}\n"
  },
  {
    "path": "packages/stores/Cargo.toml",
    "content": "[package]\nname = \"dioxus-stores\"\nversion = { workspace = true }\nedition = \"2021\"\nrepository = \"https://github.com/DioxusLabs/dioxus/\"\nhomepage = \"https://dioxuslabs.com/docs/0.5/guide/en/getting_started/fullstack.html\"\nkeywords = [\"dom\", \"ui\", \"gui\", \"react\", \"liveview\"]\nauthors = [\"Jonathan Kelley\", \"Evan Almloff\"]\nlicense = \"MIT OR Apache-2.0\"\ndescription = \"Server function macros for Dioxus\"\n\n[dependencies]\ndioxus-core = { workspace = true }\ndioxus-signals = { workspace = true }\ndioxus-stores-macro = { workspace = true, optional = true }\ngenerational-box.workspace = true\n\n[dev-dependencies]\ndioxus = { workspace = true }\n\n[features]\ndefault = [\"macro\"]\nmacro = [\"dep:dioxus-stores-macro\"]\nlarge-path = []\n\n[package.metadata.docs.rs]\ncargo-args = [\"-Zunstable-options\", \"-Zrustdoc-scrape-examples\"]\n"
  },
  {
    "path": "packages/stores/README.md",
    "content": "# Dioxus Stores\n\nStores are an extension to the Dioxus signals system for reactive nested data structures. Each store will lazily create signals for each field/member of the data structure as needed.\n\nBy default stores act a lot like [`dioxus_signals::Signal`]s, but they provide more granular\nsubscriptions without requiring nested signals. You should derive [`Store`](dioxus_stores_macro::Store) on your data\nstructures to generate selectors that let you scope the store to a specific part of your data structure.\n\n```rust, no_run\nuse dioxus::prelude::*;\nuse dioxus_stores::*;\n\nfn main() {\n    dioxus::launch(app);\n}\n\n// Deriving the store trait provides methods to scope the store to specific parts of your data structure.\n// The `Store` macro generates a `count` and `children` method for `Store<CounterTree>`\n#[derive(Store, Default)]\nstruct CounterTree {\n    count: i32,\n    children: Vec<CounterTree>,\n}\n\nfn app() -> Element {\n    let value = use_store(Default::default);\n\n    rsx! {\n        Tree {\n            value\n        }\n    }\n}\n\n#[component]\nfn Tree(value: Store<CounterTree>) -> Element {\n    // Calling the generated `count` method returns a new store that can only\n    // read and write the count field\n    let mut count = value.count();\n    let mut children = value.children();\n\n    rsx! {\n        button {\n            // Incrementing the count will only rerun parts of the app that have read the count field\n            onclick: move |_| count += 1,\n            \"Increment\"\n        }\n        button {\n            // Stores are aware of data structures like `Vec` and `Hashmap`. When we push an item to the vec\n            // it will only rerun the parts of the app that depend on the length of the vec\n            onclick: move |_| children.push(Default::default()),\n            \"Push child\"\n        }\n        ul {\n            // Iterating over the children gives us stores scoped to each child.\n            for value in children.iter() {\n                li {\n                    Tree { value }\n                }\n            }\n        }\n    }\n}\n```\n"
  },
  {
    "path": "packages/stores/src/impls/btreemap.rs",
    "content": "//! Additional utilities for `BTreeMap` stores.\n\nuse std::{\n    borrow::Borrow, collections::BTreeMap, hash::Hash, iter::FusedIterator, panic::Location,\n};\n\nuse crate::{store::Store, ReadStore};\nuse dioxus_signals::{\n    AnyStorage, BorrowError, BorrowMutError, ReadSignal, Readable, ReadableExt, UnsyncStorage,\n    Writable, WriteLock, WriteSignal,\n};\nuse generational_box::ValueDroppedError;\n\nimpl<Lens: Readable<Target = BTreeMap<K, V>> + 'static, K: 'static, V: 'static>\n    Store<BTreeMap<K, V>, Lens>\n{\n    /// Get the length of the BTreeMap. This method will track the store shallowly and only cause\n    /// re-runs when items are added or removed from the map, not when existing values are modified.\n    ///\n    /// # Example\n    /// ```rust, no_run\n    /// use dioxus_stores::*;\n    /// use dioxus::prelude::*;\n    /// use std::collections::BTreeMap;\n    /// let mut store = use_store(|| BTreeMap::new());\n    /// assert_eq!(store.len(), 0);\n    /// store.insert(0, \"value\".to_string());\n    /// assert_eq!(store.len(), 1);\n    /// ```\n    pub fn len(&self) -> usize {\n        self.selector().track_shallow();\n        self.selector().peek().len()\n    }\n\n    /// Check if the BTreeMap is empty. This method will track the store shallowly and only cause\n    /// re-runs when items are added or removed from the map, not when existing values are modified.\n    ///\n    /// # Example\n    /// ```rust, no_run\n    /// use dioxus_stores::*;\n    /// use dioxus::prelude::*;\n    /// use std::collections::BTreeMap;\n    /// let mut store = use_store(|| BTreeMap::new());\n    /// assert!(store.is_empty());\n    /// store.insert(0, \"value\".to_string());\n    /// assert!(!store.is_empty());\n    /// ```\n    pub fn is_empty(&self) -> bool {\n        self.selector().track_shallow();\n        self.selector().peek().is_empty()\n    }\n\n    /// Iterate over the current entries in the BTreeMap, returning a tuple of the key and a store for the value. This method\n    /// will track the store shallowly and only cause re-runs when items are added or removed from the map, not when existing\n    /// values are modified.\n    ///\n    /// # Example\n    ///\n    /// ```rust, no_run\n    /// use dioxus_stores::*;\n    /// use dioxus::prelude::*;\n    /// use std::collections::BTreeMap;\n    /// let mut store = use_store(|| BTreeMap::new());\n    /// store.insert(0, \"value1\".to_string());\n    /// store.insert(1, \"value2\".to_string());\n    /// for (key, value_store) in store.iter() {\n    ///     println!(\"{}: {}\", key, value_store.read());\n    /// }\n    /// ```\n    pub fn iter(\n        &self,\n    ) -> impl ExactSizeIterator<Item = (K, Store<V, GetWrite<K, Lens>>)>\n           + DoubleEndedIterator\n           + FusedIterator\n           + '_\n    where\n        K: Hash + Ord + Clone,\n        Lens: Clone,\n    {\n        self.selector().track_shallow();\n        let keys: Vec<_> = self.selector().peek_unchecked().keys().cloned().collect();\n        keys.into_iter().map(move |key| {\n            let value = self.clone().get_unchecked(key.clone());\n            (key, value)\n        })\n    }\n\n    /// Get an iterator over the values in the BTreeMap. This method will track the store shallowly and only cause\n    /// re-runs when items are added or removed from the map, not when existing values are modified.\n    ///\n    /// # Example\n    /// ```rust, no_run\n    /// use dioxus_stores::*;\n    /// use dioxus::prelude::*;\n    /// use std::collections::BTreeMap;\n    /// let mut store = use_store(|| BTreeMap::new());\n    /// store.insert(0, \"value1\".to_string());\n    /// store.insert(1, \"value2\".to_string());\n    /// for value_store in store.values() {\n    ///     println!(\"{}\", value_store.read());\n    /// }\n    /// ```\n    pub fn values(\n        &self,\n    ) -> impl ExactSizeIterator<Item = Store<V, GetWrite<K, Lens>>>\n           + DoubleEndedIterator\n           + FusedIterator\n           + '_\n    where\n        K: Hash + Ord + Clone,\n        Lens: Clone,\n    {\n        self.selector().track_shallow();\n        let keys = self.selector().peek().keys().cloned().collect::<Vec<_>>();\n        keys.into_iter()\n            .map(move |key| self.clone().get_unchecked(key))\n    }\n\n    /// Insert a new key-value pair into the BTreeMap. This method will mark the store as shallowly dirty, causing\n    /// re-runs of any reactive scopes that depend on the shape of the map.\n    ///\n    /// # Example\n    /// ```rust, no_run\n    /// use dioxus_stores::*;\n    /// use dioxus::prelude::*;\n    /// use std::collections::BTreeMap;\n    /// let mut store = use_store(|| BTreeMap::new());\n    /// assert!(store.get(0).is_none());\n    /// store.insert(0, \"value\".to_string());\n    /// assert_eq!(store.get(0).unwrap().cloned(), \"value\".to_string());\n    /// ```\n    pub fn insert(&mut self, key: K, value: V)\n    where\n        K: Ord,\n        Lens: Writable,\n    {\n        // TODO: This method was released in 0.7 without the hash bound so we don't have a way\n        // to mark only the existing value as dirty. Instead we need to check if the value already exists\n        // in the map and mark the whole map as dirty if it does.\n        // In the 0.8 release, we should change this method to only mark the existing value as dirty.\n        if self.peek().contains_key(&key) {\n            self.selector().mark_dirty();\n        } else {\n            self.selector().mark_dirty_shallow();\n        }\n        self.selector().write_untracked().insert(key, value);\n    }\n\n    /// Remove a key-value pair from the BTreeMap. This method will mark the store as shallowly dirty, causing\n    /// re-runs of any reactive scopes that depend on the shape of the map or the value of the removed key.\n    ///\n    /// # Example\n    /// ```rust, no_run\n    /// use dioxus_stores::*;\n    /// use dioxus::prelude::*;\n    /// use std::collections::BTreeMap;\n    /// let mut store = use_store(|| BTreeMap::new());\n    /// store.insert(0, \"value\".to_string());\n    /// assert_eq!(store.get(0).unwrap().cloned(), \"value\".to_string());\n    /// let removed_value = store.remove(&0);\n    /// assert_eq!(removed_value, Some(\"value\".to_string()));\n    /// assert!(store.get(0).is_none());\n    /// ```\n    pub fn remove<Q>(&mut self, key: &Q) -> Option<V>\n    where\n        Q: ?Sized + Ord + 'static,\n        K: Borrow<Q> + Ord,\n        Lens: Writable,\n    {\n        self.selector().mark_dirty_shallow();\n        self.selector().write_untracked().remove(key)\n    }\n\n    /// Clear the BTreeMap, removing all key-value pairs. This method will mark the store as shallowly dirty,\n    /// causing re-runs of any reactive scopes that depend on the shape of the map.\n    ///\n    /// # Example\n    /// ```rust, no_run\n    /// use dioxus_stores::*;\n    /// use dioxus::prelude::*;\n    /// use std::collections::BTreeMap;\n    /// let mut store = use_store(|| BTreeMap::new());\n    /// store.insert(1, \"value1\".to_string());\n    /// store.insert(2, \"value2\".to_string());\n    /// assert_eq!(store.len(), 2);\n    /// store.clear();\n    /// assert!(store.is_empty());\n    /// ```\n    pub fn clear(&mut self)\n    where\n        Lens: Writable,\n    {\n        self.selector().mark_dirty_shallow();\n        self.selector().write_untracked().clear();\n    }\n\n    /// Retain only the key-value pairs that satisfy the given predicate. This method will mark the store as shallowly dirty,\n    /// causing re-runs of any reactive scopes that depend on the shape of the map or the values retained.\n    ///\n    /// # Example\n    /// ```rust, no_run\n    /// use dioxus_stores::*;\n    /// use dioxus::prelude::*;\n    /// use std::collections::BTreeMap;\n    /// let mut store = use_store(|| BTreeMap::new());\n    /// store.insert(1, \"value1\".to_string());\n    /// store.insert(2, \"value2\".to_string());\n    /// store.retain(|key, value| *key == 1);\n    /// assert_eq!(store.len(), 1);\n    /// assert!(store.get(1).is_some());\n    /// assert!(store.get(2).is_none());\n    /// ```\n    pub fn retain(&mut self, mut f: impl FnMut(&K, &V) -> bool)\n    where\n        Lens: Writable,\n        K: Ord,\n    {\n        self.selector().mark_dirty_shallow();\n        self.selector().write_untracked().retain(|k, v| f(k, v));\n    }\n\n    /// Check if the BTreeMap contains a key. This method will track the store shallowly and only cause\n    /// re-runs when items are added or removed from the map, not when existing values are modified.\n    ///\n    /// # Example\n    /// ```rust, no_run\n    /// use dioxus_stores::*;\n    /// use dioxus::prelude::*;\n    /// use std::collections::BTreeMap;\n    /// let mut store = use_store(|| BTreeMap::new());\n    /// assert!(!store.contains_key(&0));\n    /// store.insert(0, \"value\".to_string());\n    /// assert!(store.contains_key(&0));\n    /// ```\n    pub fn contains_key<Q>(&self, key: &Q) -> bool\n    where\n        Q: ?Sized + Ord + 'static,\n        K: Borrow<Q> + Ord,\n    {\n        self.selector().track_shallow();\n        self.selector().peek().contains_key(key)\n    }\n\n    /// Get a store for the value associated with the given key. This method creates a new store scope\n    /// that tracks just changes to the value associated with the key.\n    ///\n    /// # Example\n    /// ```rust, no_run\n    /// use dioxus_stores::*;\n    /// use dioxus::prelude::*;\n    /// use std::collections::BTreeMap;\n    /// let mut store = use_store(|| BTreeMap::new());\n    /// assert!(store.get(0).is_none());\n    /// store.insert(0, \"value\".to_string());\n    /// assert_eq!(store.get(0).unwrap().cloned(), \"value\".to_string());\n    /// ```\n    pub fn get<Q>(self, key: Q) -> Option<Store<V, GetWrite<Q, Lens>>>\n    where\n        Q: Hash + Ord + 'static,\n        K: Borrow<Q> + Ord,\n    {\n        self.contains_key(&key).then(|| self.get_unchecked(key))\n    }\n\n    /// Get a store for the value associated with the given key without checking if the key exists.\n    /// This method creates a new store scope that tracks just changes to the value associated with the key.\n    ///\n    /// This is not unsafe, but it will panic when you try to read the value if it does not exist.\n    ///\n    /// # Example\n    /// ```rust, no_run\n    /// use dioxus_stores::*;\n    /// use dioxus::prelude::*;\n    /// use std::collections::BTreeMap;\n    /// let mut store = use_store(|| BTreeMap::new());\n    /// store.insert(0, \"value\".to_string());\n    /// assert_eq!(store.get_unchecked(0).cloned(), \"value\".to_string());\n    /// ```\n    #[track_caller]\n    pub fn get_unchecked<Q>(self, key: Q) -> Store<V, GetWrite<Q, Lens>>\n    where\n        Q: Hash + Ord + 'static,\n        K: Borrow<Q> + Ord,\n    {\n        let created = std::panic::Location::caller();\n        self.into_selector()\n            .hash_child_unmapped(key.borrow())\n            .map_writer(move |writer| GetWrite {\n                index: key,\n                write: writer,\n                created,\n            })\n            .into()\n    }\n}\n\n/// A specific index in a `Readable` / `Writable` BTreeMap\n#[derive(Clone, Copy)]\npub struct GetWrite<Index, Write> {\n    index: Index,\n    write: Write,\n    created: &'static Location<'static>,\n}\n\nimpl<Index, Write, K, V> Readable for GetWrite<Index, Write>\nwhere\n    Write: Readable<Target = BTreeMap<K, V>>,\n    Index: Ord + 'static,\n    K: Borrow<Index> + Ord + 'static,\n{\n    type Target = V;\n\n    type Storage = Write::Storage;\n\n    fn try_read_unchecked(&self) -> Result<dioxus_signals::ReadableRef<'static, Self>, BorrowError>\n    where\n        Self::Target: 'static,\n    {\n        self.write.try_read_unchecked().and_then(|value| {\n            Self::Storage::try_map(value, |value: &Write::Target| value.get(&self.index))\n                .ok_or_else(|| BorrowError::Dropped(ValueDroppedError::new(self.created)))\n        })\n    }\n\n    fn try_peek_unchecked(&self) -> Result<dioxus_signals::ReadableRef<'static, Self>, BorrowError>\n    where\n        Self::Target: 'static,\n    {\n        self.write.try_peek_unchecked().and_then(|value| {\n            Self::Storage::try_map(value, |value: &Write::Target| value.get(&self.index))\n                .ok_or_else(|| BorrowError::Dropped(ValueDroppedError::new(self.created)))\n        })\n    }\n\n    fn subscribers(&self) -> dioxus_core::Subscribers\n    where\n        Self::Target: 'static,\n    {\n        self.write.subscribers()\n    }\n}\n\nimpl<Index, Write, K, V> Writable for GetWrite<Index, Write>\nwhere\n    Write: Writable<Target = BTreeMap<K, V>>,\n    Index: Ord + 'static,\n    K: Borrow<Index> + Ord + 'static,\n{\n    type WriteMetadata = Write::WriteMetadata;\n\n    fn try_write_unchecked(\n        &self,\n    ) -> Result<dioxus_signals::WritableRef<'static, Self>, BorrowMutError>\n    where\n        Self::Target: 'static,\n    {\n        self.write.try_write_unchecked().and_then(|value| {\n            WriteLock::filter_map(value, |value: &mut Write::Target| {\n                value.get_mut(&self.index)\n            })\n            .ok_or_else(|| BorrowMutError::Dropped(ValueDroppedError::new(self.created)))\n        })\n    }\n}\n\nimpl<Index, Write, K, V> ::std::convert::From<Store<V, GetWrite<Index, Write>>>\n    for Store<V, WriteSignal<V>>\nwhere\n    Write::WriteMetadata: 'static,\n    Write: Writable<Target = BTreeMap<K, V>, Storage = UnsyncStorage> + 'static,\n    Index: Ord + 'static,\n    K: Borrow<Index> + Ord + 'static,\n    V: 'static,\n{\n    fn from(value: Store<V, GetWrite<Index, Write>>) -> Self {\n        value\n            .into_selector()\n            .map_writer(|writer| WriteSignal::new(writer))\n            .into()\n    }\n}\n\nimpl<Index, Write, K, V> ::std::convert::From<Store<V, GetWrite<Index, Write>>> for ReadStore<V>\nwhere\n    Write: Readable<Target = BTreeMap<K, V>, Storage = UnsyncStorage> + 'static,\n    Index: Ord + 'static,\n    K: Borrow<Index> + Ord + 'static,\n    V: 'static,\n{\n    fn from(value: Store<V, GetWrite<Index, Write>>) -> Self {\n        value\n            .into_selector()\n            .map_writer(|writer| ReadSignal::new(writer))\n            .into()\n    }\n}\n"
  },
  {
    "path": "packages/stores/src/impls/deref.rs",
    "content": "use std::ops::DerefMut;\n\nuse crate::{store::Store, MappedStore};\nuse dioxus_signals::Readable;\n\nimpl<Lens, T> Store<T, Lens>\nwhere\n    Lens: Readable<Target = T> + 'static,\n    T: DerefMut + 'static,\n{\n    /// Returns a store that dereferences the original value. The dereferenced store shares the same\n    /// subscriptions and tracking as the original store, but allows you to access the methods of the underlying type.\n    ///\n    /// # Example\n    ///\n    /// ```rust, no_run\n    /// use dioxus_stores::*;\n    /// let store = use_store(|| Box::new(vec![1, 2, 3]));\n    /// let deref_store = store.deref();\n    /// // The dereferenced store can access the store methods of the underlying type.\n    /// assert_eq!(deref_store.len(), 3);\n    /// ```\n    pub fn deref(self) -> MappedStore<T::Target, Lens> {\n        let map: fn(&T) -> &T::Target = |value| value.deref();\n        let map_mut: fn(&mut T) -> &mut T::Target = |value| value.deref_mut();\n        self.into_selector().map(map, map_mut).into()\n    }\n}\n"
  },
  {
    "path": "packages/stores/src/impls/hashmap.rs",
    "content": "//! Additional utilities for `HashMap` stores.\n\nuse std::{\n    borrow::Borrow,\n    collections::HashMap,\n    hash::{BuildHasher, Hash},\n    iter::FusedIterator,\n    panic::Location,\n};\n\nuse crate::{store::Store, ReadStore};\nuse dioxus_signals::{\n    AnyStorage, BorrowError, BorrowMutError, ReadSignal, Readable, ReadableExt, UnsyncStorage,\n    Writable, WriteLock, WriteSignal,\n};\nuse generational_box::ValueDroppedError;\n\nimpl<Lens: Readable<Target = HashMap<K, V, St>> + 'static, K: 'static, V: 'static, St: 'static>\n    Store<HashMap<K, V, St>, Lens>\n{\n    /// Get the length of the HashMap. This method will track the store shallowly and only cause\n    /// re-runs when items are added or removed from the map, not when existing values are modified.\n    ///\n    /// # Example\n    /// ```rust, no_run\n    /// use dioxus_stores::*;\n    /// use dioxus::prelude::*;\n    /// use std::collections::HashMap;\n    /// let mut store = use_store(|| HashMap::new());\n    /// assert_eq!(store.len(), 0);\n    /// store.insert(0, \"value\".to_string());\n    /// assert_eq!(store.len(), 1);\n    /// ```\n    pub fn len(&self) -> usize {\n        self.selector().track_shallow();\n        self.selector().peek().len()\n    }\n\n    /// Check if the HashMap is empty. This method will track the store shallowly and only cause\n    /// re-runs when items are added or removed from the map, not when existing values are modified.\n    ///\n    /// # Example\n    /// ```rust, no_run\n    /// use dioxus_stores::*;\n    /// use dioxus::prelude::*;\n    /// use std::collections::HashMap;\n    /// let mut store = use_store(|| HashMap::new());\n    /// assert!(store.is_empty());\n    /// store.insert(0, \"value\".to_string());\n    /// assert!(!store.is_empty());\n    /// ```\n    pub fn is_empty(&self) -> bool {\n        self.selector().track_shallow();\n        self.selector().peek().is_empty()\n    }\n\n    /// Iterate over the current entries in the HashMap, returning a tuple of the key and a store for the value. This method\n    /// will track the store shallowly and only cause re-runs when items are added or removed from the map, not when existing\n    /// values are modified.\n    ///\n    /// # Example\n    ///\n    /// ```rust, no_run\n    /// use dioxus_stores::*;\n    /// use dioxus::prelude::*;\n    /// use std::collections::HashMap;\n    /// let mut store = use_store(|| HashMap::new());\n    /// store.insert(0, \"value1\".to_string());\n    /// store.insert(1, \"value2\".to_string());\n    /// for (key, value_store) in store.iter() {\n    ///     println!(\"{}: {}\", key, value_store.read());\n    /// }\n    /// ```\n    pub fn iter(\n        &self,\n    ) -> impl ExactSizeIterator<Item = (K, Store<V, GetWrite<K, Lens>>)>\n           + DoubleEndedIterator\n           + FusedIterator\n           + '_\n    where\n        K: Eq + Hash + Clone,\n        St: BuildHasher,\n        Lens: Clone,\n    {\n        self.selector().track_shallow();\n        let keys: Vec<_> = self.selector().peek_unchecked().keys().cloned().collect();\n        keys.into_iter()\n            .map(move |key| (key.clone(), self.clone().get_unchecked(key)))\n    }\n\n    /// Get an iterator over the values in the HashMap. This method will track the store shallowly and only cause\n    /// re-runs when items are added or removed from the map, not when existing values are modified.\n    ///\n    /// # Example\n    /// ```rust, no_run\n    /// use dioxus_stores::*;\n    /// use dioxus::prelude::*;\n    /// use std::collections::HashMap;\n    /// let mut store = use_store(|| HashMap::new());\n    /// store.insert(0, \"value1\".to_string());\n    /// store.insert(1, \"value2\".to_string());\n    /// for value_store in store.values() {\n    ///     println!(\"{}\", value_store.read());\n    /// }\n    /// ```\n    pub fn values(\n        &self,\n    ) -> impl ExactSizeIterator<Item = Store<V, GetWrite<K, Lens>>>\n           + DoubleEndedIterator\n           + FusedIterator\n           + '_\n    where\n        K: Eq + Hash + Clone,\n        St: BuildHasher,\n        Lens: Clone,\n    {\n        self.selector().track_shallow();\n        let keys = self.selector().peek().keys().cloned().collect::<Vec<_>>();\n        keys.into_iter()\n            .map(move |key| self.clone().get_unchecked(key))\n    }\n\n    /// Insert a new key-value pair into the HashMap. This method will mark the store as shallowly dirty, causing\n    /// re-runs of any reactive scopes that depend on the shape of the map.\n    ///\n    /// # Example\n    /// ```rust, no_run\n    /// use dioxus_stores::*;\n    /// use dioxus::prelude::*;\n    /// use std::collections::HashMap;\n    /// let mut store = use_store(|| HashMap::new());\n    /// assert!(store.get(0).is_none());\n    /// store.insert(0, \"value\".to_string());\n    /// assert_eq!(store.get(0).unwrap().cloned(), \"value\".to_string());\n    /// ```\n    pub fn insert(&mut self, key: K, value: V)\n    where\n        K: Eq + Hash,\n        St: BuildHasher,\n        Lens: Writable,\n    {\n        // Mark the store itself as dirty since the keys may have changed\n        self.selector().mark_dirty_shallow();\n        // Mark the existing value as dirty if it exists\n        self.selector()\n            .as_ref()\n            .hash_child_unmapped(key.borrow())\n            .mark_dirty();\n        self.selector().write_untracked().insert(key, value);\n    }\n\n    /// Remove a key-value pair from the HashMap. This method will mark the store as shallowly dirty, causing\n    /// re-runs of any reactive scopes that depend on the shape of the map or the value of the removed key.\n    ///\n    /// # Example\n    /// ```rust, no_run\n    /// use dioxus_stores::*;\n    /// use dioxus::prelude::*;\n    /// use std::collections::HashMap;\n    /// let mut store = use_store(|| HashMap::new());\n    /// store.insert(0, \"value\".to_string());\n    /// assert_eq!(store.get(0).unwrap().cloned(), \"value\".to_string());\n    /// let removed_value = store.remove(&0);\n    /// assert_eq!(removed_value, Some(\"value\".to_string()));\n    /// assert!(store.get(0).is_none());\n    /// ```\n    pub fn remove<Q>(&mut self, key: &Q) -> Option<V>\n    where\n        Q: ?Sized + Hash + Eq + 'static,\n        K: Borrow<Q> + Eq + Hash,\n        St: BuildHasher,\n        Lens: Writable,\n    {\n        self.selector().mark_dirty_shallow();\n        self.selector().write_untracked().remove(key)\n    }\n\n    /// Clear the HashMap, removing all key-value pairs. This method will mark the store as shallowly dirty,\n    /// causing re-runs of any reactive scopes that depend on the shape of the map.\n    ///\n    /// # Example\n    /// ```rust, no_run\n    /// use dioxus_stores::*;\n    /// use dioxus::prelude::*;\n    /// use std::collections::HashMap;\n    /// let mut store = use_store(|| HashMap::new());\n    /// store.insert(1, \"value1\".to_string());\n    /// store.insert(2, \"value2\".to_string());\n    /// assert_eq!(store.len(), 2);\n    /// store.clear();\n    /// assert!(store.is_empty());\n    /// ```\n    pub fn clear(&mut self)\n    where\n        Lens: Writable,\n    {\n        self.selector().mark_dirty_shallow();\n        self.selector().write_untracked().clear();\n    }\n\n    /// Retain only the key-value pairs that satisfy the given predicate. This method will mark the store as shallowly dirty,\n    /// causing re-runs of any reactive scopes that depend on the shape of the map or the values retained.\n    ///\n    /// # Example\n    /// ```rust, no_run\n    /// use dioxus_stores::*;\n    /// use dioxus::prelude::*;\n    /// use std::collections::HashMap;\n    /// let mut store = use_store(|| HashMap::new());\n    /// store.insert(1, \"value1\".to_string());\n    /// store.insert(2, \"value2\".to_string());\n    /// store.retain(|key, value| *key == 1);\n    /// assert_eq!(store.len(), 1);\n    /// assert!(store.get(1).is_some());\n    /// assert!(store.get(2).is_none());\n    /// ```\n    pub fn retain(&mut self, mut f: impl FnMut(&K, &V) -> bool)\n    where\n        Lens: Writable,\n    {\n        self.selector().mark_dirty_shallow();\n        self.selector().write_untracked().retain(|k, v| f(k, v));\n    }\n\n    /// Check if the HashMap contains a key. This method will track the store shallowly and only cause\n    /// re-runs when items are added or removed from the map, not when existing values are modified.\n    ///\n    /// # Example\n    /// ```rust, no_run\n    /// use dioxus_stores::*;\n    /// use dioxus::prelude::*;\n    /// use std::collections::HashMap;\n    /// let mut store = use_store(|| HashMap::new());\n    /// assert!(!store.contains_key(&0));\n    /// store.insert(0, \"value\".to_string());\n    /// assert!(store.contains_key(&0));\n    /// ```\n    pub fn contains_key<Q>(&self, key: &Q) -> bool\n    where\n        Q: ?Sized + Hash + Eq + 'static,\n        K: Borrow<Q> + Eq + Hash,\n        St: BuildHasher,\n    {\n        self.selector().track_shallow();\n        self.selector().peek().contains_key(key)\n    }\n\n    /// Get a store for the value associated with the given key. This method creates a new store scope\n    /// that tracks just changes to the value associated with the key.\n    ///\n    /// # Example\n    /// ```rust, no_run\n    /// use dioxus_stores::*;\n    /// use dioxus::prelude::*;\n    /// use std::collections::HashMap;\n    /// let mut store = use_store(|| HashMap::new());\n    /// assert!(store.get(0).is_none());\n    /// store.insert(0, \"value\".to_string());\n    /// assert_eq!(store.get(0).unwrap().cloned(), \"value\".to_string());\n    /// ```\n    pub fn get<Q>(self, key: Q) -> Option<Store<V, GetWrite<Q, Lens>>>\n    where\n        Q: Hash + Eq + 'static,\n        K: Borrow<Q> + Eq + Hash,\n        St: BuildHasher,\n    {\n        self.contains_key(&key).then(|| self.get_unchecked(key))\n    }\n\n    /// Get a store for the value associated with the given key without checking if the key exists.\n    /// This method creates a new store scope that tracks just changes to the value associated with the key.\n    ///\n    /// This is not unsafe, but it will panic when you try to read the value if it does not exist.\n    ///\n    /// # Example\n    /// ```rust, no_run\n    /// use dioxus_stores::*;\n    /// use dioxus::prelude::*;\n    /// use std::collections::HashMap;\n    /// let mut store = use_store(|| HashMap::new());\n    /// store.insert(0, \"value\".to_string());\n    /// assert_eq!(store.get_unchecked(0).cloned(), \"value\".to_string());\n    /// ```\n    #[track_caller]\n    pub fn get_unchecked<Q>(self, key: Q) -> Store<V, GetWrite<Q, Lens>>\n    where\n        Q: Hash + Eq + 'static,\n        K: Borrow<Q> + Eq + Hash,\n        St: BuildHasher,\n    {\n        let location = Location::caller();\n        self.into_selector()\n            .hash_child_unmapped(key.borrow())\n            .map_writer(move |writer| GetWrite {\n                index: key,\n                write: writer,\n                created: location,\n            })\n            .into()\n    }\n}\n\n/// A specific index in a `Readable` / `Writable` hashmap\n#[derive(Clone, Copy)]\npub struct GetWrite<Index, Write> {\n    index: Index,\n    write: Write,\n    created: &'static Location<'static>,\n}\n\nimpl<Index, Write, K, V, St> Readable for GetWrite<Index, Write>\nwhere\n    Write: Readable<Target = HashMap<K, V, St>>,\n    Index: Hash + Eq + 'static,\n    K: Borrow<Index> + Eq + Hash + 'static,\n    St: BuildHasher + 'static,\n{\n    type Target = V;\n\n    type Storage = Write::Storage;\n\n    fn try_read_unchecked(&self) -> Result<dioxus_signals::ReadableRef<'static, Self>, BorrowError>\n    where\n        Self::Target: 'static,\n    {\n        self.write.try_read_unchecked().and_then(|value| {\n            Self::Storage::try_map(value, |value: &Write::Target| value.get(&self.index))\n                .ok_or_else(|| BorrowError::Dropped(ValueDroppedError::new(self.created)))\n        })\n    }\n\n    fn try_peek_unchecked(&self) -> Result<dioxus_signals::ReadableRef<'static, Self>, BorrowError>\n    where\n        Self::Target: 'static,\n    {\n        self.write.try_peek_unchecked().and_then(|value| {\n            Self::Storage::try_map(value, |value: &Write::Target| value.get(&self.index))\n                .ok_or_else(|| BorrowError::Dropped(ValueDroppedError::new(self.created)))\n        })\n    }\n\n    fn subscribers(&self) -> dioxus_core::Subscribers\n    where\n        Self::Target: 'static,\n    {\n        self.write.subscribers()\n    }\n}\n\nimpl<Index, Write, K, V, St> Writable for GetWrite<Index, Write>\nwhere\n    Write: Writable<Target = HashMap<K, V, St>>,\n    Index: Hash + Eq + 'static,\n    K: Borrow<Index> + Eq + Hash + 'static,\n    St: BuildHasher + 'static,\n{\n    type WriteMetadata = Write::WriteMetadata;\n\n    fn try_write_unchecked(\n        &self,\n    ) -> Result<dioxus_signals::WritableRef<'static, Self>, BorrowMutError>\n    where\n        Self::Target: 'static,\n    {\n        self.write.try_write_unchecked().and_then(|value| {\n            WriteLock::filter_map(value, |value: &mut Write::Target| {\n                value.get_mut(&self.index)\n            })\n            .ok_or_else(|| BorrowMutError::Dropped(ValueDroppedError::new(self.created)))\n        })\n    }\n}\n\nimpl<Index, Write, K, V, St> ::std::convert::From<Store<V, GetWrite<Index, Write>>>\n    for Store<V, WriteSignal<V>>\nwhere\n    Write::WriteMetadata: 'static,\n    Write: Writable<Target = HashMap<K, V, St>, Storage = UnsyncStorage> + 'static,\n    Index: Hash + Eq + 'static,\n    K: Borrow<Index> + Eq + Hash + 'static,\n    St: BuildHasher + 'static,\n    V: 'static,\n{\n    fn from(value: Store<V, GetWrite<Index, Write>>) -> Self {\n        value\n            .into_selector()\n            .map_writer(|writer| WriteSignal::new(writer))\n            .into()\n    }\n}\n\nimpl<Index, Write, K, V, St> ::std::convert::From<Store<V, GetWrite<Index, Write>>> for ReadStore<V>\nwhere\n    Write: Readable<Target = HashMap<K, V, St>, Storage = UnsyncStorage> + 'static,\n    Index: Hash + Eq + 'static,\n    K: Borrow<Index> + Eq + Hash + 'static,\n    St: BuildHasher + 'static,\n    V: 'static,\n{\n    fn from(value: Store<V, GetWrite<Index, Write>>) -> Self {\n        value\n            .into_selector()\n            .map_writer(|writer| ReadSignal::new(writer))\n            .into()\n    }\n}\n"
  },
  {
    "path": "packages/stores/src/impls/index.rs",
    "content": "//! Additional utilities for indexing into stores.\n\nuse std::{\n    collections::{BTreeMap, HashMap},\n    hash::Hash,\n    ops::{self, Index, IndexMut},\n};\n\nuse crate::{scope::SelectorScope, store::Store, ReadStore};\nuse dioxus_signals::{\n    AnyStorage, BorrowError, BorrowMutError, ReadSignal, Readable, UnsyncStorage, Writable,\n    WriteLock, WriteSignal,\n};\n\n/// The way a data structure index into its children based on a key. The selector must use this indexing\n/// method consistently to ensure that the same key always maps to the same child.\npub trait IndexSelector<Idx> {\n    /// Given a selector and an index, scope the selector to the child at the given index.\n    fn scope_selector<Lens>(selector: SelectorScope<Lens>, index: &Idx) -> SelectorScope<Lens>;\n}\n\nimpl<T> IndexSelector<usize> for Vec<T> {\n    fn scope_selector<Lens>(selector: SelectorScope<Lens>, index: &usize) -> SelectorScope<Lens> {\n        selector.child_unmapped(*index as _)\n    }\n}\n\nimpl<T> IndexSelector<usize> for [T] {\n    fn scope_selector<Lens>(selector: SelectorScope<Lens>, index: &usize) -> SelectorScope<Lens> {\n        selector.child_unmapped(*index as _)\n    }\n}\n\nimpl<K, V, I> IndexSelector<I> for HashMap<K, V>\nwhere\n    I: Hash,\n{\n    fn scope_selector<Lens>(selector: SelectorScope<Lens>, index: &I) -> SelectorScope<Lens> {\n        selector.hash_child_unmapped(&index)\n    }\n}\n\nimpl<K, V, I> IndexSelector<I> for BTreeMap<K, V>\nwhere\n    I: Hash,\n{\n    fn scope_selector<Lens>(selector: SelectorScope<Lens>, index: &I) -> SelectorScope<Lens> {\n        selector.hash_child_unmapped(&index)\n    }\n}\n\nimpl<Lens, T> Store<T, Lens> {\n    /// Index into the store, returning a store that allows access to the item at the given index. The\n    /// new store will only update when the item at the index changes.\n    ///\n    /// # Example\n    /// ```rust, no_run\n    /// use dioxus_stores::*;\n    /// let store = use_store(|| vec![1, 2, 3]);\n    /// let indexed_store = store.index(1);\n    /// // The indexed store can access the store methods of the indexed store.\n    /// assert_eq!(indexed_store(), 2);\n    /// ```\n    pub fn index<Idx>(self, index: Idx) -> Store<T::Output, IndexWrite<Idx, Lens>>\n    where\n        T: IndexMut<Idx> + 'static + IndexSelector<Idx>,\n        Lens: Readable<Target = T> + 'static,\n    {\n        T::scope_selector(self.into_selector(), &index)\n            .map_writer(move |write| IndexWrite { index, write })\n            .into()\n    }\n}\n\n/// A specific index in a `Readable` / `Writable` type\n#[derive(Clone, Copy)]\npub struct IndexWrite<Index, Write> {\n    index: Index,\n    write: Write,\n}\n\nimpl<Index, Write> Readable for IndexWrite<Index, Write>\nwhere\n    Write: Readable,\n    Write::Target: ops::Index<Index> + 'static,\n    Index: Clone,\n{\n    type Target = <Write::Target as ops::Index<Index>>::Output;\n\n    type Storage = Write::Storage;\n\n    fn try_read_unchecked(&self) -> Result<dioxus_signals::ReadableRef<'static, Self>, BorrowError>\n    where\n        Self::Target: 'static,\n    {\n        self.write.try_read_unchecked().map(|value| {\n            Self::Storage::map(value, |value: &Write::Target| {\n                value.index(self.index.clone())\n            })\n        })\n    }\n\n    fn try_peek_unchecked(&self) -> Result<dioxus_signals::ReadableRef<'static, Self>, BorrowError>\n    where\n        Self::Target: 'static,\n    {\n        self.write.try_peek_unchecked().map(|value| {\n            Self::Storage::map(value, |value: &Write::Target| {\n                value.index(self.index.clone())\n            })\n        })\n    }\n\n    fn subscribers(&self) -> dioxus_core::Subscribers\n    where\n        Self::Target: 'static,\n    {\n        self.write.subscribers()\n    }\n}\n\nimpl<Index, Write> Writable for IndexWrite<Index, Write>\nwhere\n    Write: Writable,\n    Write::Target: ops::IndexMut<Index> + 'static,\n    Index: Clone,\n{\n    type WriteMetadata = Write::WriteMetadata;\n\n    fn try_write_unchecked(\n        &self,\n    ) -> Result<dioxus_signals::WritableRef<'static, Self>, BorrowMutError>\n    where\n        Self::Target: 'static,\n    {\n        self.write.try_write_unchecked().map(|value| {\n            WriteLock::map(value, |value: &mut Write::Target| {\n                value.index_mut(self.index.clone())\n            })\n        })\n    }\n}\n\nimpl<Idx, T, Write> ::std::convert::From<Store<T, IndexWrite<Idx, Write>>>\n    for Store<T, WriteSignal<T>>\nwhere\n    Write: Writable<Storage = UnsyncStorage> + 'static,\n    Write::WriteMetadata: 'static,\n    Write::Target: ops::IndexMut<Idx, Output = T> + 'static,\n    Idx: Clone + 'static,\n    T: 'static,\n{\n    fn from(value: Store<T, IndexWrite<Idx, Write>>) -> Self {\n        value\n            .into_selector()\n            .map_writer(|writer| WriteSignal::new(writer))\n            .into()\n    }\n}\n\nimpl<Idx, T, Write> ::std::convert::From<Store<T, IndexWrite<Idx, Write>>> for ReadStore<T>\nwhere\n    Write: Readable<Storage = UnsyncStorage> + 'static,\n    Write::Target: ops::Index<Idx, Output = T> + 'static,\n    Idx: Clone + 'static,\n    T: 'static,\n{\n    fn from(value: Store<T, IndexWrite<Idx, Write>>) -> Self {\n        value\n            .into_selector()\n            .map_writer(|writer| ReadSignal::new(writer))\n            .into()\n    }\n}\n"
  },
  {
    "path": "packages/stores/src/impls/mod.rs",
    "content": "pub mod btreemap;\nmod deref;\npub mod hashmap;\npub mod index;\nmod option;\nmod result;\nmod slice;\nmod vec;\n"
  },
  {
    "path": "packages/stores/src/impls/option.rs",
    "content": "use std::ops::DerefMut;\n\nuse crate::{store::Store, MappedStore};\nuse dioxus_signals::{Readable, ReadableExt};\n\nimpl<Lens: Readable<Target = Option<T>> + 'static, T: 'static> Store<Option<T>, Lens> {\n    /// Checks if the `Option` is `Some`. This will only track the shallow state of the `Option`. It will\n    /// only cause a re-run if the `Option` could change from `None` to `Some` or vice versa.\n    ///\n    /// # Example\n    /// ```rust, no_run\n    /// use dioxus_stores::*;\n    /// let store = use_store(|| Some(42));\n    /// assert!(store.is_some());\n    /// ```\n    pub fn is_some(&self) -> bool {\n        self.selector().track_shallow();\n        self.selector().peek().is_some()\n    }\n\n    /// Returns true if the option is Some and the closure returns true. This will always track the shallow\n    /// state of the and will track the inner state of the enum if the enum is Some.\n    ///\n    /// # Example\n    /// ```rust, no_run\n    /// use dioxus_stores::*;\n    /// let store = use_store(|| Some(42));\n    /// assert!(store.is_some_and(|v| *v == 42));\n    /// ```\n    pub fn is_some_and(&self, f: impl FnOnce(&T) -> bool) -> bool {\n        self.selector().track_shallow();\n        let value = self.selector().peek();\n        if let Some(v) = &*value {\n            self.selector().track();\n            f(v)\n        } else {\n            false\n        }\n    }\n\n    /// Checks if the `Option` is `None`. This will only track the shallow state of the `Option`. It will\n    /// only cause a re-run if the `Option` could change from `Some` to `None` or vice versa.\n    ///\n    /// # Example\n    /// ```rust, no_run\n    /// use dioxus_stores::*;\n    /// let store = use_store(|| None::<i32>);\n    /// assert!(store.is_none());\n    /// ```\n    pub fn is_none(&self) -> bool {\n        self.selector().track_shallow();\n        self.selector().peek().is_none()\n    }\n\n    /// Returns true if the option is None or the closure returns true. This will always track the shallow\n    /// state of the and will track the inner state of the enum if the enum is Some.\n    ///\n    /// # Example\n    /// ```rust, no_run\n    /// use dioxus_stores::*;\n    /// let store = use_store(|| Some(42));\n    /// assert!(store.is_none_or(|v| *v == 42));\n    /// ```\n    pub fn is_none_or(&self, f: impl FnOnce(&T) -> bool) -> bool {\n        self.selector().track_shallow();\n        let value = self.selector().peek();\n        if let Some(v) = &*value {\n            self.selector().track();\n            f(v)\n        } else {\n            true\n        }\n    }\n\n    /// Transpose the `Store<Option<T>>` into a `Option<Store<T>>`. This will only track the shallow state of the `Option`. It will\n    /// only cause a re-run if the `Option` could change from `None` to `Some` or vice versa.\n    ///\n    /// # Example\n    /// ```rust, no_run\n    /// use dioxus_stores::*;\n    /// let store = use_store(|| Some(42));\n    /// let transposed = store.transpose();\n    /// match transposed {\n    ///     Some(inner_store) => assert_eq!(inner_store(), 42),\n    ///     None => panic!(\"Expected Some\"),\n    /// }\n    /// ```\n    pub fn transpose(self) -> Option<MappedStore<T, Lens>> {\n        self.is_some().then(move || {\n            let map: fn(&Option<T>) -> &T = |value| {\n                value.as_ref().unwrap_or_else(|| {\n                    panic!(\"Tried to access `Some` on an Option value\");\n                })\n            };\n            let map_mut: fn(&mut Option<T>) -> &mut T = |value| {\n                value.as_mut().unwrap_or_else(|| {\n                    panic!(\"Tried to access `Some` on an Option value\");\n                })\n            };\n            self.into_selector().child(0, map, map_mut).into()\n        })\n    }\n\n    /// Unwraps the `Option` and returns a `Store<T>`. This will only track the shallow state of the `Option`. It will\n    /// only cause a re-run if the `Option` could change from `None` to `Some` or vice versa.\n    ///\n    /// # Example\n    /// ```rust, no_run\n    /// use dioxus_stores::*;\n    /// let store = use_store(|| Some(42));\n    /// let unwrapped = store.unwrap();\n    /// assert_eq!(unwrapped(), 42);\n    /// ```\n    pub fn unwrap(self) -> MappedStore<T, Lens> {\n        self.transpose().unwrap()\n    }\n\n    /// Expects the `Option` to be `Some` and returns a `Store<T>`. If the value is `None`, this will panic with `msg`. This will\n    /// only track the shallow state of the `Option`. It will only cause a re-run if the `Option` could change from `None`\n    /// to `Some` or vice versa.\n    ///\n    /// # Example\n    /// ```rust, no_run\n    /// use dioxus_stores::*;\n    /// let store = use_store(|| Some(42));\n    /// let unwrapped = store.expect(\"the answer to life the universe and everything\");\n    /// assert_eq!(unwrapped(), 42);\n    /// ```\n    pub fn expect(self, msg: &str) -> MappedStore<T, Lens> {\n        self.transpose().expect(msg)\n    }\n\n    /// Returns a slice of the contained value, or an empty slice. This will not subscribe to any part of the store.\n    ///\n    /// # Example\n    /// ```rust, no_run\n    /// use dioxus::prelude::*;\n    /// use dioxus_stores::*;\n    /// let store = use_store(|| Some(42));\n    /// let slice = store.as_slice();\n    /// assert_eq!(&*slice.read(), [42]);\n    /// ```\n    pub fn as_slice(self) -> MappedStore<[T], Lens> {\n        let map: fn(&Option<T>) -> &[T] = |value| value.as_slice();\n        let map_mut: fn(&mut Option<T>) -> &mut [T] = |value| value.as_mut_slice();\n        self.into_selector().map(map, map_mut).into()\n    }\n\n    /// Transpose the store then coerce the contents of the Option with deref. This will only track the shallow state of the `Option`. It will\n    /// only cause a re-run if the `Option` could change from `None` to `Some` or vice versa.\n    ///\n    /// # Example\n    /// ```rust, no_run\n    /// use dioxus_stores::*;\n    /// let store = use_store(|| Some(Box::new(42)));\n    /// let derefed = store.as_deref().unwrap();\n    /// assert_eq!(derefed(), 42);\n    /// ```\n    pub fn as_deref(self) -> Option<MappedStore<T::Target, Lens>>\n    where\n        T: DerefMut,\n    {\n        self.is_some().then(move || {\n            let map: fn(&Option<T>) -> &T::Target = |value| {\n                value\n                    .as_ref()\n                    .unwrap_or_else(|| {\n                        panic!(\"Tried to access `Some` on an Option value\");\n                    })\n                    .deref()\n            };\n            let map_mut: fn(&mut Option<T>) -> &mut T::Target = |value| {\n                value\n                    .as_mut()\n                    .unwrap_or_else(|| {\n                        panic!(\"Tried to access `Some` on an Option value\");\n                    })\n                    .deref_mut()\n            };\n            self.into_selector().child(0, map, map_mut).into()\n        })\n    }\n\n    /// Transpose the store then filter the contents of the Option with a closure. This will always track the shallow\n    /// state of the and will track the inner state of the enum if the enum is Some.\n    ///\n    /// # Example\n    /// ```rust, no_run\n    /// use dioxus_stores::*;\n    /// let store = use_store(|| Some(42));\n    /// let option = store.filter(|&v| v > 40);\n    /// let value = option.unwrap();\n    /// assert_eq!(value(), 42);\n    /// ```\n    pub fn filter(self, f: impl FnOnce(&T) -> bool) -> Option<MappedStore<T, Lens>> {\n        self.is_some_and(f).then(move || {\n            let map: fn(&Option<T>) -> &T = |value| {\n                value.as_ref().unwrap_or_else(|| {\n                    panic!(\"Tried to access `Some` on an Option value\");\n                })\n            };\n            let map_mut: fn(&mut Option<T>) -> &mut T = |value| {\n                value.as_mut().unwrap_or_else(|| {\n                    panic!(\"Tried to access `Some` on an Option value\");\n                })\n            };\n            self.into_selector().child(0, map, map_mut).into()\n        })\n    }\n\n    /// Call the function with a reference to the inner value if it is Some. This will always track the shallow\n    /// state of the and will track the inner state of the enum if the enum is Some.\n    ///\n    /// # Example\n    /// ```rust, no_run\n    /// use dioxus_stores::*;\n    /// let store = use_store(|| Some(42)).inspect(|v| println!(\"{v}\"));\n    /// ```\n    pub fn inspect(self, f: impl FnOnce(&T)) -> Self {\n        {\n            self.selector().track_shallow();\n            let value = self.selector().peek();\n            if let Some(v) = &*value {\n                self.selector().track();\n                f(v);\n            }\n        }\n        self\n    }\n}\n"
  },
  {
    "path": "packages/stores/src/impls/result.rs",
    "content": "use std::fmt::Debug;\n\nuse crate::{store::Store, MappedStore};\nuse dioxus_signals::{Readable, ReadableExt, Writable};\n\nimpl<Lens, T, E> Store<Result<T, E>, Lens>\nwhere\n    Lens: Readable<Target = Result<T, E>> + 'static,\n    T: 'static,\n    E: 'static,\n{\n    /// Checks if the `Result` is `Ok`. This will only track the shallow state of the `Result`. It will\n    /// only cause a re-run if the `Result` could change from `Err` to `Ok` or vice versa.\n    ///\n    /// # Example\n    /// ```rust, no_run\n    /// use dioxus_stores::*;\n    /// let store = use_store(|| Ok::<u32, ()>(42));\n    /// assert!(store.is_ok());\n    /// ```\n    pub fn is_ok(&self) -> bool {\n        self.selector().track_shallow();\n        self.selector().peek().is_ok()\n    }\n\n    /// Returns true if the result is Ok and the closure returns true. This will always track the shallow\n    /// state of the and will track the inner state of the enum if the enum is Ok.\n    ///\n    /// # Example\n    /// ```rust, no_run\n    /// use dioxus_stores::*;\n    /// let store = use_store(|| Ok::<u32, ()>(42));\n    /// assert!(store.is_ok_and(|v| *v == 42));\n    /// ```\n    pub fn is_ok_and(&self, f: impl FnOnce(&T) -> bool) -> bool {\n        self.selector().track_shallow();\n        let value = self.selector().peek();\n        if let Ok(v) = &*value {\n            self.selector().track();\n            f(v)\n        } else {\n            false\n        }\n    }\n\n    /// Checks if the `Result` is `Err`. This will only track the shallow state of the `Result`. It will\n    /// only cause a re-run if the `Result` could change from `Ok` to `Err` or vice versa.\n    ///\n    /// # Example\n    /// ```rust, no_run\n    /// use dioxus_stores::*;\n    /// let store = use_store(|| Err::<(), u32>(42));\n    /// assert!(store.is_err());\n    /// ```\n    pub fn is_err(&self) -> bool {\n        self.selector().track_shallow();\n        self.selector().peek().is_err()\n    }\n\n    /// Returns true if the result is Err and the closure returns true. This will always track the shallow\n    /// state of the and will track the inner state of the enum if the enum is Err.\n    ///\n    /// # Example\n    /// ```rust, no_run\n    /// use dioxus_stores::*;\n    /// let store = use_store(|| Err::<(), u32>(42));\n    /// assert!(store.is_err_and(|v| *v == 42));\n    /// ```\n    pub fn is_err_and(&self, f: impl FnOnce(&E) -> bool) -> bool {\n        self.selector().track_shallow();\n        let value = self.selector().peek();\n        if let Err(e) = &*value {\n            self.selector().track();\n            f(e)\n        } else {\n            false\n        }\n    }\n\n    /// Converts `Store<Result<T, E>>` into `Option<Store<T>>`, discarding the error if present. This will\n    /// only track the shallow state of the `Result`. It will only cause a re-run if the `Result` could\n    /// change from `Err` to `Ok` or vice versa.\n    ///\n    /// # Example\n    /// ```rust, no_run\n    /// use dioxus_stores::*;\n    /// let store = use_store(|| Ok::<u32, ()>(42));\n    /// match store.ok() {\n    ///     Some(ok_store) => assert_eq!(ok_store(), 42),\n    ///     None => panic!(\"Expected Ok\"),\n    /// }\n    /// ```\n    pub fn ok(self) -> Option<MappedStore<T, Lens>> {\n        let map: fn(&Result<T, E>) -> &T = |value| {\n            value.as_ref().unwrap_or_else(|_| {\n                panic!(\"Tried to access `ok` on an Err value\");\n            })\n        };\n        let map_mut: fn(&mut Result<T, E>) -> &mut T = |value| {\n            value.as_mut().unwrap_or_else(|_| {\n                panic!(\"Tried to access `ok` on an Err value\");\n            })\n        };\n        self.is_ok()\n            .then(|| self.into_selector().child(0, map, map_mut).into())\n    }\n\n    /// Converts `Store<Result<T, E>>` into `Option<Store<E>>`, discarding the success if present. This will\n    /// only track the shallow state of the `Result`. It will only cause a re-run if the `Result` could\n    /// change from `Ok` to `Err` or vice versa.\n    ///\n    /// # Example\n    /// ```rust, no_run\n    /// use dioxus_stores::*;\n    /// let store = use_store(|| Err::<(), u32>(42));\n    /// match store.err() {\n    ///     Some(err_store) => assert_eq!(err_store(), 42),\n    ///     None => panic!(\"Expected Err\"),\n    /// }\n    /// ```\n    pub fn err(self) -> Option<MappedStore<E, Lens>>\n    where\n        Lens: Writable<Target = Result<T, E>> + 'static,\n    {\n        self.is_err().then(|| {\n            let map: fn(&Result<T, E>) -> &E = |value| match value {\n                Ok(_) => panic!(\"Tried to access `err` on an Ok value\"),\n                Err(e) => e,\n            };\n            let map_mut: fn(&mut Result<T, E>) -> &mut E = |value| match value {\n                Ok(_) => panic!(\"Tried to access `err` on an Ok value\"),\n                Err(e) => e,\n            };\n            self.into_selector().child(1, map, map_mut).into()\n        })\n    }\n\n    /// Transposes the `Store<Result<T, E>>` into a `Result<Store<T>, Store<E>>`. This will only track the\n    /// shallow state of the `Result`. It will only cause a re-run if the `Result` could change from `Err` to\n    /// `Ok` or vice versa.\n    ///\n    /// # Example\n    /// ```rust, no_run\n    /// use dioxus_stores::*;\n    /// let store = use_store(|| Ok::<u32, ()>(42));\n    /// match store.transpose() {\n    ///     Ok(ok_store) => assert_eq!(ok_store(), 42),\n    ///     Err(err_store) => assert_eq!(err_store(), ()),\n    /// }\n    /// ```\n    #[allow(clippy::result_large_err)]\n    pub fn transpose(self) -> Result<MappedStore<T, Lens>, MappedStore<E, Lens>>\n    where\n        Lens: Writable<Target = Result<T, E>> + 'static,\n    {\n        if self.is_ok() {\n            let map: fn(&Result<T, E>) -> &T = |value| match value {\n                Ok(t) => t,\n                Err(_) => panic!(\"Tried to access `ok` on an Err value\"),\n            };\n            let map_mut: fn(&mut Result<T, E>) -> &mut T = |value| match value {\n                Ok(t) => t,\n                Err(_) => panic!(\"Tried to access `ok` on an Err value\"),\n            };\n            Ok(self.into_selector().child(0, map, map_mut).into())\n        } else {\n            let map: fn(&Result<T, E>) -> &E = |value| match value {\n                Ok(_) => panic!(\"Tried to access `err` on an Ok value\"),\n                Err(e) => e,\n            };\n            let map_mut: fn(&mut Result<T, E>) -> &mut E = |value| match value {\n                Ok(_) => panic!(\"Tried to access `err` on an Ok value\"),\n                Err(e) => e,\n            };\n            Err(self.into_selector().child(1, map, map_mut).into())\n        }\n    }\n\n    /// Unwraps the `Result` and returns a `Store<T>`. This will only track the shallow state of the `Result`.\n    /// It will only cause a re-run if the `Result` could change from `Err` to `Ok` or vice versa.\n    ///\n    /// # Example\n    /// ```rust, no_run\n    /// use dioxus_stores::*;\n    /// let store = use_store(|| Ok::<u32, ()>(42));\n    /// let unwrapped = store.unwrap();\n    /// assert_eq!(unwrapped(), 42);\n    /// ```\n    pub fn unwrap(self) -> MappedStore<T, Lens>\n    where\n        Lens: Writable<Target = Result<T, E>> + 'static,\n        E: Debug,\n    {\n        self.transpose().unwrap()\n    }\n\n    /// Expects the `Result` to be `Ok` and returns a `Store<T>`. If the value is `Err`, this will panic with `msg`.\n    /// This will only track the shallow state of the `Result`. It will only cause a re-run if the `Result` could\n    /// change from `Err` to `Ok` or vice versa.\n    ///\n    /// # Example\n    /// ```rust, no_run\n    /// use dioxus_stores::*;\n    /// let store = use_store(|| Ok::<u32, ()>(42));\n    /// let unwrapped = store.expect(\"Expected Ok\");\n    /// assert_eq!(unwrapped(), 42);\n    /// ```\n    pub fn expect(self, msg: &str) -> MappedStore<T, Lens>\n    where\n        Lens: Writable<Target = Result<T, E>> + 'static,\n        E: Debug,\n    {\n        self.transpose().expect(msg)\n    }\n\n    /// Unwraps the error variant of the `Result`. This will only track the shallow state of the `Result`.\n    /// It will only cause a re-run if the `Result` could change from `Ok` to `Err` or vice versa.\n    ///\n    /// # Example\n    /// ```rust, no_run\n    /// use dioxus_stores::*;\n    /// let store = use_store(|| Err::<(), u32>(42));\n    /// let unwrapped_err = store.unwrap_err();\n    /// assert_eq!(unwrapped_err(), 42);\n    /// ```\n    pub fn unwrap_err(self) -> MappedStore<E, Lens>\n    where\n        Lens: Writable<Target = Result<T, E>> + 'static,\n        T: Debug,\n    {\n        self.transpose().unwrap_err()\n    }\n\n    /// Expects the `Result` to be `Err` and returns a `Store<E>`. If the value is `Ok`, this will panic with `msg`.\n    /// This will only track the shallow state of the `Result`. It will only cause a re-run if the `Result` could\n    /// change from `Ok` to `Err` or vice versa.\n    ///\n    /// # Example\n    /// ```rust, no_run\n    /// use dioxus_stores::*;\n    /// let store = use_store(|| Err::<(), u32>(42));\n    /// let unwrapped_err = store.expect_err(\"Expected Err\");\n    /// assert_eq!(unwrapped_err(), 42);\n    /// ```\n    pub fn expect_err(self, msg: &str) -> MappedStore<E, Lens>\n    where\n        Lens: Writable<Target = Result<T, E>> + 'static,\n        T: Debug,\n    {\n        self.transpose().expect_err(msg)\n    }\n\n    /// Call the function with a reference to the inner value if it is Ok. This will always track the shallow\n    /// state of the and will track the inner state of the enum if the enum is Ok.\n    ///\n    /// # Example\n    /// ```rust, no_run\n    /// use dioxus_stores::*;\n    /// let store = use_store(|| Ok::<u32, ()>(42)).inspect(|v| println!(\"{v}\"));\n    /// ```\n    pub fn inspect(self, f: impl FnOnce(&T)) -> Self\n    where\n        Lens: Writable<Target = Result<T, E>> + 'static,\n    {\n        {\n            self.selector().track_shallow();\n            let value = self.selector().peek();\n            if let Ok(value) = &*value {\n                self.selector().track();\n                f(value);\n            }\n        }\n        self\n    }\n\n    /// Call the function with a mutable reference to the inner value if it is Err. This will always track the shallow\n    /// state of the `Result` and will track the inner state of the enum if the enum is Err.\n    ///\n    /// # Example\n    /// ```rust, no_run\n    /// use dioxus_stores::*;\n    /// let store = use_store(|| Err::<(), u32>(42)).inspect_err(|v| println!(\"{v}\"));\n    /// ```\n    pub fn inspect_err(self, f: impl FnOnce(&E)) -> Self\n    where\n        Lens: Writable<Target = Result<T, E>> + 'static,\n    {\n        {\n            self.selector().track_shallow();\n            let value = self.selector().peek();\n            if let Err(value) = &*value {\n                self.selector().track();\n                f(value);\n            }\n        }\n        self\n    }\n\n    /// Transpose the store then coerce the contents of the Result with deref. This will only track the shallow state of the `Result`. It will\n    /// only cause a re-run if the `Result` could change from `Err` to `Ok` or vice versa.\n    ///\n    /// # Example\n    /// ```rust, no_run\n    /// use dioxus_stores::*;\n    /// let store = use_store(|| Ok::<Box<u32>, ()>(Box::new(42)));\n    /// let derefed = store.as_deref().unwrap();\n    /// assert_eq!(derefed(), 42);\n    /// ```\n    pub fn as_deref(self) -> Result<MappedStore<T::Target, Lens>, MappedStore<E, Lens>>\n    where\n        Lens: Writable<Target = Result<T, E>> + 'static,\n        T: std::ops::DerefMut,\n    {\n        if self.is_ok() {\n            let map: fn(&Result<T, E>) -> &T::Target = |value| match value {\n                Ok(t) => t.deref(),\n                Err(_) => panic!(\"Tried to access `ok` on an Err value\"),\n            };\n            let map_mut: fn(&mut Result<T, E>) -> &mut T::Target = |value| match value {\n                Ok(t) => t.deref_mut(),\n                Err(_) => panic!(\"Tried to access `ok` on an Err value\"),\n            };\n            Ok(self.into_selector().child(0, map, map_mut).into())\n        } else {\n            let map: fn(&Result<T, E>) -> &E = |value| match value {\n                Ok(_) => panic!(\"Tried to access `err` on an Ok value\"),\n                Err(e) => e,\n            };\n            let map_mut: fn(&mut Result<T, E>) -> &mut E = |value| match value {\n                Ok(_) => panic!(\"Tried to access `err` on an Ok value\"),\n                Err(e) => e,\n            };\n            Err(self.into_selector().child(1, map, map_mut).into())\n        }\n    }\n}\n"
  },
  {
    "path": "packages/stores/src/impls/slice.rs",
    "content": "use std::iter::FusedIterator;\n\nuse crate::{impls::index::IndexWrite, store::Store};\nuse dioxus_signals::{Readable, ReadableExt};\n\nimpl<Lens, I> Store<Vec<I>, Lens>\nwhere\n    Lens: Readable<Target = Vec<I>> + 'static,\n    I: 'static,\n{\n    /// Returns the length of the slice. This will only track the shallow state of the slice.\n    /// It will only cause a re-run if the length of the slice could change.\n    ///\n    /// # Example\n    /// ```rust, no_run\n    /// use dioxus_stores::*;\n    /// let store = use_store(|| vec![1, 2, 3]);\n    /// assert_eq!(store.len(), 3);\n    /// ```\n    pub fn len(&self) -> usize {\n        self.selector().track_shallow();\n        self.selector().peek().len()\n    }\n\n    /// Checks if the slice is empty. This will only track the shallow state of the slice.\n    /// It will only cause a re-run if the length of the slice could change.\n    ///\n    /// # Example\n    /// ```rust, no_run\n    /// use dioxus_stores::*;\n    /// let store = use_store(|| vec![1, 2, 3]);\n    /// assert!(!store.is_empty());\n    /// ```\n    pub fn is_empty(&self) -> bool {\n        self.selector().track_shallow();\n        self.selector().peek().is_empty()\n    }\n\n    /// Returns an iterator over the items in the slice. This will only track the shallow state of the slice.\n    /// It will only cause a re-run if the length of the slice could change.\n    /// # Example\n    /// ```rust, no_run\n    /// use dioxus_stores::*;\n    /// let store = use_store(|| vec![1, 2, 3]);\n    /// for item in store.iter() {\n    ///     println!(\"{}\", item);\n    /// }\n    /// ```\n    pub fn iter(\n        &self,\n    ) -> impl ExactSizeIterator<Item = Store<I, IndexWrite<usize, Lens>>>\n           + DoubleEndedIterator\n           + FusedIterator\n           + '_\n    where\n        Lens: Clone,\n    {\n        (0..self.len()).map(move |i| self.clone().index(i))\n    }\n\n    /// Try to get an item from slice. This will only track the shallow state of the slice.\n    /// It will only cause a re-run if the length of the slice could change. The new store\n    /// will only update when the item at the index changes.\n    ///\n    /// # Example\n    /// ```rust, no_run\n    /// use dioxus_stores::*;\n    /// let store = use_store(|| vec![1, 2, 3]);\n    /// let indexed_store = store.get(1).unwrap();\n    /// // The indexed store can access the store methods of the indexed store.\n    /// assert_eq!(indexed_store(), 2);\n    /// ```\n    pub fn get(&self, index: usize) -> Option<Store<I, IndexWrite<usize, Lens>>>\n    where\n        Lens: Clone,\n    {\n        if index >= self.len() {\n            None\n        } else {\n            Some(self.clone().index(index))\n        }\n    }\n}\n"
  },
  {
    "path": "packages/stores/src/impls/vec.rs",
    "content": "use crate::store::Store;\nuse dioxus_signals::Writable;\n\nimpl<Lens: Writable<Target = Vec<T>> + 'static, T: 'static> Store<Vec<T>, Lens> {\n    /// Pushes an item to the end of the vector. This will only mark the length of the vector as dirty.\n    ///\n    /// # Example\n    /// ```rust, no_run\n    /// use dioxus_stores::*;\n    /// let mut store = use_store(|| vec![1, 2, 3]);\n    /// store.push(4);\n    /// ```\n    pub fn push(&mut self, value: T) {\n        self.selector().mark_dirty_shallow();\n        self.selector().write_untracked().push(value);\n    }\n\n    /// Removes an item from the vector at the specified index and returns it. This will mark items after\n    /// the index and the length of the vector as dirty.\n    ///\n    /// # Example\n    /// ```rust, no_run\n    /// use dioxus_stores::*;\n    /// let mut store = use_store(|| vec![1, 2, 3]);\n    /// let removed = store.remove(1);\n    /// assert_eq!(removed, 2);\n    /// ```\n    pub fn remove(&mut self, index: usize) -> T {\n        self.selector().mark_dirty_shallow();\n        self.selector().mark_dirty_at_and_after_index(index);\n        self.selector().write_untracked().remove(index)\n    }\n\n    /// Inserts an item at the specified index in the vector. This will mark items at and after the index\n    /// and the length of the vector as dirty.\n    ///\n    /// # Example\n    /// ```rust, no_run\n    /// use dioxus_stores::*;\n    /// let mut store = use_store(|| vec![1, 2, 3]);\n    /// store.insert(1, 4);\n    /// ```\n    pub fn insert(&mut self, index: usize, value: T) {\n        self.selector().mark_dirty_shallow();\n        self.selector().mark_dirty_at_and_after_index(index);\n        self.selector().write_untracked().insert(index, value);\n    }\n\n    /// Clears the vector, marking it as dirty.\n    ///\n    /// # Example\n    /// ```rust, no_run\n    /// use dioxus_stores::*;\n    /// let mut store = use_store(|| vec![1, 2, 3]);\n    /// store.clear();\n    /// ```\n    pub fn clear(&mut self) {\n        self.selector().mark_dirty();\n        self.selector().write_untracked().clear();\n    }\n\n    /// Retains only the elements specified by the predicate. This will only mark the length of the vector\n    /// and items after the first removed item as dirty.\n    ///\n    /// # Example\n    /// ```rust, no_run\n    /// use dioxus_stores::*;\n    /// let mut store = use_store(|| vec![1, 2, 3, 4, 5]);\n    /// store.retain(|&x| x % 2 == 0);\n    /// assert_eq!(store.len(), 2);\n    /// ```\n    pub fn retain(&mut self, mut f: impl FnMut(&T) -> bool) {\n        let mut index = 0;\n        let mut first_removed_index = None;\n        self.selector().write_untracked().retain(|item| {\n            let keep = f(item);\n            if !keep {\n                first_removed_index = first_removed_index.or(Some(index));\n            }\n            index += 1;\n            keep\n        });\n        if let Some(index) = first_removed_index {\n            self.selector().mark_dirty_shallow();\n            self.selector().mark_dirty_at_and_after_index(index);\n        }\n    }\n}\n"
  },
  {
    "path": "packages/stores/src/lib.rs",
    "content": "#![doc = include_str!(\"../README.md\")]\n#![doc(html_logo_url = \"https://avatars.githubusercontent.com/u/79236386\")]\n#![doc(html_favicon_url = \"https://avatars.githubusercontent.com/u/79236386\")]\n#![warn(missing_docs)]\n#![allow(clippy::type_complexity)]\n\nmod impls;\nmod store;\nmod subscriptions;\npub use impls::*;\npub use store::*;\npub mod scope;\n\n#[cfg(feature = \"macro\")]\npub use dioxus_stores_macro::{store, Store};\n\n/// Re-exports for the store derive macro\n#[doc(hidden)]\npub mod macro_helpers {\n    pub use dioxus_core;\n    pub use dioxus_signals;\n}\n"
  },
  {
    "path": "packages/stores/src/scope.rs",
    "content": "//! This module contains the `SelectorScope` type with raw access to the underlying store system. Most applications should\n//! use the [`Store`](dioxus_stores_macro::Store) macro to derive stores for their data structures, which provides a more ergonomic API.\n\nuse std::{fmt::Debug, hash::Hash};\n\nuse crate::subscriptions::{PathKey, StoreSubscriptions, TinyVec};\nuse dioxus_core::Subscribers;\nuse dioxus_signals::{\n    BorrowError, BorrowMutError, MappedMutSignal, Readable, ReadableRef, Writable, WritableExt,\n    WritableRef,\n};\n\n/// SelectorScope is the primitive that backs the store system.\n///\n/// Under the hood stores consist of two different parts:\n/// - The underlying lock that contains the data in the store.\n/// - A tree of subscriptions used to make the store reactive.\n///\n/// The `SelectorScope` contains a view into the lock (`Lens`) and a path into the subscription tree. When\n/// the selector is read to, it will track the current path in the subscription tree. When it is written to\n/// it marks itself and all its children as dirty.\n///\n/// When you derive the [`Store`](dioxus_stores_macro::Store) macro on your data structure,\n/// it generates methods that map the lock to a new type and scope the path to a specific part of the subscription structure.\n/// For example, a `Counter` store might look like this:\n///\n/// ```rust, ignore\n/// #[derive(Store)]\n/// struct Counter {\n///     count: i32,\n/// }\n///\n/// impl CounterStoreExt for Store<Counter> {\n///     fn count(\n///         self,\n///     ) -> dioxus_stores::Store<\n///         i32,\n///         dioxus_stores::macro_helpers::dioxus_signals::MappedMutSignal<i32, __W>,\n///     > {\n///         let __map_field: fn(&CounterTree) -> &i32 = |value| &value.count;\n///         let __map_mut_field: fn(&mut CounterTree) -> &mut i32 = |value| &mut value.count;\n///         let scope = self.selector().scope(0u32, __map_field, __map_mut_field);\n///         dioxus_stores::Store::new(scope)\n///     }\n/// }\n/// ```\n///\n/// The `count` method maps the lock to the `i32` type and creates a child `0` path in the subscription tree. Only writes\n/// to that `0` path or its parents will trigger a re-render of the components that read the `count` field.\n#[derive(PartialEq)]\npub struct SelectorScope<Lens> {\n    path: TinyVec,\n    store: StoreSubscriptions,\n    write: Lens,\n}\n\nimpl<Lens> Debug for SelectorScope<Lens> {\n    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {\n        f.debug_struct(\"SelectorScope\")\n            .field(\"path\", &self.path)\n            .finish()\n    }\n}\n\nimpl<Lens> Clone for SelectorScope<Lens>\nwhere\n    Lens: Clone,\n{\n    fn clone(&self) -> Self {\n        Self {\n            path: self.path,\n            store: self.store,\n            write: self.write.clone(),\n        }\n    }\n}\n\nimpl<Lens> Copy for SelectorScope<Lens> where Lens: Copy {}\n\nimpl<Lens> SelectorScope<Lens> {\n    pub(crate) fn new(path: TinyVec, store: StoreSubscriptions, write: Lens) -> Self {\n        Self { path, store, write }\n    }\n\n    /// Create a child selector scope for a hash key. The scope will only be marked as dirty when a\n    /// write occurs to that key or its parents.\n    ///\n    /// Note the hash is lossy, so there may rarely be collisions. If a collision does occur, it may\n    /// cause reruns in a part of the app that has not changed. As long as derived data is pure,\n    /// this should not cause issues.\n    pub fn hash_child<U: ?Sized, T, F, FMut>(\n        self,\n        index: &impl Hash,\n        map: F,\n        map_mut: FMut,\n    ) -> SelectorScope<MappedMutSignal<U, Lens, F, FMut>>\n    where\n        F: Fn(&T) -> &U,\n        FMut: Fn(&mut T) -> &mut U,\n    {\n        let hash = self.store.hash(index);\n        self.child(hash, map, map_mut)\n    }\n\n    /// Create a child selector scope for a specific index. The scope will only be marked as dirty when a\n    /// write occurs to that index or its parents.\n    pub fn child<U: ?Sized, T, F, FMut>(\n        self,\n        index: PathKey,\n        map: F,\n        map_mut: FMut,\n    ) -> SelectorScope<MappedMutSignal<U, Lens, F, FMut>>\n    where\n        F: Fn(&T) -> &U,\n        FMut: Fn(&mut T) -> &mut U,\n    {\n        self.child_unmapped(index).map(map, map_mut)\n    }\n\n    /// Create a hashed child selector scope for a specific index without mapping the writer. The scope will only\n    /// be marked as dirty when a write occurs to that index or its parents.\n    pub fn hash_child_unmapped(self, index: &impl Hash) -> SelectorScope<Lens> {\n        let hash = self.store.hash(index);\n        self.child_unmapped(hash)\n    }\n\n    /// Create a child selector scope for a specific index without mapping the writer. The scope will only\n    /// be marked as dirty when a write occurs to that index or its parents.\n    pub fn child_unmapped(mut self, index: PathKey) -> SelectorScope<Lens> {\n        self.path.push(index);\n        self\n    }\n\n    /// Map the view into the writable data without creating a child selector scope\n    pub fn map<U: ?Sized, T, F, FMut>(\n        self,\n        map: F,\n        map_mut: FMut,\n    ) -> SelectorScope<MappedMutSignal<U, Lens, F, FMut>>\n    where\n        F: Fn(&T) -> &U,\n        FMut: Fn(&mut T) -> &mut U,\n    {\n        self.map_writer(move |write| MappedMutSignal::new(write, map, map_mut))\n    }\n\n    /// Track this scope shallowly.\n    pub fn track_shallow(&self) {\n        self.store.track(&self.path);\n    }\n\n    /// Track this scope recursively.\n    pub fn track(&self) {\n        self.store.track_recursive(&self.path);\n    }\n\n    /// Mark this scope as dirty recursively.\n    pub fn mark_dirty(&self) {\n        self.store.mark_dirty(&self.path);\n    }\n\n    /// Mark this scope as dirty shallowly.\n    pub fn mark_dirty_shallow(&self) {\n        self.store.mark_dirty_shallow(&self.path);\n    }\n\n    /// Mark this scope as dirty at and after the given index.\n    pub fn mark_dirty_at_and_after_index(&self, index: usize) {\n        self.store.mark_dirty_at_and_after_index(&self.path, index);\n    }\n\n    /// Map the writer to a new type.\n    pub fn map_writer<W2>(self, map: impl FnOnce(Lens) -> W2) -> SelectorScope<W2> {\n        SelectorScope {\n            path: self.path,\n            store: self.store,\n            write: map(self.write),\n        }\n    }\n\n    /// Write without notifying subscribers.\n    pub fn write_untracked(&self) -> WritableRef<'static, Lens>\n    where\n        Lens: Writable,\n    {\n        self.write.write_unchecked()\n    }\n\n    /// Borrow the writer\n    pub(crate) fn as_ref(&self) -> SelectorScope<&Lens> {\n        SelectorScope {\n            path: self.path,\n            store: self.store,\n            write: &self.write,\n        }\n    }\n}\n\nimpl<Lens: Readable> Readable for SelectorScope<Lens> {\n    type Target = Lens::Target;\n    type Storage = Lens::Storage;\n\n    fn try_read_unchecked(&self) -> Result<ReadableRef<'static, Lens>, BorrowError> {\n        self.track();\n        self.write.try_read_unchecked()\n    }\n\n    fn try_peek_unchecked(&self) -> Result<ReadableRef<'static, Lens>, BorrowError> {\n        self.write.try_peek_unchecked()\n    }\n\n    fn subscribers(&self) -> Subscribers {\n        self.store.subscribers(&self.path)\n    }\n}\n\nimpl<Lens: Writable> Writable for SelectorScope<Lens> {\n    type WriteMetadata = Lens::WriteMetadata;\n\n    fn try_write_unchecked(&self) -> Result<WritableRef<'static, Lens>, BorrowMutError> {\n        self.mark_dirty();\n        self.write.try_write_unchecked()\n    }\n}\n"
  },
  {
    "path": "packages/stores/src/store.rs",
    "content": "use crate::{\n    scope::SelectorScope,\n    subscriptions::{StoreSubscriptions, TinyVec},\n};\nuse dioxus_core::{\n    use_hook, AttributeValue, DynamicNode, IntoAttributeValue, IntoDynNode, Subscribers, SuperInto,\n};\nuse dioxus_signals::{\n    read_impls, write_impls, BorrowError, BorrowMutError, BoxedSignalStorage, CopyValue,\n    CreateBoxedSignalStorage, Global, InitializeFromFunction, MappedMutSignal, ReadSignal,\n    Readable, ReadableExt, ReadableRef, Storage, SyncStorage, UnsyncStorage, Writable, WritableExt,\n    WritableRef, WriteSignal,\n};\nuse std::marker::PhantomData;\n\n/// A type alias for a store that has been mapped with a function\npub(crate) type MappedStore<\n    T,\n    Lens,\n    F = fn(&<Lens as Readable>::Target) -> &T,\n    FMut = fn(&mut <Lens as Readable>::Target) -> &mut T,\n> = Store<T, MappedMutSignal<T, Lens, F, FMut>>;\n\n/// A type alias for a boxed read-only store.\npub type ReadStore<T, S = UnsyncStorage> = Store<T, ReadSignal<T, S>>;\n\n/// A type alias for a boxed writable-only store.\npub type WriteStore<T, S = UnsyncStorage> = Store<T, WriteSignal<T, S>>;\n\n/// A type alias for a store backed by SyncStorage.\npub type SyncStore<T> = Store<T, CopyValue<T, SyncStorage>>;\n\n/// Stores are a reactive type built for nested data structures. Each store will lazily create signals\n/// for each field/member of the data structure as needed.\n///\n/// By default stores act a lot like [`dioxus_signals::Signal`]s, but they provide more granular\n/// subscriptions without requiring nested signals. You should derive [`Store`](dioxus_stores_macro::Store) on your data\n/// structures to generate selectors that let you scope the store to a specific part of your data.\n///\n/// You can also use the [`#[store]`](dioxus_stores_macro::store) macro on an impl block to add any additional methods to your store\n/// with an extension trait. This lets you add methods to the store even though the type is not defined in your crate.\n///\n/// # Example\n///\n/// ```rust, no_run\n/// use dioxus::prelude::*;\n/// use dioxus_stores::*;\n///\n/// fn main() {\n///     dioxus::launch(app);\n/// }\n///\n/// // Deriving the store trait provides methods to scope the store to specific parts of your data structure.\n/// // The `Store` macro generates a `count` and `children` method for the `CounterTree` struct.\n/// #[derive(Store, Default)]\n/// struct CounterTree {\n///     count: i32,\n///     children: Vec<CounterTree>,\n/// }\n///\n/// // The store macro generates an extension trait with additional methods for the store based on the impl block.\n/// #[store]\n/// impl<Lens> Store<CounterTree, Lens> {\n///     // Methods that take &self automatically require the lens to implement `Readable` which lets you read the store.\n///     fn sum(&self) -> i32 {\n///        self.count().cloned() + self.children().iter().map(|c| c.sum()).sum::<i32>()\n///     }\n/// }\n///\n/// fn app() -> Element {\n///     let value = use_store(Default::default);\n///\n///     rsx! {\n///         Tree {\n///             value\n///         }\n///     }\n/// }\n///\n/// #[component]\n/// fn Tree(value: Store<CounterTree>) -> Element {\n///     // Calling the generated `count` method returns a new store that can only\n///     // read and write the count field\n///     let mut count = value.count();\n///     let mut children = value.children();\n///     rsx! {\n///         button {\n///             // Incrementing the count will only rerun parts of the app that have read the count field\n///             onclick: move |_| count += 1,\n///             \"Increment\"\n///         }\n///         button {\n///             // Stores are aware of data structures like `Vec` and `Hashmap`. When we push an item to the vec\n///             // it will only rerun the parts of the app that depend on the length of the vec\n///             onclick: move |_| children.push(Default::default()),\n///             \"Push child\"\n///         }\n///         \"sum: {value.sum()}\"\n///         ul {\n///             // Iterating over the children gives us stores scoped to each child.\n///             for value in children.iter() {\n///                 li {\n///                     Tree { value }\n///                 }\n///             }\n///         }\n///     }\n/// }\n/// ```\npub struct Store<T: ?Sized, Lens = WriteSignal<T>> {\n    selector: SelectorScope<Lens>,\n    _phantom: PhantomData<Box<T>>,\n}\n\nimpl<T: 'static, S: Storage<T>> Store<T, CopyValue<T, S>> {\n    /// Creates a new `Store` that might be sync. This allocates memory in the current scope, so this should only be called\n    /// inside of an initialization closure like the closure passed to [`use_hook`].\n    #[track_caller]\n    pub fn new_maybe_sync(value: T) -> Self {\n        let store = StoreSubscriptions::new();\n        let value = CopyValue::new_maybe_sync(value);\n\n        let path = TinyVec::new();\n        let selector = SelectorScope::new(path, store, value);\n        selector.into()\n    }\n}\n\nimpl<T: 'static> Store<T> {\n    /// Creates a new `Store`. This allocates memory in the current scope, so this should only be called\n    /// inside of an initialization closure like the closure passed to [`use_hook`].\n    #[track_caller]\n    pub fn new(value: T) -> Self {\n        let store = StoreSubscriptions::new();\n        let value = CopyValue::new_maybe_sync(value);\n        let value = value.into();\n\n        let path = TinyVec::new();\n        let selector = SelectorScope::new(path, store, value);\n        selector.into()\n    }\n}\n\nimpl<T: ?Sized, Lens> Store<T, Lens> {\n    /// Get the underlying selector for this store. The selector provides low level access to the lazy tracking system\n    /// of the store. This can be useful to create selectors for custom data structures in libraries. For most applications\n    /// the selectors generated by the [`Store`](dioxus_stores_macro::Store) macro provide all the functionality you need.\n    pub fn selector(&self) -> &SelectorScope<Lens> {\n        &self.selector\n    }\n\n    /// Convert the store into the underlying selector\n    pub fn into_selector(self) -> SelectorScope<Lens> {\n        self.selector\n    }\n}\n\nimpl<T: ?Sized, Lens> From<SelectorScope<Lens>> for Store<T, Lens> {\n    fn from(selector: SelectorScope<Lens>) -> Self {\n        Self {\n            selector,\n            _phantom: PhantomData,\n        }\n    }\n}\n\nimpl<T: ?Sized, Lens> PartialEq for Store<T, Lens>\nwhere\n    Lens: PartialEq,\n{\n    fn eq(&self, other: &Self) -> bool {\n        self.selector == other.selector\n    }\n}\nimpl<T: ?Sized, Lens> Clone for Store<T, Lens>\nwhere\n    Lens: Clone,\n{\n    fn clone(&self) -> Self {\n        Self {\n            selector: self.selector.clone(),\n            _phantom: ::std::marker::PhantomData,\n        }\n    }\n}\nimpl<T: ?Sized, Lens> Copy for Store<T, Lens> where Lens: Copy {}\n\nimpl<__F, __FMut, T: ?Sized, S, Lens> ::std::convert::From<MappedStore<T, Lens, __F, __FMut>>\n    for WriteStore<T, S>\nwhere\n    Lens: Writable<Storage = S> + 'static,\n    __F: Fn(&Lens::Target) -> &T + 'static,\n    __FMut: Fn(&mut Lens::Target) -> &mut T + 'static,\n    S: BoxedSignalStorage<T> + CreateBoxedSignalStorage<MappedMutSignal<T, Lens, __F, __FMut>>,\n    T: 'static,\n{\n    fn from(value: MappedStore<T, Lens, __F, __FMut>) -> Self {\n        Store {\n            selector: value.selector.map_writer(::std::convert::Into::into),\n            _phantom: ::std::marker::PhantomData,\n        }\n    }\n}\nimpl<__F, __FMut, T: ?Sized, S, Lens> ::std::convert::From<MappedStore<T, Lens, __F, __FMut>>\n    for ReadStore<T, S>\nwhere\n    Lens: Writable<Storage = S> + 'static,\n    __F: Fn(&Lens::Target) -> &T + 'static,\n    __FMut: Fn(&mut Lens::Target) -> &mut T + 'static,\n    S: BoxedSignalStorage<T> + CreateBoxedSignalStorage<MappedMutSignal<T, Lens, __F, __FMut>>,\n    T: 'static,\n{\n    fn from(value: MappedStore<T, Lens, __F, __FMut>) -> Self {\n        Store {\n            selector: value.selector.map_writer(::std::convert::Into::into),\n            _phantom: ::std::marker::PhantomData,\n        }\n    }\n}\nimpl<T, S> ::std::convert::From<WriteStore<T, S>> for ReadStore<T, S>\nwhere\n    T: ?Sized + 'static,\n    S: BoxedSignalStorage<T> + CreateBoxedSignalStorage<WriteSignal<T, S>>,\n{\n    fn from(value: Store<T, WriteSignal<T, S>>) -> Self {\n        Self {\n            selector: value.selector.map_writer(::std::convert::Into::into),\n            _phantom: ::std::marker::PhantomData,\n        }\n    }\n}\nimpl<T, S> ::std::convert::From<Store<T, CopyValue<T, S>>> for ReadStore<T, S>\nwhere\n    T: 'static,\n    S: BoxedSignalStorage<T> + CreateBoxedSignalStorage<CopyValue<T, S>> + Storage<T>,\n{\n    fn from(value: Store<T, CopyValue<T, S>>) -> Self {\n        Self {\n            selector: value.selector.map_writer(::std::convert::Into::into),\n            _phantom: ::std::marker::PhantomData,\n        }\n    }\n}\nimpl<T, S> ::std::convert::From<Store<T, CopyValue<T, S>>> for WriteStore<T, S>\nwhere\n    T: 'static,\n    S: BoxedSignalStorage<T> + CreateBoxedSignalStorage<CopyValue<T, S>> + Storage<T>,\n{\n    fn from(value: Store<T, CopyValue<T, S>>) -> Self {\n        Self {\n            selector: value.selector.map_writer(::std::convert::Into::into),\n            _phantom: ::std::marker::PhantomData,\n        }\n    }\n}\n\n#[doc(hidden)]\npub struct SuperIntoReadSignalMarker;\nimpl<T, S, Lens> SuperInto<ReadSignal<T, S>, SuperIntoReadSignalMarker> for Store<T, Lens>\nwhere\n    T: ?Sized + 'static,\n    Lens: Readable<Target = T, Storage = S> + 'static,\n    S: CreateBoxedSignalStorage<Store<T, Lens>> + BoxedSignalStorage<T>,\n{\n    fn super_into(self) -> ReadSignal<T, S> {\n        ReadSignal::new_maybe_sync(self)\n    }\n}\n\n#[doc(hidden)]\npub struct SuperIntoWriteSignalMarker;\nimpl<T, S, Lens> SuperInto<WriteSignal<T, S>, SuperIntoWriteSignalMarker> for Store<T, Lens>\nwhere\n    T: ?Sized + 'static,\n    Lens: Writable<Target = T, Storage = S> + 'static,\n    S: CreateBoxedSignalStorage<Store<T, Lens>> + BoxedSignalStorage<T>,\n{\n    fn super_into(self) -> WriteSignal<T, S> {\n        WriteSignal::new_maybe_sync(self)\n    }\n}\n\nimpl<T: ?Sized, Lens> Readable for Store<T, Lens>\nwhere\n    Lens: Readable<Target = T>,\n    T: 'static,\n{\n    type Storage = Lens::Storage;\n    type Target = T;\n    fn try_read_unchecked(&self) -> Result<ReadableRef<'static, Self>, BorrowError> {\n        self.selector.try_read_unchecked()\n    }\n    fn try_peek_unchecked(&self) -> Result<ReadableRef<'static, Self>, BorrowError> {\n        self.selector.try_peek_unchecked()\n    }\n    fn subscribers(&self) -> Subscribers {\n        self.selector.subscribers()\n    }\n}\nimpl<T: ?Sized, Lens> Writable for Store<T, Lens>\nwhere\n    Lens: Writable<Target = T>,\n    T: 'static,\n{\n    type WriteMetadata = Lens::WriteMetadata;\n    fn try_write_unchecked(&self) -> Result<WritableRef<'static, Self>, BorrowMutError> {\n        self.selector.try_write_unchecked()\n    }\n}\nimpl<T, Lens> IntoAttributeValue for Store<T, Lens>\nwhere\n    Self: Readable<Target = T>,\n    T: ::std::clone::Clone + IntoAttributeValue + 'static,\n{\n    fn into_value(self) -> AttributeValue {\n        ReadableExt::cloned(&self).into_value()\n    }\n}\nimpl<T, Lens> IntoDynNode for Store<T, Lens>\nwhere\n    Self: Readable<Target = T>,\n    T: ::std::clone::Clone + IntoDynNode + 'static,\n{\n    fn into_dyn_node(self) -> DynamicNode {\n        ReadableExt::cloned(&self).into_dyn_node()\n    }\n}\nimpl<T, Lens> ::std::ops::Deref for Store<T, Lens>\nwhere\n    Self: Readable<Target = T> + 'static,\n    T: ::std::clone::Clone + 'static,\n{\n    type Target = dyn Fn() -> T;\n    fn deref(&self) -> &Self::Target {\n        unsafe { ReadableExt::deref_impl(self) }\n    }\n}\n\nread_impls!(Store<T, Lens> where Lens: Readable<Target = T>);\nwrite_impls!(Store<T, Lens> where Lens: Writable<Target = T>);\n\n/// Create a new [`Store`]. Stores are a reactive type built for nested data structures.\n///\n///\n/// By default stores act a lot like [`dioxus_signals::Signal`]s, but they provide more granular\n/// subscriptions without requiring nested signals. You should derive [`Store`](dioxus_stores_macro::Store) on your data\n/// structures to generate selectors that let you scope the store to a specific part of your data structure.\n///\n/// # Example\n///\n/// ```rust, no_run\n/// use dioxus::prelude::*;\n/// use dioxus_stores::*;\n///\n/// fn main() {\n///     dioxus::launch(app);\n/// }\n///\n/// // Deriving the store trait provides methods to scope the store to specific parts of your data structure.\n/// // The `Store` macro generates a `count` and `children` method for `Store<CounterTree>`.\n/// #[derive(Store, Default)]\n/// struct CounterTree {\n///     count: i32,\n///     children: Vec<CounterTree>,\n/// }\n///\n/// fn app() -> Element {\n///     let value = use_store(Default::default);\n///\n///     rsx! {\n///         Tree {\n///             value\n///         }\n///     }\n/// }\n///\n/// #[component]\n/// fn Tree(value: Store<CounterTree>) -> Element {\n///     // Calling the generated `count` method returns a new store that can only\n///     // read and write the count field\n///     let mut count = value.count();\n///     let mut children = value.children();\n///     rsx! {\n///         button {\n///             // Incrementing the count will only rerun parts of the app that have read the count field\n///             onclick: move |_| count += 1,\n///             \"Increment\"\n///         }\n///         button {\n///             // Stores are aware of data structures like `Vec` and `Hashmap`. When we push an item to the vec\n///             // it will only rerun the parts of the app that depend on the length of the vec\n///             onclick: move |_| children.push(Default::default()),\n///             \"Push child\"\n///         }\n///         ul {\n///             // Iterating over the children gives us stores scoped to each child.\n///             for value in children.iter() {\n///                 li {\n///                     Tree { value }\n///                 }\n///             }\n///         }\n///     }\n/// }\n/// ```\npub fn use_store<T: 'static>(init: impl FnOnce() -> T) -> Store<T> {\n    use_hook(move || Store::new(init()))\n}\n\n/// Create a new [`SyncStore`]. Stores are a reactive type built for nested data structures.\n/// `SyncStore` is a Store backed by `SyncStorage`.\n///\n/// Like [`use_store`], but produces `SyncStore<T>` instead of `Store<T>`\npub fn use_store_sync<T: Send + Sync + 'static>(init: impl FnOnce() -> T) -> SyncStore<T> {\n    use_hook(|| Store::new_maybe_sync(init()))\n}\n\n/// A type alias for global stores\n///\n/// # Example\n/// ```rust, no_run\n/// use dioxus::prelude::*;\n/// use dioxus_stores::*;\n///\n/// #[derive(Store)]\n/// struct Counter {\n///    count: i32,\n/// }\n///\n/// static COUNTER: GlobalStore<Counter> = Global::new(|| Counter { count: 0 });\n///\n/// fn app() -> Element {\n///     let mut count = COUNTER.resolve().count();\n///\n///     rsx! {\n///         button {\n///             onclick: move |_| count += 1,\n///             \"{count}\"\n///         }\n///     }\n/// }\n/// ```\npub type GlobalStore<T> = Global<Store<T>, T>;\n\nimpl<T: 'static> InitializeFromFunction<T> for Store<T> {\n    fn initialize_from_function(f: fn() -> T) -> Self {\n        Store::new(f())\n    }\n}\n"
  },
  {
    "path": "packages/stores/src/subscriptions.rs",
    "content": "use dioxus_core::{ReactiveContext, SubscriberList, Subscribers};\nuse dioxus_signals::{CopyValue, ReadableExt, SyncStorage, Writable, WritableExt};\nuse std::fmt::Debug;\nuse std::hash::BuildHasher;\nuse std::{\n    collections::{HashMap, HashSet},\n    hash::Hash,\n    ops::Deref,\n    sync::Arc,\n};\n\n/// A single node in the [`StoreSubscriptions`] tree. Each path is a specific view into the store\n/// and can be subscribed to and marked dirty separately. If the whole store is read or written to, all\n/// nodes in the subtree are subscribed to or marked as dirty.\n#[derive(Clone, Default)]\npub(crate) struct SelectorNode {\n    subscribers: HashSet<ReactiveContext>,\n    root: HashMap<PathKey, SelectorNode>,\n}\n\nimpl SelectorNode {\n    /// Get an existing selector node by its path.\n    fn get(&self, path: &[PathKey]) -> Option<&SelectorNode> {\n        let [first, rest @ ..] = path else {\n            return Some(self);\n        };\n        self.root.get(first).and_then(|child| child.get(rest))\n    }\n\n    /// Get an existing selector node by its path mutably.\n    fn get_mut(&mut self, path: &[PathKey]) -> Option<&mut SelectorNode> {\n        let [first, rest @ ..] = path else {\n            return Some(self);\n        };\n        self.root\n            .get_mut(first)\n            .and_then(|child| child.get_mut(rest))\n    }\n\n    /// Get a selector mutably or create one if it doesn't exist. This is used when subscribing to\n    /// a path that may not exist yet.\n    fn get_mut_or_default(&mut self, path: &[PathKey]) -> &mut SelectorNode {\n        let [first, rest @ ..] = path else {\n            return self;\n        };\n        self.root\n            .entry(*first)\n            .or_default()\n            .get_mut_or_default(rest)\n    }\n\n    /// Get the path to each node under this node\n    ///\n    /// This is used to mark nodes dirty recursively when a Store is written to.\n    fn paths_under(&self, current_path: &[PathKey], paths: &mut Vec<Box<[PathKey]>>) {\n        paths.push(current_path.into());\n        for (i, child) in self.root.iter() {\n            let mut child_path: Vec<PathKey> = current_path.into();\n            child_path.push(*i);\n            child.paths_under(&child_path, paths);\n        }\n    }\n\n    /// Get paths to only children before a certain index.\n    ///\n    /// This is used when inserting a new item into a list.\n    /// Items after the index that is inserted need to be marked dirty because the value that index points to may have changed.\n    fn paths_at_and_after_index(\n        &self,\n        path: &[PathKey],\n        index: usize,\n        paths: &mut Vec<Box<[PathKey]>>,\n    ) {\n        let Some(node) = self.get(path) else {\n            return;\n        };\n\n        // Mark the nodes before the index as dirty\n        for (i, child) in node.root.iter() {\n            if *i as usize >= index {\n                let mut child_path: Vec<PathKey> = path.into();\n                child_path.push(*i);\n                child.paths_under(&child_path, paths);\n            }\n        }\n    }\n\n    /// Remove a path from the subscription tree\n    fn remove(&mut self, path: &[PathKey]) {\n        let [first, rest @ ..] = path else {\n            return;\n        };\n        if let Some(node) = self.root.get_mut(first) {\n            if rest.is_empty() {\n                self.root.remove(first);\n            } else {\n                node.remove(rest);\n            }\n        }\n    }\n}\n\npub(crate) type PathKey = u16;\n#[cfg(feature = \"large-path\")]\nconst PATH_LENGTH: usize = 32;\n#[cfg(not(feature = \"large-path\"))]\nconst PATH_LENGTH: usize = 16;\n\n#[derive(Copy, Clone, PartialEq)]\npub(crate) struct TinyVec {\n    length: usize,\n    path: [PathKey; PATH_LENGTH],\n}\n\nimpl Default for TinyVec {\n    fn default() -> Self {\n        Self::new()\n    }\n}\n\nimpl Debug for TinyVec {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_struct(\"TinyVec\")\n            .field(\"path\", &&self.path[..self.length])\n            .finish()\n    }\n}\n\nimpl TinyVec {\n    pub(crate) const fn new() -> Self {\n        Self {\n            length: 0,\n            path: [0; PATH_LENGTH],\n        }\n    }\n\n    pub(crate) const fn push(&mut self, index: u16) {\n        if self.length < self.path.len() {\n            self.path[self.length] = index;\n            self.length += 1;\n        } else {\n            panic!(\"SelectorPath is full\");\n        }\n    }\n}\n\nimpl Deref for TinyVec {\n    type Target = [u16];\n\n    fn deref(&self) -> &Self::Target {\n        &self.path[..self.length]\n    }\n}\n\n#[derive(Default)]\npub(crate) struct StoreSubscriptionsInner {\n    root: SelectorNode,\n    hasher: std::collections::hash_map::RandomState,\n}\n\n#[derive(Default)]\npub(crate) struct StoreSubscriptions {\n    inner: CopyValue<StoreSubscriptionsInner, SyncStorage>,\n}\n\nimpl Clone for StoreSubscriptions {\n    fn clone(&self) -> Self {\n        *self\n    }\n}\n\nimpl Copy for StoreSubscriptions {}\n\nimpl PartialEq for StoreSubscriptions {\n    fn eq(&self, other: &Self) -> bool {\n        self.inner == other.inner\n    }\n}\n\nimpl StoreSubscriptions {\n    /// Create a new instance of StoreSubscriptions.\n    pub(crate) fn new() -> Self {\n        Self {\n            inner: CopyValue::new_maybe_sync(StoreSubscriptionsInner {\n                root: SelectorNode::default(),\n                hasher: std::collections::hash_map::RandomState::new(),\n            }),\n        }\n    }\n\n    /// Hash an index into a PathKey using the hasher. The hash should be consistent\n    /// across calls\n    pub(crate) fn hash(&self, index: &impl Hash) -> PathKey {\n        (self.inner.write_unchecked().hasher.hash_one(index) % PathKey::MAX as u64) as PathKey\n    }\n\n    /// Subscribe to a specific path in the store.\n    pub(crate) fn track(&self, key: &[PathKey]) {\n        if let Some(rc) = ReactiveContext::current() {\n            let subscribers = self.subscribers(key);\n            rc.subscribe(subscribers);\n        }\n    }\n\n    /// Subscribe to a path and all of its children recursively. This should be called any time we give out\n    /// a raw reference to a store, because the user could read any level of the store.\n    pub(crate) fn track_recursive(&self, key: &[PathKey]) {\n        if let Some(rc) = ReactiveContext::current() {\n            let mut paths = Vec::new();\n            {\n                let mut write = self.inner.write_unchecked();\n\n                let root = write.root.get_mut_or_default(key);\n                let mut nodes = vec![(key.to_vec(), &*root)];\n                while let Some((path, node)) = nodes.pop() {\n                    for (child_key, child_node) in &node.root {\n                        let mut new_path = path.clone();\n                        new_path.push(*child_key);\n                        nodes.push((new_path, child_node));\n                    }\n                    paths.push(path);\n                }\n            }\n            for path in paths {\n                let subscribers = self.subscribers(&path);\n                rc.subscribe(subscribers);\n            }\n        }\n    }\n\n    /// Mark the node and all its children as dirty\n    pub(crate) fn mark_dirty(&self, key: &[PathKey]) {\n        let paths = {\n            let read = &self.inner.read_unchecked();\n            let Some(node) = read.root.get(key) else {\n                return;\n            };\n            let mut paths = Vec::new();\n            node.paths_under(key, &mut paths);\n            paths\n        };\n        for path in paths {\n            self.mark_dirty_shallow(&path);\n        }\n    }\n\n    /// Mark a single node as dirty\n    pub(crate) fn mark_dirty_shallow(&self, key: &[PathKey]) {\n        // We cannot hold the subscribers lock while calling mark_dirty, because mark_dirty can run user code which may cause a new subscriber to be added. If we hold the lock, we will deadlock.\n        #[allow(clippy::mutable_key_type)]\n        let mut subscribers = {\n            let mut write = self.inner.write_unchecked();\n            let Some(node) = write.root.get_mut(key) else {\n                return;\n            };\n            std::mem::take(&mut node.subscribers)\n        };\n        subscribers.retain(|reactive_context| reactive_context.mark_dirty());\n        // Extend the subscribers list instead of overwriting it in case a subscriber is added while reactive contexts are marked dirty\n        let mut write = self.inner.write_unchecked();\n        let Some(node) = write.root.get_mut(key) else {\n            return;\n        };\n        node.subscribers.extend(subscribers);\n    }\n\n    /// Mark all nodes after the index and their children as dirty\n    pub(crate) fn mark_dirty_at_and_after_index(&self, key: &[PathKey], index: usize) {\n        let paths = {\n            let read = self.inner.read_unchecked();\n            let mut paths = Vec::new();\n            read.root.paths_at_and_after_index(key, index, &mut paths);\n            paths\n        };\n        for path in paths {\n            self.mark_dirty_shallow(&path);\n        }\n    }\n\n    /// Get a subscriber list for a specific path in the store. This is used to subscribe to changes\n    /// to a specific path in the store and remove the node from the subscription tree when it is no longer needed.\n    pub(crate) fn subscribers(&self, key: &[PathKey]) -> Subscribers {\n        Arc::new(StoreSubscribers {\n            subscriptions: *self,\n            path: key.to_vec().into_boxed_slice(),\n        })\n        .into()\n    }\n}\n\n/// A subscriber list implementation that handles garbage collection of the subscription tree.\nstruct StoreSubscribers {\n    subscriptions: StoreSubscriptions,\n    path: Box<[PathKey]>,\n}\n\nimpl SubscriberList for StoreSubscribers {\n    /// Add a subscriber to the subscription list for this path in the store, creating the node if it doesn't exist.\n    fn add(&self, subscriber: ReactiveContext) {\n        let Ok(mut write) = self.subscriptions.inner.try_write_unchecked() else {\n            return;\n        };\n        let node = write.root.get_mut_or_default(&self.path);\n        node.subscribers.insert(subscriber);\n    }\n\n    /// Remove a subscriber from the subscription list for this path in the store. If the node has no subscribers left\n    /// remove that node from the subscription tree.\n    fn remove(&self, subscriber: &ReactiveContext) {\n        let Ok(mut write) = self.subscriptions.inner.try_write_unchecked() else {\n            return;\n        };\n        let Some(node) = write.root.get_mut(&self.path) else {\n            return;\n        };\n        node.subscribers.remove(subscriber);\n        if node.subscribers.is_empty() && node.root.is_empty() {\n            write.root.remove(&self.path);\n        }\n    }\n\n    /// Visit all subscribers for this path in the store, calling the provided function on each subscriber.\n    fn visit(&self, f: &mut dyn FnMut(&ReactiveContext)) {\n        let Ok(read) = self.subscriptions.inner.try_read() else {\n            return;\n        };\n        let Some(node) = read.root.get(&self.path) else {\n            return;\n        };\n        node.subscribers.iter().for_each(f);\n    }\n}\n"
  },
  {
    "path": "packages/stores/tests/coercions.rs",
    "content": "#![allow(unused)]\n\nuse dioxus::prelude::*;\nuse dioxus_stores::*;\n\n#[derive(Store)]\nstruct TodoItem {\n    checked: bool,\n    contents: String,\n}\n\nfn app() -> Element {\n    let item = use_store(|| TodoItem {\n        checked: false,\n        contents: \"Learn about stores\".to_string(),\n    });\n\n    rsx! {\n        TakesReadSignal {\n            item,\n        }\n        TakesReadStore {\n            item,\n        }\n        TakesStr {\n            item: item.contents().deref(),\n        }\n        TakesStrStore {\n            item: item.contents().deref(),\n        }\n    }\n}\n\n#[component]\nfn TakesStr(item: ReadSignal<str>) -> Element {\n    rsx! {\n        TakesStr {\n            item,\n        }\n    }\n}\n\n#[component]\nfn TakesStrStore(item: ReadStore<str>) -> Element {\n    rsx! {\n        TakesStrStore {\n            item,\n        }\n    }\n}\n\n#[component]\nfn TakesReadSignal(item: ReadSignal<TodoItem>) -> Element {\n    rsx! {\n        TakesReadSignal {\n            item,\n        }\n    }\n}\n\n#[component]\nfn TakesReadStore(item: ReadStore<TodoItem>) -> Element {\n    rsx! {\n        TakesReadStore {\n            item,\n        }\n    }\n}\n"
  },
  {
    "path": "packages/stores/tests/marco.rs",
    "content": "#[allow(unused)]\n#[allow(clippy::disallowed_names)]\nmod macro_tests {\n    use dioxus_signals::*;\n    use dioxus_stores::*;\n    use std::collections::HashMap;\n\n    fn derive_unit() {\n        #[derive(Store)]\n        struct TodoItem;\n    }\n\n    fn derive_struct() {\n        #[derive(Store)]\n        struct TodoItem {\n            checked: bool,\n            contents: String,\n        }\n\n        #[store]\n        impl Store<TodoItem> {\n            fn is_checked(&self) -> bool {\n                self.checked().cloned()\n            }\n\n            fn check(&mut self) {\n                self.checked().set(true);\n            }\n        }\n\n        let mut store = use_store(|| TodoItem {\n            checked: false,\n            contents: \"Learn about stores\".to_string(),\n        });\n\n        // The store macro creates an extension trait with methods for each field\n        // that returns a store scoped to that field.\n        let checked: Store<bool, _> = store.checked();\n        let contents: Store<String, _> = store.contents();\n        let checked: bool = checked();\n        let contents: String = contents();\n\n        // It also generates a `transpose` method returns a variant of your structure\n        // with stores wrapping each of your data types. This can be very useful when destructuring\n        // or matching your type\n        let TodoItemStoreTransposed { checked, contents } = store.transpose();\n        let checked: bool = checked();\n        let contents: String = contents();\n\n        let is_checked = store.is_checked();\n        store.check();\n    }\n\n    fn derive_generic_struct() {\n        #[derive(Store)]\n        struct Item<T> {\n            checked: bool,\n            contents: T,\n        }\n\n        #[store]\n        impl<T> Store<Item<T>> {\n            fn is_checked(&self) -> bool\n            where\n                T: 'static,\n            {\n                self.checked().cloned()\n            }\n\n            fn check(&mut self)\n            where\n                T: 'static,\n            {\n                self.checked().set(true);\n            }\n        }\n\n        let mut store = use_store(|| Item {\n            checked: false,\n            contents: \"Learn about stores\".to_string(),\n        });\n\n        let checked: Store<bool, _> = store.checked();\n        let contents: Store<String, _> = store.contents();\n        let checked: bool = checked();\n        let contents: String = contents();\n\n        let ItemStoreTransposed { checked, contents } = store.transpose();\n        let checked: bool = checked();\n        let contents: String = contents();\n\n        let is_checked = store.is_checked();\n        store.check();\n    }\n\n    fn derive_generic_struct_with_bounds() {\n        #[derive(Store)]\n        struct Item<T: ?Sized>\n        where\n            T: 'static,\n        {\n            checked: bool,\n            contents: &'static T,\n        }\n\n        #[store]\n        impl<T: ?Sized + 'static> Store<Item<T>> {\n            fn is_checked(&self) -> bool\n            where\n                T: 'static,\n            {\n                self.checked().cloned()\n            }\n\n            fn check(&mut self)\n            where\n                T: 'static,\n            {\n                self.checked().set(true);\n            }\n        }\n\n        let mut store = use_store(|| Item {\n            checked: false,\n            contents: \"Learn about stores\",\n        });\n\n        let checked: Store<bool, _> = store.checked();\n        let contents: Store<&'static str, _> = store.contents();\n        let checked: bool = checked();\n        let contents: &'static str = contents();\n\n        let ItemStoreTransposed { checked, contents } = store.transpose();\n        let checked: bool = checked();\n        let contents: &'static str = contents();\n\n        let is_checked = store.is_checked();\n        store.check();\n    }\n\n    fn derive_generic_struct_transposed_passthrough() {\n        #[derive(Store)]\n        struct Item<const COUNT: usize, T> {\n            contents: T,\n        }\n\n        let mut store = use_store(|| Item::<0, _> {\n            contents: \"Learn about stores\".to_string(),\n        });\n\n        let Item { contents } = store.transpose();\n        let contents: String = contents();\n    }\n\n    fn derive_tuple() {\n        #[derive(Store, PartialEq, Clone, Debug)]\n        struct Item(bool, String);\n\n        let store = use_store(|| Item(true, \"Hello\".to_string()));\n\n        let first = store.field_0();\n        let first: bool = first();\n\n        let transposed = store.transpose();\n        let first = transposed.0;\n        let second = transposed.1;\n        let first: bool = first();\n        let second: String = second();\n    }\n\n    fn derive_enum() {\n        #[derive(Store, PartialEq, Clone, Debug)]\n        #[non_exhaustive]\n        enum Enum {\n            Foo,\n            Bar(String),\n            Baz { foo: i32, bar: String },\n            FooBar(u32, String),\n            BarFoo { foo: String },\n        }\n\n        #[store]\n        impl Store<Enum> {\n            fn is_foo_or_bar(&self) -> bool {\n                matches!(self.cloned(), Enum::Foo | Enum::Bar(_))\n            }\n\n            fn make_foo(&mut self) {\n                self.set(Enum::Foo);\n            }\n        }\n\n        let mut store = use_store(|| Enum::Bar(\"Hello\".to_string()));\n\n        let foo = store.is_foo();\n        let bar = store.is_bar();\n        let baz = store.is_baz();\n        let foobar = store.is_foo_bar();\n        let barfoo = store.is_bar_foo();\n\n        let foo = store.bar().unwrap();\n        let foo: String = foo();\n        let bar = store.bar_foo().unwrap();\n        let bar: String = bar();\n\n        let transposed = store.transpose();\n        use EnumStoreTransposed::*;\n        match transposed {\n            EnumStoreTransposed::Foo => {}\n            Bar(bar) => {\n                let bar: String = bar();\n            }\n            Baz { foo, bar } => {\n                let foo: i32 = foo();\n                let bar: String = bar();\n            }\n            FooBar(foo, bar) => {\n                let foo: u32 = foo();\n                let bar: String = bar();\n            }\n            BarFoo { foo } => {\n                let foo: String = foo();\n            }\n        }\n\n        let is_foo_or_bar = store.is_foo_or_bar();\n        store.make_foo();\n    }\n\n    fn derive_generic_enum() {\n        #[derive(Store, PartialEq, Clone, Debug)]\n        #[non_exhaustive]\n        enum Enum<T> {\n            Foo,\n            Bar(T),\n            Baz { foo: i32, bar: T },\n            FooBar(u32, T),\n            BarFoo { foo: T },\n        }\n\n        #[store]\n        impl<T> Store<Enum<T>> {\n            fn is_foo_or_bar(&self) -> bool\n            where\n                T: Clone + 'static,\n            {\n                matches!(self.cloned(), Enum::Foo | Enum::Bar(_))\n            }\n\n            fn make_foo(&mut self)\n            where\n                T: 'static,\n            {\n                self.set(Enum::Foo);\n            }\n        }\n\n        let mut store = use_store(|| Enum::Bar(\"Hello\".to_string()));\n\n        let foo = store.is_foo();\n        let bar = store.is_bar();\n        let baz = store.is_baz();\n        let foobar = store.is_foo_bar();\n        let barfoo = store.is_bar_foo();\n\n        let foo = store.bar().unwrap();\n        let foo: String = foo();\n        let bar = store.bar_foo().unwrap();\n        let bar: String = bar();\n\n        let transposed = store.transpose();\n        use EnumStoreTransposed::*;\n        match transposed {\n            EnumStoreTransposed::Foo => {}\n            Bar(bar) => {\n                let bar: String = bar();\n            }\n            Baz { foo, bar } => {\n                let foo: i32 = foo();\n                let bar: String = bar();\n            }\n            FooBar(foo, bar) => {\n                let foo: u32 = foo();\n                let bar: String = bar();\n            }\n            BarFoo { foo } => {\n                let foo: String = foo();\n            }\n        }\n\n        let is_foo_or_bar = store.is_foo_or_bar();\n        store.make_foo();\n    }\n\n    fn derive_generic_enum_with_bounds() {\n        #[derive(Store, PartialEq, Clone, Debug)]\n        #[non_exhaustive]\n        enum Enum<T: ?Sized>\n        where\n            T: 'static,\n        {\n            Foo,\n            Bar(&'static T),\n            Baz { foo: i32, bar: &'static T },\n            FooBar(u32, &'static T),\n            BarFoo { foo: &'static T },\n        }\n\n        #[store]\n        impl<T: ?Sized + 'static> Store<Enum<T>> {\n            fn is_foo_or_bar(&self) -> bool\n            where\n                T: Clone + 'static,\n            {\n                matches!(self.cloned(), Enum::Foo | Enum::Bar(_))\n            }\n\n            fn make_foo(&mut self)\n            where\n                T: 'static,\n            {\n                self.set(Enum::Foo);\n            }\n        }\n\n        let mut store = use_store(|| Enum::Bar(\"Hello\"));\n\n        let foo = store.is_foo();\n        let bar = store.is_bar();\n        let baz = store.is_baz();\n        let foobar = store.is_foo_bar();\n        let barfoo = store.is_bar_foo();\n\n        let foo = store.bar().unwrap();\n        let foo: &'static str = foo();\n        let bar = store.bar_foo().unwrap();\n        let bar: &'static str = bar();\n\n        let transposed = store.transpose();\n        use EnumStoreTransposed::*;\n        match transposed {\n            EnumStoreTransposed::Foo => {}\n            Bar(bar) => {\n                let bar: &'static str = bar();\n            }\n            Baz { foo, bar } => {\n                let foo: i32 = foo();\n                let bar: &'static str = bar();\n            }\n            FooBar(foo, bar) => {\n                let foo: u32 = foo();\n                let bar: &'static str = bar();\n            }\n            BarFoo { foo } => {\n                let foo: &'static str = foo();\n            }\n        }\n    }\n\n    fn derive_generic_enum_transpose_passthrough() {\n        #[derive(Store, PartialEq, Clone, Debug)]\n        #[non_exhaustive]\n        enum Enum<const COUNT: usize, T> {\n            Foo,\n            Bar(T),\n            BarFoo { foo: T },\n        }\n\n        let mut store = use_store(|| Enum::<0, _>::Bar(\"Hello\".to_string()));\n\n        let transposed = store.transpose();\n        use Enum::*;\n        match transposed {\n            Enum::Foo => {}\n            Bar(bar) => {\n                let bar: String = bar();\n            }\n            BarFoo { foo } => {\n                let foo: String = foo();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "packages/stores-macro/Cargo.toml",
    "content": "[package]\nname = \"dioxus-stores-macro\"\nversion = { workspace = true }\nedition = \"2021\"\nrepository = \"https://github.com/DioxusLabs/dioxus/\"\nhomepage = \"https://dioxuslabs.com/docs/0.5/guide/en/getting_started/fullstack.html\"\nkeywords = [\"dom\", \"ui\", \"gui\", \"react\", \"liveview\"]\nauthors = [\"Jonathan Kelley\", \"Evan Almloff\"]\nlicense = \"MIT OR Apache-2.0\"\ndescription = \"Server function macros for Dioxus\"\n\n[dependencies]\nproc-macro2 = { workspace = true }\nquote = { workspace = true }\nsyn = { workspace = true, features = [\"full\"] }\nconvert_case = { workspace = true }\n\n[dev-dependencies]\ndioxus = { workspace = true }\ndioxus-stores = { workspace = true }\n\n[lib]\nproc-macro = true\n\n[package.metadata.docs.rs]\ncargo-args = [\"-Zunstable-options\", \"-Zrustdoc-scrape-examples\"]\n"
  },
  {
    "path": "packages/stores-macro/src/derive.rs",
    "content": "use convert_case::{Case, Casing};\nuse proc_macro2::TokenStream as TokenStream2;\nuse quote::{format_ident, quote, ToTokens};\nuse syn::{\n    parse_quote, spanned::Spanned, DataEnum, DataStruct, DeriveInput, Field, Fields, Generics,\n    Ident, Index, LitInt,\n};\n\npub(crate) fn derive_store(input: DeriveInput) -> syn::Result<TokenStream2> {\n    let item_name = &input.ident;\n    let extension_trait_name = format_ident!(\"{}StoreExt\", item_name);\n    let transposed_name = format_ident!(\"{}StoreTransposed\", item_name);\n\n    // Create generics for the extension trait and transposed struct. Both items need the original generics\n    // and bounds plus an extra __Lens type used in the store generics\n    let generics = &input.generics;\n    let mut extension_generics = generics.clone();\n    extension_generics.params.insert(0, parse_quote!(__Lens));\n\n    match &input.data {\n        syn::Data::Struct(data_struct) => derive_store_struct(\n            &input,\n            data_struct,\n            extension_trait_name,\n            transposed_name,\n            extension_generics,\n        ),\n        syn::Data::Enum(data_enum) => derive_store_enum(\n            &input,\n            data_enum,\n            extension_trait_name,\n            transposed_name,\n            extension_generics,\n        ),\n        syn::Data::Union(_) => Err(syn::Error::new(\n            input.span(),\n            \"Store macro does not support unions\",\n        )),\n    }\n}\n\n// For structs, we derive two items:\n// - An extension trait with methods to access the fields of the struct as stores and a `transpose` method\n// - A transposed version of the struct with all fields wrapped in stores\nfn derive_store_struct(\n    input: &DeriveInput,\n    structure: &DataStruct,\n    extension_trait_name: Ident,\n    transposed_name: Ident,\n    extension_generics: Generics,\n) -> syn::Result<TokenStream2> {\n    let struct_name = &input.ident;\n    let fields = &structure.fields;\n    let visibility = &input.vis;\n\n    // We don't need to do anything if there are no fields\n    if fields.is_empty() {\n        return Ok(quote! {});\n    }\n\n    let generics = &input.generics;\n    let (_, ty_generics, _) = generics.split_for_impl();\n    let (extension_impl_generics, extension_ty_generics, extension_where_clause) =\n        extension_generics.split_for_impl();\n\n    // We collect the definitions and implementations for the extension trait methods along with the types of the fields in the transposed struct\n    let mut implementations = Vec::new();\n    let mut definitions = Vec::new();\n    let mut transposed_fields = Vec::new();\n\n    for (field_index, field) in fields.iter().enumerate() {\n        generate_field_methods(\n            field_index,\n            field,\n            struct_name,\n            &ty_generics,\n            &mut transposed_fields,\n            &mut definitions,\n            &mut implementations,\n        );\n    }\n\n    // Add a transpose method to turn the stored struct into a struct with all fields as stores\n    // We need the copy bound here because the store will be copied into the selector for each field\n    let definition = quote! {\n        fn transpose(\n            self,\n        ) -> #transposed_name #extension_ty_generics where Self: ::std::marker::Copy;\n    };\n    definitions.push(definition);\n    let field_names = fields\n        .iter()\n        .enumerate()\n        .map(|(i, field)| function_name_from_field(i, field))\n        .collect::<Vec<_>>();\n    // Construct the transposed struct with the fields as stores from the field variables in scope\n    let construct = match &structure.fields {\n        Fields::Named(_) => {\n            quote! { #transposed_name { #(#field_names),* } }\n        }\n        Fields::Unnamed(_) => {\n            quote! { #transposed_name(#(#field_names),*) }\n        }\n        Fields::Unit => {\n            quote! { #transposed_name }\n        }\n    };\n    let implementation = quote! {\n        fn transpose(\n            self,\n        ) -> #transposed_name #extension_ty_generics where Self: ::std::marker::Copy {\n            // Convert each field into the corresponding store\n            #(\n                let #field_names = self.#field_names();\n            )*\n            #construct\n        }\n    };\n    implementations.push(implementation);\n\n    // Generate the transposed struct definition\n    let transposed_struct = transposed_struct(\n        visibility,\n        struct_name,\n        &transposed_name,\n        structure,\n        generics,\n        &extension_generics,\n        &transposed_fields,\n    );\n\n    // Expand to the extension trait and its implementation for the store alongside the transposed struct\n    Ok(quote! {\n        #visibility trait #extension_trait_name #extension_impl_generics #extension_where_clause {\n            #(\n                #definitions\n            )*\n        }\n\n        impl #extension_impl_generics #extension_trait_name #extension_ty_generics for dioxus_stores::Store<#struct_name #ty_generics, __Lens> #extension_where_clause {\n            #(\n                #implementations\n            )*\n        }\n\n        #transposed_struct\n    })\n}\n\nfn field_type_generic(field: &Field, generics: &syn::Generics) -> bool {\n    generics.type_params().any(|param| {\n        matches!(&field.ty, syn::Type::Path(type_path) if type_path.path.is_ident(&param.ident))\n    })\n}\n\nfn transposed_struct(\n    visibility: &syn::Visibility,\n    struct_name: &Ident,\n    transposed_name: &Ident,\n    structure: &DataStruct,\n    generics: &syn::Generics,\n    extension_generics: &syn::Generics,\n    transposed_fields: &[TokenStream2],\n) -> TokenStream2 {\n    let (extension_impl_generics, _, extension_where_clause) = extension_generics.split_for_impl();\n    // Only use a type alias if:\n    // - There are no bounds on the type generics\n    // - All fields are generic types\n    let use_type_alias = generics.type_params().all(|param| param.bounds.is_empty())\n        && structure\n            .fields\n            .iter()\n            .all(|field| field_type_generic(field, generics));\n    if use_type_alias {\n        let generics = transpose_generics(struct_name, generics);\n        return quote! {#visibility type #transposed_name #extension_impl_generics = #struct_name #generics;};\n    }\n    match &structure.fields {\n        Fields::Named(fields) => {\n            let fields = fields.named.iter();\n            let fields = fields.zip(transposed_fields.iter()).map(|(f, t)| {\n                let vis = &f.vis;\n                let ident = &f.ident;\n                let colon = f.colon_token.as_ref();\n                quote! { #vis #ident #colon #t }\n            });\n            quote! {\n                #visibility struct #transposed_name #extension_impl_generics #extension_where_clause {\n                    #(\n                        #fields\n                    ),*\n                }\n            }\n        }\n        Fields::Unnamed(fields) => {\n            let fields = fields.unnamed.iter();\n            let fields = fields.zip(transposed_fields.iter()).map(|(f, t)| {\n                let vis = &f.vis;\n                quote! { #vis #t }\n            });\n            quote! {\n                #visibility struct #transposed_name #extension_impl_generics (\n                    #(\n                        #fields\n                    ),*\n                )\n                #extension_where_clause;\n            }\n        }\n        Fields::Unit => {\n            quote! {#visibility struct #transposed_name #extension_impl_generics #extension_where_clause}\n        }\n    }\n}\n\nfn generate_field_methods(\n    field_index: usize,\n    field: &syn::Field,\n    struct_name: &Ident,\n    ty_generics: &syn::TypeGenerics,\n    transposed_fields: &mut Vec<TokenStream2>,\n    definitions: &mut Vec<TokenStream2>,\n    implementations: &mut Vec<TokenStream2>,\n) {\n    let field_name = &field.ident;\n\n    // When we map the field, we need to use either the field name for named fields or the index for unnamed fields.\n    let field_accessor = field_name.as_ref().map_or_else(\n        || Index::from(field_index).to_token_stream(),\n        |name| name.to_token_stream(),\n    );\n    let function_name = function_name_from_field(field_index, field);\n    let field_type = &field.ty;\n    let store_type = mapped_type(struct_name, ty_generics, field_type);\n\n    transposed_fields.push(store_type.clone());\n\n    // Each field gets its own reactive scope within the child based on the field's index\n    let ordinal = LitInt::new(&field_index.to_string(), field.span());\n\n    let definition = quote! {\n        fn #function_name(\n            self,\n        ) -> #store_type;\n    };\n    definitions.push(definition);\n    let implementation = quote! {\n        fn #function_name(\n            self,\n        ) -> #store_type {\n            let __map_field: fn(&#struct_name #ty_generics) -> &#field_type = |value| &value.#field_accessor;\n            let __map_mut_field: fn(&mut #struct_name #ty_generics) -> &mut #field_type = |value| &mut value.#field_accessor;\n            // Map the field into a child selector that tracks the field\n            let scope = self.into_selector().child(\n                #ordinal,\n                __map_field,\n                __map_mut_field,\n            );\n            // Convert the selector into a store\n            ::std::convert::Into::into(scope)\n        }\n    };\n    implementations.push(implementation);\n}\n\n// For enums, we derive two items:\n// - An extension trait with methods to check if the store is a specific variant and a method\n//   to access the field of that variant if there is only one field\n// - A transposed version of the enum with all fields wrapped in stores\nfn derive_store_enum(\n    input: &DeriveInput,\n    structure: &DataEnum,\n    extension_trait_name: Ident,\n    transposed_name: Ident,\n    extension_generics: Generics,\n) -> syn::Result<TokenStream2> {\n    let enum_name = &input.ident;\n    let variants = &structure.variants;\n    let visibility = &input.vis;\n\n    let generics = &input.generics;\n    let (_, ty_generics, _) = generics.split_for_impl();\n    let (extension_impl_generics, extension_ty_generics, extension_where_clause) =\n        extension_generics.split_for_impl();\n\n    // We collect the definitions and implementations for the extension trait methods along with the types of the fields in the transposed enum\n    // and the match arms for the transposed enum.\n    let mut implementations = Vec::new();\n    let mut definitions = Vec::new();\n    let mut transposed_variants = Vec::new();\n    let mut transposed_match_arms = Vec::new();\n\n    // The generated items that check the variant of the enum need to read the enum which requires these extra bounds\n    let readable_bounds = quote! { __Lens: dioxus_stores::macro_helpers::dioxus_signals::Readable<Target = #enum_name #ty_generics>, #enum_name #ty_generics: 'static };\n\n    for variant in variants {\n        let variant_name = &variant.ident;\n        let snake_case_variant = format_ident!(\"{}\", variant_name.to_string().to_case(Case::Snake));\n        let is_fn = format_ident!(\"is_{}\", snake_case_variant);\n\n        generate_is_variant_method(\n            &is_fn,\n            variant_name,\n            enum_name,\n            readable_bounds.clone(),\n            &mut definitions,\n            &mut implementations,\n        );\n\n        let mut transposed_fields = Vec::new();\n        let mut transposed_field_selectors = Vec::new();\n        let fields = &variant.fields;\n        for (i, field) in fields.iter().enumerate() {\n            let field_type = &field.ty;\n            let store_type = mapped_type(enum_name, &ty_generics, field_type);\n\n            // Push the field for the transposed enum\n            transposed_fields.push(store_type.clone());\n\n            // Generate the code to get Store<Field, W> from the enum\n            let select_field = select_enum_variant_field(\n                enum_name,\n                &ty_generics,\n                variant_name,\n                field,\n                i,\n                fields.len(),\n            );\n\n            // If there is only one field, generate a field() -> Option<Store<O, W>> method\n            if fields.len() == 1 {\n                generate_as_variant_method(\n                    &is_fn,\n                    &snake_case_variant,\n                    &select_field,\n                    &store_type,\n                    &readable_bounds,\n                    &mut definitions,\n                    &mut implementations,\n                );\n            }\n\n            transposed_field_selectors.push(select_field);\n        }\n\n        // Now that we have the types for the field selectors within the variant,\n        // we can construct the transposed variant and the logic to turn the normal\n        // version of that variant into the store version\n        let field_names = fields\n            .iter()\n            .enumerate()\n            .map(|(i, field)| function_name_from_field(i, field))\n            .collect::<Vec<_>>();\n        // Turn each field into its store\n        let construct_fields = field_names\n            .iter()\n            .zip(transposed_field_selectors.iter())\n            .map(|(name, selector)| {\n                quote! { let #name = { #selector }; }\n            });\n        // Merge the stores into the variant\n        let construct_variant = match &fields {\n            Fields::Named(_) => {\n                quote! { #transposed_name::#variant_name { #(#field_names),* } }\n            }\n            Fields::Unnamed(_) => {\n                quote! { #transposed_name::#variant_name(#(#field_names),*) }\n            }\n            Fields::Unit => {\n                quote! { #transposed_name::#variant_name }\n            }\n        };\n        let match_arm = quote! {\n            #enum_name::#variant_name { .. } => {\n                #(#construct_fields)*\n                #construct_variant\n            },\n        };\n        transposed_match_arms.push(match_arm);\n\n        // Push the type definition of the variant to the transposed enum\n        let transposed_variant = match &fields {\n            Fields::Named(named) => {\n                let fields = named.named.iter();\n                let fields = fields.zip(transposed_fields.iter()).map(|(f, t)| {\n                    let vis = &f.vis;\n                    let ident = &f.ident;\n                    let colon = f.colon_token.as_ref();\n                    quote! { #vis #ident #colon #t }\n                });\n                quote! { #variant_name {\n                    #(\n                        #fields\n                    ),*\n                } }\n            }\n            Fields::Unnamed(unnamed) => {\n                let fields = unnamed.unnamed.iter();\n                let fields = fields.zip(transposed_fields.iter()).map(|(f, t)| {\n                    let vis = &f.vis;\n                    quote! { #vis #t }\n                });\n                quote! { #variant_name (\n                    #(\n                        #fields\n                    ),*\n                ) }\n            }\n            Fields::Unit => {\n                quote! { #variant_name }\n            }\n        };\n        transposed_variants.push(transposed_variant);\n    }\n\n    let definition = quote! {\n        fn transpose(\n            self,\n        ) -> #transposed_name #extension_ty_generics where #readable_bounds, Self: ::std::marker::Copy;\n    };\n    definitions.push(definition);\n    let implementation = quote! {\n        fn transpose(\n            self,\n        ) -> #transposed_name #extension_ty_generics where #readable_bounds, Self: ::std::marker::Copy {\n            // We only do a shallow read of the store to get the current variant. We only need to rerun\n            // this match when the variant changes, not when the fields change\n            self.selector().track_shallow();\n            let read = dioxus_stores::macro_helpers::dioxus_signals::ReadableExt::peek(self.selector());\n            match &*read {\n                #(#transposed_match_arms)*\n                // The enum may be #[non_exhaustive]\n                #[allow(unreachable)]\n                _ => unreachable!(),\n            }\n        }\n    };\n    implementations.push(implementation);\n\n    // Only use a type alias if:\n    // - There are no bounds on the type generics\n    // - All fields are generic types\n    let use_type_alias = generics.type_params().all(|param| param.bounds.is_empty())\n        && structure\n            .variants\n            .iter()\n            .flat_map(|variant| variant.fields.iter())\n            .all(|field| field_type_generic(field, generics));\n\n    let transposed_enum = if use_type_alias {\n        let generics = transpose_generics(enum_name, generics);\n\n        quote! {#visibility type #transposed_name #extension_generics = #enum_name #generics;}\n    } else {\n        quote! { #visibility enum #transposed_name #extension_impl_generics #extension_where_clause {#(#transposed_variants),*} }\n    };\n\n    // Expand to the extension trait and its implementation for the store alongside the transposed enum\n    Ok(quote! {\n        #visibility trait #extension_trait_name #extension_impl_generics #extension_where_clause {\n            #(\n                #definitions\n            )*\n        }\n\n        impl #extension_impl_generics #extension_trait_name #extension_ty_generics for dioxus_stores::Store<#enum_name #ty_generics, __Lens> #extension_where_clause {\n            #(\n                #implementations\n            )*\n        }\n\n        #transposed_enum\n    })\n}\n\nfn generate_is_variant_method(\n    is_fn: &Ident,\n    variant_name: &Ident,\n    enum_name: &Ident,\n    readable_bounds: TokenStream2,\n    definitions: &mut Vec<TokenStream2>,\n    implementations: &mut Vec<TokenStream2>,\n) {\n    // Generate a is_variant method that checks if the store is a specific variant\n    let definition = quote! {\n        fn #is_fn(\n            &self,\n        ) -> bool where #readable_bounds;\n    };\n    definitions.push(definition);\n    let implementation = quote! {\n        fn #is_fn(\n            &self,\n        ) -> bool where #readable_bounds {\n            // Reading the current variant only tracks the shallow value of the store. Writing to a specific\n            // variant will not cause the variant to change, so we don't need to subscribe deeply\n            self.selector().track_shallow();\n            let ref_self = dioxus_stores::macro_helpers::dioxus_signals::ReadableExt::peek(self.selector());\n            matches!(&*ref_self, #enum_name::#variant_name { .. })\n        }\n    };\n    implementations.push(implementation);\n}\n\n/// Generate a method to turn Store<Enum, W> into Option<Store<VariantField, W>> if the variant only has one field.\nfn generate_as_variant_method(\n    is_fn: &Ident,\n    snake_case_variant: &Ident,\n    select_field: &TokenStream2,\n    store_type: &TokenStream2,\n    readable_bounds: &TokenStream2,\n    definitions: &mut Vec<TokenStream2>,\n    implementations: &mut Vec<TokenStream2>,\n) {\n    let definition = quote! {\n        fn #snake_case_variant(\n            self,\n        ) -> Option<#store_type> where #readable_bounds;\n    };\n    definitions.push(definition);\n    let implementation = quote! {\n        fn #snake_case_variant(\n            self,\n        ) -> Option<#store_type> where #readable_bounds {\n            self.#is_fn().then(|| {\n                #select_field\n            })\n        }\n    };\n    implementations.push(implementation);\n}\n\nfn select_enum_variant_field(\n    enum_name: &Ident,\n    ty_generics: &syn::TypeGenerics,\n    variant_name: &Ident,\n    field: &Field,\n    field_index: usize,\n    field_count: usize,\n) -> TokenStream2 {\n    // Generate the match arm for the field\n    let function_name = function_name_from_field(field_index, field);\n    let field_type = &field.ty;\n    let match_field = if field.ident.is_none() {\n        let ignore_before = (0..field_index).map(|_| quote!(_));\n        let ignore_after = (field_index + 1..field_count).map(|_| quote!(_));\n        quote!( ( #(#ignore_before,)* #function_name, #(#ignore_after),* ) )\n    } else {\n        quote!( { #function_name, .. })\n    };\n    let ordinal = LitInt::new(&field_index.to_string(), variant_name.span());\n    quote! {\n        let __map_field: fn(&#enum_name #ty_generics) -> &#field_type = |value| match value {\n            #enum_name::#variant_name #match_field => #function_name,\n            _ => panic!(\"Selector that was created to match {} read after variant changed\", stringify!(#variant_name)),\n        };\n        let __map_mut_field: fn(&mut #enum_name #ty_generics) -> &mut #field_type = |value| match value {\n            #enum_name::#variant_name #match_field => #function_name,\n            _ => panic!(\"Selector that was created to match {} written after variant changed\", stringify!(#variant_name)),\n        };\n        // Each field within the variant gets its own reactive scope. Writing to one field will not notify the enum or\n        // other fields\n        let scope = self.into_selector().child(\n            #ordinal,\n            __map_field,\n            __map_mut_field,\n        );\n        ::std::convert::Into::into(scope)\n    }\n}\n\nfn function_name_from_field(index: usize, field: &syn::Field) -> Ident {\n    // Generate a function name from the field's identifier or index\n    field\n        .ident\n        .as_ref()\n        .map_or_else(|| format_ident!(\"field_{index}\"), |name| name.clone())\n}\n\nfn mapped_type(\n    item: &Ident,\n    ty_generics: &syn::TypeGenerics,\n    field_type: &syn::Type,\n) -> TokenStream2 {\n    // The zoomed in store type is a MappedMutSignal with function pointers to map the reference to the enum into a reference to the field\n    let write_type = quote! { dioxus_stores::macro_helpers::dioxus_signals::MappedMutSignal<#field_type, __Lens, fn(&#item #ty_generics) -> &#field_type, fn(&mut #item #ty_generics) -> &mut #field_type> };\n    quote! { dioxus_stores::Store<#field_type, #write_type> }\n}\n\n/// Take the generics from the original type with only generic fields into the generics for the transposed type\nfn transpose_generics(name: &Ident, generics: &syn::Generics) -> TokenStream2 {\n    let (_, ty_generics, _) = generics.split_for_impl();\n    let mut transposed_generics = generics.clone();\n    let mut generics = Vec::new();\n    for gen in transposed_generics.params.iter_mut() {\n        match gen {\n            // Map type generics into Store<Type, MappedMutSignal<...>>\n            syn::GenericParam::Type(type_param) => {\n                let ident = &type_param.ident;\n                let ty = mapped_type(name, &ty_generics, &parse_quote!(#ident));\n                generics.push(ty);\n            }\n            // Forward const and lifetime generics as-is\n            syn::GenericParam::Const(const_param) => {\n                let ident = &const_param.ident;\n                generics.push(quote! { #ident });\n            }\n            syn::GenericParam::Lifetime(lt_param) => {\n                let ident = &lt_param.lifetime;\n                generics.push(quote! { #ident });\n            }\n        }\n    }\n\n    quote!(<#(#generics),*> )\n}\n"
  },
  {
    "path": "packages/stores-macro/src/extend.rs",
    "content": "use proc_macro2::TokenStream;\nuse quote::quote;\nuse syn::parse::Parse;\nuse syn::spanned::Spanned;\nuse syn::{\n    parse_quote, Ident, ImplItem, ImplItemConst, ImplItemType, ItemImpl, PathArguments, Type,\n    WherePredicate,\n};\n\npub(crate) fn extend_store(args: ExtendArgs, mut input: ItemImpl) -> syn::Result<TokenStream> {\n    // Extract the type name the store is generic over\n    let store_type = &*input.self_ty;\n    let store = parse_store_type(store_type)?;\n    let store_path = &store.store_path;\n    let item = store.store_generic;\n    let lens_generic = store.store_lens;\n    let visibility = args\n        .visibility\n        .unwrap_or_else(|| syn::Visibility::Inherited);\n    if input.trait_.is_some() {\n        return Err(syn::Error::new_spanned(\n            input.trait_.unwrap().1,\n            \"The `store` attribute can only be used on `impl Store<T> { ... }` blocks, not trait implementations.\",\n        ));\n    }\n\n    let extension_name = match args.name {\n        Some(attr) => attr,\n        None => {\n            // Otherwise, generate a name based on the type name\n            let type_name = stringify_type(&item)?;\n            Ident::new(&format!(\"{}StoreImplExt\", type_name), item.span())\n        }\n    };\n\n    // Go through each method in the impl block and add extra bounds to lens as needed\n    let immutable_bounds: WherePredicate = parse_quote!(#lens_generic: dioxus_stores::macro_helpers::dioxus_signals::Readable<Target = #item> + ::std::marker::Copy + 'static);\n    let mutable_bounds: WherePredicate = parse_quote!(#lens_generic: dioxus_stores::macro_helpers::dioxus_signals::Writable<Target = #item> + ::std::marker::Copy + 'static);\n    for item in &mut input.items {\n        let ImplItem::Fn(func) = item else {\n            continue; // Only process function items\n        };\n        // Only add bounds if the function has a self argument\n        let Some(receiver) = func.sig.inputs.iter().find_map(|arg| {\n            if let syn::FnArg::Receiver(receiver) = arg {\n                Some(receiver)\n            } else {\n                None\n            }\n        }) else {\n            continue;\n        };\n        let extra_bounds = match (&receiver.reference, &receiver.mutability) {\n            // The function takes &self\n            (Some(_), None) => &immutable_bounds,\n            // The function takes &mut self\n            (Some(_), Some(_)) => &mutable_bounds,\n            _ => {\n                // If the function doesn't take &self or &mut self, we don't need to add any bounds\n                continue;\n            }\n        };\n        func.sig\n            .generics\n            .make_where_clause()\n            .predicates\n            .push(extra_bounds.clone());\n    }\n\n    // Push a __Lens generic to the impl if it doesn't already exist\n    let contains_lens_generic = input.generics.params.iter().any(|param| {\n        if let syn::GenericParam::Type(ty) = param {\n            ty.ident == lens_generic\n        } else {\n            false\n        }\n    });\n    if !contains_lens_generic {\n        input\n            .generics\n            .params\n            .push(parse_quote!(#lens_generic: ::std::marker::Copy + 'static));\n    }\n\n    // quote as the trait definition\n    let trait_definition = impl_to_trait_body(&extension_name, &input, &visibility)?;\n\n    // Reformat the type to be generic over the lens\n    input.self_ty = Box::new(parse_quote!(#store_path<#item, #lens_generic>));\n    // Change the standalone impl block to a trait impl block\n    let (_, trait_generics, _) = input.generics.split_for_impl();\n    input.trait_ = Some((\n        None,\n        parse_quote!(#extension_name #trait_generics),\n        parse_quote!(for),\n    ));\n\n    Ok(quote! {\n        #trait_definition\n\n        #input\n    })\n}\n\nfn stringify_type(ty: &Type) -> syn::Result<String> {\n    match ty {\n        Type::Array(type_array) => {\n            let elem = stringify_type(&type_array.elem)?;\n            Ok(format!(\"Array{elem}\"))\n        }\n        Type::Slice(type_slice) => {\n            let elem = stringify_type(&type_slice.elem)?;\n            Ok(format!(\"Slice{elem}\"))\n        }\n        Type::Paren(type_paren) => stringify_type(&type_paren.elem),\n        Type::Path(type_path) => {\n            let last_segment = type_path.path.segments.last().ok_or_else(|| {\n                syn::Error::new_spanned(type_path, \"Type path must have at least one segment\")\n            })?;\n            let ident = &last_segment.ident;\n            Ok(ident.to_string())\n        }\n        _ => Err(syn::Error::new_spanned(\n            ty,\n            \"Unsupported type in store implementation\",\n        )),\n    }\n}\n\nfn impl_to_trait_body(\n    trait_name: &Ident,\n    item: &ItemImpl,\n    visibility: &syn::Visibility,\n) -> syn::Result<TokenStream> {\n    let ItemImpl {\n        attrs,\n        defaultness,\n        unsafety,\n        items,\n        ..\n    } = item;\n\n    let generics = &item.generics;\n\n    let items = items\n        .iter()\n        .map(item_to_trait_definition)\n        .collect::<syn::Result<Vec<_>>>()?;\n\n    Ok(quote! {\n        #(#attrs)*\n        #visibility #defaultness #unsafety trait #trait_name #generics {\n            #(#items)*\n        }\n    })\n}\n\nfn item_to_trait_definition(item: &syn::ImplItem) -> syn::Result<proc_macro2::TokenStream> {\n    match item {\n        syn::ImplItem::Fn(func) => {\n            let sig = &func.sig;\n\n            Ok(quote! {\n                #sig;\n            })\n        }\n        syn::ImplItem::Const(impl_item_const) => {\n            let ImplItemConst {\n                attrs,\n                const_token,\n                ident,\n                generics,\n                colon_token,\n                ty,\n                semi_token,\n                ..\n            } = impl_item_const;\n\n            Ok(quote! {\n                #(#attrs)*\n                #const_token #ident #generics #colon_token #ty #semi_token\n            })\n        }\n        syn::ImplItem::Type(impl_item_type) => {\n            let ImplItemType {\n                attrs,\n                type_token,\n                ident,\n                generics,\n                eq_token,\n                ty,\n                semi_token,\n                ..\n            } = impl_item_type;\n\n            Ok(quote! {\n                #(#attrs)*\n                #type_token #ident #generics #eq_token #ty #semi_token\n            })\n        }\n        _ => Err(syn::Error::new_spanned(item, \"Unsupported item type\")),\n    }\n}\n\nfn argument_as_type(arg: &syn::GenericArgument) -> Option<Type> {\n    if let syn::GenericArgument::Type(ty) = arg {\n        Some(ty.clone())\n    } else {\n        None\n    }\n}\n\nstruct StorePath {\n    store_path: syn::Path,\n    store_generic: syn::Type,\n    store_lens: syn::Ident,\n}\n\nfn parse_store_type(store_type: &Type) -> syn::Result<StorePath> {\n    if let Type::Path(type_path) = store_type {\n        if let Some(segment) = type_path.path.segments.last() {\n            if let PathArguments::AngleBracketed(args) = &segment.arguments {\n                if let Some(store_generics) = args.args.first().and_then(argument_as_type) {\n                    let store_lens = args\n                        .args\n                        .iter()\n                        .nth(1)\n                        .and_then(argument_as_type)\n                        .unwrap_or_else(|| parse_quote!(__Lens));\n                    let store_lens = parse_quote!(#store_lens);\n                    let mut path_without_generics = type_path.path.clone();\n                    for segment in &mut path_without_generics.segments {\n                        segment.arguments = PathArguments::None;\n                    }\n                    return Ok(StorePath {\n                        store_path: path_without_generics,\n                        store_generic: store_generics,\n                        store_lens,\n                    });\n                }\n            }\n        }\n    }\n    Err(syn::Error::new_spanned(\n        store_type,\n        \"The implementation must be in the form `impl Store<T> {...}`\",\n    ))\n}\n\n/// The args the `#[store]` attribute macro accepts\npub(crate) struct ExtendArgs {\n    /// The name of the extension trait generated\n    name: Option<Ident>,\n    /// The visibility of the extension trait\n    visibility: Option<syn::Visibility>,\n}\n\nimpl Parse for ExtendArgs {\n    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {\n        // Try to parse visibility if it exists\n        let visibility = if input.peek(syn::Token![pub]) {\n            let vis: syn::Visibility = input.parse()?;\n            Some(vis)\n        } else {\n            None\n        };\n        // Try to parse name = ident if it exists\n        let name = if input.peek(Ident) && input.peek2(syn::Token![=]) {\n            let ident: Ident = input.parse()?;\n            if ident != \"name\" {\n                return Err(syn::Error::new_spanned(ident, \"Expected `name` argument\"));\n            }\n            let _eq_token: syn::Token![=] = input.parse()?;\n            let ident: Ident = input.parse()?;\n            Some(ident)\n        } else {\n            None\n        };\n        Ok(ExtendArgs { name, visibility })\n    }\n}\n"
  },
  {
    "path": "packages/stores-macro/src/lib.rs",
    "content": "use proc_macro::TokenStream;\nuse syn::{parse_macro_input, DeriveInput, ItemImpl};\n\nuse crate::extend::ExtendArgs;\n\nmod derive;\nmod extend;\n\n/// # `derive(Store)`\n///\n/// The `Store` macro is used to create an extension trait for stores that makes it possible to access the fields or variants\n/// of an item as stores.\n///\n/// ## Expansion\n///\n/// The macro expands to two different items:\n/// - An extension trait which is implemented for `Store<YourType, W>` with methods to access fields and variants for your type.\n/// - A transposed version of your type which contains the fields or variants as stores.\n///\n/// ### Structs\n///\n/// For structs, the store macro generates methods for each field that returns a store scoped to that field and a `transpose` method that returns a struct with all fields as stores:\n///\n/// ```rust, no_run\n/// use dioxus::prelude::*;\n/// use dioxus_stores::*;\n///\n/// #[derive(Store)]\n/// struct TodoItem {\n///     checked: bool,\n///     contents: String,\n/// }\n///\n/// let store = use_store(|| TodoItem {\n///     checked: false,\n///     contents: \"Learn about stores\".to_string(),\n/// });\n///\n/// // The store macro creates an extension trait with methods for each field\n/// // that returns a store scoped to that field.\n/// let checked: Store<bool, _> = store.checked();\n/// let contents: Store<String, _> = store.contents();\n///\n/// // It also generates a `transpose` method returns a variant of your structure\n/// // with stores wrapping each of your data types. This can be very useful when destructuring\n/// // or matching your type\n/// let TodoItemStoreTransposed { checked, contents } = store.transpose();\n/// let checked: bool = checked();\n/// let contents: String = contents();\n/// ```\n///\n///\n/// ### Enums\n///\n/// For enums, the store macro generates methods for each variant that checks if the store is that variant. It also generates a `transpose` method that returns an enum with all fields as stores.\n///\n/// ```rust, no_run\n/// use dioxus::prelude::*;\n/// use dioxus_stores::*;\n///\n/// #[derive(Store, PartialEq, Clone, Debug)]\n/// enum Enum {\n///     Foo(String),\n///     Bar { foo: i32, bar: String },\n/// }\n///\n/// let store = use_store(|| Enum::Foo(\"Hello\".to_string()));\n/// // The store macro creates an extension trait with methods for each variant to check\n/// // if the store is that variant.\n/// let foo: bool = store.is_foo();\n/// let bar: bool = store.is_bar();\n///\n/// // If there is only one field in the variant, it also generates a method to try\n/// // to downcast the store to that variant.\n/// let foo: Option<Store<String, _>> = store.foo();\n/// if let Some(foo) = foo {\n///     println!(\"Foo: {foo}\");\n/// }\n///\n/// // It also generates a `transpose` method that returns a variant of your enum where all\n/// // the fields are stores. You can use this to match your enum\n/// let transposed = store.transpose();\n/// use EnumStoreTransposed::*;\n/// match transposed {\n///     EnumStoreTransposed::Foo(foo) => println!(\"Foo: {foo}\"),\n///     EnumStoreTransposed::Bar { foo, bar } => {\n///         let foo: i32 = foo();\n///         let bar: String = bar();\n///         println!(\"Bar: foo = {foo}, bar = {bar}\");\n///     }\n/// }\n/// ```\n#[proc_macro_derive(Store)]\npub fn derive_store(input: TokenStream) -> TokenStream {\n    // Parse the input tokens into a syntax tree\n    let input = parse_macro_input!(input as DeriveInput);\n\n    let expanded = match derive::derive_store(input) {\n        Ok(tokens) => tokens,\n        Err(err) => {\n            // If there was an error, return it as a compile error\n            return err.to_compile_error().into();\n        }\n    };\n\n    // Hand the output tokens back to the compiler\n    TokenStream::from(expanded)\n}\n\n/// # `#[store]`\n///\n/// The `store` attribute macro is used to create an extension trait for store implementations. The extension traits lets you add\n/// methods to the store even though the type is not defined in your crate.\n///\n/// ## Arguments\n///\n/// - `pub`: Makes the generated extension trait public. If not provided, the trait will be private.\n/// - `name = YourExtensionName`: The name of the extension trait. If not provided, it will be generated based on the type name.\n///\n/// ## Bounds\n///\n/// The generated extension trait will have bounds on the lens generic parameter to ensure it implements `Readable` or `Writable` as needed.\n///\n/// - If a method accepts `&self`, the lens will require `Readable` which lets you read the value of the store.\n/// - If a method accepts `&mut self`, the lens will require `Writable` which lets you change the value of the store.\n///\n/// ## Example\n///\n/// ```rust, no_run\n/// use dioxus::prelude::*;\n/// use dioxus_stores::*;\n///\n/// #[derive(Store)]\n/// struct TodoItem {\n///     checked: bool,\n///     contents: String,\n/// }\n///\n/// // You can use the store attribute macro to add methods to your stores\n/// #[store]\n/// impl<Lens> Store<TodoItem, Lens> {\n///    // Since this method takes &mut self, the lens will require Writable automatically. It cannot be used\n///    // with ReadStore<TodoItem>\n///    fn toggle_checked(&mut self) {\n///        self.checked().toggle();\n///    }\n///\n///    // Since this method takes &self, the lens will require Readable automatically\n///    fn checked_contents(&self) -> Option<String> {\n///        self.checked().cloned().then(|| self.contents().to_string())\n///    }\n/// }\n///\n/// let mut store = use_store(|| TodoItem {\n///     checked: false,\n///     contents: \"Learn about stores\".to_string(),\n/// });\n///\n/// // You can use the methods defined in the extension trait\n/// store.toggle_checked();\n/// let contents: Option<String> = store.checked_contents();\n/// ```\n#[proc_macro_attribute]\npub fn store(args: TokenStream, input: TokenStream) -> TokenStream {\n    // Parse the input tokens into a syntax tree\n    let args = parse_macro_input!(args as ExtendArgs);\n    let input = parse_macro_input!(input as ItemImpl);\n\n    let expanded = match extend::extend_store(args, input) {\n        Ok(tokens) => tokens,\n        Err(err) => {\n            // If there was an error, return it as a compile error\n            return err.to_compile_error().into();\n        }\n    };\n\n    // Hand the output tokens back to the compiler\n    TokenStream::from(expanded)\n}\n"
  },
  {
    "path": "packages/subsecond/README.md",
    "content": "# Subsecond\n\nSubsecond is a hot-reloading library for Rust. It makes it easy to add Rust hot-reloading to your\nexisting Rust project with minimal integration overhead.\n\n## Usage:\n\nFor library authors you can use \"hot\" functions with the `subsecond::current` function:\n\n```rust\n/// A user-facing tick / launch / start function\n///\n/// Typically this will be a request/response handler, a game loop, a main function, callback, etc\n///\n/// `current` accepts function pointers and Fn types\npub fn tick(handler: Fn(Request) -> Response) {\n    // Create a \"hot\" function that we can inspect\n    let hot_fn = subsecond::current(handler);\n\n    // Check if this function has been patched\n    if hot_fn.changed() {\n        // do thing\n    }\n\n    // Register a handler to be called when the function is patched\n    hot_fn.on_changed(|| /* do thing */);\n\n    // Call the hot function\n    hot_fn.call((request))\n}\n```\n\nFor application authors, you can use `subsecond::call()` to make a function hot-reloadable:\n\n```rust\nfn handle_request(request: Request) -> Response {\n    subsecond::call(|| {\n        // do_thing...\n    })\n}\n```\n\nIf a hot function is actively being called, then subsecond will rewind the stack to the \"cleanest\" entrypoint. For example, a hot-reloadable server will have two \"hot\" points: at the start of the server, and at the start of the request handler. When the server is reloaded, subsecond will rewind the stack to the first hot point, and then call the function again.\n\n```rust\n// Changes to `serve` will reload the server\nfn serve() {\n    let router = Router::new();\n    router.get(\"/\", handle_request);\n    router.serve(\"0.0.0.0:8080\");\n}\n\n// Changes below \"handle_request\" won't be reload the router\nfn handle_request(request: Request) -> Response {\n    // do thing\n}\n```\n\nFramework authors can interleave their own hot-reload entrypoints alongside user code. This lets you add new anchors into long-running stateful code:\n\n```rust\nfn main() {\n    // serve is \"hot\" and will rebuild the router if it changes\n    webserver::serve(\"0.0.0.0:8080\", || {\n        Router::new()\n            // get is \"hot\" and changes to handle_request won't rebuild the router\n            .get(\"/\", |req| Response::websocket(handle_socket))\n    })\n}\n\nfn handle_socket(ws: &WebSocket) {\n    subsecond::call(|| {\n        // do things with the websocket\n    })\n}\n```\n"
  },
  {
    "path": "packages/subsecond/subsecond/Cargo.toml",
    "content": "[package]\nname = \"subsecond\"\nedition = \"2021\"\nversion.workspace = true\nauthors = [\"Jonathan Kelley\"]\ndescription = \"A runtime hotpatching engine for Rust hot-reloading.\"\nlicense = \"MIT OR Apache-2.0\"\nrepository = \"https://github.com/DioxusLabs/dioxus/tree/main/packages/subsecond\"\nkeywords = [\"hotpatch\", \"engine\", \"subsecond\", \"dioxus\", \"hot-reload\"]\nreadme = \"../README.md\"\n\n[dependencies]\nserde = { workspace = true, features = [\"derive\"] }\nsubsecond-types = { workspace = true }\nthiserror = { workspace = true }\n\n[target.'cfg(target_arch = \"wasm32\")'.dependencies]\nweb-sys = { workspace = true, features = [\"FetchEvent\", \"Request\", \"Window\", \"Response\", \"ResponseType\", \"console\"] }\nwasm-bindgen = { workspace = true }\nwasm-bindgen-futures = { workspace = true }\njs-sys = { workspace = true}\n\n[target.'cfg(not(target_arch = \"wasm32\"))'.dependencies]\nlibloading = { workspace = true}\nlibc = { workspace = true}\nmemmap2 = { workspace = true}\n\n[target.'cfg(target_os = \"android\")'.dependencies]\nmemfd = { workspace = true}\n"
  },
  {
    "path": "packages/subsecond/subsecond/src/lib.rs",
    "content": "#![allow(clippy::needless_doctest_main)]\n//! # Subsecond: Hot-patching for Rust\n//!\n//! Subsecond is a library that enables hot-patching for Rust applications. This allows you to change\n//! the code of a running application without restarting it. This is useful for game engines, servers,\n//! and other long-running applications where the typical edit-compile-run cycle is too slow.\n//!\n//! Subsecond also implements a technique we call \"ThinLinking\" which makes compiling Rust code\n//! significantly faster in development mode, which can be used outside of hot-patching.\n//!\n//! # Usage\n//!\n//! Subsecond is designed to be as simple for both application developers and library authors.\n//!\n//! Simply call your existing functions with [`call`] and Subsecond will automatically detour\n//! that call to the latest version of the function.\n//!\n//! ```rust\n//! for x in 0..5 {\n//!     subsecond::call(|| {\n//!         println!(\"Hello, world! {}\", x);\n//!     });\n//! }\n//! ```\n//!\n//! To actually load patches into your application, a third-party tool that implements the Subsecond\n//! compiler and protocol is required. Subsecond is built and maintained by the Dioxus team, so we\n//! suggest using the dioxus CLI tool to use subsecond.\n//!\n//! To install the Dioxus CLI, we recommend using [`cargo binstall`](https://crates.io/crates/cargo-binstall):\n//!\n//! ```sh\n//! cargo binstall dioxus-cli\n//! ```\n//!\n//! The Dioxus CLI provides several tools for development. To run your application with Subsecond enabled,\n//! use `dx serve` - this takes the same arguments as `cargo run` but will automatically hot-reload your\n//! application when changes are detected.\n//!\n//! As of Dioxus 0.7, \"--hotpatch\" is required to use hotpatching while Subsecond is still experimental.\n//!\n//! ```sh\n//! dx serve --hotpatch\n//! ```\n//!\n//! ## How it works\n//!\n//! Subsecond works by detouring function calls through a jump table. This jump table contains the latest\n//! version of the program's function pointers, and when a function is called, Subsecond will look up\n//! the function in the jump table and call that instead.\n//!\n//! Unlike libraries like [detour](https://crates.io/crates/detour), Subsecond *does not* modify your\n//! process memory. Patching pointers is wildly unsafe and can lead to crashes and undefined behavior.\n//!\n//! Instead, an external tool compiles only the parts of your project that changed, links them together\n//! using the addresses of the functions in your running program, and then sends the new jump table to\n//! your application. Subsecond then applies the patch and continues running. Since Subsecond doesn't\n//! modify memory, the program must have a runtime integration to handle the patching.\n//!\n//! If the framework you're using doesn't integrate with subsecond, you can rely on the fact that calls\n//! to stale [`call`] instances will emit a safe panic that is automatically caught and retried\n//! by the next [`call`] instance up the callstack.\n//!\n//! Subsecond is only enabled when debug_assertions are enabled so you can safely ship your application\n//! with Subsecond enabled without worrying about the performance overhead.\n//!\n//! ## Workspace support\n//!\n//! Subsecond currently only patches the \"tip\" crate - ie the crate in which your `main.rs` is located.\n//! Changes to crates outside this crate will be ignored, which can be confusing. We plan to add full\n//! workspace support in the future, but for now be aware of this limitation. Crate setups that have\n//! a `main.rs` importing a `lib.rs` won't patch sensibly since the crate becomes a library for itself.\n//!\n//! This is due to limitations in rustc itself where the build-graph is non-deterministic and changes\n//! to functions that forward generics can cause a cascade of codegen changes.\n//!\n//! ## Globals, statics, and thread-locals\n//!\n//! Subsecond *does* support hot-reloading of globals, statics, and thread locals. However, there are several limitations:\n//!\n//! - You may add new globals at runtime, but their destructors will never be called.\n//! - Globals are tracked across patches, but renames are considered to be *new* globals.\n//! - Changes to static initializers will not be observed.\n//!\n//! Subsecond purposefully handles statics this way since many libraries like Dioxus and Tokio rely\n//! on persistent global runtimes.\n//!\n//! HUGE WARNING: Currently, thread-locals in the \"tip\" crate (the one being patched) will seemingly\n//! reset to their initial value on new patches. This is because we don't currently bind thread-locals\n//! in the patches to their original addresses in the main program. If you rely on thread-locals heavily\n//! in your tip crate, you should be aware of this. Sufficiently complex setups might crash or even\n//! segfault. We plan to fix this in the future, but for now, you should be aware of this limitation.\n//!\n//! ## Struct layout and alignment\n//!\n//! Subsecond currently does not support hot-reloading of structs. This is because the generated code\n//! assumes a particular layout and alignment of the struct. If layout or alignment change and new\n//! functions are called referencing an old version of the struct, the program will crash.\n//!\n//! To mitigate this, framework authors can integrate with Subsecond to either dispose of the old struct\n//! or to re-allocate the struct in a way that is compatible with the new layout. This is called \"re-instancing.\"\n//!\n//! In practice, frameworks that implement subsecond patching properly will throw out the old state\n//! and thus you should never witness a segfault due to misalignment or size changes. Frameworks are\n//! encouraged to aggressively dispose of old state that might cause size and alignment changes.\n//!\n//! We'd like to lift this limitation in the future by providing utilities to re-instantiate structs,\n//! but for now it's up to the framework authors to handle this. For example, Dioxus apps simply throw\n//! out the old state and rebuild it from scratch.\n//!\n//! ## Pointer versioning\n//!\n//! Currently, Subsecond does not \"version\" function pointers. We have plans to provide this metadata\n//! so framework authors can safely memoize changes without much runtime overhead. Frameworks like\n//! Dioxus and Bevy circumvent this issue by using the TypeID of structs passed to hot functions as\n//! well as the `ptr_address` method on [`HotFn`] to determine if the function pointer has changed.\n//!\n//! Currently, the `ptr_address` method will always return the most up-to-date version of the function\n//! even if the function contents itself did not change. In essence, this is equivalent to a version\n//! of the function where every function is considered \"new.\" This means that framework authors who\n//! integrate re-instancing in their apps might dispose of old state too aggressively. For now, this\n//! is the safer and more practical approach.\n//!\n//! ## Nesting Calls\n//!\n//! Subsecond calls are designed to be nested. This provides clean integration points to know exactly\n//! where a hooked function is called.\n//!\n//! The highest level call is `fn main()` though by default this is not hooked since initialization code\n//! tends to be side-effectual and modify global state. Instead, we recommend wrapping the hot-patch\n//! points manually with [`call`].\n//!\n//! ```rust\n//! fn main() {\n//!     // Changes to the `for` loop will cause an unwind to this call.\n//!     subsecond::call(|| {\n//!         for x in 0..5 {\n//!             // Changes to the `println!` will be isolated to this call.\n//!             subsecond::call(|| {\n//!                 println!(\"Hello, world! {}\", x);\n//!             });\n//!         }\n//!    });\n//! }\n//! ```\n//!\n//! The goal here is to provide granular control over where patches are applied to limit loss of state\n//! when new code is loaded.\n//!\n//! ## Applying patches\n//!\n//! When running under the Dioxus CLI, the `dx serve` command will automatically apply patches when\n//! changes are detected. Patches are delivered over the [Dioxus Devtools](https://crates.io/crates/dioxus-devtools)\n//! websocket protocol and received by corresponding websocket.\n//!\n//! If you're using Subsecond in your own application that doesn't have a runtime integration, you can\n//! build an integration using the [`apply_patch`] function. This function takes a `JumpTable` which\n//! the dioxus-cli crate can generate.\n//!\n//! To add support for the Dioxus Devtools protocol to your app, you can use the [dioxus-devtools](https://crates.io/crates/dioxus-devtools)\n//! crate which provides a `connect` method that will automatically apply patches to your application.\n//!\n//! Unfortunately, one design quirk of Subsecond is that running apps need to communicate the address\n//! of `main` to the patcher. This is due to a security technique called [ASLR](https://en.wikipedia.org/wiki/Address_space_layout_randomization)\n//! which randomizes the address of functions in memory. See the subsecond-harness and subsecond-cli\n//! for more details on how to implement the protocol.\n//!\n//! ## ThinLink\n//!\n//! ThinLink is a program linker for Rust that is designed to be used with Subsecond. It implements\n//! the powerful patching system that Subsecond uses to hot-reload Rust applications.\n//!\n//! ThinLink is simply a wrapper around your existing linker but with extra features:\n//!\n//! - Automatic dynamic linking to dependencies\n//! - Generation of Subsecond jump tables\n//! - Diffing of object files for function invalidation\n//!\n//! Because ThinLink performs very to little actual linking, it drastically speeds up traditional Rust\n//! development. With a development-optimized profile, ThinLink can shrink an incremental build to less than 500ms.\n//!\n//! ThinLink is automatically integrated into the Dioxus CLI though it's currently not available as\n//! a standalone tool.\n//!\n//! ## Limitations\n//!\n//! Subsecond is a powerful tool but it has several limitations. We talk about them above, but here's\n//! a quick summary:\n//!\n//! - Struct hot reloading requires instancing or unwinding\n//! - Statics are tracked but not destructed\n//!\n//! ## Platform support\n//!\n//! Subsecond works across all major platforms:\n//!\n//! - Android (arm64-v8a, armeabi-v7a)\n//! - iOS (arm64)\n//! - Linux (x86_64, aarch64)\n//! - macOS (x86_64, aarch64)\n//! - Windows (x86_64, arm64)\n//! - WebAssembly (wasm32)\n//!\n//! If you have a new platform you'd like to see supported, please open an issue on the Subsecond repository.\n//! We are keen to add support for new platforms like wasm64, riscv64, and more.\n//!\n//! Note that iOS device is currently not supported due to code-signing requirements. We hope to fix\n//! this in the future, but for now you can use the simulator to test your app.\n//!\n//! ## Adding the Subsecond badge to your project\n//!\n//! If you're a framework author and want your users to know that your library supports Subsecond, you\n//! can add the Subsecond badge to your README! Users will know that your library is hot-reloadable and\n//! can be used with Subsecond.\n//!\n//! [![Subsecond](https://img.shields.io/badge/Subsecond-Enabled-orange)](https://crates.io/crates/subsecond)\n//!\n//! ```markdown\n//! [![Subsecond](https://img.shields.io/badge/Subsecond-Enabled-orange)](https://crates.io/crates/subsecond)\n//! ```\n//!\n//! ## License\n//!\n//! Subsecond and ThinLink are licensed under the MIT license. See the LICENSE file for more information.\n//!\n//! ## Supporting this work\n//!\n//! Subsecond is a project by the Dioxus team. If you'd like to support our work, please consider\n//! [sponsoring us on GitHub](https://github.com/sponsors/DioxusLabs) or eventually deploying your\n//! apps with Dioxus Deploy (currently under construction).\n\npub use subsecond_types::JumpTable;\n\nuse std::{\n    backtrace,\n    mem::transmute,\n    panic::AssertUnwindSafe,\n    sync::{atomic::AtomicPtr, Arc, Mutex},\n};\n\n/// Call a given function with hot-reloading enabled. If the function's code changes, `call` will use\n/// the new version of the function. If code *above* the function changes, this will emit a panic\n/// that forces an unwind to the next [`call`] instance.\n///\n/// WASM/rust does not support unwinding, so [`call`] will not track dependency graph changes.\n/// If you are building a framework for use on WASM, you will need to use `Subsecond::HotFn` directly.\n///\n/// However, if you wrap your calling code in a future, you *can* simply drop the future which will\n/// cause `drop` to execute and get something similar to unwinding. Not great if refcells are open.\npub fn call<O>(mut f: impl FnMut() -> O) -> O {\n    // Only run in debug mode - the rest of this function will dissolve away\n    if !cfg!(debug_assertions) {\n        return f();\n    }\n\n    let mut hotfn = HotFn::current(f);\n    loop {\n        let res = std::panic::catch_unwind(AssertUnwindSafe(|| hotfn.call(())));\n\n        // If the call succeeds just return the result, otherwise we try to handle the panic if its our own.\n        let err = match res {\n            Ok(res) => return res,\n            Err(err) => err,\n        };\n\n        // If this is our panic then let's handle it, otherwise we just resume unwinding\n        let Some(_hot_payload) = err.downcast_ref::<HotFnPanic>() else {\n            std::panic::resume_unwind(err);\n        };\n    }\n}\n\n// We use an AtomicPtr with a leaked JumpTable and Relaxed ordering to give us a global jump table\n// with very little overhead. Reading this amounts of a Relaxed atomic load which basically\n// is no overhead. We might want to look into using a thread_local with a stop-the-world approach\n// just in case multiple threads try to call the jump table before synchronization with the runtime.\n// For Dioxus purposes, this is not a big deal, but for libraries like bevy which heavily rely on\n// multithreading, it might become an issue.\nstatic APP_JUMP_TABLE: AtomicPtr<JumpTable> = AtomicPtr::new(std::ptr::null_mut());\nstatic HOTRELOAD_HANDLERS: Mutex<Vec<Arc<dyn Fn() + Send + Sync>>> = Mutex::new(Vec::new());\n\n/// Register a function that will be called whenever a patch is applied.\n///\n/// This handler will be run immediately after the patch library is loaded into the process and the\n/// JumpTable has been set.\npub fn register_handler(handler: Arc<dyn Fn() + Send + Sync + 'static>) {\n    HOTRELOAD_HANDLERS.lock().unwrap().push(handler);\n}\n\n/// Get the current jump table, if it exists.\n///\n/// This will return `None` if no jump table has been set yet.\n///\n/// # Safety\n///\n/// The `JumpTable` returned here is a pointer into a leaked box. While technically this reference is\n/// valid, we might change the implementation to invalidate the pointer between hotpatches.\n///\n/// You should only use this lifetime in temporary contexts - not *across* hotpatches!\npub unsafe fn get_jump_table() -> Option<&'static JumpTable> {\n    let ptr = APP_JUMP_TABLE.load(std::sync::atomic::Ordering::Relaxed);\n    if ptr.is_null() {\n        return None;\n    }\n\n    Some(unsafe { &*ptr })\n}\nunsafe fn commit_patch(table: JumpTable) {\n    APP_JUMP_TABLE.store(\n        Box::into_raw(Box::new(table)),\n        std::sync::atomic::Ordering::Relaxed,\n    );\n    HOTRELOAD_HANDLERS\n        .lock()\n        .unwrap()\n        .clone()\n        .iter()\n        .for_each(|handler| {\n            handler();\n        });\n}\n\n/// A panic issued by the [`call`] function if the caller would be stale if called. This causes\n/// an unwind to the next [`call`] instance that can properly handle the panic and retry the call.\n///\n/// This technique allows Subsecond to provide hot-reloading of codebases that don't have a runtime integration.\n#[derive(Debug)]\npub struct HotFnPanic {\n    _backtrace: backtrace::Backtrace,\n}\n\n/// A pointer to a hot patched function\n#[non_exhaustive]\n#[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)]\npub struct HotFnPtr(pub u64);\n\nimpl HotFnPtr {\n    /// Create a new [`HotFnPtr`].\n    ///\n    /// The safe way to get one is through [`HotFn::ptr_address`].\n    ///\n    /// # Safety\n    ///\n    /// The underlying `u64` must point to a valid function.\n    pub unsafe fn new(index: u64) -> Self {\n        Self(index)\n    }\n}\n\n/// A hot-reloadable function.\n///\n/// To call this function, use the [`HotFn::call`] method. This will automatically use the latest\n/// version of the function from the JumpTable.\npub struct HotFn<A, M, F>\nwhere\n    F: HotFunction<A, M>,\n{\n    inner: F,\n    _marker: std::marker::PhantomData<(A, M)>,\n}\n\nimpl<A, M, F: HotFunction<A, M>> HotFn<A, M, F> {\n    /// Create a new [`HotFn`] instance with the current function.\n    ///\n    /// Whenever you call [`HotFn::call`], it will use the current function from the [`JumpTable`].\n    pub const fn current(f: F) -> HotFn<A, M, F> {\n        HotFn {\n            inner: f,\n            _marker: std::marker::PhantomData,\n        }\n    }\n\n    /// Call the function with the given arguments.\n    ///\n    /// This will unwrap the [`HotFnPanic`] panic, propagating up to the next [`HotFn::call`].\n    ///\n    /// If you want to handle the panic yourself, use [`HotFn::try_call`].\n    pub fn call(&mut self, args: A) -> F::Return {\n        self.try_call(args).unwrap()\n    }\n\n    /// Get the address of the function in memory which might be different than the original.\n    ///\n    /// This is useful for implementing a memoization strategy to safely preserve state across\n    /// hot-patches. If the ptr_address of a function did not change between patches, then the\n    /// state that exists \"above\" the function is still valid.\n    ///\n    /// Note that Subsecond does not track this state over time, so it's up to the runtime integration\n    /// to track this state and diff it.\n    pub fn ptr_address(&self) -> HotFnPtr {\n        if size_of::<F>() == size_of::<fn() -> ()>() {\n            let ptr: usize = unsafe { std::mem::transmute_copy(&self.inner) };\n            return HotFnPtr(ptr as u64);\n        }\n\n        let known_fn_ptr = <F as HotFunction<A, M>>::call_it as *const () as usize;\n        if let Some(jump_table) = unsafe { get_jump_table() } {\n            if let Some(ptr) = jump_table.map.get(&(known_fn_ptr as u64)).cloned() {\n                return HotFnPtr(ptr);\n            }\n        }\n\n        HotFnPtr(known_fn_ptr as u64)\n    }\n\n    /// Attempt to call the function with the given arguments.\n    ///\n    /// If this function is stale and can't be updated in place (ie, changes occurred above this call),\n    /// then this function will emit an [`HotFnPanic`] which can be unwrapped and handled by next [`call`]\n    /// instance.\n    pub fn try_call(&mut self, args: A) -> Result<F::Return, HotFnPanic> {\n        if !cfg!(debug_assertions) {\n            return Ok(self.inner.call_it(args));\n        }\n\n        unsafe {\n            // Try to handle known function pointers. This is *really really* unsafe, but due to how\n            // rust trait objects work, it's impossible to make an arbitrary usize-sized type implement Fn()\n            // since that would require a vtable pointer, pushing out the bounds of the pointer size.\n            if size_of::<F>() == size_of::<fn() -> ()>() {\n                return Ok(self.inner.call_as_ptr(args));\n            }\n\n            // Handle trait objects. This will occur for sizes other than usize. Normal rust functions\n            // become ZST's and thus their <T as SomeFn>::call becomes a function pointer to the function.\n            //\n            // For non-zst (trait object) types, then there might be an issue. The real call function\n            // will likely end up in the vtable and will never be hot-reloaded since signature takes self.\n            if let Some(jump_table) = get_jump_table() {\n                let known_fn_ptr = <F as HotFunction<A, M>>::call_it as *const () as u64;\n                if let Some(ptr) = jump_table.map.get(&known_fn_ptr).cloned() {\n                    // The type sig of the cast should match the call_it function\n                    // Technically function pointers need to be aligned, but that alignment is 1 so we're good\n                    let call_it = transmute::<*const (), fn(&F, A) -> F::Return>(ptr as _);\n                    return Ok(call_it(&self.inner, args));\n                }\n            }\n\n            Ok(self.inner.call_it(args))\n        }\n    }\n\n    /// Attempt to call the function with the given arguments, using the given [`HotFnPtr`].\n    ///\n    /// You can get a [`HotFnPtr`] from [`Self::ptr_address`].\n    ///\n    /// If this function is stale and can't be updated in place (ie, changes occurred above this call),\n    /// then this function will emit an [`HotFnPanic`] which can be unwrapped and handled by next [`call`]\n    /// instance.\n    ///\n    /// # Safety\n    ///\n    /// The [`HotFnPtr`] must be to a function whose arguments layouts haven't changed.\n    pub unsafe fn try_call_with_ptr(\n        &mut self,\n        ptr: HotFnPtr,\n        args: A,\n    ) -> Result<F::Return, HotFnPanic> {\n        if !cfg!(debug_assertions) {\n            return Ok(self.inner.call_it(args));\n        }\n\n        unsafe {\n            // Try to handle known function pointers. This is *really really* unsafe, but due to how\n            // rust trait objects work, it's impossible to make an arbitrary usize-sized type implement Fn()\n            // since that would require a vtable pointer, pushing out the bounds of the pointer size.\n            if size_of::<F>() == size_of::<fn() -> ()>() {\n                return Ok(self.inner.call_as_ptr(args));\n            }\n\n            // Handle trait objects. This will occur for sizes other than usize. Normal rust functions\n            // become ZST's and thus their <T as SomeFn>::call becomes a function pointer to the function.\n            //\n            // For non-zst (trait object) types, then there might be an issue. The real call function\n            // will likely end up in the vtable and will never be hot-reloaded since signature takes self.\n            // The type sig of the cast should match the call_it function\n            // Technically function pointers need to be aligned, but that alignment is 1 so we're good\n            let call_it = transmute::<*const (), fn(&F, A) -> F::Return>(ptr.0 as _);\n            Ok(call_it(&self.inner, args))\n        }\n    }\n}\n\n/// Apply the patch using a given jump table.\n///\n/// # Safety\n///\n/// This function is unsafe because it detours existing functions in memory. This is *wildly* unsafe,\n/// especially if the JumpTable is malformed. Only run this if you know what you're doing.\n///\n/// If the pointers are incorrect, function type signatures will be incorrect and the program will crash,\n/// sometimes in a way that requires a restart of your entire computer. Be careful.\n///\n/// # Warning\n///\n/// This function will load the library and thus allocates. In cannot be used when the program is\n/// stopped (ie in a signal handler).\npub unsafe fn apply_patch(mut table: JumpTable) -> Result<(), PatchError> {\n    // On non-wasm platforms we can just use libloading and the known aslr offsets to load the library\n    #[cfg(any(unix, windows))]\n    {\n        // on android we try to circumvent permissions issues by copying the library to a memmap and then libloading that\n        #[cfg(target_os = \"android\")]\n        let lib = Box::leak(Box::new(android_memmap_dlopen(&table.lib)?));\n\n        #[cfg(not(target_os = \"android\"))]\n        let lib = Box::leak(Box::new({\n            match libloading::Library::new(&table.lib) {\n                Ok(lib) => lib,\n                Err(err) => return Err(PatchError::Dlopen(err.to_string())),\n            }\n        }));\n\n        // Use the `main` symbol as a sentinel for the current executable. This is basically a\n        // cross-platform version of `__mh_execute_header` on macOS that we can use to base the executable.\n        let old_offset = aslr_reference() - table.aslr_reference as usize;\n\n        // Use the `main` symbol as a sentinel for the loaded library. Might want to move away\n        // from this at some point, or make it configurable\n        let new_offset = unsafe {\n            // Leak the library. dlopen is basically a no-op on many platforms and if we even try to drop it,\n            // some code might be called (ie drop) that results in really bad crashes (restart your computer...)\n            //\n            // This code currently assumes \"main\" always makes it to the export list (which it should)\n            // and requires coordination from the CLI to export it.\n            lib.get::<*const ()>(b\"main\")\n                .ok()\n                .unwrap()\n                .try_as_raw_ptr()\n                .unwrap()\n                .wrapping_byte_sub(table.new_base_address as usize) as usize\n        };\n\n        // Modify the jump table to be relative to the base address of the loaded library\n        table.map = table\n            .map\n            .iter()\n            .map(|(k, v)| {\n                (\n                    (*k as usize + old_offset) as u64,\n                    (*v as usize + new_offset) as u64,\n                )\n            })\n            .collect();\n\n        commit_patch(table);\n    };\n\n    // On wasm, we need to download the module, compile it, and then run it.\n    #[cfg(target_arch = \"wasm32\")]\n    wasm_bindgen_futures::spawn_local(async move {\n        use js_sys::{\n            ArrayBuffer, Object, Reflect,\n            WebAssembly::{self, Memory, Table},\n        };\n        use wasm_bindgen::prelude::*;\n        use wasm_bindgen::JsValue;\n        use wasm_bindgen::UnwrapThrowExt;\n        use wasm_bindgen_futures::JsFuture;\n\n        let funcs: Table = wasm_bindgen::function_table().unchecked_into();\n        let memory: Memory = wasm_bindgen::memory().unchecked_into();\n        let exports: Object = wasm_bindgen::exports().unchecked_into();\n        let buffer: ArrayBuffer = memory.buffer().unchecked_into();\n\n        let path = table.lib.to_str().unwrap();\n        if !path.ends_with(\".wasm\") {\n            return;\n        }\n\n        // Start the fetch of the module\n        let response = web_sys::window().unwrap_throw().fetch_with_str(&path);\n\n        // Wait for the fetch to complete - we need the wasm module size in bytes to reserve in the memory\n        let response: web_sys::Response = JsFuture::from(response).await.unwrap().unchecked_into();\n\n        // If the status is not success, we bail\n        if !response.ok() {\n            panic!(\n                \"Failed to patch wasm module at {} - response failed with: {}\",\n                path,\n                response.status_text()\n            );\n        }\n\n        let dl_bytes: ArrayBuffer = JsFuture::from(response.array_buffer().unwrap())\n            .await\n            .unwrap()\n            .unchecked_into();\n\n        // Expand the memory and table size to accommodate the new data and functions\n        //\n        // Normally we wouldn't be able to trust that we are allocating *enough* memory\n        // for BSS segments, but ld emits them in the binary when using import-memory.\n        //\n        // Make sure we align the memory base to the page size\n        const PAGE_SIZE: u32 = 64 * 1024;\n        let page_count = (buffer.byte_length() as f64 / PAGE_SIZE as f64).ceil() as u32;\n        let memory_base = (page_count + 1) * PAGE_SIZE;\n\n        // We need to grow the memory to accommodate the new module\n        memory.grow((dl_bytes.byte_length() as f64 / PAGE_SIZE as f64).ceil() as u32 + 1);\n\n        // We grow the ifunc table to accommodate the new functions\n        // In theory we could just put all the ifuncs in the jump map and use that for our count,\n        // but there's no guarantee from the jump table that it references \"itself\"\n        // We might need a sentinel value for each ifunc in the jump map to indicate that it is\n        let table_base = funcs.grow(table.ifunc_count as u32).unwrap();\n\n        // Adjust the jump table to be relative to the new base address\n        for v in table.map.values_mut() {\n            *v += table_base as u64;\n        }\n\n        // Build up the import object. We copy everything over from the current exports, but then\n        // need to add in the memory and table base offsets for the relocations to work.\n        //\n        // let imports = {\n        //     env: {\n        //         memory: base.memory,\n        //         __tls_base: base.__tls_base,\n        //         __stack_pointer: base.__stack_pointer,\n        //         __indirect_function_table: base.__indirect_function_table,\n        //         __memory_base: memory_base,\n        //         __table_base: table_base,\n        //        ..base_exports\n        //     },\n        // };\n        let env = Object::new();\n\n        // Move memory, __tls_base, __stack_pointer, __indirect_function_table, and all exports over\n        for key in Object::keys(&exports) {\n            Reflect::set(&env, &key, &Reflect::get(&exports, &key).unwrap()).unwrap();\n        }\n\n        // Set the memory and table in the imports\n        // Following this pattern: Global.new({ value: \"i32\", mutable: false }, value)\n        for (name, value) in [(\"__table_base\", table_base), (\"__memory_base\", memory_base)] {\n            let descriptor = Object::new();\n            Reflect::set(&descriptor, &\"value\".into(), &\"i32\".into()).unwrap();\n            Reflect::set(&descriptor, &\"mutable\".into(), &false.into()).unwrap();\n            let value = WebAssembly::Global::new(&descriptor, &value.into()).unwrap();\n            Reflect::set(&env, &name.into(), &value.into()).unwrap();\n        }\n\n        // Set the memory and table in the imports\n        let imports = Object::new();\n        Reflect::set(&imports, &\"env\".into(), &env).unwrap();\n\n        // Download the module, returning { module, instance }\n        // we unwrap here instead of using `?` since this whole thing is async\n        let result_object = JsFuture::from(WebAssembly::instantiate_module(\n            dl_bytes.unchecked_ref(),\n            &imports,\n        ))\n        .await\n        .unwrap();\n\n        // We need to run the data relocations and then fire off the constructors\n        let res: Object = result_object.unchecked_into();\n        let instance: Object = Reflect::get(&res, &\"instance\".into())\n            .unwrap()\n            .unchecked_into();\n        let exports: Object = Reflect::get(&instance, &\"exports\".into())\n            .unwrap()\n            .unchecked_into();\n        _ = Reflect::get(&exports, &\"__wasm_apply_data_relocs\".into())\n            .unwrap()\n            .unchecked_into::<js_sys::Function>()\n            .call0(&JsValue::undefined());\n        _ = Reflect::get(&exports, &\"__wasm_apply_global_relocs\".into())\n            .unwrap()\n            .unchecked_into::<js_sys::Function>()\n            .call0(&JsValue::undefined());\n        _ = Reflect::get(&exports, &\"__wasm_call_ctors\".into())\n            .unwrap()\n            .unchecked_into::<js_sys::Function>()\n            .call0(&JsValue::undefined());\n\n        unsafe { commit_patch(table) };\n    });\n\n    Ok(())\n}\n\n#[derive(Debug, PartialEq, thiserror::Error)]\npub enum PatchError {\n    /// The patch failed to apply.\n    ///\n    /// This returns a string instead of the Dlopen error type so we don't need to bring the libloading\n    /// dependency into the public API.\n    #[error(\"Failed to load library: {0}\")]\n    Dlopen(String),\n\n    /// The patch failed to apply on Android, most likely due to a permissions issue.\n    #[error(\"Failed to load library on Android: {0}\")]\n    AndroidMemfd(String),\n}\n\n/// This function returns the address of the main function in the current executable. This is used as\n/// an anchor to reference the current executable's base address.\n///\n/// The point here being that we have a stable address both at runtime and compile time, making it\n/// possible to calculate the ASLR offset from within the process to correct the jump table.\n///\n/// It should only be called from the main executable *first* and not from a shared library since it\n/// self-initializes.\n#[doc(hidden)]\npub fn aslr_reference() -> usize {\n    #[cfg(target_family = \"wasm\")]\n    return 0;\n\n    #[cfg(not(target_family = \"wasm\"))]\n    unsafe {\n        use std::ffi::c_void;\n\n        // The first call to this function should occur in the\n        static mut MAIN_PTR: *mut c_void = std::ptr::null_mut();\n\n        if MAIN_PTR.is_null() {\n            #[cfg(unix)]\n            {\n                MAIN_PTR = libc::dlsym(libc::RTLD_DEFAULT, c\"main\".as_ptr() as _);\n            }\n\n            #[cfg(windows)]\n            {\n                extern \"system\" {\n                    fn GetModuleHandleA(lpModuleName: *const i8) -> *mut std::ffi::c_void;\n                    fn GetProcAddress(\n                        hModule: *mut std::ffi::c_void,\n                        lpProcName: *const i8,\n                    ) -> *mut std::ffi::c_void;\n                }\n\n                MAIN_PTR =\n                    GetProcAddress(GetModuleHandleA(std::ptr::null()), c\"main\".as_ptr() as _) as _;\n            }\n        }\n\n        MAIN_PTR as usize\n    }\n}\n\n/// On Android, we can't dlopen libraries that aren't placed inside /data/data/<package_name>/lib/\n///\n/// If the device isn't rooted, then we can't push the library there.\n/// This is a workaround to copy the library to a memfd and then dlopen it.\n///\n/// I haven't tested it on device yet, so if if it doesn't work, then we can simply revert to using\n/// \"adb root\" and then pushing the library to the /data/data folder instead of the tmp folder.\n///\n/// Android provides us a flag when calling dlopen to use a file descriptor instead of a path, presumably\n/// because they want to support this.\n/// - https://developer.android.com/ndk/reference/group/libdl\n/// - https://developer.android.com/ndk/reference/structandroid/dlextinfo\n#[cfg(target_os = \"android\")]\nunsafe fn android_memmap_dlopen(file: &std::path::Path) -> Result<libloading::Library, PatchError> {\n    use std::ffi::{c_void, CStr, CString};\n    use std::os::fd::{AsRawFd, BorrowedFd};\n    use std::ptr;\n\n    #[repr(C)]\n    struct ExtInfo {\n        flags: u64,\n        reserved_addr: *const c_void,\n        reserved_size: libc::size_t,\n        relro_fd: libc::c_int,\n        library_fd: libc::c_int,\n        library_fd_offset: libc::off64_t,\n        library_namespace: *const c_void,\n    }\n\n    extern \"C\" {\n        fn android_dlopen_ext(\n            filename: *const libc::c_char,\n            flags: libc::c_int,\n            ext_info: *const ExtInfo,\n        ) -> *const c_void;\n    }\n\n    use memmap2::MmapAsRawDesc;\n    use std::os::unix::prelude::{FromRawFd, IntoRawFd};\n\n    let contents = std::fs::read(file)\n        .map_err(|e| PatchError::AndroidMemfd(format!(\"Failed to read file: {}\", e)))?;\n    let mut mfd = memfd::MemfdOptions::default()\n        .create(\"subsecond-patch\")\n        .map_err(|e| PatchError::AndroidMemfd(format!(\"Failed to create memfd: {}\", e)))?;\n    mfd.as_file()\n        .set_len(contents.len() as u64)\n        .map_err(|e| PatchError::AndroidMemfd(format!(\"Failed to set memfd length: {}\", e)))?;\n\n    let raw_fd = mfd.into_raw_fd();\n\n    let mut map = memmap2::MmapMut::map_mut(raw_fd)\n        .map_err(|e| PatchError::AndroidMemfd(format!(\"Failed to map memfd: {}\", e)))?;\n    map.copy_from_slice(&contents);\n    let map = map\n        .make_exec()\n        .map_err(|e| PatchError::AndroidMemfd(format!(\"Failed to make memfd executable: {}\", e)))?;\n\n    let filename = c\"/subsecond-patch\";\n\n    let info = ExtInfo {\n        flags: 0x10, // ANDROID_DLEXT_USE_LIBRARY_FD\n        reserved_addr: ptr::null(),\n        reserved_size: 0,\n        relro_fd: 0,\n        library_fd: raw_fd,\n        library_fd_offset: 0,\n        library_namespace: ptr::null(),\n    };\n\n    let flags = libloading::os::unix::RTLD_LAZY | libloading::os::unix::RTLD_LOCAL;\n\n    let handle = libloading::os::unix::with_dlerror(\n        || {\n            let ptr = android_dlopen_ext(filename.as_ptr() as _, flags, &info);\n            if ptr.is_null() {\n                return None;\n            } else {\n                return Some(ptr);\n            }\n        },\n        |err| err.to_str().unwrap_or_default().to_string(),\n    )\n    .map_err(|e| {\n        PatchError::AndroidMemfd(format!(\n            \"android_dlopen_ext failed: {}\",\n            e.unwrap_or_default()\n        ))\n    })?;\n\n    let lib = unsafe { libloading::os::unix::Library::from_raw(handle as *mut c_void) };\n    let lib: libloading::Library = lib.into();\n    Ok(lib)\n}\n\n/// A trait that enables types to be hot-patched.\n///\n/// This trait is only implemented for FnMut types which naturally includes function pointers and\n/// closures that can be re-ran. FnOnce closures are currently not supported since the hot-patching\n/// system we use implies that the function can be called multiple times.\npub trait HotFunction<Args, Marker> {\n    /// The return type of the function.\n    type Return;\n\n    /// The real function type. This is meant to be a function pointer.\n    /// When we call `call_as_ptr`, we will transmute the function to this type and call it.\n    type Real;\n\n    /// Call the HotFunction with the given arguments.\n    ///\n    /// # Why\n    ///\n    /// \"rust-call\" isn't stable, so we wrap the underlying call with our own, giving it a stable vtable entry.\n    /// This is more important than it seems since this function becomes \"real\" and can be hot-patched.\n    fn call_it(&mut self, args: Args) -> Self::Return;\n\n    /// Call the HotFunction as if it were a function pointer.\n    ///\n    /// # Safety\n    ///\n    /// This is only safe if the underlying type is a function (function pointer or virtual/fat pointer).\n    /// Using this will use the JumpTable to find the patched function and call it.\n    unsafe fn call_as_ptr(&mut self, _args: Args) -> Self::Return;\n}\n\nmacro_rules! impl_hot_function {\n    (\n        $(\n            ($marker:ident, $($arg:ident),*)\n        ),*\n    ) => {\n        $(\n            /// A marker type for the function.\n            /// This is hidden with the intention to seal this trait.\n            #[doc(hidden)]\n            pub struct $marker;\n\n            impl<T, $($arg,)* R> HotFunction<($($arg,)*), $marker> for T\n            where\n                T: FnMut($($arg),*) -> R,\n            {\n                type Return = R;\n                type Real = fn($($arg),*) -> R;\n\n                fn call_it(&mut self, args: ($($arg,)*)) -> Self::Return {\n                    #[allow(non_snake_case)]\n                    let ( $($arg,)* ) = args;\n                    self($($arg),*)\n                }\n\n                unsafe fn call_as_ptr(&mut self, args: ($($arg,)*)) -> Self::Return {\n                    unsafe {\n                        if let Some(jump_table) = get_jump_table() {\n                            let real = std::mem::transmute_copy::<Self, Self::Real>(&self) as *const ();\n\n                            // Android implements MTE / pointer tagging and we need to preserve the tag.\n                            // If we leave the tag, then indexing our jump table will fail and patching won't work (or crash!)\n                            // This is only implemented on 64-bit platforms since pointer tagging is not available on 32-bit platforms\n                            // In dev, Dioxus disables MTE to work around this issue, but we still handle it anyways.\n                            #[cfg(all(target_pointer_width = \"64\", target_os = \"android\"))] let nibble  = real as u64 & 0xFF00_0000_0000_0000;\n                            #[cfg(all(target_pointer_width = \"64\", target_os = \"android\"))] let real    = real as u64 & 0x00FFF_FFF_FFFF_FFFF;\n\n                            #[cfg(target_pointer_width = \"64\")] let real  = real as u64;\n\n                            // No nibble on 32-bit platforms, but we still need to assume u64 since the host always writes 64-bit addresses\n                            #[cfg(target_pointer_width = \"32\")] let real = real as u64;\n\n                            if let Some(ptr) = jump_table.map.get(&real).cloned() {\n                                // Re-apply the nibble - though this might not be required (we aren't calling malloc for a new pointer)\n                                #[cfg(all(target_pointer_width = \"64\", target_os = \"android\"))] let ptr: u64 = ptr | nibble;\n\n                                #[cfg(target_pointer_width = \"64\")] let ptr: u64 = ptr;\n                                #[cfg(target_pointer_width = \"32\")] let ptr: u32 = ptr as u32;\n\n                                // Macro-rules requires unpacking the tuple before we call it\n                                #[allow(non_snake_case)]\n                                let ( $($arg,)* ) = args;\n\n\n                                #[cfg(target_pointer_width = \"64\")]\n                                type PtrWidth = u64;\n                                #[cfg(target_pointer_width = \"32\")]\n                                type PtrWidth = u32;\n\n                                return std::mem::transmute::<PtrWidth, Self::Real>(ptr)($($arg),*);\n                            }\n                        }\n\n                        self.call_it(args)\n                    }\n                }\n            }\n        )*\n    };\n}\n\nimpl_hot_function!(\n    (Fn0Marker,),\n    (Fn1Marker, A),\n    (Fn2Marker, A, B),\n    (Fn3Marker, A, B, C),\n    (Fn4Marker, A, B, C, D),\n    (Fn5Marker, A, B, C, D, E),\n    (Fn6Marker, A, B, C, D, E, F),\n    (Fn7Marker, A, B, C, D, E, F, G),\n    (Fn8Marker, A, B, C, D, E, F, G, H),\n    (Fn9Marker, A, B, C, D, E, F, G, H, I)\n);\n"
  },
  {
    "path": "packages/subsecond/subsecond-tests/cross-tls-crate/Cargo.toml",
    "content": "[package]\nname = \"cross-tls-crate\"\nversion = \"0.1.0\"\nedition = \"2021\"\npublish = false\n\n[dependencies]\n"
  },
  {
    "path": "packages/subsecond/subsecond-tests/cross-tls-crate/src/lib.rs",
    "content": "pub use std::cell::Cell;\nuse std::{cell::RefCell, thread::LocalKey};\n\n#[derive(Debug)]\npub struct StoredItem {\n    pub name: String,\n    pub value: f32,\n    pub items: Vec<String>,\n}\n\nthread_local! {\n    pub static BAR: RefCell<Option<StoredItem>> = const { RefCell::new(None) };\n}\n\npub fn get_bar() -> &'static LocalKey<RefCell<Option<StoredItem>>> {\n    if BAR.with(|f| f.borrow().is_none()) {\n        BAR.set(Some(StoredItem {\n            name: \"BAR\".to_string(),\n            value: 0.0,\n            items: vec![\"item1\".to_string(), \"item2\".to_string()],\n        }));\n    }\n\n    &BAR\n}\n"
  },
  {
    "path": "packages/subsecond/subsecond-tests/cross-tls-crate-dylib/Cargo.toml",
    "content": "[package]\nname = \"cross-tls-crate-dylib\"\nversion = \"0.1.0\"\nedition = \"2021\"\npublish = false\n\n[lib]\n# when testing, use a dylib\n# crate-type = [\"dylib\"]\n\n[dependencies]\n"
  },
  {
    "path": "packages/subsecond/subsecond-tests/cross-tls-crate-dylib/src/lib.rs",
    "content": "pub use std::cell::Cell;\nuse std::{cell::RefCell, thread::LocalKey};\n\n#[derive(Debug)]\npub struct StoredItem {\n    pub name: String,\n    pub value: f32,\n    pub items: Vec<String>,\n}\n\nthread_local! {\n    pub static BAZ: RefCell<Option<StoredItem>> = const { RefCell::new(None) };\n}\n\npub fn get_baz() -> &'static LocalKey<RefCell<Option<StoredItem>>> {\n    if BAZ.with(|f| f.borrow().is_none()) {\n        BAZ.set(Some(StoredItem {\n            name: \"BAR\".to_string(),\n            value: 0.0,\n            items: vec![\"item1\".to_string(), \"item2\".to_string()],\n        }));\n    }\n\n    &BAZ\n}\n"
  },
  {
    "path": "packages/subsecond/subsecond-tests/cross-tls-test/Cargo.toml",
    "content": "[package]\nname = \"subsecond-tls-harness\"\nversion = \"0.1.0\"\nedition = \"2021\"\npublish = false\n\n[dependencies]\ndioxus-devtools = { workspace = true }\ncross-tls-crate = { path = \"../cross-tls-crate\" }\ncross-tls-crate-dylib = { path = \"../cross-tls-crate-dylib\" }\n"
  },
  {
    "path": "packages/subsecond/subsecond-tests/cross-tls-test/README.md",
    "content": "harness for testing cross-crate TLS imports.\n\nTLS is the hardest to get right with binary patchinig since we need to rewrite initializers.\n\nthis crate relies on two crates - one as a dylib and the other as an rlib\n\nthe dylib should make it into our bundle and the rlib should just be linked.\n\nboth shouldn't cause access errors or segfaults during hotpatch.\n\n```sh\ncargo run --package dioxus-cli -- serve --hotpatch\n```\n"
  },
  {
    "path": "packages/subsecond/subsecond-tests/cross-tls-test/src/main.rs",
    "content": "use std::{cell::Cell, thread, time::Duration};\n\nuse cross_tls_crate::get_bar;\nuse cross_tls_crate_dylib::get_baz;\n\nfn main() {\n    dioxus_devtools::connect_subsecond();\n    loop {\n        dioxus_devtools::subsecond::call(|| {\n            use cross_tls_crate::BAR;\n            use cross_tls_crate_dylib::BAZ;\n\n            thread_local! {\n                pub static FOO: Cell<f32> = const { Cell::new(2.0) };\n            }\n\n            println!(\"Hello  123s123123s: {}\", FOO.get());\n            get_bar().with(|f| println!(\"Bar: {:?}\", f.borrow()));\n            thread::sleep(Duration::from_secs(1));\n\n            FOO.set(2.0);\n            get_bar().with(|f| f.borrow_mut().as_mut().unwrap().value = 3.0);\n            get_baz().with(|f| f.borrow_mut().as_mut().unwrap().value = 4.0);\n\n            BAR.with_borrow(|f| {\n                println!(\"Bar: {:?}\", f);\n            });\n            BAZ.with_borrow(|f| {\n                println!(\"Baz: {:?}\", f);\n            });\n        });\n    }\n}\n"
  },
  {
    "path": "packages/subsecond/subsecond-types/Cargo.toml",
    "content": "[package]\nname = \"subsecond-types\"\nversion.workspace = true\nedition = \"2021\"\nauthors = [\"Jonathan Kelley\"]\ndescription = \"Types crate for the Subsecond hotpatch engine.\"\nlicense = \"MIT OR Apache-2.0\"\nrepository = \"https://github.com/DioxusLabs/dioxus/tree/main/packages/subsecond\"\nkeywords = [\"hotpatch\", \"engine\", \"subsecond\", \"dioxus\"]\nreadme = \"../README.md\"\n\n[dependencies]\nserde = { workspace = true, features = [\"derive\"] }\n"
  },
  {
    "path": "packages/subsecond/subsecond-types/src/lib.rs",
    "content": "use serde::{Deserialize, Serialize};\nuse std::{\n    collections::HashMap,\n    hash::{BuildHasherDefault, Hasher},\n    path::PathBuf,\n};\n\n#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]\npub struct JumpTable {\n    /// The dylib containing the patch. This should be a valid path so you can just pass it to LibLoading\n    ///\n    /// On wasm you will need to fetch() this file and then pass it to the WebAssembly.instantiate() function\n    pub lib: PathBuf,\n\n    /// old -> new\n    /// does not take into account the base address of the patch when loaded into memory - need dlopen for that\n    ///\n    /// These are intended to be `*const ()` pointers but need to be `u64` for the hashmap. On 32-bit platforms\n    /// you will need to cast to `usize` before using them.\n    pub map: AddressMap,\n\n    /// the address of the base address of the old original binary\n    ///\n    /// machos: this is the address of the `_mh_execute_header` symbol usually at 0x100000000 and loaded near 0x100000000\n    /// linux: this is the address of the `__executable_start` symbol usually at 0x0 but loaded around 0x555555550000\n    /// windows: this is the address of the `ImageBase` field of the PE header\n    /// wasm: not useful since there's no ASLR\n    ///\n    /// While we can generally guess that these values are, it's possible they are different and thus reading\n    /// them dynamically is worthwhile.\n    pub aslr_reference: u64,\n\n    /// the address of the base address of the new binary\n    ///\n    /// machos: this is the address of the `_mh_execute_header` symbol usually at 0x100000000 and loaded near 0x100000000\n    /// linux: this is the address of the `__executable_start` symbol usually at 0x0 but loaded around 0x555555550000\n    /// windows: this is the address of the `ImageBase` field of the PE header\n    /// wasm: not useful since there's no ASLR\n    ///\n    /// While we can generally guess that these values are, it's possible they are different and thus reading\n    /// them dynamically is worthwhile.\n    pub new_base_address: u64,\n\n    /// The amount of ifuncs this will register. This is used by WASM to know how much space to allocate\n    /// for the ifuncs in the ifunc table\n    pub ifunc_count: u64,\n}\n\n/// An address to address hashmap that does not hash addresses since addresses are by definition unique.\npub type AddressMap = HashMap<u64, u64, BuildAddressHasher>;\npub type BuildAddressHasher = BuildHasherDefault<AddressHasher>;\n\n#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]\npub struct AddressHasher(u64);\nimpl Hasher for AddressHasher {\n    fn write(&mut self, _: &[u8]) {\n        panic!(\"Invalid use of NoHashHasher\")\n    }\n    fn write_u8(&mut self, n: u8) {\n        self.0 = u64::from(n)\n    }\n    fn write_u16(&mut self, n: u16) {\n        self.0 = u64::from(n)\n    }\n    fn write_u32(&mut self, n: u32) {\n        self.0 = u64::from(n)\n    }\n    fn write_u64(&mut self, n: u64) {\n        self.0 = n\n    }\n    fn write_usize(&mut self, n: usize) {\n        self.0 = n as u64\n    }\n    fn write_i8(&mut self, n: i8) {\n        self.0 = n as u64\n    }\n    fn write_i16(&mut self, n: i16) {\n        self.0 = n as u64\n    }\n    fn write_i32(&mut self, n: i32) {\n        self.0 = n as u64\n    }\n    fn write_i64(&mut self, n: i64) {\n        self.0 = n as u64\n    }\n    fn write_isize(&mut self, n: isize) {\n        self.0 = n as u64\n    }\n    fn finish(&self) -> u64 {\n        self.0\n    }\n}\n"
  },
  {
    "path": "packages/wasm-split/README.md",
    "content": "# This folder contains the wasm-split sub-workspace\n\nwasm-split is a tool that allows you to split a wasm binary into multiple chunks that are lazily loaded on demand.\n\nThis workspace is comprised of:\n- the harness on which we test against\n- the wasm-split user-facing crate\n- the wasm-split-macro crate which is used to generate the wasm-split loader\n- the wasm-split-cli crate which is imported by the dioxus-cli and used when building the wasm modules\n- the wasm-used crate which provides a custom walrus `Used` struct that makes it easier to debug why a emit_wasm might be failing\n"
  },
  {
    "path": "packages/wasm-split/wasm-split/Cargo.toml",
    "content": "[package]\nname = \"wasm-splitter\"\nversion = { workspace = true }\nedition = \"2021\"\nauthors = [\"Jonathan Kelley\"]\ndescription = \"A tool for splitting up large WASM binaries into smaller chunks\"\nrepository = \"https://github.com/DioxusLabs/dioxus/\"\nlicense = \"MIT OR Apache-2.0\"\nkeywords = [\"wasm\", \"cli\", \"split\", \"dioxus\"]\nrust-version = \"1.81.0\"\n\n\n[dependencies]\nasync-once-cell = { workspace = true, features = [\"std\"] }\nwasm-split-macro = { workspace = true }\n"
  },
  {
    "path": "packages/wasm-split/wasm-split/src/lib.rs",
    "content": "use std::{\n    cell::Cell,\n    ffi::c_void,\n    future::Future,\n    pin::Pin,\n    rc::Rc,\n    task::{Context, Poll, Waker},\n    thread::LocalKey,\n};\n\npub use wasm_split_macro::{lazy_loader, wasm_split};\n\npub type Result<T> = std::result::Result<T, SplitLoaderError>;\n\n#[non_exhaustive]\n#[derive(Debug, Clone)]\npub enum SplitLoaderError {\n    FailedToLoad,\n}\nimpl std::fmt::Display for SplitLoaderError {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        match self {\n            SplitLoaderError::FailedToLoad => write!(f, \"Failed to load wasm-split module\"),\n        }\n    }\n}\n\n/// A lazy loader that can be used to load a function from a split out `.wasm` file.\n///\n/// # Example\n///\n/// To use the split loader, you must first create the loader using the `lazy_loader` macro. This macro\n/// requires the complete signature of the function you want to load. The extern abi string denotes\n/// which module the function should be loaded from. If you don't know which module to use, use `auto`\n/// and wasm-split will automatically combine all the modules into one.\n///\n/// ```rust, ignore\n/// static LOADER: wasm_split::LazyLoader<Args, Ret> = wasm_split::lazy_loader!(extern \"auto\" fn SomeFunction(args: Args) -> Ret);\n///\n/// fn SomeFunction(args: Args) -> Ret {\n///     // Implementation\n/// }\n/// ```\n///\n/// ## The `#[component(lazy)]` macro\n///\n/// If you're using wasm-split with Dioxus, the `#[component(lazy)]` macro is provided that wraps\n/// the lazy loader with suspense. This means that the component will suspense until its body has\n/// been loaded.\n///\n/// ```rust, ignore\n/// fn app() -> Element {\n///     rsx! {\n///         Suspense {\n///             fallback: rsx! { \"Loading...\" },\n///             LazyComponent { abc: 0 }\n///         }\n///     }\n/// }\n///\n/// #[component(lazy)]\n/// fn LazyComponent(abc: i32) -> Element {\n///     rsx! {\n///         div {\n///             \"This is a lazy component! {abc}\"\n///         }\n///     }\n/// }\n/// ```\npub struct LazyLoader<Args, Ret> {\n    imported: unsafe extern \"C\" fn(arg: Args) -> Ret,\n    key: &'static LocalKey<LazySplitLoader>,\n}\n\nimpl<Args, Ret> LazyLoader<Args, Ret> {\n    /// Create a new lazy loader from a lazy imported function and a LazySplitLoader\n    ///\n    /// # Safety\n    /// This is unsafe because we're taking an arbitrary function pointer and using it as the loader.\n    /// This function is likely not instantiated when passed here, so it should never be called directly.\n    #[doc(hidden)]\n    pub const unsafe fn new(\n        imported: unsafe extern \"C\" fn(arg: Args) -> Ret,\n        key: &'static LocalKey<LazySplitLoader>,\n    ) -> Self {\n        Self { imported, key }\n    }\n\n    /// Create a new lazy loader that is already resolved.\n    pub const fn preloaded(f: fn(Args) -> Ret) -> Self {\n        let imported =\n            unsafe { std::mem::transmute::<fn(Args) -> Ret, unsafe extern \"C\" fn(Args) -> Ret>(f) };\n\n        thread_local! {\n            static LAZY: LazySplitLoader = LazySplitLoader::preloaded();\n        };\n\n        Self {\n            imported,\n            key: &LAZY,\n        }\n    }\n\n    /// Load the lazy loader, returning an boolean indicating whether it loaded successfully\n    pub async fn load(&'static self) -> bool {\n        *self.key.with(|inner| inner.lazy.clone()).as_ref().await\n    }\n\n    /// Call the lazy loader with the given arguments\n    pub fn call(&'static self, args: Args) -> Result<Ret> {\n        let Some(true) = self.key.with(|inner| inner.lazy.try_get().copied()) else {\n            return Err(SplitLoaderError::FailedToLoad);\n        };\n\n        Ok(unsafe { (self.imported)(args) })\n    }\n}\n\ntype Lazy = async_once_cell::Lazy<bool, SplitLoaderFuture>;\ntype LoadCallbackFn = unsafe extern \"C\" fn(*const c_void, bool) -> ();\ntype LoadFn = unsafe extern \"C\" fn(LoadCallbackFn, *const c_void) -> ();\n\npub struct LazySplitLoader {\n    lazy: Pin<Rc<Lazy>>,\n}\n\nimpl LazySplitLoader {\n    /// Create a new lazy split loader from a load function that is generated by the wasm-split macro\n    ///\n    /// # Safety\n    ///\n    /// This is unsafe because we're taking an arbitrary function pointer and using it as the loader.\n    /// It is likely not instantiated when passed here, so it should never be called directly.\n    #[doc(hidden)]\n    pub unsafe fn new(load: LoadFn) -> Self {\n        Self {\n            lazy: Rc::pin(Lazy::new({\n                SplitLoaderFuture {\n                    loader: Rc::new(SplitLoader {\n                        state: Cell::new(SplitLoaderState::Deferred(load)),\n                        waker: Cell::new(None),\n                    }),\n                }\n            })),\n        }\n    }\n\n    fn preloaded() -> Self {\n        Self {\n            lazy: Rc::pin(Lazy::new({\n                SplitLoaderFuture {\n                    loader: Rc::new(SplitLoader {\n                        state: Cell::new(SplitLoaderState::Completed(true)),\n                        waker: Cell::new(None),\n                    }),\n                }\n            })),\n        }\n    }\n\n    /// Wait for the lazy loader to load\n    pub async fn ensure_loaded(loader: &'static std::thread::LocalKey<LazySplitLoader>) -> bool {\n        *loader.with(|inner| inner.lazy.clone()).as_ref().await\n    }\n}\n\nstruct SplitLoader {\n    state: Cell<SplitLoaderState>,\n    waker: Cell<Option<Waker>>,\n}\n\n#[derive(Clone, Copy)]\nenum SplitLoaderState {\n    Deferred(LoadFn),\n    Pending,\n    Completed(bool),\n}\n\nstruct SplitLoaderFuture {\n    loader: Rc<SplitLoader>,\n}\n\nimpl Future for SplitLoaderFuture {\n    type Output = bool;\n\n    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<bool> {\n        unsafe extern \"C\" fn load_callback(loader: *const c_void, success: bool) {\n            let loader = unsafe { Rc::from_raw(loader as *const SplitLoader) };\n            loader.state.set(SplitLoaderState::Completed(success));\n            if let Some(waker) = loader.waker.take() {\n                waker.wake()\n            }\n        }\n\n        match self.loader.state.get() {\n            SplitLoaderState::Deferred(load) => {\n                self.loader.state.set(SplitLoaderState::Pending);\n                self.loader.waker.set(Some(cx.waker().clone()));\n                unsafe {\n                    load(\n                        load_callback,\n                        Rc::<SplitLoader>::into_raw(self.loader.clone()) as *const c_void,\n                    )\n                };\n                Poll::Pending\n            }\n            SplitLoaderState::Pending => {\n                self.loader.waker.set(Some(cx.waker().clone()));\n                Poll::Pending\n            }\n            SplitLoaderState::Completed(value) => Poll::Ready(value),\n        }\n    }\n}\n"
  },
  {
    "path": "packages/wasm-split/wasm-split-cli/Cargo.toml",
    "content": "[package]\nname = \"wasm-split-cli\"\nedition = \"2021\"\nversion = { workspace = true }\nauthors = [\"Jonathan Kelley\"]\ndescription = \"CLI-support for wasm-split - a tool for splitting up large WASM binaries into smaller chunks\"\nrepository = \"https://github.com/DioxusLabs/dioxus/\"\nlicense = \"MIT OR Apache-2.0\"\nkeywords = [\"wasm\", \"cli\", \"split\", \"dioxus\"]\nrust-version = \"1.81.0\"\n\n\n[dependencies]\nanyhow =  { workspace = true }\nitertools = { workspace = true }\nwalrus = { workspace = true, features = [\"parallel\"]}\nwasmparser = { workspace = true }\nid-arena = { workspace = true }\nrayon = { workspace = true }\ntracing-subscriber = { workspace = true, features = [\"env-filter\", \"fmt\"] }\ntracing = { workspace = true }\nclap = { workspace = true, features = [\"derive\"] }\nwasm-used = { workspace = true}\n"
  },
  {
    "path": "packages/wasm-split/wasm-split-cli/data/.gitignore",
    "content": "# this folder contains the wasm output for the test harnessa, so ignore it\n\n*.wasm\nbindgen/\n"
  },
  {
    "path": "packages/wasm-split/wasm-split-cli/src/__wasm_split.js",
    "content": "// when running the harness we need to make sure to uncommon this out...\n\nexport function makeLoad(url, deps, fusedImports, initIt) {\n  let alreadyLoaded = false;\n  return async (callbackIndex, callbackData) => {\n    await Promise.all(deps.map((dep) => dep()));\n    if (alreadyLoaded) return;\n    try {\n      const response = await fetch(url);\n      const initSync = initIt || globalThis.__wasm_split_main_initSync;\n      const mainExports = initSync(undefined, undefined);\n\n      let imports = {\n        env: {\n          memory: mainExports.memory,\n        },\n        __wasm_split: {\n          __indirect_function_table: mainExports.__indirect_function_table,\n          __stack_pointer: mainExports.__stack_pointer,\n          __tls_base: mainExports.__tls_base,\n          memory: mainExports.memory,\n        },\n      };\n\n      for (let mainExport in mainExports) {\n        imports[\"__wasm_split\"][mainExport] = mainExports[mainExport];\n      }\n\n      for (let name in fusedImports) {\n        imports[\"__wasm_split\"][name] = fusedImports[name];\n      }\n\n      let new_exports = await WebAssembly.instantiateStreaming(\n        response,\n        imports\n      );\n\n      alreadyLoaded = true;\n\n      for (let name in new_exports.instance.exports) {\n        fusedImports[name] = new_exports.instance.exports[name];\n      }\n\n      if (callbackIndex !== undefined) {\n        mainExports.__indirect_function_table.get(callbackIndex)(\n          callbackData,\n          true\n        );\n      }\n    } catch (e) {\n      console.error(\n        \"Failed to load wasm-split module\",\n        e,\n        url,\n        deps,\n        fusedImports\n      );\n      return;\n    }\n  };\n}\n\nlet fusedImports = {};\n"
  },
  {
    "path": "packages/wasm-split/wasm-split-cli/src/lib.rs",
    "content": "use anyhow::{Context, Result};\nuse itertools::Itertools;\nuse rayon::prelude::{IntoParallelIterator, ParallelIterator};\nuse std::{\n    collections::{BTreeMap, BTreeSet, HashMap, HashSet, VecDeque},\n    hash::Hash,\n    ops::Range,\n    sync::{Arc, RwLock},\n};\nuse walrus::{\n    ir::{self, dfs_in_order, Visitor},\n    ConstExpr, DataKind, ElementItems, ElementKind, ExportId, ExportItem, FunctionBuilder,\n    FunctionId, FunctionKind, GlobalKind, ImportId, ImportKind, Module, ModuleConfig, RefType,\n    TableId, TypeId,\n};\nuse wasmparser::{\n    BinaryReader, Linking, LinkingSectionReader, Payload, RelocSectionReader, RelocationEntry,\n    SymbolInfo,\n};\n\npub const MAKE_LOAD_JS: &str = include_str!(\"./__wasm_split.js\");\n\n/// A parsed wasm module with additional metadata and functionality for splitting and patching.\n///\n/// This struct assumes that relocations will be present in incoming wasm binary.\n/// Upon construction, all the required metadata will be constructed.\npub struct Splitter<'a> {\n    /// The original module we use as a reference\n    source_module: Module,\n\n    // The byte sources of the pre and post wasm-bindgen .wasm files\n    // We need the original around since wasm-bindgen ruins the relocation locations.\n    original: &'a [u8],\n    bindgened: &'a [u8],\n\n    // Mapping of indices of source functions\n    // This lets us use a much faster approach to emitting split modules simply by maintaining a mapping\n    // between the original Module and the new Module. Ideally we could just index the new module\n    // with old FunctionIds but the underlying IndexMap actually checks that a key belongs to a particular\n    // arena.\n    fns_to_ids: HashMap<FunctionId, usize>,\n    _ids_to_fns: Vec<FunctionId>,\n\n    shared_symbols: BTreeSet<Node>,\n    split_points: Vec<SplitPoint>,\n    chunks: Vec<HashSet<Node>>,\n    data_symbols: BTreeMap<usize, DataSymbol>,\n    main_graph: HashSet<Node>,\n    call_graph: HashMap<Node, HashSet<Node>>,\n    parent_graph: HashMap<Node, HashSet<Node>>,\n}\n\n/// The results of splitting the wasm module with some additional metadata for later use.\npub struct OutputModules {\n    /// The main chunk\n    pub main: SplitModule,\n\n    /// The modules of the wasm module that were split.\n    pub modules: Vec<SplitModule>,\n\n    /// The chunks that might be imported by the main modules\n    pub chunks: Vec<SplitModule>,\n}\n\n/// A wasm module that was split from the main module.\n///\n/// All IDs here correspond to *this* module - not the parent main module\npub struct SplitModule {\n    pub module_name: String,\n    pub hash_id: Option<String>,\n    pub component_name: Option<String>,\n    pub bytes: Vec<u8>,\n    pub relies_on_chunks: HashSet<usize>,\n}\n\nimpl<'a> Splitter<'a> {\n    /// Create a new \"splitter\" instance using the original wasm and the wasm from the output of wasm-bindgen.\n    ///\n    /// This will use the relocation data from the original module to create a call graph that we\n    /// then use with the post-bindgened module to create the split modules.\n    ///\n    /// It's important to compile the wasm with --emit-relocs such that the relocations are available\n    /// to construct the callgraph.\n    pub fn new(original: &'a [u8], bindgened: &'a [u8]) -> Result<Self> {\n        let (module, ids, fns_to_ids) = parse_module_with_ids(bindgened)?;\n\n        let split_points = accumulate_split_points(&module);\n\n        // Note that we can't trust the normal symbols - just the data symbols - and we can't use the data offset\n        // since that's not reliable after bindgening\n        let raw_data = parse_bytes_to_data_segment(bindgened)?;\n\n        let mut module = Self {\n            source_module: module,\n            original,\n            bindgened,\n            split_points,\n            data_symbols: raw_data.data_symbols,\n            _ids_to_fns: ids,\n            fns_to_ids,\n            main_graph: Default::default(),\n            chunks: Default::default(),\n            call_graph: Default::default(),\n            parent_graph: Default::default(),\n            shared_symbols: Default::default(),\n        };\n\n        module.build_call_graph()?;\n        module.build_split_chunks();\n\n        Ok(module)\n    }\n\n    /// Split the module into multiple modules at the boundaries of split points.\n    ///\n    /// Note that the binaries might still be \"large\" at the end of this process. In practice, you\n    /// need to push these binaries through wasm-bindgen and wasm-opt to take advantage of the\n    /// optimizations and splitting. We perform a few steps like zero-ing out the data segments\n    /// that will only be removed by the memory-packing step of wasm-opt.\n    ///\n    /// This returns the list of chunks, an import map, and some javascript to link everything together.\n    pub fn emit(self) -> Result<OutputModules> {\n        tracing::info!(\"Emitting split modules.\");\n\n        let chunks = (0..self.chunks.len())\n            .into_par_iter()\n            .map(|idx| self.emit_split_chunk(idx))\n            .collect::<Result<Vec<SplitModule>>>()?;\n\n        let modules = (0..self.split_points.len())\n            .into_par_iter()\n            .map(|idx| self.emit_split_module(idx))\n            .collect::<Result<Vec<SplitModule>>>()?;\n\n        // Emit the main module, consuming self since we're going to\n        let main = self.emit_main_module()?;\n\n        Ok(OutputModules {\n            modules,\n            chunks,\n            main,\n        })\n    }\n\n    /// Emit the main module.\n    ///\n    /// This will analyze the call graph and then perform some transformations on the module.\n    /// - Clear out active segments that the split modules will initialize\n    /// - Wipe away unused functions and data symbols\n    /// - Re-export the memories, globals, and other items that the split modules will need\n    /// - Convert the split module import functions to real functions that call the indirect function\n    ///\n    /// Once this is done, all the split module functions will have been removed, making the main module smaller.\n    ///\n    /// Emitting the main module is conceptually pretty simple. Emitting the split modules is more\n    /// complex.\n    fn emit_main_module(mut self) -> Result<SplitModule> {\n        tracing::info!(\"Emitting main bundle split module\");\n\n        // Perform some analysis of the module before we start messing with it\n        let unused_symbols = self.unused_main_symbols();\n\n        // Use the original module that contains all the right ids\n        let mut out = std::mem::take(&mut self.source_module);\n\n        // 1. Clear out the active segments that try to initialize functions for modules we just split off.\n        //    When the side modules load, they will initialize functions into the table where the \"holes\" are.\n        self.replace_segments_with_holes(&mut out, &unused_symbols);\n\n        // 2. Wipe away the unused functions and data symbols\n        self.prune_main_symbols(&mut out, &unused_symbols)?;\n\n        // 3. Change the functions called from split modules to be local functions that call the indirect function\n        self.create_ifunc_table(&mut out);\n\n        // 4. Re-export the memories, globals, and other stuff\n        self.re_export_items(&mut out);\n\n        // 6. Remove the reloc and linking custom sections\n        self.remove_custom_sections(&mut out);\n\n        // 7. Run the garbage collector to remove unused functions\n        walrus::passes::gc::run(&mut out);\n\n        Ok(SplitModule {\n            module_name: \"main\".to_string(),\n            component_name: None,\n            bytes: out.emit_wasm(),\n            relies_on_chunks: Default::default(),\n            hash_id: None,\n        })\n    }\n\n    /// Write the contents of the split modules to the output\n    fn emit_split_module(&self, split_idx: usize) -> Result<SplitModule> {\n        let split = self.split_points[split_idx].clone();\n\n        // These are the symbols that will only exist in this module and not in the main module.\n        let mut unique_symbols = split\n            .reachable_graph\n            .difference(&self.main_graph)\n            .cloned()\n            .collect::<HashSet<_>>();\n\n        // The functions we'll need to import\n        let mut symbols_to_import: HashSet<_> = split\n            .reachable_graph\n            .intersection(&self.main_graph)\n            .cloned()\n            .collect();\n\n        // Identify the functions we'll delete\n        let symbols_to_delete: HashSet<_> = self\n            .main_graph\n            .difference(&split.reachable_graph)\n            .cloned()\n            .collect();\n\n        // Convert split chunk functions to imports\n        let mut relies_on_chunks = HashSet::new();\n        for (idx, chunk) in self.chunks.iter().enumerate() {\n            let nodes_to_extract = unique_symbols\n                .intersection(chunk)\n                .cloned()\n                .collect::<Vec<_>>();\n            for node in nodes_to_extract {\n                if !self.main_graph.contains(&node) {\n                    unique_symbols.remove(&node);\n                    symbols_to_import.insert(node);\n                    relies_on_chunks.insert(idx);\n                }\n            }\n        }\n\n        tracing::info!(\n            \"Emitting module {}/{} {}: {:?}\",\n            split_idx,\n            self.split_points.len(),\n            split.module_name,\n            relies_on_chunks\n        );\n\n        let (mut out, ids_to_fns, _fns_to_ids) = parse_module_with_ids(self.bindgened)?;\n\n        // Remap the graph to our module's IDs\n        let shared_funcs = self\n            .shared_symbols\n            .iter()\n            .map(|f| self.remap_id(&ids_to_fns, f))\n            .collect::<Vec<_>>();\n\n        let unique_symbols = self.remap_ids(&unique_symbols, &ids_to_fns);\n        let symbols_to_delete = self.remap_ids(&symbols_to_delete, &ids_to_fns);\n        let symbols_to_import = self.remap_ids(&symbols_to_import, &ids_to_fns);\n        let split_export_func = ids_to_fns[self.fns_to_ids[&split.export_func]];\n\n        // Do some basic cleanup of the module to make it smaller\n        // This removes exports, imports, and the start function\n        self.prune_split_module(&mut out);\n\n        // Clear away the data segments\n        self.clear_data_segments(&mut out, &unique_symbols);\n\n        // Clear out the element segments and then add in the initializers for the shared imports\n        self.create_ifunc_initializers(&mut out, &unique_symbols);\n\n        // Convert our split module's functions to real functions that call the indirect function\n        self.add_split_imports(\n            &mut out,\n            split.index,\n            split_export_func,\n            split.export_name,\n            &symbols_to_import,\n            &shared_funcs,\n        );\n\n        // Delete all the functions that are not reachable from the main module\n        self.delete_main_funcs_from_split(&mut out, &symbols_to_delete);\n\n        // Remove the reloc and linking custom sections\n        self.remove_custom_sections(&mut out);\n\n        // Run the gc to remove unused functions - also validates the module to ensure we can emit it properly\n        // todo(jon): prefer to delete the items as we go so we don't need to run a gc pass. it/it's quite slow\n        walrus::passes::gc::run(&mut out);\n\n        Ok(SplitModule {\n            bytes: out.emit_wasm(),\n            module_name: split.module_name.clone(),\n            component_name: Some(split.component_name.clone()),\n            relies_on_chunks,\n            hash_id: Some(split.hash_name.clone()),\n        })\n    }\n\n    /// Write a split chunk - this is a chunk with no special functions, just exports + initializers\n    fn emit_split_chunk(&self, idx: usize) -> Result<SplitModule> {\n        tracing::info!(\"emitting chunk {}\", idx);\n\n        let unique_symbols = &self.chunks[idx];\n\n        // The functions we'll need to import\n        let symbols_to_import: HashSet<_> = unique_symbols\n            .intersection(&self.main_graph)\n            .cloned()\n            .collect();\n\n        // Delete everything except the symbols that are reachable from this module\n        let symbols_to_delete: HashSet<_> = self\n            .main_graph\n            .difference(unique_symbols)\n            .cloned()\n            .collect();\n\n        // Make sure to remap any ids from the main module to this module\n        let (mut out, ids_to_fns, _fns_to_ids) = parse_module_with_ids(self.bindgened)?;\n\n        // Remap the graph to our module's IDs\n        let shared_funcs = self\n            .shared_symbols\n            .iter()\n            .map(|f| self.remap_id(&ids_to_fns, f))\n            .collect::<Vec<_>>();\n\n        let unique_symbols = self.remap_ids(unique_symbols, &ids_to_fns);\n        let symbols_to_import = self.remap_ids(&symbols_to_import, &ids_to_fns);\n        let symbols_to_delete = self.remap_ids(&symbols_to_delete, &ids_to_fns);\n\n        self.prune_split_module(&mut out);\n\n        // Clear away the data segments\n        self.clear_data_segments(&mut out, &unique_symbols);\n\n        // Clear out the element segments and then add in the initializers for the shared imports\n        self.create_ifunc_initializers(&mut out, &unique_symbols);\n\n        // We have to make sure our table matches that of the other tables even though we don't call them.\n        let ifunc_table_id = self.load_funcref_table(&mut out);\n        let segment_start = self\n            .expand_ifunc_table_max(\n                &mut out,\n                ifunc_table_id,\n                self.split_points.len() + shared_funcs.len(),\n            )\n            .unwrap();\n\n        self.convert_shared_to_imports(&mut out, segment_start, &shared_funcs, &symbols_to_import);\n\n        // Make sure we haven't deleted anything important....\n        self.delete_main_funcs_from_split(&mut out, &symbols_to_delete);\n\n        // Remove the reloc and linking custom sections\n        self.remove_custom_sections(&mut out);\n\n        // Run the gc to remove unused functions - also validates the module to ensure we can emit it properly\n        walrus::passes::gc::run(&mut out);\n\n        Ok(SplitModule {\n            bytes: out.emit_wasm(),\n            module_name: \"split\".to_string(),\n            component_name: None,\n            relies_on_chunks: Default::default(),\n            hash_id: None,\n        })\n    }\n\n    /// Convert functions coming in from outside the module to indirect calls to the ifunc table created in the main module\n    fn convert_shared_to_imports(\n        &self,\n        out: &mut Module,\n        segment_start: usize,\n        ifuncs: &Vec<Node>,\n        symbols_to_import: &HashSet<Node>,\n    ) {\n        let ifunc_table_id = self.load_funcref_table(out);\n\n        let mut idx = self.split_points.len();\n        for node in ifuncs {\n            if let Node::Function(ifunc) = node {\n                if symbols_to_import.contains(node) {\n                    let ty_id = out.funcs.get(*ifunc).ty();\n                    let stub = (idx + segment_start) as _;\n                    out.funcs.get_mut(*ifunc).kind =\n                        self.make_stub_funcs(out, ifunc_table_id, ty_id, stub);\n                }\n\n                idx += 1;\n            }\n        }\n    }\n\n    /// Convert split import functions to local functions that call an indirect function that will\n    /// be filled in from the loaded split module.\n    ///\n    /// This is because these imports are going to be delayed until the split module is loaded\n    /// and loading in the main module these as imports won't be possible since the imports won't\n    /// be resolved until the split module is loaded.\n    fn create_ifunc_table(&self, out: &mut Module) {\n        let ifunc_table = self.load_funcref_table(out);\n        let dummy_func = self.make_dummy_func(out);\n\n        out.exports.add(\"__indirect_function_table\", ifunc_table);\n\n        // Expand the ifunc table to accommodate the new ifuncs\n        let segment_start = self\n            .expand_ifunc_table_max(\n                out,\n                ifunc_table,\n                self.split_points.len() + self.shared_symbols.len(),\n            )\n            .expect(\"failed to expand ifunc table\");\n\n        // Delete the split import functions and replace them with local functions\n        //\n        // Start by pushing all the shared imports into the list\n        // These don't require an additional stub function\n        let mut ifuncs = vec![];\n\n        // Push the split import functions into the list - after we've pushed in the shared imports\n        for idx in 0..self.split_points.len() {\n            // this is okay since we're in the main module\n            let import_func = self.split_points[idx].import_func;\n            let import_id = self.split_points[idx].import_id;\n            let ty_id = out.funcs.get(import_func).ty();\n            let stub_idx = segment_start + ifuncs.len();\n\n            // Replace the import function with a local function that calls the indirect function\n            out.funcs.get_mut(import_func).kind =\n                self.make_stub_funcs(out, ifunc_table, ty_id, stub_idx as _);\n\n            // And remove the corresponding import\n            out.imports.delete(import_id);\n\n            // Push into the list the properly typed dummy func so the entry is populated\n            // unclear if the typing is important here\n            ifuncs.push(dummy_func);\n        }\n\n        // Add the stub functions to the ifunc table\n        // The callers of these functions will call the stub instead of the import\n        let mut _idx = 0;\n        for func in self.shared_symbols.iter() {\n            if let Node::Function(id) = func {\n                ifuncs.push(*id);\n                _idx += 1;\n            }\n        }\n\n        // Now add segments to the ifunc table\n        out.tables\n            .get_mut(ifunc_table)\n            .elem_segments\n            .insert(out.elements.add(\n                ElementKind::Active {\n                    table: ifunc_table,\n                    offset: ConstExpr::Value(ir::Value::I32(segment_start as _)),\n                },\n                ElementItems::Functions(ifuncs),\n            ));\n    }\n\n    /// Re-export the memories, globals, and other items from the main module to the side modules\n    fn re_export_items(&self, out: &mut Module) {\n        // Re-export memories\n        for (idx, memory) in out.memories.iter().enumerate() {\n            let name = memory\n                .name\n                .clone()\n                .unwrap_or_else(|| format!(\"__memory_{}\", idx));\n            out.exports.add(&name, memory.id());\n        }\n\n        // Re-export globals\n        for (idx, global) in out.globals.iter().enumerate() {\n            let global_name = format!(\"__global__{idx}\");\n            out.exports.add(&global_name, global.id());\n        }\n\n        // Export any tables\n        for (idx, table) in out.tables.iter().enumerate() {\n            if table.element_ty != RefType::Funcref {\n                let table_name = format!(\"__imported_table_{}\", idx);\n                out.exports.add(&table_name, table.id());\n            }\n        }\n    }\n\n    fn prune_main_symbols(&self, out: &mut Module, unused_symbols: &HashSet<Node>) -> Result<()> {\n        // Wipe the split point exports\n        for split in self.split_points.iter() {\n            // it's okay that we're not re-mapping IDs since this is just used by the main module\n            out.exports.delete(split.export_id);\n        }\n\n        // And then any actual symbols from the callgraph\n        for symbol in unused_symbols.iter().cloned() {\n            match symbol {\n                // Simply delete functions\n                Node::Function(id) => {\n                    out.funcs.delete(id);\n                }\n\n                // Otherwise, zero out the data segment, which should lead to elimination by wasm-opt\n                Node::DataSymbol(id) => {\n                    let symbol = self\n                        .data_symbols\n                        .get(&id)\n                        .context(\"Failed to find data symbol\")?;\n\n                    // VERY IMPORTANT\n                    //\n                    // apparently wasm-bindgen makes data segments that aren't the main one\n                    // even *touching* those will break the vtable / binding layer\n                    // We can only interact with the first data segment - the rest need to stay available\n                    // for the `.js` to interact with.\n                    if symbol.which_data_segment == 0 {\n                        let data_id = out.data.iter().nth(symbol.which_data_segment).unwrap().id();\n                        let data = out.data.get_mut(data_id);\n                        for i in symbol.segment_offset..symbol.segment_offset + symbol.symbol_size {\n                            data.value[i] = 0;\n                        }\n                    }\n                }\n            }\n        }\n\n        Ok(())\n    }\n\n    // 2.1 Create a dummy func that will be overridden later as modules pop in\n    // 2.2 swap the segment entries with the dummy func, leaving hole in its placed that will be filled in later\n    fn replace_segments_with_holes(&self, out: &mut Module, unused_symbols: &HashSet<Node>) {\n        let dummy_func = self.make_dummy_func(out);\n        for element in out.elements.iter_mut() {\n            match &mut element.items {\n                ElementItems::Functions(vec) => {\n                    for item in vec.iter_mut() {\n                        if unused_symbols.contains(&Node::Function(*item)) {\n                            *item = dummy_func;\n                        }\n                    }\n                }\n                ElementItems::Expressions(_ref_type, const_exprs) => {\n                    for item in const_exprs.iter_mut() {\n                        if let &mut ConstExpr::RefFunc(id) = item {\n                            if unused_symbols.contains(&Node::Function(id)) {\n                                *item = ConstExpr::RefFunc(dummy_func);\n                            }\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    /// Creates the jump points\n    fn create_ifunc_initializers(&self, out: &mut Module, unique_symbols: &HashSet<Node>) {\n        let ifunc_table = self.load_funcref_table(out);\n\n        let mut initializers = HashMap::new();\n        for segment in out.elements.iter_mut() {\n            let ElementKind::Active { offset, .. } = &mut segment.kind else {\n                continue;\n            };\n\n            let ConstExpr::Value(ir::Value::I32(offset)) = offset else {\n                continue;\n            };\n\n            match &segment.items {\n                ElementItems::Functions(vec) => {\n                    for (idx, id) in vec.iter().enumerate() {\n                        if unique_symbols.contains(&Node::Function(*id)) {\n                            initializers\n                                .insert(*offset + idx as i32, ElementItems::Functions(vec![*id]));\n                        }\n                    }\n                }\n\n                ElementItems::Expressions(ref_type, const_exprs) => {\n                    for (idx, expr) in const_exprs.iter().enumerate() {\n                        if let ConstExpr::RefFunc(id) = expr {\n                            if unique_symbols.contains(&Node::Function(*id)) {\n                                initializers.insert(\n                                    *offset + idx as i32,\n                                    ElementItems::Expressions(\n                                        *ref_type,\n                                        vec![ConstExpr::RefFunc(*id)],\n                                    ),\n                                );\n                            }\n                        }\n                    }\n                }\n            }\n        }\n\n        // Wipe away references to these segments\n        for table in out.tables.iter_mut() {\n            table.elem_segments.clear();\n        }\n\n        // Wipe away the element segments themselves\n        let segments_to_delete: Vec<_> = out.elements.iter().map(|e| e.id()).collect();\n        for id in segments_to_delete {\n            out.elements.delete(id);\n        }\n\n        // Add in our new segments\n        let ifunc_table_ = out.tables.get_mut(ifunc_table);\n        for (offset, items) in initializers {\n            let kind = ElementKind::Active {\n                table: ifunc_table,\n                offset: ConstExpr::Value(ir::Value::I32(offset)),\n            };\n\n            ifunc_table_\n                .elem_segments\n                .insert(out.elements.add(kind, items));\n        }\n    }\n\n    fn add_split_imports(\n        &self,\n        out: &mut Module,\n        split_idx: usize,\n        split_export_func: FunctionId,\n        split_export_name: String,\n        symbols_to_import: &HashSet<Node>,\n        ifuncs: &Vec<Node>,\n    ) {\n        let ifunc_table_id = self.load_funcref_table(out);\n        let segment_start = self\n            .expand_ifunc_table_max(out, ifunc_table_id, self.split_points.len() + ifuncs.len())\n            .unwrap();\n\n        // Make sure to re-export the split func\n        out.exports.add(&split_export_name, split_export_func);\n\n        // Add the elements back to the table\n        out.tables\n            .get_mut(ifunc_table_id)\n            .elem_segments\n            .insert(out.elements.add(\n                ElementKind::Active {\n                    table: ifunc_table_id,\n                    offset: ConstExpr::Value(ir::Value::I32((segment_start + split_idx) as i32)),\n                },\n                ElementItems::Functions(vec![split_export_func]),\n            ));\n\n        self.convert_shared_to_imports(out, segment_start, ifuncs, symbols_to_import);\n    }\n\n    fn delete_main_funcs_from_split(&self, out: &mut Module, symbols_to_delete: &HashSet<Node>) {\n        for node in symbols_to_delete {\n            if let Node::Function(id) = *node {\n                // if out.exports.get_exported_func(id).is_none() {\n                out.funcs.delete(id);\n                // }\n            }\n        }\n    }\n\n    /// Remove un-needed stuff and then hoist\n    fn prune_split_module(&self, out: &mut Module) {\n        // Clear the module's start/main\n        if let Some(start) = out.start.take() {\n            if let Some(export) = out.exports.get_exported_func(start) {\n                out.exports.delete(export.id());\n            }\n        }\n\n        // We're going to import the funcref table, so wipe it altogether\n        for table in out.tables.iter_mut() {\n            table.elem_segments.clear();\n        }\n\n        // Wipe all our imports - we're going to use a different set of imports\n        let all_imports: HashSet<_> = out.imports.iter().map(|i| i.id()).collect();\n        for import_id in all_imports {\n            out.imports.delete(import_id);\n        }\n\n        // Wipe away memories\n        let all_memories: Vec<_> = out.memories.iter().map(|m| m.id()).collect();\n        for memory_id in all_memories {\n            out.memories.get_mut(memory_id).data_segments.clear();\n        }\n\n        // Add exports that call the corresponding import\n        let exports = out.exports.iter().map(|e| e.id()).collect::<Vec<_>>();\n        for export_id in exports {\n            out.exports.delete(export_id);\n        }\n\n        // Convert the tables to imports.\n        // Should be as simple as adding a new import and then writing the `.import` field\n        for (idx, table) in out.tables.iter_mut().enumerate() {\n            let name = table.name.clone().unwrap_or_else(|| {\n                if table.element_ty == RefType::Funcref {\n                    \"__indirect_function_table\".to_string()\n                } else {\n                    format!(\"__imported_table_{}\", idx)\n                }\n            });\n            let import = out.imports.add(\"__wasm_split\", &name, table.id());\n            table.import = Some(import);\n        }\n\n        // Convert the memories to imports\n        // Should be as simple as adding a new import and then writing the `.import` field\n        for (idx, memory) in out.memories.iter_mut().enumerate() {\n            let name = memory\n                .name\n                .clone()\n                .unwrap_or_else(|| format!(\"__memory_{}\", idx));\n            let import = out.imports.add(\"__wasm_split\", &name, memory.id());\n            memory.import = Some(import);\n        }\n\n        // Convert the globals to imports\n        // We might not use the global, so if we don't, we can just get\n        let global_ids: Vec<_> = out.globals.iter().map(|t| t.id()).collect();\n        for (idx, global_id) in global_ids.into_iter().enumerate() {\n            let global = out.globals.get_mut(global_id);\n            let global_name = format!(\"__global__{idx}\");\n            let import = out.imports.add(\"__wasm_split\", &global_name, global.id());\n            global.kind = GlobalKind::Import(import);\n        }\n    }\n\n    fn make_dummy_func(&self, out: &mut Module) -> FunctionId {\n        let mut b = FunctionBuilder::new(&mut out.types, &[], &[]);\n        b.name(\"dummy\".into()).func_body().unreachable();\n        b.finish(vec![], &mut out.funcs)\n    }\n\n    fn clear_data_segments(&self, out: &mut Module, unique_symbols: &HashSet<Node>) {\n        // Preserve the data symbols for this module and then clear them away\n        let data_ids: Vec<_> = out.data.iter().map(|t| t.id()).collect();\n        for (idx, data_id) in data_ids.into_iter().enumerate() {\n            let data = out.data.get_mut(data_id);\n\n            // Take the data out of the vec - zeroing it out unless we patch it in manually\n            let contents = data.value.split_off(0);\n\n            // Zero out the non-primary data segments\n            if idx != 0 {\n                continue;\n            }\n\n            let DataKind::Active { memory, offset } = data.kind else {\n                continue;\n            };\n\n            let ConstExpr::Value(ir::Value::I32(data_offset)) = offset else {\n                continue;\n            };\n\n            // And then assign chunks of the data to new data entries that will override the individual slots\n            for unique in unique_symbols {\n                if let Node::DataSymbol(id) = unique {\n                    if let Some(symbol) = self.data_symbols.get(id) {\n                        if symbol.which_data_segment == idx {\n                            let range =\n                                symbol.segment_offset..symbol.segment_offset + symbol.symbol_size;\n                            let offset = ConstExpr::Value(ir::Value::I32(\n                                data_offset + symbol.segment_offset as i32,\n                            ));\n                            out.data.add(\n                                DataKind::Active { memory, offset },\n                                contents[range].to_vec(),\n                            );\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    /// Load the funcref table from the main module. This *should* exist for all modules created by\n    /// Rustc or Wasm-Bindgen, but we create it if it doesn't exist.\n    fn load_funcref_table(&self, out: &mut Module) -> TableId {\n        let ifunc_table = out\n            .tables\n            .iter()\n            .find(|t| t.element_ty == RefType::Funcref)\n            .map(|t| t.id());\n\n        if let Some(table) = ifunc_table {\n            table\n        } else {\n            out.tables.add_local(false, 0, None, RefType::Funcref)\n        }\n    }\n\n    /// Convert the imported function to a local function that calls an indirect function from the table\n    ///\n    /// This will enable the main module (and split modules) to call functions from outside their own module.\n    /// The functions might not exist when the main module is loaded, so we'll register some elements\n    /// that fill those in eventually.\n    fn make_stub_funcs(\n        &self,\n        out: &mut Module,\n        table: TableId,\n        ty_id: TypeId,\n        table_idx: i32,\n    ) -> FunctionKind {\n        // Convert the import function to a local function that calls the indirect function from the table\n        let ty = out.types.get(ty_id);\n\n        let params = ty.params().to_vec();\n        let results = ty.results().to_vec();\n        let args: Vec<_> = params.iter().map(|ty| out.locals.add(*ty)).collect();\n\n        // New function that calls the indirect function\n        let mut builder = FunctionBuilder::new(&mut out.types, &params, &results);\n        let mut body = builder.name(\"stub\".into()).func_body();\n\n        // Push the params onto the stack\n        for arg in args.iter() {\n            body.local_get(*arg);\n        }\n\n        // And then the address of the indirect function\n        body.instr(ir::Instr::Const(ir::Const {\n            value: ir::Value::I32(table_idx),\n        }));\n\n        // And call it\n        body.instr(ir::Instr::CallIndirect(ir::CallIndirect {\n            ty: ty_id,\n            table,\n        }));\n\n        FunctionKind::Local(builder.local_func(args))\n    }\n\n    /// Expand the ifunc table to accommodate the new ifuncs\n    ///\n    /// returns the old maximum\n    fn expand_ifunc_table_max(\n        &self,\n        out: &mut Module,\n        table: TableId,\n        num_ifuncs: usize,\n    ) -> Option<usize> {\n        let ifunc_table_ = out.tables.get_mut(table);\n\n        if let Some(max) = ifunc_table_.maximum {\n            ifunc_table_.maximum = Some(max + num_ifuncs as u64);\n            ifunc_table_.initial += num_ifuncs as u64;\n            return Some(max as usize);\n        }\n\n        None\n    }\n\n    // only keep the target-features and names section so wasm-opt can use it to optimize the output\n    fn remove_custom_sections(&self, out: &mut Module) {\n        let sections_to_delete = out\n            .customs\n            .iter()\n            .filter_map(|(id, section)| {\n                if section.name() == \"target_features\" {\n                    None\n                } else {\n                    Some(id)\n                }\n            })\n            .collect::<Vec<_>>();\n\n        for id in sections_to_delete {\n            out.customs.delete(id);\n        }\n    }\n\n    /// Accumulate any shared funcs between multiple chunks into a single residual chunk.\n    /// This prevents duplicates from being downloaded.\n    /// Eventually we need to group the chunks into smarter \"communities\" - ie the Louvain algorithm\n    ///\n    /// Todo: we could chunk up the main module itself! Not going to now but it would enable parallel downloads of the main chunk\n    fn build_split_chunks(&mut self) {\n        // create a single chunk that contains all functions used by multiple modules\n        let mut funcs_used_by_chunks: HashMap<Node, HashSet<usize>> = HashMap::new();\n        for split in self.split_points.iter() {\n            for item in split.reachable_graph.iter() {\n                if self.main_graph.contains(item) {\n                    continue;\n                }\n            }\n        }\n\n        // Only consider funcs that are used by multiple modules - otherwise they can just stay in their respective module\n        funcs_used_by_chunks.retain(|_, v| v.len() > 1);\n\n        // todo: break down this chunk if it exceeds a certain size (100kb?) by identifying different groups\n\n        self.chunks\n            .push(funcs_used_by_chunks.keys().cloned().collect());\n    }\n\n    fn unused_main_symbols(&self) -> HashSet<Node> {\n        self.split_points\n            .iter()\n            .flat_map(|split| split.reachable_graph.iter())\n            .filter(|sym| {\n                // Make sure the symbol isn't in the main graph\n                if self.main_graph.contains(sym) {\n                    return false;\n                }\n\n                // And ensure we aren't also exporting it\n                match sym {\n                    Node::Function(u) => self.source_module.exports.get_exported_func(*u).is_none(),\n                    _ => true,\n                }\n            })\n            .cloned()\n            .collect()\n    }\n\n    /// Accumulate the relocations from the original module, create a relocation map, and then convert\n    /// that to our *new* module's symbols.\n    fn build_call_graph(&mut self) -> Result<()> {\n        let original = ModuleWithRelocations::new(self.original)?;\n\n        let old_names: HashMap<String, FunctionId> = original\n            .module\n            .funcs\n            .iter()\n            .flat_map(|f| Some((f.name.clone()?, f.id())))\n            .collect();\n\n        let new_names: HashMap<String, FunctionId> = self\n            .source_module\n            .funcs\n            .iter()\n            .flat_map(|f| Some((f.name.clone()?, f.id())))\n            .collect();\n\n        let mut old_to_new = HashMap::new();\n        let mut new_call_graph: HashMap<Node, HashSet<Node>> = HashMap::new();\n\n        for (new_name, new_func) in new_names.iter() {\n            if let Some(old_func) = old_names.get(new_name) {\n                old_to_new.insert(*old_func, new_func);\n            } else {\n                new_call_graph.insert(Node::Function(*new_func), HashSet::new());\n            }\n        }\n\n        let get_old = |old: &Node| -> Option<Node> {\n            match old {\n                Node::Function(id) => old_to_new.get(id).map(|new_id| Node::Function(**new_id)),\n                Node::DataSymbol(id) => Some(Node::DataSymbol(*id)),\n            }\n        };\n\n        // the symbols that we can't find in the original module touch functions that unfortunately\n        // we can't figure out where should exist in the call graph\n        //\n        // we're going to walk and find every child we possibly can and then add it to the call graph\n        // at the root\n        //\n        // wasm-bindgen will dissolve describe functions into the shim functions, but we don't have a\n        // sense of lining up old to new, so we just assume everything ends up in the main chunk.\n        let mut lost_children = HashSet::new();\n        self.call_graph = original\n            .call_graph\n            .iter()\n            .flat_map(|(old, children)| {\n                // If the old function isn't in the new module, we need to move all its descendents into the main chunk\n                let Some(new) = get_old(old) else {\n                    for child in children {\n                        fn descend(\n                            lost_children: &mut HashSet<Node>,\n                            old_graph: &HashMap<Node, HashSet<Node>>,\n                            node: Node,\n                        ) {\n                            if !lost_children.insert(node) {\n                                return;\n                            }\n\n                            if let Some(children) = old_graph.get(&node) {\n                                for child in children {\n                                    descend(lost_children, old_graph, *child);\n                                }\n                            }\n                        }\n\n                        descend(&mut lost_children, &original.call_graph, *child);\n                    }\n                    return None;\n                };\n\n                let mut new_children = HashSet::new();\n                for child in children {\n                    if let Some(new) = get_old(child) {\n                        new_children.insert(new);\n                    }\n                }\n\n                Some((new, new_children))\n            })\n            .collect();\n\n        let mut recovered_children = HashSet::new();\n        for lost in lost_children {\n            match lost {\n                // Functions need to be found - the wasm describe functions are usually completely dissolved\n                Node::Function(id) => {\n                    let func = original.module.funcs.get(id);\n                    let name = func.name.as_ref().unwrap();\n                    if let Some(entry) = new_names.get(name) {\n                        recovered_children.insert(Node::Function(*entry));\n                    }\n                }\n\n                // Data symbols are unchanged and fine to remap\n                Node::DataSymbol(id) => {\n                    recovered_children.insert(Node::DataSymbol(id));\n                }\n            }\n        }\n\n        // We're going to attach the recovered children to the main function\n        let main_fn = self.source_module.funcs.by_name(\"main\").context(\"Failed to find `main` function - was this built with LTO, --emit-relocs, and debug symbols?\")?;\n        let main_fn_entry = new_call_graph.entry(Node::Function(main_fn)).or_default();\n        main_fn_entry.extend(recovered_children);\n\n        // Also attach any truly new symbols to the main function. Usually these are the shim functions\n        for (name, new) in new_names.iter() {\n            if !old_names.contains_key(name) {\n                main_fn_entry.insert(Node::Function(*new));\n            }\n        }\n\n        // Walk the functions and try to disconnect any holes manually\n        // This will attempt to resolve any of the new symbols like the shim functions\n        for func in self.source_module.funcs.iter() {\n            struct CallGrapher<'a> {\n                cur: FunctionId,\n                call_graph: &'a mut HashMap<Node, HashSet<Node>>,\n            }\n            impl<'a> Visitor<'a> for CallGrapher<'a> {\n                fn visit_function_id(&mut self, function: &walrus::FunctionId) {\n                    self.call_graph\n                        .entry(Node::Function(self.cur))\n                        .or_default()\n                        .insert(Node::Function(*function));\n                }\n            }\n            if let FunctionKind::Local(local) = &func.kind {\n                let mut call_grapher = CallGrapher {\n                    cur: func.id(),\n                    call_graph: &mut self.call_graph,\n                };\n                dfs_in_order(&mut call_grapher, local, local.entry_block());\n            }\n        }\n\n        // Fill in the parent graph\n        for (parnet, children) in self.call_graph.iter() {\n            for child in children {\n                self.parent_graph.entry(*child).or_default().insert(*parnet);\n            }\n        }\n\n        // Now go fill in the reachability graph for each of the split points\n        // We want to be as narrow as possible since we've reparented any new symbols to the main module\n        self.split_points.iter_mut().for_each(|split| {\n            let roots: HashSet<_> = [Node::Function(split.export_func)].into();\n            split.reachable_graph = reachable_graph(&self.call_graph, &roots);\n        });\n\n        // And then the reachability graph for main\n        self.main_graph = reachable_graph(&self.call_graph, &self.main_roots());\n\n        // And then the symbols shared between all\n        self.shared_symbols = {\n            let mut shared_funcs = HashSet::new();\n\n            // Add all the symbols shared between the various modules\n            for split in self.split_points.iter() {\n                shared_funcs.extend(self.main_graph.intersection(&split.reachable_graph));\n            }\n\n            // And then all our imports will be callabale via the ifunc table too\n            for import in self.source_module.imports.iter() {\n                if let ImportKind::Function(id) = import.kind {\n                    shared_funcs.insert(Node::Function(id));\n                }\n            }\n\n            // Make sure to make this *ordered*\n            shared_funcs.into_iter().collect()\n        };\n\n        Ok(())\n    }\n\n    fn main_roots(&self) -> HashSet<Node> {\n        // Accumulate all the split entrypoints\n        // This will include wasm_bindgen functions too\n        let exported_splits = self\n            .split_points\n            .iter()\n            .map(|f| f.export_func)\n            .collect::<HashSet<_>>();\n\n        // And only return the functions that are reachable from the main module's start function\n        let mut roots = self\n            .source_module\n            .exports\n            .iter()\n            .filter_map(|e| match e.item {\n                ExportItem::Function(id) if !exported_splits.contains(&id) => {\n                    Some(Node::Function(id))\n                }\n                _ => None,\n            })\n            .chain(self.source_module.start.map(Node::Function))\n            .collect::<HashSet<Node>>();\n\n        // Also add \"imports\" to the roots\n        for import in self.source_module.imports.iter() {\n            if let ImportKind::Function(id) = import.kind {\n                roots.insert(Node::Function(id));\n            }\n        }\n\n        roots\n    }\n\n    /// Convert this set of nodes to reference the new module\n    fn remap_ids(&self, set: &HashSet<Node>, ids_to_fns: &[FunctionId]) -> HashSet<Node> {\n        let mut out = HashSet::with_capacity(set.len());\n        for node in set {\n            out.insert(self.remap_id(ids_to_fns, node));\n        }\n        out\n    }\n\n    fn remap_id(&self, ids_to_fns: &[id_arena::Id<walrus::Function>], node: &Node) -> Node {\n        match node {\n            // Remap the function IDs\n            Node::Function(id) => Node::Function(ids_to_fns[self.fns_to_ids[id]]),\n            // data symbols don't need remapping\n            Node::DataSymbol(id) => Node::DataSymbol(*id),\n        }\n    }\n}\n\n/// Parse a module and return the mapping of index to FunctionID.\n/// We'll use this mapping to remap ModuleIDs\nfn parse_module_with_ids(\n    bindgened: &[u8],\n) -> Result<(Module, Vec<FunctionId>, HashMap<FunctionId, usize>)> {\n    let ids = Arc::new(RwLock::new(Vec::new()));\n    let ids_ = ids.clone();\n    let module = Module::from_buffer_with_config(\n        bindgened,\n        ModuleConfig::new().on_parse(move |_m, our_ids| {\n            let mut ids = ids_.write().expect(\"No shared writers\");\n            let mut idx = 0;\n            while let Ok(entry) = our_ids.get_func(idx) {\n                ids.push(entry);\n                idx += 1;\n            }\n\n            Ok(())\n        }),\n    )?;\n    let mut ids_ = ids.write().expect(\"No shared writers\");\n    let mut ids = vec![];\n    std::mem::swap(&mut ids, &mut *ids_);\n\n    let mut fns_to_ids = HashMap::new();\n    for (idx, id) in ids.iter().enumerate() {\n        fns_to_ids.insert(*id, idx);\n    }\n\n    Ok((module, ids, fns_to_ids))\n}\n\nstruct ModuleWithRelocations<'a> {\n    module: Module,\n    symbols: Vec<SymbolInfo<'a>>,\n    names_to_funcs: HashMap<String, FunctionId>,\n    call_graph: HashMap<Node, HashSet<Node>>,\n    parents: HashMap<Node, HashSet<Node>>,\n    relocation_map: HashMap<Node, Vec<RelocationEntry>>,\n    data_symbols: BTreeMap<usize, DataSymbol>,\n    data_section_range: Range<usize>,\n}\n\nimpl<'a> ModuleWithRelocations<'a> {\n    fn new(bytes: &'a [u8]) -> Result<Self> {\n        let module = Module::from_buffer(bytes)?;\n        let raw_data = parse_bytes_to_data_segment(bytes)?;\n        let names_to_funcs = module\n            .funcs\n            .iter()\n            .flat_map(|f| Some((f.name.clone()?, f.id())))\n            .collect();\n\n        let mut module = Self {\n            module,\n            data_symbols: raw_data.data_symbols,\n            data_section_range: raw_data.data_range,\n            symbols: raw_data.symbols,\n            names_to_funcs,\n            call_graph: Default::default(),\n            relocation_map: Default::default(),\n            parents: Default::default(),\n        };\n\n        module.build_code_call_graph()?;\n        module.build_data_call_graph()?;\n\n        for (func, children) in module.call_graph.iter() {\n            for child in children {\n                module.parents.entry(*child).or_default().insert(*func);\n            }\n        }\n\n        Ok(module)\n    }\n\n    fn build_code_call_graph(&mut self) -> Result<()> {\n        let codes_relocations = self.collect_relocations_from_section(\"reloc.CODE\")?;\n        let mut relocations = codes_relocations.iter().peekable();\n\n        for (func_id, local) in self.module.funcs.iter_local() {\n            let range = local\n                .original_range\n                .clone()\n                .context(\"local function has no range\")?;\n\n            // Walk with relocation\n            while let Some(entry) =\n                relocations.next_if(|entry| entry.relocation_range().start < range.end)\n            {\n                let reloc_range = entry.relocation_range();\n                assert!(reloc_range.start >= range.start);\n                assert!(reloc_range.end <= range.end);\n\n                if let Some(target) = self.get_symbol_dep_node(entry.index as usize)? {\n                    let us = Node::Function(func_id);\n                    self.call_graph.entry(us).or_default().insert(target);\n                    self.relocation_map.entry(us).or_default().push(*entry);\n                }\n            }\n        }\n\n        assert!(relocations.next().is_none());\n\n        Ok(())\n    }\n\n    fn build_data_call_graph(&mut self) -> Result<()> {\n        let data_relocations = self.collect_relocations_from_section(\"reloc.DATA\")?;\n        let mut relocations = data_relocations.iter().peekable();\n\n        let symbols_sorted = self\n            .data_symbols\n            .values()\n            .sorted_by(|a, b| a.range.start.cmp(&b.range.start));\n\n        for symbol in symbols_sorted {\n            let start = symbol.range.start - self.data_section_range.start;\n            let end = symbol.range.end - self.data_section_range.start;\n            let range = start..end;\n\n            while let Some(entry) =\n                relocations.next_if(|entry| entry.relocation_range().start < range.end)\n            {\n                let reloc_range = entry.relocation_range();\n                assert!(reloc_range.start >= range.start);\n                assert!(reloc_range.end <= range.end);\n\n                if let Some(target) = self.get_symbol_dep_node(entry.index as usize)? {\n                    let dep = Node::DataSymbol(symbol.index);\n                    self.call_graph.entry(dep).or_default().insert(target);\n                    self.relocation_map.entry(dep).or_default().push(*entry);\n                }\n            }\n        }\n\n        assert!(relocations.next().is_none());\n\n        Ok(())\n    }\n\n    /// Accumulate all relocations from a section.\n    ///\n    /// Parses the section using the RelocSectionReader and returns a vector of relocation entries.\n    fn collect_relocations_from_section(&self, name: &str) -> Result<Vec<RelocationEntry>> {\n        let (_reloc_id, code_reloc) = self\n            .module\n            .customs\n            .iter()\n            .find(|(_, c)| c.name() == name)\n            .context(\"Module does not contain the reloc section\")?;\n\n        let code_reloc_data = code_reloc.data(&Default::default());\n        let reader = BinaryReader::new(&code_reloc_data, 0);\n        let relocations = RelocSectionReader::new(reader)\n            .context(\"failed to parse reloc section\")?\n            .entries()\n            .into_iter()\n            .flatten()\n            .collect();\n\n        Ok(relocations)\n    }\n\n    /// Get the symbol's corresponding entry in the call graph\n    ///\n    /// This might panic if the source module isn't built properly. Make sure to enable LTO and `--emit-relocs`\n    /// when building the source module.\n    fn get_symbol_dep_node(&self, index: usize) -> Result<Option<Node>> {\n        let res = match self.symbols[index] {\n            SymbolInfo::Data { .. } => Some(Node::DataSymbol(index)),\n            SymbolInfo::Func { name, .. } => Some(Node::Function({\n                let name = name.context(\n                    \"Function symbol has no name - did you forget to enable debug symbols\",\n                )?;\n\n                let func_id = self.names_to_funcs.get(name);\n\n                // wbindgen will synthesize some functions that don't exist in the original module (eg describe functions)\n                // Previously this was a hard error, but now we just ignore it. It used to mean that the user\n                let Some(res) = func_id else {\n                    if !name.contains(\"__wbindgen_\") {\n                        tracing::error!(\"Could not find function symbol {name:?} in module - was this built with LTO, --emit-relocs, and debug symbols? Ignoring.\");\n                    }\n                    return Ok(None);\n                };\n\n                *res\n            })),\n\n            _ => None,\n        };\n\n        Ok(res)\n    }\n}\n\n#[derive(Debug, Clone)]\npub struct SplitPoint {\n    module_name: String,\n    import_id: ImportId,\n    export_id: ExportId,\n    import_func: FunctionId,\n    export_func: FunctionId,\n    component_name: String,\n    index: usize,\n    reachable_graph: HashSet<Node>,\n    hash_name: String,\n\n    #[allow(unused)]\n    import_name: String,\n\n    #[allow(unused)]\n    export_name: String,\n}\n\n/// Search the module's imports and exports for functions marked as split points.\n///\n/// These will be in the form of:\n///\n/// `__wasm_split_00<module>00_<import|export>_<hash>_<function>`\n///\n/// For a function named `SomeRoute2` in the module `add_body_element`, the pairings would be:\n///\n/// `__wasm_split_00add_body_element00_import_abef5ee3ebe66ff17677c56ee392b4c2_SomeRoute2`\n/// `__wasm_split_00add_body_element00_export_abef5ee3ebe66ff17677c56ee392b4c2_SomeRoute2`\n///\nfn accumulate_split_points(module: &Module) -> Vec<SplitPoint> {\n    let mut index = 0;\n\n    module\n        .imports\n        .iter()\n        .sorted_by(|a, b| a.name.cmp(&b.name))\n        .flat_map(|import| {\n            if !import.name.starts_with(\"__wasm_split_00\") {\n                return None;\n            }\n\n            let ImportKind::Function(import_func) = import.kind else {\n                return None;\n            };\n\n            // Parse the import name to get the module name, the hash, and the function name\n            let remain = import.name.trim_start_matches(\"__wasm_split_00___\");\n            let (module_name, rest) = remain.split_once(\"___00\").unwrap();\n            let (hash, fn_name) = rest.trim_start_matches(\"_import_\").split_once(\"_\").unwrap();\n\n            // Look for the export with the same name\n            let export_name =\n                format!(\"__wasm_split_00___{module_name}___00_export_{hash}_{fn_name}\");\n            let export_func = module\n                .exports\n                .get_func(&export_name)\n                .expect(\"Could not find export\");\n            let export = module.exports.get_exported_func(export_func).unwrap();\n\n            let our_index = index;\n            index += 1;\n\n            Some(SplitPoint {\n                export_id: export.id(),\n                import_id: import.id(),\n                module_name: module_name.to_string(),\n                import_name: import.name.clone(),\n                import_func,\n                export_func,\n                export_name,\n                hash_name: hash.to_string(),\n                component_name: fn_name.to_string(),\n                index: our_index,\n                reachable_graph: Default::default(),\n            })\n        })\n        .collect()\n}\n\n#[derive(Debug, PartialEq, Eq, Hash, Copy, PartialOrd, Ord, Clone)]\npub enum Node {\n    Function(FunctionId),\n    DataSymbol(usize),\n}\n\nfn reachable_graph(deps: &HashMap<Node, HashSet<Node>>, roots: &HashSet<Node>) -> HashSet<Node> {\n    let mut queue: VecDeque<Node> = roots.iter().copied().collect();\n    let mut reachable = HashSet::<Node>::new();\n    let mut parents = HashMap::<Node, Node>::new();\n\n    while let Some(node) = queue.pop_front() {\n        reachable.insert(node);\n        let Some(children) = deps.get(&node) else {\n            continue;\n        };\n        for child in children {\n            if reachable.contains(child) {\n                continue;\n            }\n            parents.entry(*child).or_insert(node);\n            queue.push_back(*child);\n        }\n    }\n\n    reachable\n}\n\nstruct RawDataSection<'a> {\n    data_range: Range<usize>,\n    symbols: Vec<SymbolInfo<'a>>,\n    data_symbols: BTreeMap<usize, DataSymbol>,\n}\n\n#[derive(Debug)]\nstruct DataSymbol {\n    index: usize,\n    range: Range<usize>,\n    segment_offset: usize,\n    symbol_size: usize,\n    which_data_segment: usize,\n}\n\n/// Manually parse the data section from a wasm module\n///\n/// We need to do this for data symbols because walrus doesn't provide the right range and offset\n/// information for data segments. Fortunately, it provides it for code sections, so we only need to\n/// do a small amount extra of parsing here.\nfn parse_bytes_to_data_segment(bytes: &[u8]) -> Result<RawDataSection<'_>> {\n    let parser = wasmparser::Parser::new(0);\n    let mut parser = parser.parse_all(bytes);\n    let mut segments = vec![];\n    let mut data_range = 0..0;\n    let mut symbols = vec![];\n\n    // Process the payloads in the raw wasm file so we can extract the specific sections we need\n    while let Some(Ok(payload)) = parser.next() {\n        match payload {\n            Payload::DataSection(section) => {\n                data_range = section.range();\n                segments = section.into_iter().collect::<Result<Vec<_>, _>>()?\n            }\n            Payload::CustomSection(section) if section.name() == \"linking\" => {\n                let reader = BinaryReader::new(section.data(), 0);\n                let reader = LinkingSectionReader::new(reader)?;\n                for subsection in reader.subsections() {\n                    if let Linking::SymbolTable(map) = subsection? {\n                        symbols = map.into_iter().collect::<Result<Vec<_>, _>>()?;\n                    }\n                }\n            }\n            _ => {}\n        }\n    }\n\n    // Accumulate the data symbols into a btreemap for later use\n    let mut data_symbols = BTreeMap::new();\n    for (index, symbol) in symbols.iter().enumerate() {\n        let SymbolInfo::Data {\n            symbol: Some(symbol),\n            ..\n        } = symbol\n        else {\n            continue;\n        };\n\n        if symbol.size == 0 {\n            continue;\n        }\n\n        let data_segment = segments\n            .get(symbol.index as usize)\n            .context(\"Failed to find data segment\")?;\n        let offset: usize =\n            data_segment.range.end - data_segment.data.len() + (symbol.offset as usize);\n        let range = offset..(offset + symbol.size as usize);\n\n        data_symbols.insert(\n            index,\n            DataSymbol {\n                index,\n                range,\n                segment_offset: symbol.offset as usize,\n                symbol_size: symbol.size as usize,\n                which_data_segment: symbol.index as usize,\n            },\n        );\n    }\n\n    Ok(RawDataSection {\n        data_range,\n        symbols,\n        data_symbols,\n    })\n}\n"
  },
  {
    "path": "packages/wasm-split/wasm-split-cli/src/main.rs",
    "content": "use clap::Parser;\nuse std::path::PathBuf;\nuse wasm_split_cli::SplitModule;\n\nfn main() {\n    tracing_subscriber::fmt()\n        .without_time()\n        .compact()\n        .with_env_filter(\"debug,walrus=info\")\n        .init();\n\n    match Commands::parse() {\n        Commands::Split(split_args) => split(split_args),\n        Commands::Validate(validate_args) => validate(validate_args),\n    }\n}\n\n#[derive(Parser)]\nenum Commands {\n    /// Split a wasm module into multiple chunks\n    #[clap(name = \"split\")]\n    Split(SplitArgs),\n\n    /// Validate the main module of a wasm module\n    #[clap(name = \"validate\")]\n    Validate(ValidateArgs),\n}\n\n#[derive(Parser)]\nstruct SplitArgs {\n    /// The wasm module emitted by rustc\n    original: PathBuf,\n\n    /// The wasm module emitted by wasm-bindgen\n    bindgened: PathBuf,\n\n    /// The output *directory* to write the split wasm files to\n    out_dir: PathBuf,\n}\n\nfn split(args: SplitArgs) {\n    let original = std::fs::read(&args.original).expect(\"failed to read input file\");\n    let bindgened = std::fs::read(&args.bindgened).expect(\"failed to read input file\");\n\n    _ = std::fs::remove_dir_all(&args.out_dir);\n    std::fs::create_dir_all(&args.out_dir).expect(\"failed to create output dir\");\n\n    tracing::info!(\"Building split module\");\n\n    let module = wasm_split_cli::Splitter::new(&original, &bindgened).unwrap();\n\n    let mut chunks = module.emit().unwrap();\n\n    // Write out the main module\n    tracing::info!(\n        \"Writing main module to {}\",\n        args.out_dir.join(\"main.wasm\").display()\n    );\n    std::fs::write(args.out_dir.join(\"main.wasm\"), &chunks.main.bytes).unwrap();\n\n    // Write the js module\n    std::fs::write(\n        args.out_dir.join(\"__wasm_split.js\"),\n        emit_js(&chunks.chunks, &chunks.modules),\n    )\n    .expect(\"failed to write js module\");\n\n    for (idx, chunk) in chunks.chunks.iter().enumerate() {\n        tracing::info!(\n            \"Writing chunk {} to {}\",\n            idx,\n            args.out_dir\n                .join(format!(\"chunk_{}_{}.wasm\", idx, chunk.module_name))\n                .display()\n        );\n        std::fs::write(\n            args.out_dir\n                .join(format!(\"chunk_{}_{}.wasm\", idx, chunk.module_name)),\n            &chunk.bytes,\n        )\n        .expect(\"failed to write chunk\");\n    }\n\n    for (idx, module) in chunks.modules.iter_mut().enumerate() {\n        tracing::info!(\n            \"Writing module {} to {}\",\n            idx,\n            args.out_dir\n                .join(format!(\n                    \"module_{}_{}.wasm\",\n                    idx,\n                    module.component_name.as_ref().unwrap()\n                ))\n                .display()\n        );\n        std::fs::write(\n            args.out_dir.join(format!(\n                \"module_{}_{}.wasm\",\n                idx,\n                module.component_name.as_ref().unwrap()\n            )),\n            &module.bytes,\n        )\n        .expect(\"failed to write chunk\");\n    }\n}\n\nfn emit_js(chunks: &[SplitModule], modules: &[SplitModule]) -> String {\n    use std::fmt::Write;\n    let mut glue = format!(\n        r#\"import {{ initSync }} from \"./main.js\";\n{}\"#,\n        include_str!(\"./__wasm_split.js\")\n    );\n\n    for (idx, chunk) in chunks.iter().enumerate() {\n        tracing::debug!(\"emitting chunk: {:?}\", chunk.module_name);\n        writeln!(\n                glue,\n                \"export const __wasm_split_load_chunk_{idx} = makeLoad(\\\"/harness/split/chunk_{idx}_{module}.wasm\\\", [], fusedImports, initSync);\",\n                module = chunk.module_name\n            ).expect(\"failed to write to string\");\n    }\n\n    // Now write the modules\n    for (idx, module) in modules.iter().enumerate() {\n        let deps = module\n            .relies_on_chunks\n            .iter()\n            .map(|idx| format!(\"__wasm_split_load_chunk_{idx}\"))\n            .collect::<Vec<_>>()\n            .join(\", \");\n        let hash_id = module.hash_id.as_ref().unwrap();\n\n        writeln!(\n                glue,\n                \"export const __wasm_split_load_{module}_{hash_id}_{cname} = makeLoad(\\\"/harness/split/module_{idx}_{cname}.wasm\\\", [{deps}], fusedImports, initSync);\",\n                module = module.module_name,\n                idx = idx,\n                cname = module.component_name.as_ref().unwrap(),\n                deps = deps\n            )\n            .expect(\"failed to write to string\");\n    }\n\n    glue\n}\n\n#[derive(Parser)]\nstruct ValidateArgs {\n    /// The input wasm file to validate\n    main: PathBuf,\n\n    chunks: Vec<PathBuf>,\n}\n\nfn validate(args: ValidateArgs) {\n    let bytes = std::fs::read(&args.main).expect(\"failed to read input file\");\n    let main_module = walrus::Module::from_buffer(&bytes).unwrap();\n\n    for chunk in args.chunks {\n        let bytes = std::fs::read(chunk).expect(\"failed to read input file\");\n        let chunk_module = walrus::Module::from_buffer(&bytes).unwrap();\n\n        assert!(chunk_module.tables.iter().count() == 1);\n\n        for import in chunk_module.imports.iter() {\n            let matching = main_module.exports.iter().find(|e| e.name == import.name);\n\n            let Some(matching) = matching else {\n                tracing::error!(\"Could not find matching export for import {import:#?}\");\n                continue;\n            };\n\n            tracing::debug!(\"import: {:?}\", matching.name);\n        }\n    }\n}\n"
  },
  {
    "path": "packages/wasm-split/wasm-split-macro/Cargo.toml",
    "content": "[package]\nname = \"wasm-split-macro\"\nedition = \"2021\"\nversion = { workspace = true }\nauthors = [\"Jonathan Kelley\"]\ndescription = \"macro crate for wasm-split - a tool for splitting up large WASM binaries into smaller chunks\"\nrepository = \"https://github.com/DioxusLabs/dioxus/\"\nlicense = \"MIT OR Apache-2.0\"\nkeywords = [\"wasm\", \"cli\", \"split\", \"dioxus\"]\nrust-version = \"1.81.0\"\n\n\n[dependencies]\nsyn = { workspace = true, features = [\"full\"] }\nbase16 = { workspace = true }\ndigest = { workspace = true }\nquote = { workspace = true }\nsha2 = { workspace = true }\nproc-macro2 = { workspace = true }\n\n[lib]\nproc-macro = true\n"
  },
  {
    "path": "packages/wasm-split/wasm-split-macro/src/lib.rs",
    "content": "use proc_macro::TokenStream;\n\nuse digest::Digest;\nuse quote::{format_ident, quote};\nuse syn::{parse_macro_input, parse_quote, FnArg, Ident, ItemFn, ReturnType, Signature};\n\n#[proc_macro_attribute]\npub fn wasm_split(args: TokenStream, input: TokenStream) -> TokenStream {\n    let module_ident = parse_macro_input!(args as Ident);\n    let item_fn = parse_macro_input!(input as ItemFn);\n\n    if item_fn.sig.asyncness.is_none() {\n        panic!(\"wasm_split functions must be async. Use a LazyLoader with synchronous functions instead.\");\n    }\n\n    let LoaderNames {\n        split_loader_ident,\n        impl_import_ident,\n        impl_export_ident,\n        load_module_ident,\n        ..\n    } = LoaderNames::new(item_fn.sig.ident.clone(), module_ident.to_string());\n\n    let mut desugard_async_sig = item_fn.sig.clone();\n    desugard_async_sig.asyncness = None;\n    desugard_async_sig.output = match &desugard_async_sig.output {\n        ReturnType::Default => {\n            parse_quote! { -> ::std::pin::Pin<Box<dyn ::std::future::Future<Output = ()>>> }\n        }\n        ReturnType::Type(_, ty) => {\n            parse_quote! { -> ::std::pin::Pin<Box<dyn ::std::future::Future<Output = #ty>>> }\n        }\n    };\n\n    let import_sig = Signature {\n        ident: impl_import_ident.clone(),\n        ..desugard_async_sig.clone()\n    };\n\n    let export_sig = Signature {\n        ident: impl_export_ident.clone(),\n        ..desugard_async_sig.clone()\n    };\n\n    let default_item = item_fn.clone();\n\n    let mut wrapper_sig = item_fn.sig;\n    wrapper_sig.asyncness = Some(Default::default());\n\n    let mut args = Vec::new();\n    for (i, param) in wrapper_sig.inputs.iter_mut().enumerate() {\n        match param {\n            syn::FnArg::Receiver(_) => args.push(format_ident!(\"self\")),\n            syn::FnArg::Typed(pat_type) => {\n                let param_ident = format_ident!(\"__wasm_split_arg_{i}\");\n                args.push(param_ident.clone());\n                *pat_type.pat = syn::Pat::Ident(syn::PatIdent {\n                    attrs: vec![],\n                    by_ref: None,\n                    mutability: None,\n                    ident: param_ident,\n                    subpat: None,\n                });\n            }\n        }\n    }\n\n    let attrs = &item_fn.attrs;\n    let stmts = &item_fn.block.stmts;\n\n    quote! {\n        #[cfg(target_arch = \"wasm32\")]\n        #wrapper_sig {\n            #(#attrs)*\n            #[allow(improper_ctypes_definitions)]\n            #[no_mangle]\n            pub extern \"C\" #export_sig {\n                Box::pin(async move { #(#stmts)* })\n            }\n\n            #[link(wasm_import_module = \"./__wasm_split.js\")]\n            extern \"C\" {\n                #[no_mangle]\n                fn #load_module_ident (\n                    callback: unsafe extern \"C\" fn(*const ::std::ffi::c_void, bool),\n                    data: *const ::std::ffi::c_void\n                );\n\n                #[allow(improper_ctypes)]\n                #[no_mangle]\n                #import_sig;\n            }\n\n            thread_local! {\n                static #split_loader_ident: wasm_split::LazySplitLoader = unsafe {\n                    wasm_split::LazySplitLoader::new(#load_module_ident)\n                };\n            }\n\n            // Initiate the download by calling the load_module_ident function which will kick-off the loader\n            if !wasm_split::LazySplitLoader::ensure_loaded(&#split_loader_ident).await {\n                panic!(\"Failed to load wasm-split module\");\n            }\n\n            unsafe { #impl_import_ident( #(#args),* ) }.await\n        }\n\n        #[cfg(not(target_arch = \"wasm32\"))]\n        #default_item\n    }\n    .into()\n}\n\n/// Create a lazy loader for a given function. Meant to be used in statics. Designed for libraries to\n/// integrate with.\n///\n/// ```rust, ignore\n/// fn SomeFunction(args: Args) -> Ret {}\n///\n/// static LOADER: wasm_split::LazyLoader<Args, Ret> = lazy_loader!(SomeFunction);\n///\n/// LOADER.load().await.call(args)\n/// ```\n#[proc_macro]\npub fn lazy_loader(input: TokenStream) -> TokenStream {\n    // We can only accept idents/paths that will be the source function\n    let sig = parse_macro_input!(input as Signature);\n    let params = sig.inputs.clone();\n    let outputs = sig.output.clone();\n    let Some(FnArg::Typed(arg)) = params.first().cloned() else {\n        panic!(\n            \"Lazy Loader must define a single input argument to satisfy the LazyLoader signature\"\n        )\n    };\n    let arg_ty = arg.ty.clone();\n    let LoaderNames {\n        name,\n        split_loader_ident,\n        impl_import_ident,\n        impl_export_ident,\n        load_module_ident,\n        ..\n    } = LoaderNames::new(\n        sig.ident.clone(),\n        sig.abi\n            .as_ref()\n            .and_then(|abi| abi.name.as_ref().map(|f| f.value()))\n            .expect(\"abi to be module name\")\n            .to_string(),\n    );\n\n    quote! {\n        {\n            #[cfg(target_arch = \"wasm32\")]\n            {\n                #[link(wasm_import_module = \"./__wasm_split.js\")]\n                extern \"C\" {\n                    // The function we'll use to initiate the download of the module\n                    #[no_mangle]\n                    fn #load_module_ident(\n                        callback: unsafe extern \"C\" fn(*const ::std::ffi::c_void, bool),\n                        data: *const ::std::ffi::c_void,\n                    );\n\n                    #[allow(improper_ctypes)]\n                    #[no_mangle]\n                    fn #impl_import_ident(arg: #arg_ty) #outputs;\n                }\n\n\n                #[allow(improper_ctypes_definitions)]\n                #[no_mangle]\n                pub extern \"C\" fn #impl_export_ident(arg: #arg_ty) #outputs {\n                    #name(arg)\n                }\n\n                thread_local! {\n                    static #split_loader_ident: wasm_split::LazySplitLoader = unsafe {\n                        wasm_split::LazySplitLoader::new(#load_module_ident)\n                    };\n                };\n\n                unsafe {\n                    wasm_split::LazyLoader::new(#impl_import_ident, &#split_loader_ident)\n                }\n            }\n\n            #[cfg(not(target_arch = \"wasm32\"))]\n            {\n                wasm_split::LazyLoader::preloaded(#name)\n            }\n        }\n    }\n    .into()\n}\n\nstruct LoaderNames {\n    name: Ident,\n    split_loader_ident: Ident,\n    impl_import_ident: Ident,\n    impl_export_ident: Ident,\n    load_module_ident: Ident,\n}\n\nimpl LoaderNames {\n    fn new(name: Ident, module: String) -> Self {\n        let unique_identifier = base16::encode_lower(\n            &sha2::Sha256::digest(format!(\"{name} {span:?}\", name = name, span = name.span()))\n                [..16],\n        );\n\n        Self {\n            split_loader_ident: format_ident!(\"__wasm_split_loader_{module}\"),\n            impl_export_ident: format_ident!(\n                \"__wasm_split_00___{module}___00_export_{unique_identifier}_{name}\"\n            ),\n            impl_import_ident: format_ident!(\n                \"__wasm_split_00___{module}___00_import_{unique_identifier}_{name}\"\n            ),\n            load_module_ident: format_ident!(\n                \"__wasm_split_load_{module}_{unique_identifier}_{name}\"\n            ),\n            name,\n        }\n    }\n}\n"
  },
  {
    "path": "packages/wasm-split/wasm-used/Cargo.toml",
    "content": "[package]\nname = \"wasm-used\"\nedition = \"2021\"\nversion.workspace = true\nauthors = [\"Jonathan Kelley\"]\nlicense = \"MIT OR Apache-2.0\"\nrepository = \"https://github.com/DioxusLabs/dioxus/\"\nhomepage = \"https://dioxuslabs.com\"\ndescription = \"wasm-used implementation for Rust\"\nkeywords = [\"wasm\", \"wasm-used\", \"wasm-split\", \"dioxus\" ]\n\n[dependencies]\nwalrus = { workspace = true, features = [\"parallel\"] }\nid-arena = { workspace = true }\ntracing = { workspace = true }\n"
  },
  {
    "path": "packages/wasm-split/wasm-used/src/lib.rs",
    "content": "use std::collections::HashSet;\n\nuse id_arena::Id;\nuse walrus::{ir::*, ExportId};\nuse walrus::{ConstExpr, Data, DataId, DataKind, Element, ExportItem, Function};\nuse walrus::{ElementId, ElementItems, ElementKind, Module, RefType, Type, TypeId};\nuse walrus::{FunctionId, FunctionKind, Global, GlobalId};\nuse walrus::{GlobalKind, Memory, MemoryId, Table, TableId};\n\ntype IdHashSet<T> = HashSet<Id<T>>;\n\n/// Set of all root used items in a wasm module.\n#[derive(Debug, Default)]\npub struct Roots {\n    tables: Vec<TableId>,\n    funcs: Vec<(FunctionId, Location)>,\n    globals: Vec<GlobalId>,\n    memories: Vec<MemoryId>,\n    data: Vec<DataId>,\n    elements: Vec<ElementId>,\n    used: Used,\n}\n\n#[allow(dead_code)]\n#[derive(Debug)]\npub enum Location {\n    Start,\n    Export { export: ExportId },\n    Table { table: TableId },\n    Memory { memory: MemoryId },\n    Global { global: GlobalId },\n    Data,\n    Element { element: ElementId },\n    Code { func: FunctionId },\n}\n\nimpl Roots {\n    /// Creates a new set of empty roots.\n    pub fn new() -> Roots {\n        Roots::default()\n    }\n\n    /// Adds a new function to the set of roots\n    pub fn push_func(&mut self, func: FunctionId, from: Location) -> &mut Roots {\n        if self.used.funcs.insert(func) {\n            // log::trace!(\"function is used: {:?}\", func);\n            self.funcs.push((func, from));\n        }\n        self\n    }\n\n    /// Adds a new table to the set of roots\n    pub fn push_table(&mut self, table: TableId) -> &mut Roots {\n        if self.used.tables.insert(table) {\n            // log::trace!(\"table is used: {:?}\", table);\n            self.tables.push(table);\n        }\n        self\n    }\n\n    /// Adds a new memory to the set of roots\n    pub fn push_memory(&mut self, memory: MemoryId) -> &mut Roots {\n        if self.used.memories.insert(memory) {\n            // log::trace!(\"memory is used: {:?}\", memory);\n            self.memories.push(memory);\n        }\n        self\n    }\n\n    /// Adds a new global to the set of roots\n    pub fn push_global(&mut self, global: GlobalId) -> &mut Roots {\n        if self.used.globals.insert(global) {\n            // log::trace!(\"global is used: {:?}\", global);\n            self.globals.push(global);\n        }\n        self\n    }\n\n    fn push_data(&mut self, data: DataId) -> &mut Roots {\n        if self.used.data.insert(data) {\n            // log::trace!(\"data is used: {:?}\", data);\n            self.data.push(data);\n        }\n        self\n    }\n\n    fn push_element(&mut self, element: ElementId) -> &mut Roots {\n        if self.used.elements.insert(element) {\n            // log::trace!(\"element is used: {:?}\", element);\n            self.elements.push(element);\n        }\n        self\n    }\n}\n\n/// Finds the things within a module that are used.\n///\n/// This is useful for implementing something like a linker's `--gc-sections` so\n/// that our emitted `.wasm` binaries are small and don't contain things that\n/// are not used.\n#[derive(Debug, Default)]\npub struct Used {\n    /// The module's used tables.\n    pub tables: IdHashSet<Table>,\n    /// The module's used types.\n    pub types: IdHashSet<Type>,\n    /// The module's used functions.\n    pub funcs: IdHashSet<Function>,\n    /// The module's used globals.\n    pub globals: IdHashSet<Global>,\n    /// The module's used memories.\n    pub memories: IdHashSet<Memory>,\n    /// The module's used passive element segments.\n    pub elements: IdHashSet<Element>,\n    /// The module's used passive data segments.\n    pub data: IdHashSet<Data>,\n}\n\nimpl Used {\n    /// Construct a new `Used` set for the given module.\n    pub fn new(module: &Module, deleted: &HashSet<FunctionId>) -> Used {\n        // log::debug!(\"starting to calculate used set\");\n        let mut stack = Roots::default();\n\n        // All exports are roots\n        for export in module.exports.iter() {\n            match export.item {\n                ExportItem::Function(f) => stack.push_func(\n                    f,\n                    Location::Export {\n                        export: export.id(),\n                    },\n                ),\n                ExportItem::Table(t) => stack.push_table(t),\n                ExportItem::Memory(m) => stack.push_memory(m),\n                ExportItem::Global(g) => stack.push_global(g),\n            };\n        }\n\n        // The start function is an implicit root as well\n        if let Some(f) = module.start {\n            stack.push_func(f, Location::Start);\n        }\n\n        // Initialization of memories or tables is a side-effectful operation\n        // because they can be out-of-bounds, so keep all active segments.\n        for data in module.data.iter() {\n            if let DataKind::Active { .. } = &data.kind {\n                stack.push_data(data.id());\n            }\n        }\n        for elem in module.elements.iter() {\n            match elem.kind {\n                // Active segments are rooted because they initialize imported\n                // tables.\n                ElementKind::Active { table, .. } => {\n                    if module.tables.get(table).import.is_some() {\n                        stack.push_element(elem.id());\n                    }\n                }\n                // Declared segments can probably get gc'd but for now we're\n                // conservative and we root them\n                ElementKind::Declared => {\n                    stack.push_element(elem.id());\n                }\n                ElementKind::Passive => {}\n            }\n        }\n\n        // // And finally ask custom sections for their roots\n        // for (_id, section) in module.customs.iter() {\n        //     section.add_gc_roots(&mut stack);\n        // }\n        // tracing::info!(\"Used roots: {:#?}\", stack);\n\n        // Iteratively visit all items until our stack is empty\n        while !stack.funcs.is_empty()\n            || !stack.tables.is_empty()\n            || !stack.memories.is_empty()\n            || !stack.globals.is_empty()\n            || !stack.data.is_empty()\n            || !stack.elements.is_empty()\n        {\n            while let Some((f, _loc)) = stack.funcs.pop() {\n                if deleted.contains(&f) {\n                    let func = module.funcs.get(f);\n                    let name = func\n                        .name\n                        .as_ref()\n                        .cloned()\n                        .unwrap_or_else(|| format!(\"unknown - {}\", f.index()));\n                    // panic!(\n                    //     \"Found a function that should be deleted but is still used: {:?} - {:?}\",\n                    //     name, f\n                    // );\n                    tracing::error!(\n                        \"Found a function that should be deleted but is still used: {:?} - {:?} - {:?}\",\n                        name,\n                        f,\n                        _loc\n                    );\n                    if let Location::Code { func } = _loc {\n                        let func_name = module.funcs.get(func).name.as_ref().unwrap();\n                        tracing::error!(\"Function {:?} is used by {:?}\", f, func_name);\n                    }\n\n                    // continue;\n                }\n\n                let func = module.funcs.get(f);\n                stack.used.types.insert(func.ty());\n\n                match &func.kind {\n                    FunctionKind::Local(func) => {\n                        let mut visitor = UsedVisitor {\n                            cur_func: f,\n                            stack: &mut stack,\n                        };\n                        dfs_in_order(&mut visitor, func, func.entry_block());\n                    }\n                    FunctionKind::Import(_) => {}\n                    FunctionKind::Uninitialized(_) => unreachable!(),\n                }\n            }\n\n            while let Some(t) = stack.tables.pop() {\n                for elem in module.tables.get(t).elem_segments.iter() {\n                    stack.push_element(*elem);\n                }\n            }\n\n            while let Some(t) = stack.globals.pop() {\n                match &module.globals.get(t).kind {\n                    GlobalKind::Import(_) => {}\n                    GlobalKind::Local(ConstExpr::Global(global)) => {\n                        stack.push_global(*global);\n                    }\n                    GlobalKind::Local(ConstExpr::RefFunc(func)) => {\n                        stack.push_func(*func, Location::Global { global: t });\n                    }\n                    GlobalKind::Local(ConstExpr::Value(_))\n                    | GlobalKind::Local(ConstExpr::RefNull(_)) => {}\n                }\n            }\n\n            while let Some(t) = stack.memories.pop() {\n                for data in &module.memories.get(t).data_segments {\n                    stack.push_data(*data);\n                }\n            }\n\n            while let Some(d) = stack.data.pop() {\n                let d = module.data.get(d);\n                if let DataKind::Active { memory, offset } = &d.kind {\n                    stack.push_memory(*memory);\n                    if let ConstExpr::Global(g) = offset {\n                        stack.push_global(*g);\n                    }\n                }\n            }\n\n            while let Some(e) = stack.elements.pop() {\n                let e = module.elements.get(e);\n                if let ElementItems::Functions(function_ids) = &e.items {\n                    function_ids.iter().for_each(|f| {\n                        stack.push_func(*f, Location::Element { element: e.id() });\n                    });\n                }\n                if let ElementItems::Expressions(RefType::Funcref, items) = &e.items {\n                    for item in items {\n                        match item {\n                            ConstExpr::Global(g) => {\n                                stack.push_global(*g);\n                            }\n                            ConstExpr::RefFunc(f) => {\n                                stack.push_func(*f, Location::Element { element: e.id() });\n                            }\n                            _ => {}\n                        }\n                    }\n                }\n                if let ElementKind::Active { offset, table } = &e.kind {\n                    if let ConstExpr::Global(g) = offset {\n                        stack.push_global(*g);\n                    }\n                    stack.push_table(*table);\n                }\n            }\n        }\n\n        // Wabt seems to have weird behavior where a `data` segment, if present\n        // even if passive, requires a `memory` declaration. Our GC pass is\n        // pretty aggressive and if you have a passive data segment and only\n        // `data.drop` instructions you technically don't need the `memory`.\n        // Let's keep `wabt` passing though and just say that if there are data\n        // segments kept, but no memories, then we try to add the first memory,\n        // if any, to the used set.\n        if !stack.used.data.is_empty() && stack.used.memories.is_empty() {\n            if let Some(mem) = module.memories.iter().next() {\n                stack.used.memories.insert(mem.id());\n            }\n        }\n\n        stack.used\n    }\n}\n\nstruct UsedVisitor<'a> {\n    cur_func: FunctionId,\n    stack: &'a mut Roots,\n}\n\nimpl Visitor<'_> for UsedVisitor<'_> {\n    fn visit_function_id(&mut self, &func: &FunctionId) {\n        self.stack.push_func(\n            func,\n            Location::Code {\n                func: self.cur_func,\n            },\n        );\n    }\n\n    fn visit_memory_id(&mut self, &m: &MemoryId) {\n        self.stack.push_memory(m);\n    }\n\n    fn visit_global_id(&mut self, &g: &GlobalId) {\n        self.stack.push_global(g);\n    }\n\n    fn visit_table_id(&mut self, &t: &TableId) {\n        self.stack.push_table(t);\n    }\n\n    fn visit_type_id(&mut self, &t: &TypeId) {\n        self.stack.used.types.insert(t);\n    }\n\n    fn visit_data_id(&mut self, &d: &DataId) {\n        self.stack.push_data(d);\n    }\n\n    fn visit_element_id(&mut self, &e: &ElementId) {\n        self.stack.push_element(e);\n    }\n}\n"
  },
  {
    "path": "packages/web/.gitignore",
    "content": "dist/\n"
  },
  {
    "path": "packages/web/.vscode/settings.json",
    "content": "{\n    \"rust-analyzer.cargo.target\": \"wasm32-unknown-unknown\",\n}\n"
  },
  {
    "path": "packages/web/Cargo.toml",
    "content": "[package]\nname = \"dioxus-web\"\nversion = { workspace = true }\nauthors = [\"Jonathan Kelley\"]\nedition = \"2021\"\ndescription = \"Web-sys renderer for Dioxus: Build fullstack web, desktop, and mobile apps with a single codebase.\"\nlicense = \"MIT OR Apache-2.0\"\nrepository = \"https://github.com/DioxusLabs/dioxus/\"\nhomepage = \"https://dioxuslabs.com/learn/0.7/getting_started\"\nkeywords = [\"dom\", \"ui\", \"gui\", \"web\", \"wasm\"]\n\n[dependencies]\ndioxus-core = { workspace = true }\ndioxus-core-types = { workspace = true }\ndioxus-cli-config = { workspace = true, features = [\"web\"] }\ndioxus-html = { workspace = true }\ndioxus-history = { workspace = true }\ndioxus-document = { workspace = true }\ndioxus-devtools = { workspace = true }\ndioxus-signals = { workspace = true }\ndioxus-interpreter-js = { workspace = true, features = [\n    \"minimal_bindings\",\n    \"webonly\",\n] }\ndioxus-fullstack-core = { workspace = true, features = [\"web\"], optional = true }\ngenerational-box = { workspace = true }\n\njs-sys = { workspace = true }\nwasm-bindgen = { workspace = true }\nwasm-bindgen-futures = { workspace = true }\ntracing = { workspace = true }\nrustc-hash = { workspace = true }\nfutures-util = { workspace = true, features = [\"std\", \"async-await\", \"async-await-macro\"] }\nfutures-channel = { workspace = true }\nserde_json = { version = \"1.0\", optional = true }\nserde = { version = \"1.0\", optional = true }\nserde-wasm-bindgen = { version = \"0.6.5\", optional = true }\n\nciborium = { workspace = true, optional = true }\ngloo-timers = { workspace = true, optional = true, features = [\"futures\"] }\nsend_wrapper = { workspace = true, features = [\"futures\"] }\nwasm-streams = \"0.4.2\"\n\n[dependencies.web-sys]\nversion = \"0.3.77\"\nfeatures = [\n    \"AnimationEvent\",\n    \"ClipboardEvent\",\n    \"CloseEvent\",\n    \"Comment\",\n    \"CompositionEvent\",\n    \"console\",\n    \"CustomEvent\",\n    \"DataTransfer\",\n    \"DataTransferItemList\",\n    \"DataTransferItem\",\n    \"Document\",\n    \"DomRectReadOnly\",\n    \"DragEvent\",\n    \"FocusEvent\",\n    \"History\",\n    \"HtmlElement\",\n    \"HtmlFormElement\",\n    \"HtmlHeadElement\",\n    \"HtmlInputElement\",\n    \"HtmlSelectElement\",\n    \"HtmlTextAreaElement\",\n    \"IntersectionObserverEntry\",\n    \"InputEvent\",\n    \"KeyboardEvent\",\n    \"MouseEvent\",\n    \"NodeList\",\n    \"PointerEvent\",\n    \"ResizeObserverEntry\",\n    \"ResizeObserverSize\",\n    \"ScrollRestoration\",\n    \"ScrollToOptions\",\n    \"Text\",\n    \"Touch\",\n    \"TouchEvent\",\n    \"TouchList\",\n    \"TransitionEvent\",\n    \"WheelEvent\",\n    \"Window\",\n    \"File\",\n    \"FileList\",\n    \"FileReader\",\n    \"ReadableStream\",\n    \"FormData\"\n]\n\n[build-dependencies]\nlazy-js-bundle = { workspace = true }\n\n[features]\ndefault = [\"mounted\", \"devtools\", \"document\"]\nhydrate = [\"web-sys/Comment\", \"dep:serde\", \"dep:dioxus-fullstack-core\"]\nmounted = [\n    \"web-sys/Element\",\n    \"web-sys/Element\",\n    \"web-sys/DomRect\",\n    \"web-sys/ScrollIntoViewOptions\",\n    \"web-sys/ScrollLogicalPosition\",\n    \"web-sys/ScrollBehavior\",\n    \"web-sys/HtmlElement\",\n]\ndevtools = [\n  \"web-sys/MessageEvent\",\n  \"web-sys/WebSocket\",\n  \"web-sys/Location\",\n  \"dep:serde_json\",\n  \"dep:serde\",\n  \"dioxus-core/serialize\",\n  \"dep:gloo-timers\"\n]\ndocument = [\"dep:serde-wasm-bindgen\", \"dep:serde_json\", \"dep:serde\"]\n\n[dev-dependencies]\ndioxus = { workspace = true, default-features = true }\nwasm-bindgen-test = \"0.3.50\"\ndioxus-ssr = { workspace = true, default-features = false }\ngloo-dialogs = { workspace = true }\ndioxus-web = { path = \".\", features = [\"hydrate\"] }\ntracing-wasm = { workspace = true }\n\n[package.metadata.docs.rs]\ncargo-args = [\"-Zunstable-options\", \"-Zrustdoc-scrape-examples\"]\n"
  },
  {
    "path": "packages/web/README.md",
    "content": "# Dioxus-web\n\n[![Crates.io][crates-badge]][crates-url]\n[![MIT licensed][mit-badge]][mit-url]\n[![Build Status][actions-badge]][actions-url]\n[![Discord chat][discord-badge]][discord-url]\n\n[crates-badge]: https://img.shields.io/crates/v/dioxus-web.svg\n[crates-url]: https://crates.io/crates/dioxus-web\n[mit-badge]: https://img.shields.io/badge/license-MIT-blue.svg\n[mit-url]: https://github.com/dioxuslabs/dioxus/blob/main/LICENSE-MIT\n[actions-badge]: https://github.com/dioxuslabs/dioxus/actions/workflows/main.yml/badge.svg\n[actions-url]: https://github.com/dioxuslabs/dioxus/actions?query=workflow%3ACI+branch%3Amaster\n[discord-badge]: https://img.shields.io/discord/899851952891002890.svg?logo=discord&style=flat-square\n[discord-url]: https://discord.gg/XgGxMSkvUM\n\n[Website](https://dioxuslabs.com) |\n[Guides](https://dioxuslabs.com/learn/0.7/) |\n[API Docs](https://docs.rs/dioxus-web/latest/dioxus_web) |\n[Chat](https://discord.gg/XgGxMSkvUM)\n\n## Overview\n\nRun Dioxus in the browser using WebAssembly.\n\n- Relies on [sledgehammer-bindgen](https://github.com/Demonthos/sledgehammer_bindgen) and [web-sys](https://github.com/rustwasm/wasm-bindgen/tree/main/crates/web-sys) to modify the dom\n- Supports instant hot reloading via the Dioxus CLI\n- Around 60k gzipped\n\n## Contributing\n\n- Report issues on our [issue tracker](https://github.com/dioxuslabs/dioxus/issues).\n- Join the discord and ask questions!\n\n## License\n\nThis project is licensed under the [MIT license].\n\n[mit license]: https://github.com/dioxuslabs/dioxus/blob/main/LICENSE-MIT\n\nUnless you explicitly state otherwise, any contribution intentionally submitted\nfor inclusion in Dioxus by you shall be licensed as MIT without any additional\nterms or conditions.\n"
  },
  {
    "path": "packages/web/build.rs",
    "content": "fn main() {\n    // If any TS files change, re-run the build script\n    lazy_js_bundle::LazyTypeScriptBindings::new()\n        .with_watching(\"./src/ts\")\n        .with_binding(\"./src/ts/eval.ts\", \"./src/js/eval.js\")\n        .run();\n}\n"
  },
  {
    "path": "packages/web/src/cfg.rs",
    "content": "use std::rc::Rc;\n\nuse dioxus_core::LaunchConfig;\nuse wasm_bindgen::JsCast as _;\n\n///  Configuration for the WebSys renderer for the Dioxus VirtualDOM.\n///\n/// This struct helps configure the specifics of hydration and render destination for WebSys.\n///\n/// # Example\n///\n/// ```rust, ignore\n/// dioxus_web::launch(App, Config::new().hydrate(true).root_name(\"myroot\"))\n/// ```\npub struct Config {\n    pub(crate) hydrate: bool,\n    #[allow(dead_code)]\n    pub(crate) panic_hook: bool,\n    pub(crate) root: ConfigRoot,\n    #[cfg(feature = \"document\")]\n    pub(crate) history: Option<Rc<dyn dioxus_history::History>>,\n}\n\nimpl LaunchConfig for Config {}\n\npub(crate) enum ConfigRoot {\n    RootName(String),\n    RootNode(web_sys::Node),\n}\n\nimpl Config {\n    /// Create a new Default instance of the Config.\n    ///\n    /// This is no different than calling `Config::default()`\n    pub fn new() -> Self {\n        Self::default()\n    }\n\n    #[cfg(feature = \"hydrate\")]\n    /// Enable SSR hydration\n    ///\n    /// This enables Dioxus to pick up work from a pre-rendered HTML file. Hydration will completely skip over any async\n    /// work and suspended nodes.\n    ///\n    /// Dioxus will load up all the elements with the `dio_el` data attribute into memory when the page is loaded.\n    pub fn hydrate(mut self, f: bool) -> Self {\n        self.hydrate = f;\n        self\n    }\n\n    /// Set the name of the element that Dioxus will use as the root.\n    ///\n    /// This is akin to calling React.render() on the element with the specified name.\n    /// Note that this only works on the current document, i.e. `window.document`.\n    /// To use a different document (popup, iframe, ...) use [Self::rootelement] instead.\n    pub fn rootname(mut self, name: impl Into<String>) -> Self {\n        self.root = ConfigRoot::RootName(name.into());\n        self\n    }\n\n    /// Set the element that Dioxus will use as root.\n    ///\n    /// This is akin to calling React.render() on the given element.\n    pub fn rootelement(mut self, elem: web_sys::Element) -> Self {\n        self.root = ConfigRoot::RootNode(elem.unchecked_into());\n        self\n    }\n\n    /// Set the node that Dioxus will use as root.\n    ///\n    /// This is akin to calling React.render() on the given element.\n    pub fn rootnode(mut self, node: web_sys::Node) -> Self {\n        self.root = ConfigRoot::RootNode(node);\n        self\n    }\n\n    /// Set the history provider for the application.\n    ///\n    /// `dioxus-web` provides two history providers:\n    /// - `dioxus_history::WebHistory`: A history provider that uses the browser history API.\n    /// - `dioxus_history::HashHistory`: A history provider that uses the `#` url fragment.\n    ///\n    /// By default, `dioxus-web` uses the `WebHistory` provider, but this method can be used to configure\n    /// a different history provider.\n    pub fn history(mut self, history: Rc<dyn dioxus_history::History>) -> Self {\n        #[cfg(feature = \"document\")]\n        {\n            self.history = Some(history);\n        }\n        self\n    }\n}\n\nimpl Default for Config {\n    fn default() -> Self {\n        Self {\n            hydrate: false,\n            root: ConfigRoot::RootName(\"main\".to_string()),\n            #[cfg(feature = \"document\")]\n            history: None,\n            panic_hook: true,\n        }\n    }\n}\n"
  },
  {
    "path": "packages/web/src/data_transfer.rs",
    "content": "use crate::WebFileData;\nuse dioxus_html::{FileData, NativeDataTransfer};\n\n/// A wrapper around the web_sys::DataTransfer to implement NativeDataTransfer\n#[derive(Clone)]\npub struct WebDataTransfer {\n    pub(crate) data: web_sys::DataTransfer,\n}\n\nimpl WebDataTransfer {\n    /// Create a new WebDataTransfer from a web_sys::DataTransfer\n    pub fn new(data: web_sys::DataTransfer) -> Self {\n        Self { data }\n    }\n}\n\nunsafe impl Send for WebDataTransfer {}\nunsafe impl Sync for WebDataTransfer {}\n\nimpl NativeDataTransfer for WebDataTransfer {\n    fn get_data(&self, format: &str) -> Option<String> {\n        self.data.get_data(format).ok()\n    }\n    fn set_data(&self, format: &str, data: &str) -> Result<(), String> {\n        self.data.set_data(format, data).map_err(|e| {\n            format!(\n                \"Failed to set data for format {format}: {:?}\",\n                e.as_string()\n            )\n        })\n    }\n    fn clear_data(&self, format: Option<&str>) -> Result<(), String> {\n        match format {\n            Some(f) => self.data.clear_data_with_format(f),\n            None => self.data.clear_data(),\n        }\n        .map_err(|e| format!(\"{:?}\", e))\n    }\n    fn effect_allowed(&self) -> String {\n        self.data.effect_allowed()\n    }\n    fn set_effect_allowed(&self, effect: &str) {\n        self.data.set_effect_allowed(effect);\n    }\n    fn drop_effect(&self) -> String {\n        self.data.drop_effect()\n    }\n    fn set_drop_effect(&self, effect: &str) {\n        self.data.set_drop_effect(effect);\n    }\n\n    fn files(&self) -> Vec<FileData> {\n        let mut result = Vec::new();\n        if let Some(file_list) = self.data.files() {\n            for i in 0..file_list.length() {\n                if let Some(file) = file_list.item(i) {\n                    result.push(FileData::new(WebFileData::new(\n                        file,\n                        web_sys::FileReader::new().unwrap(),\n                    )));\n                }\n            }\n        }\n        result\n    }\n}\n"
  },
  {
    "path": "packages/web/src/devtools.rs",
    "content": "//! Handler code for hotreloading.\n//!\n//! This sets up a websocket connection to the devserver and handles messages from it.\n//! We also set up a little recursive timer that will attempt to reconnect if the connection is lost.\n\nuse dioxus_devtools::{DevserverMsg, HotReloadMsg};\nuse futures_channel::mpsc::{unbounded, UnboundedReceiver, UnboundedSender};\nuse js_sys::JsString;\nuse std::fmt::Display;\nuse std::time::Duration;\nuse wasm_bindgen::prelude::*;\nuse wasm_bindgen::JsCast;\nuse wasm_bindgen::{closure::Closure, JsValue};\nuse web_sys::{window, CloseEvent, MessageEvent, WebSocket};\n\nconst POLL_INTERVAL_MIN: i32 = 250;\nconst POLL_INTERVAL_MAX: i32 = 4000;\nconst POLL_INTERVAL_SCALE_FACTOR: i32 = 2;\n\n/// Amount of time that toats should be displayed.\nconst TOAST_TIMEOUT: Duration = Duration::from_secs(5);\nconst TOAST_TIMEOUT_LONG: Duration = Duration::from_secs(3600); // Duration::MAX is too long for JS.\n\npub(crate) fn init(config: &crate::Config) -> UnboundedReceiver<HotReloadMsg> {\n    // Create the tx/rx pair that we'll use for the top-level future in the dioxus loop\n    let (tx, rx) = unbounded();\n\n    // Wire up the websocket to the devserver\n    make_ws(tx.clone(), POLL_INTERVAL_MIN, false);\n\n    // Set up the playground\n    playground(tx);\n\n    // Set up the panic hook\n    if config.panic_hook {\n        std::panic::set_hook(Box::new(|info| {\n            hook_impl(info);\n        }));\n    }\n\n    rx\n}\n\nfn make_ws(tx: UnboundedSender<HotReloadMsg>, poll_interval: i32, reload: bool) {\n    // Get the location of the devserver, using the current location plus the /_dioxus path\n    // The idea here being that the devserver is always located on the /_dioxus behind a proxy\n    let location = web_sys::window().unwrap().location();\n    let url = format!(\n        \"{protocol}//{host}/_dioxus?build_id={build_id}\",\n        protocol = match location.protocol().unwrap() {\n            prot if prot == \"https:\" => \"wss:\",\n            _ => \"ws:\",\n        },\n        host = location.host().unwrap(),\n        build_id = dioxus_cli_config::build_id(),\n    );\n\n    let ws = WebSocket::new(&url).unwrap();\n\n    // Set the onmessage handler to bounce messages off to the main dioxus loop\n    let tx_ = tx.clone();\n    ws.set_onmessage(Some(\n        Closure::<dyn FnMut(MessageEvent)>::new(move |e: MessageEvent| {\n            let Ok(text) = e.data().dyn_into::<JsString>() else {\n                return;\n            };\n\n            // The devserver messages have some &'static strs in them, so we need to leak the source string\n            let string: String = text.into();\n            let string = Box::leak(string.into_boxed_str());\n\n            match serde_json::from_str::<DevserverMsg>(string) {\n                Ok(DevserverMsg::HotReload(hr)) => _ = tx_.unbounded_send(hr),\n\n                // todo: we want to throw a screen here that shows the user that the devserver has disconnected\n                // Would be nice to do that with dioxus itself or some html/css\n                // But if the dev server shutsdown we don't want to be super aggressive about it... let's\n                // play with other devservers to see how they handle this\n                Ok(DevserverMsg::Shutdown) => {\n                    web_sys::console::error_1(&\"Connection to the devserver was closed\".into())\n                }\n\n                // The devserver is telling us that it started a full rebuild. This does not mean that it is ready.\n                Ok(DevserverMsg::FullReloadStart) => show_toast(\n                    \"Your app is being rebuilt.\",\n                    \"A non-hot-reloadable change occurred and we must rebuild.\",\n                    ToastLevel::Info,\n                    TOAST_TIMEOUT_LONG,\n                    false,\n                ),\n\n                // The devserver is telling us that it started a full rebuild. This does not mean that it is ready.\n                Ok(DevserverMsg::HotPatchStart) => show_toast(\n                    \"Hot-patching app...\",\n                    \"Hot-patching modified Rust code.\",\n                    ToastLevel::Info,\n                    TOAST_TIMEOUT_LONG,\n                    false,\n                ),\n\n                // The devserver is telling us that the full rebuild failed.\n                Ok(DevserverMsg::FullReloadFailed) => show_toast(\n                    \"Oops! The build failed.\",\n                    \"We tried to rebuild your app, but something went wrong.\",\n                    ToastLevel::Error,\n                    TOAST_TIMEOUT_LONG,\n                    false,\n                ),\n\n                // The devserver is telling us to reload the whole page\n                Ok(DevserverMsg::FullReloadCommand) => {\n                    show_toast(\n                        \"Successfully rebuilt.\",\n                        \"Your app was rebuilt successfully and without error.\",\n                        ToastLevel::Success,\n                        TOAST_TIMEOUT,\n                        true,\n                    );\n                    window().unwrap().location().reload().unwrap()\n                }\n\n                Err(e) => web_sys::console::error_1(\n                    &format!(\"Error parsing devserver message: {}\", e).into(),\n                ),\n\n                e => {\n                    web_sys::console::error_1(\n                        &format!(\"Error parsing devserver message: {:?}\", e).into(),\n                    );\n                }\n            }\n        })\n        .into_js_value()\n        .as_ref()\n        .unchecked_ref(),\n    ));\n\n    // Set the onclose handler to reload the page if the connection is closed\n    ws.set_onclose(Some(\n        Closure::<dyn FnMut(CloseEvent)>::new(move |e: CloseEvent| {\n            // Firefox will send a 1001 code when the connection is closed because the page is reloaded\n            // Only firefox will trigger the onclose event when the page is reloaded manually: https://stackoverflow.com/questions/10965720/should-websocket-onclose-be-triggered-by-user-navigation-or-refresh\n            // We should not reload the page in this case\n            if e.code() == 1001 {\n                return;\n            }\n\n            // set timeout to reload the page in timeout_ms\n            let tx = tx.clone();\n            web_sys::window()\n                .unwrap()\n                .set_timeout_with_callback_and_timeout_and_arguments_0(\n                    Closure::<dyn FnMut()>::new(move || {\n                        make_ws(\n                            tx.clone(),\n                            POLL_INTERVAL_MAX.min(poll_interval * POLL_INTERVAL_SCALE_FACTOR),\n                            true,\n                        );\n                    })\n                    .into_js_value()\n                    .as_ref()\n                    .unchecked_ref(),\n                    poll_interval,\n                )\n                .unwrap();\n        })\n        .into_js_value()\n        .as_ref()\n        .unchecked_ref(),\n    ));\n\n    // Set the onopen handler to reload the page if the connection is closed\n    ws.set_onopen(Some(\n        Closure::<dyn FnMut(MessageEvent)>::new(move |_evt| {\n            if reload {\n                window().unwrap().location().reload().unwrap();\n            }\n        })\n        .into_js_value()\n        .as_ref()\n        .unchecked_ref(),\n    ));\n\n    // monkey patch our console.log / console.error to send the logs to the websocket\n    // this will let us see the logs in the devserver!\n    // We only do this if we're not reloading the page, since that will cause duplicate monkey patches\n    if !reload {\n        // the method we need to patch:\n        // https://developer.mozilla.org/en-US/docs/Web/API/Console/log\n        // log, info, warn, error, debug\n        let ws: &JsValue = ws.as_ref();\n        dioxus_interpreter_js::minimal_bindings::monkeyPatchConsole(ws.clone());\n    }\n}\n\n/// Represents what color the toast should have.\npub(crate) enum ToastLevel {\n    /// Green\n    Success,\n    /// Blue\n    Info,\n    /// Red\n    Error,\n}\n\nimpl Display for ToastLevel {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        match self {\n            ToastLevel::Success => write!(f, \"success\"),\n            ToastLevel::Info => write!(f, \"info\"),\n            ToastLevel::Error => write!(f, \"error\"),\n        }\n    }\n}\n\n#[wasm_bindgen(inline_js = r#\"\nexport function js_show_toast(header_text, message, level, as_ms) {\n    if (typeof showDXToast !== \"undefined\") {{\n        window.showDXToast(header_text, message, level, as_ms);\n    }}\n}\nexport function js_schedule_toast(header_text, message, level, as_ms) {\n    if (typeof scheduleDXToast !== \"undefined\") {{\n        window.scheduleDXToast(header_text, message, level, as_ms);\n    }}\n}\n\"#)]\nextern \"C\" {\n    fn js_schedule_toast(header_text: &str, message: &str, level: String, as_ms: u32);\n    fn js_show_toast(header_text: &str, message: &str, level: String, as_ms: u32);\n}\n\n/// Displays a toast to the developer.\npub(crate) fn show_toast(\n    header_text: &str,\n    message: &str,\n    level: ToastLevel,\n    duration: Duration,\n    after_reload: bool,\n) {\n    let as_ms: u32 = duration.as_millis().try_into().unwrap();\n\n    match after_reload {\n        true => js_schedule_toast(header_text, message, level.to_string(), as_ms),\n        false => js_show_toast(header_text, message, level.to_string(), as_ms),\n    }\n}\n\n/// Force a hotreload of the assets on this page by walking them and changing their URLs to include\n/// some extra entropy.\n///\n/// This should... mostly work.\npub(crate) fn invalidate_browser_asset_cache() {\n    // it might be triggering a reload of assets\n    // invalidate all the stylesheets on the page\n    let links = web_sys::window()\n        .unwrap()\n        .document()\n        .unwrap()\n        .query_selector_all(\"link[rel=stylesheet]\")\n        .unwrap();\n\n    let noise = js_sys::Math::random();\n\n    for x in 0..links.length() {\n        use wasm_bindgen::JsCast;\n        let link: web_sys::Element = links.get(x).unwrap().unchecked_into();\n        if let Some(href) = link.get_attribute(\"href\") {\n            let (url, query) = href.split_once('?').unwrap_or((&href, \"\"));\n            let mut query_params: Vec<&str> = query.split('&').collect();\n            // Remove the old force reload param\n            query_params.retain(|param| !param.starts_with(\"dx_force_reload=\"));\n            // Add the new force reload param\n            let force_reload = format!(\"dx_force_reload={noise}\");\n            query_params.push(&force_reload);\n\n            // Rejoin the query\n            let query = query_params.join(\"&\");\n\n            _ = link.set_attribute(\"href\", &format!(\"{url}?{query}\"));\n        }\n    }\n}\n\n/// Initialize required devtools for dioxus-playground.\n///\n/// This listens for window message events from other Windows (such as window.top when this is running in an iframe).\nfn playground(tx: UnboundedSender<HotReloadMsg>) {\n    let window = web_sys::window().expect(\"this code should be running in a web context\");\n\n    let binding = Closure::<dyn FnMut(MessageEvent)>::new(move |e: MessageEvent| {\n        let Ok(text) = e.data().dyn_into::<JsString>() else {\n            return;\n        };\n        let string: String = text.into();\n        let Ok(hr_msg) = serde_json::from_str::<HotReloadMsg>(&string) else {\n            return;\n        };\n        _ = tx.unbounded_send(hr_msg);\n    });\n\n    let callback = binding.as_ref().unchecked_ref();\n    window\n        .add_event_listener_with_callback(\"message\", callback)\n        .expect(\"event listener should be added successfully\");\n\n    binding.forget();\n}\n\nfn hook_impl(info: &std::panic::PanicHookInfo) {\n    #[wasm_bindgen]\n    extern \"C\" {\n        #[wasm_bindgen(js_namespace = console)]\n        fn error(msg: String);\n\n        type Error;\n\n        #[wasm_bindgen(constructor)]\n        fn new() -> Error;\n\n        #[wasm_bindgen(structural, method, getter)]\n        fn stack(error: &Error) -> String;\n    }\n\n    let mut msg = info.to_string();\n\n    // Add the error stack to our message.\n    //\n    // This ensures that even if the `console` implementation doesn't\n    // include stacks for `console.error`, the stack is still available\n    // for the user. Additionally, Firefox's console tries to clean up\n    // stack traces, and ruins Rust symbols in the process\n    // (https://bugzilla.mozilla.org/show_bug.cgi?id=1519569) but since\n    // it only touches the logged message's associated stack, and not\n    // the message's contents, by including the stack in the message\n    // contents we make sure it is available to the user.\n    msg.push_str(\"\\n\\nStack:\\n\\n\");\n    let e = Error::new();\n    let stack = e.stack();\n    msg.push_str(&stack);\n\n    // Safari's devtools, on the other hand, _do_ mess with logged\n    // messages' contents, so we attempt to break their heuristics for\n    // doing that by appending some whitespace.\n    // https://github.com/rustwasm/console_error_panic_hook/issues/7\n    msg.push_str(\"\\n\\n\");\n\n    // Log the panic with `console.error`!\n    error(msg.clone());\n\n    show_toast(\n        \"App panicked! See console for details.\",\n        &msg,\n        ToastLevel::Error,\n        TOAST_TIMEOUT_LONG,\n        false,\n    )\n}\n"
  },
  {
    "path": "packages/web/src/document.rs",
    "content": "use dioxus_core::queue_effect;\nuse dioxus_core::ScopeId;\nuse dioxus_core::{provide_context, Runtime};\nuse dioxus_document::{\n    Document, Eval, EvalError, Evaluator, LinkProps, MetaProps, ScriptProps, StyleProps,\n};\nuse dioxus_history::History;\nuse futures_util::FutureExt;\nuse generational_box::{AnyStorage, GenerationalBox, UnsyncStorage};\nuse js_sys::Function;\nuse serde::Serialize;\nuse serde_json::Value;\nuse std::future::Future;\nuse std::pin::Pin;\nuse std::result;\nuse std::{rc::Rc, str::FromStr};\nuse wasm_bindgen::prelude::*;\nuse wasm_bindgen_futures::JsFuture;\n\nuse crate::history::WebHistory;\n\n#[wasm_bindgen::prelude::wasm_bindgen]\npub struct JSOwner {\n    _owner: Box<dyn std::any::Any>,\n}\n\nimpl JSOwner {\n    pub fn new(owner: impl std::any::Any) -> Self {\n        Self {\n            _owner: Box::new(owner),\n        }\n    }\n}\n\n#[wasm_bindgen::prelude::wasm_bindgen(module = \"/src/js/eval.js\")]\nextern \"C\" {\n    pub type WeakDioxusChannel;\n\n    #[wasm_bindgen(method, js_name = \"rustSend\")]\n    pub fn rust_send(this: &WeakDioxusChannel, value: wasm_bindgen::JsValue);\n\n    #[wasm_bindgen(method, js_name = \"rustRecv\")]\n    pub async fn rust_recv(this: &WeakDioxusChannel) -> wasm_bindgen::JsValue;\n}\n\n#[wasm_bindgen::prelude::wasm_bindgen(module = \"/src/js/eval.js\")]\nextern \"C\" {\n    pub type WebDioxusChannel;\n\n    #[wasm_bindgen(constructor)]\n    pub fn new(owner: JSOwner) -> WebDioxusChannel;\n\n    #[wasm_bindgen(method, js_name = \"rustSend\")]\n    pub fn rust_send(this: &WebDioxusChannel, value: wasm_bindgen::JsValue);\n\n    #[wasm_bindgen(method, js_name = \"rustRecv\")]\n    pub async fn rust_recv(this: &WebDioxusChannel) -> wasm_bindgen::JsValue;\n\n    #[wasm_bindgen(method)]\n    pub fn send(this: &WebDioxusChannel, value: wasm_bindgen::JsValue);\n\n    #[wasm_bindgen(method)]\n    pub async fn recv(this: &WebDioxusChannel) -> wasm_bindgen::JsValue;\n\n    #[wasm_bindgen(method)]\n    pub fn weak(this: &WebDioxusChannel) -> WeakDioxusChannel;\n\n}\n\nfn init_document_with(document: impl FnOnce(), history: impl FnOnce()) {\n    use dioxus_core::has_context;\n    Runtime::current().in_scope(ScopeId::ROOT, || {\n        if has_context::<Rc<dyn Document>>().is_none() {\n            document();\n        }\n        if has_context::<Rc<dyn History>>().is_none() {\n            history();\n        }\n    })\n}\n\n/// Provides the Document through [`dioxus_core::provide_context`].\npub fn init_document() {\n    // If hydrate is enabled, we add the FullstackWebDocument with the initial hydration data\n    #[cfg(not(feature = \"hydrate\"))]\n    {\n        use dioxus_history::provide_history_context;\n\n        init_document_with(\n            || {\n                provide_context(Rc::new(WebDocument) as Rc<dyn Document>);\n            },\n            || {\n                provide_history_context(Rc::new(WebHistory::default()));\n            },\n        );\n    }\n}\n\n#[cfg(feature = \"hydrate\")]\npub fn init_fullstack_document() {\n    use dioxus_fullstack_core::{\n        document::FullstackWebDocument, history::provide_fullstack_history_context,\n    };\n\n    init_document_with(\n        || {\n            provide_context(Rc::new(FullstackWebDocument::from(WebDocument)) as Rc<dyn Document>);\n        },\n        || provide_fullstack_history_context(WebHistory::default()),\n    );\n}\n\n/// The web-target's document provider.\n#[derive(Clone)]\npub struct WebDocument;\nimpl Document for WebDocument {\n    fn eval(&self, js: String) -> Eval {\n        Eval::new(WebEvaluator::create(js))\n    }\n\n    /// Set the title of the document\n    fn set_title(&self, title: String) {\n        let myself = self.clone();\n        queue_effect(move || {\n            myself.eval(format!(\"document.title = {title:?};\"));\n        });\n    }\n\n    /// Create a new meta tag in the head\n    fn create_meta(&self, props: MetaProps) {\n        queue_effect(move || {\n            _ = append_element_to_head(\"meta\", &props.attributes(), None);\n        });\n    }\n\n    /// Create a new script tag in the head\n    fn create_script(&self, props: ScriptProps) {\n        queue_effect(move || {\n            _ = append_element_to_head(\n                \"script\",\n                &props.attributes(),\n                props.script_contents().ok().as_deref(),\n            );\n        });\n    }\n\n    /// Create a new style tag in the head\n    fn create_style(&self, props: StyleProps) {\n        queue_effect(move || {\n            _ = append_element_to_head(\n                \"style\",\n                &props.attributes(),\n                props.style_contents().ok().as_deref(),\n            );\n        });\n    }\n\n    /// Create a new link tag in the head\n    fn create_link(&self, props: LinkProps) {\n        queue_effect(move || {\n            _ = append_element_to_head(\"link\", &props.attributes(), None);\n        });\n    }\n}\n\nfn append_element_to_head(\n    local_name: &str,\n    attributes: &Vec<(&'static str, String)>,\n    text_content: Option<&str>,\n) -> Result<(), JsValue> {\n    let window = web_sys::window().expect(\"no global `window` exists\");\n    let document = window.document().expect(\"should have a document on window\");\n    let head = document.head().expect(\"document should have a head\");\n\n    let element = document.create_element(local_name)?;\n    for (name, value) in attributes {\n        element.set_attribute(name, value)?;\n    }\n    if text_content.is_some() {\n        element.set_text_content(text_content);\n    }\n    head.append_child(&element)?;\n\n    Ok(())\n}\n\n/// Required to avoid blocking the Rust WASM thread.\nconst PROMISE_WRAPPER: &str = r#\"\n    return (async function(){\n        {JS_CODE}\n\n        dioxus.close();\n    })();\n\"#;\n\ntype NextPoll = Pin<Box<dyn Future<Output = Result<serde_json::Value, EvalError>>>>;\n\n/// Represents a web-target's JavaScript evaluator.\nstruct WebEvaluator {\n    channels: WeakDioxusChannel,\n    next_future: Option<NextPoll>,\n    result: Pin<Box<dyn Future<Output = result::Result<Value, EvalError>>>>,\n}\n\nimpl WebEvaluator {\n    /// Creates a new evaluator for web-based targets.\n    fn create(js: String) -> GenerationalBox<Box<dyn Evaluator>> {\n        let owner = UnsyncStorage::owner();\n\n        // add the drop handler to DioxusChannel so that it gets dropped when the channel is dropped in js\n        let channels = WebDioxusChannel::new(JSOwner::new(owner.clone()));\n\n        // The Rust side of the channel is a weak reference to the DioxusChannel\n        let weak_channels = channels.weak();\n\n        // Wrap the evaluated JS in a promise so that wasm can continue running (send/receive data from js)\n        let code = PROMISE_WRAPPER.replace(\"{JS_CODE}\", &js);\n\n        let result = match Function::new_with_args(\"dioxus\", &code).call1(&JsValue::NULL, &channels)\n        {\n            Ok(result) => {\n                let future = js_sys::Promise::resolve(&result);\n                let js_future = JsFuture::from(future);\n                Box::pin(async move {\n                    let result = js_future.await.map_err(|e| {\n                        EvalError::Communication(format!(\"Failed to await result - {:?}\", e))\n                    })?;\n                    let stringified = js_sys::JSON::stringify(&result).map_err(|e| {\n                        EvalError::Communication(format!(\"Failed to stringify result - {:?}\", e))\n                    })?;\n                    if !stringified.is_undefined() && stringified.is_valid_utf16() {\n                        let string: String = stringified.into();\n                        Value::from_str(&string).map_err(|e| {\n                            EvalError::Communication(format!(\"Failed to parse result - {}\", e))\n                        })\n                    } else {\n                        Err(EvalError::Communication(\n                            \"Failed to stringify result - undefined or not valid utf16\".to_string(),\n                        ))\n                    }\n                })\n                    as Pin<Box<dyn Future<Output = result::Result<Value, EvalError>>>>\n            }\n            Err(err) => Box::pin(futures_util::future::ready(Err(EvalError::InvalidJs(\n                err.as_string().unwrap_or(\"unknown\".to_string()),\n            )))),\n        };\n\n        owner.insert(Box::new(Self {\n            channels: weak_channels,\n            result,\n            next_future: None,\n        }) as Box<dyn Evaluator>)\n    }\n}\n\nimpl Evaluator for WebEvaluator {\n    /// Runs the evaluated JavaScript.\n    fn poll_join(\n        &mut self,\n        cx: &mut std::task::Context<'_>,\n    ) -> std::task::Poll<Result<serde_json::Value, EvalError>> {\n        self.result.poll_unpin(cx)\n    }\n\n    /// Sends a message to the evaluated JavaScript.\n    fn send(&self, data: serde_json::Value) -> Result<(), EvalError> {\n        let serializer = serde_wasm_bindgen::Serializer::json_compatible();\n\n        let data = match data.serialize(&serializer) {\n            Ok(d) => d,\n            Err(e) => return Err(EvalError::Communication(e.to_string())),\n        };\n\n        self.channels.rust_send(data);\n        Ok(())\n    }\n\n    /// Gets an UnboundedReceiver to receive messages from the evaluated JavaScript.\n    fn poll_recv(\n        &mut self,\n        context: &mut std::task::Context<'_>,\n    ) -> std::task::Poll<Result<serde_json::Value, EvalError>> {\n        if self.next_future.is_none() {\n            let channels: WebDioxusChannel = self.channels.clone().into();\n            let pinned = Box::pin(async move {\n                let fut = channels.rust_recv();\n                let data = fut.await;\n                serde_wasm_bindgen::from_value::<serde_json::Value>(data)\n                    .map_err(|err| EvalError::Communication(err.to_string()))\n            });\n            self.next_future = Some(pinned);\n        }\n        let fut = self.next_future.as_mut().unwrap();\n        let mut pinned = std::pin::pin!(fut);\n        let result = pinned.as_mut().poll(context);\n        if result.is_ready() {\n            self.next_future = None;\n        }\n        result\n    }\n}\n"
  },
  {
    "path": "packages/web/src/dom.rs",
    "content": "//! Implementation of a renderer for Dioxus on the web.\n//!\n//! Outstanding todos:\n//! - Passive event listeners\n//! - no-op event listener patch for safari\n//! - tests to ensure dyn_into works for various event types.\n//! - Partial delegation?\n\nuse std::{any::Any, rc::Rc};\n\nuse dioxus_core::Runtime;\nuse dioxus_core::{ElementId, Template};\nuse dioxus_interpreter_js::unified_bindings::Interpreter;\nuse rustc_hash::FxHashMap;\nuse wasm_bindgen::{closure::Closure, JsCast};\nuse web_sys::{Document, Event, Node};\n\nuse crate::{load_document, virtual_event_from_websys_event, Config, WebEventConverter};\n\npub struct WebsysDom {\n    #[allow(dead_code)]\n    pub(crate) root: Node,\n    pub(crate) document: Document,\n    pub(crate) templates: FxHashMap<Template, u16>,\n    pub(crate) interpreter: Interpreter,\n\n    #[cfg(feature = \"mounted\")]\n    pub(crate) runtime: Rc<Runtime>,\n\n    #[cfg(feature = \"mounted\")]\n    pub(crate) queued_mounted_events: Vec<ElementId>,\n\n    // We originally started with a different `WriteMutations` for collecting templates during hydration.\n    // When profiling the binary size of web applications, this caused a large increase in binary size\n    // because diffing code in core is generic over the `WriteMutation` object.\n    //\n    // The fact that diffing is generic over WriteMutations instead of dynamic dispatch or a vec is nice\n    // because we can directly write mutations to sledgehammer and avoid the runtime and binary size overhead\n    // of dynamic dispatch\n    //\n    // Instead we now store a flag to see if we should be writing templates at all if hydration is enabled.\n    // This has a small overhead, but it avoids dynamic dispatch and reduces the binary size\n    //\n    // NOTE: running the virtual dom with the `write_mutations` flag set to true is different from running\n    // it with no mutation writer because it still assigns ids to nodes, but it doesn't write them to the dom\n    #[cfg(feature = \"hydrate\")]\n    pub(crate) skip_mutations: bool,\n\n    #[cfg(feature = \"hydrate\")]\n    pub(crate) suspense_hydration_ids: crate::hydration::SuspenseHydrationIds,\n}\n\nimpl WebsysDom {\n    pub fn new(cfg: Config, runtime: Rc<Runtime>) -> Self {\n        let (document, root) = match cfg.root {\n            crate::cfg::ConfigRoot::RootName(rootname) => {\n                // eventually, we just want to let the interpreter do all the work of decoding events into our event type\n                // a match here in order to avoid some error during runtime browser test\n                let document = load_document();\n                let root = match document.get_element_by_id(&rootname) {\n                    Some(root) => root,\n                    None => {\n                        web_sys::console::error_1(\n                            &format!(\"element '#{}' not found. mounting to the body.\", rootname)\n                                .into(),\n                        );\n                        document.create_element(\"body\").ok().unwrap()\n                    }\n                };\n                (document, root.unchecked_into())\n            }\n            crate::cfg::ConfigRoot::RootNode(root) => {\n                let document = match root.owner_document() {\n                    Some(document) => document,\n                    None => load_document(),\n                };\n                (document, root)\n            }\n        };\n\n        let interpreter = Interpreter::default();\n\n        // The closure type we pass to the dom may be invoked recursively if one event triggers another. For example,\n        // one event could focus another element which triggers the focus event of the new element like inhttps://github.com/DioxusLabs/dioxus/issues/2882.\n        // The Closure<dyn Fn(_)> type can invoked recursively, but Closure<dyn FnMut()> cannot\n        let handler: Closure<dyn Fn(&Event)> = Closure::wrap(Box::new({\n            let runtime = runtime.clone();\n            move |web_sys_event: &web_sys::Event| {\n                let name = web_sys_event.type_();\n                let element = walk_event_for_id(web_sys_event);\n                let bubbles = web_sys_event.bubbles();\n\n                let Some((element, target)) = element else {\n                    return;\n                };\n\n                let data = virtual_event_from_websys_event(web_sys_event.clone(), target);\n\n                let event = dioxus_core::Event::new(Rc::new(data) as Rc<dyn Any>, bubbles);\n                runtime.handle_event(name.as_str(), event.clone(), element);\n\n                // Prevent the default action if the user set prevent default on the event\n                let prevent_default = !event.default_action_enabled();\n                if prevent_default {\n                    web_sys_event.prevent_default();\n                }\n            }\n        }));\n\n        let _interpreter = interpreter.base();\n        _interpreter.initialize(\n            root.clone().unchecked_into(),\n            handler.as_ref().unchecked_ref(),\n        );\n\n        dioxus_html::set_event_converter(Box::new(WebEventConverter));\n        handler.forget();\n\n        Self {\n            document,\n            root,\n            interpreter,\n            templates: FxHashMap::default(),\n            #[cfg(feature = \"mounted\")]\n            runtime,\n            #[cfg(feature = \"mounted\")]\n            queued_mounted_events: Default::default(),\n            #[cfg(feature = \"hydrate\")]\n            skip_mutations: false,\n            #[cfg(feature = \"hydrate\")]\n            suspense_hydration_ids: Default::default(),\n        }\n    }\n}\n\nfn walk_event_for_id(event: &web_sys::Event) -> Option<(ElementId, web_sys::Element)> {\n    let target = event\n        .target()\n        .expect(\"missing target\")\n        .dyn_into::<web_sys::Node>()\n        .expect(\"not a valid node\");\n\n    walk_element_for_id(&target)\n}\n\nfn walk_element_for_id(target: &Node) -> Option<(ElementId, web_sys::Element)> {\n    let mut current_target_element = target.dyn_ref::<web_sys::Element>().cloned();\n\n    loop {\n        match (\n            current_target_element\n                .as_ref()\n                .and_then(|el| el.get_attribute(\"data-dioxus-id\").map(|f| f.parse())),\n            current_target_element,\n        ) {\n            // This node is an element, and has a dioxus id, so we can stop walking\n            (Some(Ok(id)), Some(target)) => return Some((ElementId(id), target)),\n\n            // Walk the tree upwards until we actually find an event target\n            (None, target_element) => {\n                let parent = match target_element.as_ref() {\n                    Some(el) => el.parent_element(),\n                    // if this is the first node and not an element, we need to get the parent from the target node\n                    None => target.parent_element(),\n                };\n                match parent {\n                    Some(parent) => current_target_element = Some(parent),\n                    _ => return None,\n                }\n            }\n\n            // This node is an element with an invalid dioxus id, give up\n            _ => return None,\n        }\n    }\n}\n"
  },
  {
    "path": "packages/web/src/events/animation.rs",
    "content": "use dioxus_html::HasAnimationData;\nuse web_sys::AnimationEvent;\n\nuse super::{Synthetic, WebEventExt};\n\nimpl HasAnimationData for Synthetic<AnimationEvent> {\n    fn animation_name(&self) -> String {\n        self.event.animation_name()\n    }\n\n    fn pseudo_element(&self) -> String {\n        self.event.pseudo_element()\n    }\n\n    fn elapsed_time(&self) -> f32 {\n        self.event.elapsed_time()\n    }\n\n    fn as_any(&self) -> &dyn std::any::Any {\n        &self.event\n    }\n}\n\nimpl WebEventExt for dioxus_html::AnimationData {\n    type WebEvent = web_sys::AnimationEvent;\n\n    #[inline(always)]\n    fn try_as_web_event(&self) -> Option<web_sys::AnimationEvent> {\n        self.downcast::<web_sys::AnimationEvent>().cloned()\n    }\n}\n"
  },
  {
    "path": "packages/web/src/events/cancel.rs",
    "content": "use super::{Synthetic, WebEventExt};\nuse dioxus_html::HasCancelData;\n\nimpl HasCancelData for Synthetic<web_sys::Event> {\n    fn as_any(&self) -> &dyn std::any::Any {\n        &self.event\n    }\n}\n\nimpl WebEventExt for dioxus_html::CancelData {\n    type WebEvent = web_sys::Event;\n\n    #[inline(always)]\n    fn try_as_web_event(&self) -> Option<Self::WebEvent> {\n        self.downcast::<web_sys::Event>().cloned()\n    }\n}\n"
  },
  {
    "path": "packages/web/src/events/clipboard.rs",
    "content": "use dioxus_html::HasClipboardData;\nuse web_sys::Event;\n\nuse super::{Synthetic, WebEventExt};\n\nimpl From<&Event> for Synthetic<Event> {\n    fn from(e: &Event) -> Self {\n        Synthetic::new(e.clone())\n    }\n}\n\nimpl HasClipboardData for Synthetic<Event> {\n    fn as_any(&self) -> &dyn std::any::Any {\n        &self.event\n    }\n}\n\nimpl WebEventExt for dioxus_html::ClipboardData {\n    type WebEvent = web_sys::Event;\n\n    #[inline(always)]\n    fn try_as_web_event(&self) -> Option<Self::WebEvent> {\n        self.downcast::<web_sys::Event>().cloned()\n    }\n}\n"
  },
  {
    "path": "packages/web/src/events/composition.rs",
    "content": "use dioxus_html::HasCompositionData;\nuse web_sys::CompositionEvent;\n\nuse super::{Synthetic, WebEventExt};\n\nimpl HasCompositionData for Synthetic<CompositionEvent> {\n    fn data(&self) -> std::string::String {\n        self.event.data().unwrap_or_default()\n    }\n\n    fn as_any(&self) -> &dyn std::any::Any {\n        &self.event\n    }\n}\n\nimpl WebEventExt for dioxus_html::CompositionData {\n    type WebEvent = web_sys::CompositionEvent;\n\n    #[inline(always)]\n    fn try_as_web_event(&self) -> Option<Self::WebEvent> {\n        self.downcast::<web_sys::CompositionEvent>().cloned()\n    }\n}\n"
  },
  {
    "path": "packages/web/src/events/drag.rs",
    "content": "use crate::{WebDataTransfer, WebFileData, WebFileEngine};\n\nuse super::{Synthetic, WebEventExt};\nuse dioxus_html::{\n    geometry::{ClientPoint, ElementPoint, PagePoint, ScreenPoint},\n    input_data::{decode_mouse_button_set, MouseButton},\n    FileData, HasDataTransferData, HasDragData, HasFileData, HasMouseData,\n    InteractionElementOffset, InteractionLocation, Modifiers, ModifiersInteraction,\n    PointerInteraction,\n};\nuse web_sys::{DragEvent, FileReader};\n\nimpl InteractionLocation for Synthetic<DragEvent> {\n    fn client_coordinates(&self) -> ClientPoint {\n        ClientPoint::new(self.event.client_x().into(), self.event.client_y().into())\n    }\n\n    fn page_coordinates(&self) -> PagePoint {\n        PagePoint::new(self.event.page_x().into(), self.event.page_y().into())\n    }\n\n    fn screen_coordinates(&self) -> ScreenPoint {\n        ScreenPoint::new(self.event.screen_x().into(), self.event.screen_y().into())\n    }\n}\n\nimpl InteractionElementOffset for Synthetic<DragEvent> {\n    fn element_coordinates(&self) -> ElementPoint {\n        ElementPoint::new(self.event.offset_x().into(), self.event.offset_y().into())\n    }\n}\n\nimpl ModifiersInteraction for Synthetic<DragEvent> {\n    fn modifiers(&self) -> Modifiers {\n        let mut modifiers = Modifiers::empty();\n\n        if self.event.alt_key() {\n            modifiers.insert(Modifiers::ALT);\n        }\n        if self.event.ctrl_key() {\n            modifiers.insert(Modifiers::CONTROL);\n        }\n        if self.event.meta_key() {\n            modifiers.insert(Modifiers::META);\n        }\n        if self.event.shift_key() {\n            modifiers.insert(Modifiers::SHIFT);\n        }\n\n        modifiers\n    }\n}\n\nimpl PointerInteraction for Synthetic<DragEvent> {\n    fn held_buttons(&self) -> dioxus_html::input_data::MouseButtonSet {\n        decode_mouse_button_set(self.event.buttons())\n    }\n\n    fn trigger_button(&self) -> Option<MouseButton> {\n        Some(MouseButton::from_web_code(self.event.button()))\n    }\n}\n\nimpl HasMouseData for Synthetic<DragEvent> {\n    fn as_any(&self) -> &dyn std::any::Any {\n        &self.event\n    }\n}\n\nimpl HasDragData for Synthetic<DragEvent> {\n    fn as_any(&self) -> &dyn std::any::Any {\n        &self.event\n    }\n}\n\nimpl HasDataTransferData for Synthetic<DragEvent> {\n    fn data_transfer(&self) -> dioxus_html::DataTransfer {\n        use wasm_bindgen::JsCast;\n\n        if let Some(target) = self.event.dyn_ref::<web_sys::DragEvent>() {\n            if let Some(data) = target.data_transfer() {\n                let web_data_transfer = WebDataTransfer::new(data);\n                return dioxus_html::DataTransfer::new(web_data_transfer);\n            }\n        }\n\n        // Return an empty DataTransfer if we couldn't get one from the event\n        let web_data_transfer = WebDataTransfer::new(web_sys::DataTransfer::new().unwrap());\n        dioxus_html::DataTransfer::new(web_data_transfer)\n    }\n}\n\nimpl HasFileData for Synthetic<DragEvent> {\n    fn files(&self) -> Vec<FileData> {\n        use wasm_bindgen::JsCast;\n\n        if let Some(target) = self.event.dyn_ref::<web_sys::DragEvent>() {\n            if let Some(data_transfer) = target.data_transfer() {\n                if let Some(file_list) = data_transfer.files() {\n                    return WebFileEngine::new(file_list).to_files();\n                } else {\n                    let items = data_transfer.items();\n                    let mut files = vec![];\n                    for i in 0..items.length() {\n                        if let Some(item) = items.get(i) {\n                            if item.kind() == \"file\" {\n                                if let Ok(Some(file)) = item.get_as_file() {\n                                    let web_data =\n                                        WebFileData::new(file, FileReader::new().unwrap());\n                                    files.push(FileData::new(web_data));\n                                }\n                            }\n                        }\n                    }\n                    return files;\n                }\n            }\n        } else {\n            tracing::warn!(\"DragEvent target was not a DragEvent\");\n        }\n\n        vec![]\n    }\n}\n\nimpl WebEventExt for dioxus_html::DragData {\n    type WebEvent = web_sys::DragEvent;\n\n    #[inline(always)]\n    fn try_as_web_event(&self) -> Option<web_sys::DragEvent> {\n        self.downcast::<DragEvent>().cloned()\n    }\n}\n"
  },
  {
    "path": "packages/web/src/events/file.rs",
    "content": "use dioxus_html::{FileData, HasFileData};\nuse web_sys::FileReader;\n\nuse crate::{WebFileData, WebFileEngine};\n\nuse super::Synthetic;\n\nimpl HasFileData for Synthetic<web_sys::Event> {\n    fn files(&self) -> Vec<FileData> {\n        use wasm_bindgen::JsCast;\n        let target = self.event.target();\n\n        if let Some(target) = target\n            .clone()\n            .and_then(|t| t.dyn_into::<web_sys::HtmlInputElement>().ok())\n        {\n            if let Some(file_list) = target.files() {\n                return WebFileEngine::new(file_list).to_files();\n            }\n        }\n\n        if let Some(target) = target.and_then(|t| t.dyn_into::<web_sys::DragEvent>().ok()) {\n            if let Some(data_transfer) = target.data_transfer() {\n                if let Some(file_list) = data_transfer.files() {\n                    return WebFileEngine::new(file_list).to_files();\n                } else {\n                    let items = data_transfer.items();\n                    let mut files = vec![];\n                    for i in 0..items.length() {\n                        if let Some(item) = items.get(i) {\n                            if item.kind() == \"file\" {\n                                if let Ok(Some(file)) = item.get_as_file() {\n                                    let web_data =\n                                        WebFileData::new(file, FileReader::new().unwrap());\n                                    files.push(FileData::new(web_data));\n                                }\n                            }\n                        }\n                    }\n                    return files;\n                }\n            }\n        }\n\n        vec![]\n    }\n}\n"
  },
  {
    "path": "packages/web/src/events/focus.rs",
    "content": "use dioxus_html::HasFocusData;\n\nuse super::{Synthetic, WebEventExt};\n\nimpl HasFocusData for Synthetic<web_sys::FocusEvent> {\n    fn as_any(&self) -> &dyn std::any::Any {\n        &self.event\n    }\n}\n\nimpl WebEventExt for dioxus_html::FocusData {\n    type WebEvent = web_sys::FocusEvent;\n\n    #[inline(always)]\n    fn try_as_web_event(&self) -> Option<Self::WebEvent> {\n        self.downcast::<web_sys::FocusEvent>().cloned()\n    }\n}\n"
  },
  {
    "path": "packages/web/src/events/form.rs",
    "content": "use super::WebEventExt;\nuse crate::WebFileData;\nuse dioxus_html::{FileData, FormValue, HasFileData, HasFormData};\nuse js_sys::Array;\nuse std::any::Any;\nuse wasm_bindgen::{prelude::wasm_bindgen, JsCast};\nuse web_sys::{Element, Event, FileReader};\n\npub(crate) struct WebFormData {\n    element: Element,\n    event: Event,\n}\n\nimpl WebEventExt for dioxus_html::FormData {\n    type WebEvent = Event;\n\n    #[inline(always)]\n    fn try_as_web_event(&self) -> Option<Self::WebEvent> {\n        self.downcast::<Event>().cloned()\n    }\n}\n\nimpl WebFormData {\n    pub fn new(element: Element, event: Event) -> Self {\n        Self { element, event }\n    }\n}\n\nimpl HasFormData for WebFormData {\n    fn value(&self) -> String {\n        let target = &self.element;\n        target\n            .dyn_ref()\n            .map(|input: &web_sys::HtmlInputElement| {\n                // todo: special case more input types\n                match input.type_().as_str() {\n                    \"checkbox\" => {\n                        match input.checked() {\n                            true => \"true\".to_string(),\n                            false => \"false\".to_string(),\n                        }\n                    },\n                    _ => {\n                        input.value()\n                    }\n                }\n            })\n            .or_else(|| {\n                target\n                    .dyn_ref()\n                    .map(|input: &web_sys::HtmlTextAreaElement| input.value())\n            })\n            // select elements are NOT input events - because - why woudn't they be??\n            .or_else(|| {\n                target\n                    .dyn_ref()\n                    .map(|input: &web_sys::HtmlSelectElement| input.value())\n            })\n            .or_else(|| {\n                target\n                    .dyn_ref::<web_sys::HtmlElement>()\n                    .unwrap()\n                    .text_content()\n            })\n            .expect(\"only an InputElement or TextAreaElement or an element with contenteditable=true can have an oninput event listener\")\n    }\n\n    fn values(&self) -> Vec<(String, FormValue)> {\n        let mut values = Vec::new();\n\n        // try to fill in form values\n        if let Some(form) = self.element.dyn_ref::<web_sys::HtmlFormElement>() {\n            let form_data = web_sys::FormData::new_with_form(form).unwrap();\n\n            for entry in form_data.entries().into_iter().flatten() {\n                if let Ok(array) = entry.dyn_into::<Array>() {\n                    if let Some(name) = array.get(0).as_string() {\n                        let value = array.get(1);\n                        if let Some(file) = value.dyn_ref::<web_sys::File>() {\n                            if file.name().is_empty() {\n                                values.push((name, FormValue::File(None)));\n                            } else {\n                                let data =\n                                    WebFileData::new(file.clone(), FileReader::new().unwrap());\n                                let as_file = FileData::new(data);\n\n                                values.push((name, FormValue::File(Some(as_file))));\n                            }\n                        } else if let Some(s) = value.as_string() {\n                            values.push((name, FormValue::Text(s)));\n                        }\n                    }\n                }\n            }\n        } else if let Some(select) = self.element.dyn_ref::<web_sys::HtmlSelectElement>() {\n            // try to fill in select element values\n            let options = get_select_data(select);\n            for option in &options {\n                values.push((select.name(), FormValue::Text(option.clone())));\n            }\n        }\n\n        values\n    }\n\n    fn as_any(&self) -> &dyn Any {\n        &self.event as &dyn Any\n    }\n\n    fn valid(&self) -> bool {\n        self.event\n            .target()\n            .and_then(|t| t.dyn_into::<web_sys::HtmlInputElement>().ok())\n            .map(|input| input.check_validity())\n            .unwrap_or(true)\n    }\n}\n\nimpl HasFileData for WebFormData {\n    fn files(&self) -> Vec<FileData> {\n        use wasm_bindgen::JsCast;\n        self.event\n            .target()\n            .and_then(|t| t.dyn_into::<web_sys::HtmlInputElement>().ok())\n            .and_then(|input| input.files())\n            .map(crate::files::WebFileEngine::new)\n            .map(|engine| engine.to_files())\n            .unwrap_or_default()\n    }\n}\n\n// web-sys does not expose the keys api for select data, so we need to manually bind to it\n#[wasm_bindgen(inline_js = r#\"\nexport function get_select_data(select) {\n    let values = [];\n    for (let i = 0; i < select.options.length; i++) {\n      let option = select.options[i];\n      if (option.selected) {\n        values.push(option.value.toString());\n      }\n    }\n\n    return values;\n}\n\"#)]\nextern \"C\" {\n    fn get_select_data(select: &web_sys::HtmlSelectElement) -> Vec<String>;\n}\n"
  },
  {
    "path": "packages/web/src/events/keyboard.rs",
    "content": "use std::str::FromStr;\n\nuse dioxus_html::{\n    input_data::decode_key_location, Code, HasKeyboardData, Key, Location, Modifiers,\n    ModifiersInteraction,\n};\nuse web_sys::KeyboardEvent;\n\nuse super::{Synthetic, WebEventExt};\n\nimpl HasKeyboardData for Synthetic<KeyboardEvent> {\n    fn key(&self) -> Key {\n        Key::from_str(self.event.key().as_str()).unwrap_or(Key::Unidentified)\n    }\n\n    fn code(&self) -> Code {\n        Code::from_str(self.event.code().as_str()).unwrap_or(Code::Unidentified)\n    }\n\n    fn location(&self) -> Location {\n        decode_key_location(self.event.location() as usize)\n    }\n\n    fn is_auto_repeating(&self) -> bool {\n        self.event.repeat()\n    }\n\n    fn is_composing(&self) -> bool {\n        self.event.is_composing()\n    }\n\n    fn as_any(&self) -> &dyn std::any::Any {\n        &self.event\n    }\n}\n\nimpl ModifiersInteraction for Synthetic<KeyboardEvent> {\n    fn modifiers(&self) -> Modifiers {\n        let mut modifiers = Modifiers::empty();\n\n        if self.event.alt_key() {\n            modifiers.insert(Modifiers::ALT);\n        }\n        if self.event.ctrl_key() {\n            modifiers.insert(Modifiers::CONTROL);\n        }\n        if self.event.meta_key() {\n            modifiers.insert(Modifiers::META);\n        }\n        if self.event.shift_key() {\n            modifiers.insert(Modifiers::SHIFT);\n        }\n\n        modifiers\n    }\n}\n\nimpl WebEventExt for dioxus_html::KeyboardData {\n    type WebEvent = web_sys::KeyboardEvent;\n\n    #[inline(always)]\n    fn try_as_web_event(&self) -> Option<web_sys::KeyboardEvent> {\n        self.downcast::<web_sys::KeyboardEvent>().cloned()\n    }\n}\n"
  },
  {
    "path": "packages/web/src/events/load.rs",
    "content": "use std::any::Any;\n\nuse dioxus_html::HasImageData;\nuse web_sys::Event;\n\nuse super::WebEventExt;\n\n#[derive(Clone)]\npub(crate) struct WebImageEvent {\n    raw: Event,\n    error: bool,\n}\n\nimpl WebImageEvent {\n    pub fn new(raw: Event, error: bool) -> Self {\n        Self { raw, error }\n    }\n}\n\nimpl HasImageData for WebImageEvent {\n    fn load_error(&self) -> bool {\n        self.error\n    }\n\n    fn as_any(&self) -> &dyn Any {\n        &self.raw\n    }\n}\n\nimpl WebEventExt for dioxus_html::ImageData {\n    type WebEvent = Event;\n\n    #[inline(always)]\n    fn try_as_web_event(&self) -> Option<Event> {\n        self.downcast::<WebImageEvent>().map(|e| e.raw.clone())\n    }\n}\n"
  },
  {
    "path": "packages/web/src/events/media.rs",
    "content": "use super::{Synthetic, WebEventExt};\nuse dioxus_html::HasMediaData;\n\nimpl HasMediaData for Synthetic<web_sys::Event> {\n    fn as_any(&self) -> &dyn std::any::Any {\n        &self.event\n    }\n}\n\nimpl WebEventExt for dioxus_html::MediaData {\n    type WebEvent = web_sys::Event;\n\n    #[inline(always)]\n    fn try_as_web_event(&self) -> Option<Self::WebEvent> {\n        self.downcast::<web_sys::Event>().cloned()\n    }\n}\n"
  },
  {
    "path": "packages/web/src/events/mod.rs",
    "content": "use dioxus_html::{\n    DragData, FormData, HtmlEventConverter, ImageData, MountedData, PlatformEventData,\n};\nuse form::WebFormData;\nuse load::WebImageEvent;\nuse wasm_bindgen::JsCast;\nuse web_sys::{Document, Element, Event};\n\nmod animation;\nmod cancel;\nmod clipboard;\nmod composition;\nmod drag;\nmod file;\nmod focus;\nmod form;\nmod keyboard;\nmod load;\nmod media;\n#[cfg(feature = \"mounted\")]\nmod mounted;\nmod mouse;\nmod pointer;\nmod resize;\nmod scroll;\nmod selection;\nmod toggle;\nmod touch;\nmod transition;\nmod visible;\nmod wheel;\n\n/// A wrapper for the websys event that allows us to give it the impls from dioxus-html\npub(crate) struct Synthetic<T: 'static> {\n    /// The inner web sys event that the synthetic event wraps\n    pub event: T,\n}\n\nimpl<T: 'static> Synthetic<T> {\n    /// Create a new synthetic event from a web sys event\n    pub fn new(event: T) -> Self {\n        Self { event }\n    }\n}\n\npub(crate) struct WebEventConverter;\n\n#[inline(always)]\nfn downcast_event(event: &dioxus_html::PlatformEventData) -> &GenericWebSysEvent {\n    event\n        .downcast::<GenericWebSysEvent>()\n        .expect(\"event should be a GenericWebSysEvent\")\n}\n\nimpl HtmlEventConverter for WebEventConverter {\n    #[inline(always)]\n    fn convert_animation_data(\n        &self,\n        event: &dioxus_html::PlatformEventData,\n    ) -> dioxus_html::AnimationData {\n        Synthetic::<web_sys::AnimationEvent>::from(downcast_event(event).raw.clone()).into()\n    }\n\n    #[inline(always)]\n    fn convert_cancel_data(\n        &self,\n        event: &dioxus_html::PlatformEventData,\n    ) -> dioxus_html::CancelData {\n        Synthetic::new(downcast_event(event).raw.clone()).into()\n    }\n\n    #[inline(always)]\n    fn convert_clipboard_data(\n        &self,\n        event: &dioxus_html::PlatformEventData,\n    ) -> dioxus_html::ClipboardData {\n        Synthetic::new(downcast_event(event).raw.clone()).into()\n    }\n\n    #[inline(always)]\n    fn convert_composition_data(\n        &self,\n        event: &dioxus_html::PlatformEventData,\n    ) -> dioxus_html::CompositionData {\n        Synthetic::<web_sys::CompositionEvent>::from(downcast_event(event).raw.clone()).into()\n    }\n\n    #[inline(always)]\n    fn convert_drag_data(&self, event: &dioxus_html::PlatformEventData) -> dioxus_html::DragData {\n        let event = downcast_event(event);\n        DragData::new(Synthetic::new(\n            event.raw.clone().unchecked_into::<web_sys::DragEvent>(),\n        ))\n    }\n\n    #[inline(always)]\n    fn convert_focus_data(&self, event: &dioxus_html::PlatformEventData) -> dioxus_html::FocusData {\n        Synthetic::<web_sys::FocusEvent>::from(downcast_event(event).raw.clone()).into()\n    }\n\n    #[inline(always)]\n    fn convert_form_data(&self, event: &dioxus_html::PlatformEventData) -> dioxus_html::FormData {\n        let event = downcast_event(event);\n        FormData::new(WebFormData::new(event.element.clone(), event.raw.clone()))\n    }\n\n    #[inline(always)]\n    fn convert_image_data(&self, event: &dioxus_html::PlatformEventData) -> dioxus_html::ImageData {\n        let event = downcast_event(event);\n        let error = event.raw.type_() == \"error\";\n        ImageData::new(WebImageEvent::new(event.raw.clone(), error))\n    }\n\n    #[inline(always)]\n    fn convert_keyboard_data(\n        &self,\n        event: &dioxus_html::PlatformEventData,\n    ) -> dioxus_html::KeyboardData {\n        Synthetic::<web_sys::KeyboardEvent>::from(downcast_event(event).raw.clone()).into()\n    }\n\n    #[inline(always)]\n    fn convert_media_data(&self, event: &dioxus_html::PlatformEventData) -> dioxus_html::MediaData {\n        Synthetic::new(downcast_event(event).raw.clone()).into()\n    }\n\n    #[allow(unused_variables)]\n    #[inline(always)]\n    fn convert_mounted_data(&self, event: &dioxus_html::PlatformEventData) -> MountedData {\n        #[cfg(feature = \"mounted\")]\n        {\n            Synthetic::new(\n                event\n                    .downcast::<web_sys::Element>()\n                    .expect(\"event should be a web_sys::Element\")\n                    .clone(),\n            )\n            .into()\n        }\n        #[cfg(not(feature = \"mounted\"))]\n        {\n            panic!(\"mounted events are not supported without the mounted feature on the dioxus-web crate enabled\")\n        }\n    }\n\n    #[inline(always)]\n    fn convert_mouse_data(&self, event: &dioxus_html::PlatformEventData) -> dioxus_html::MouseData {\n        Synthetic::<web_sys::MouseEvent>::from(downcast_event(event).raw.clone()).into()\n    }\n\n    #[inline(always)]\n    fn convert_pointer_data(\n        &self,\n        event: &dioxus_html::PlatformEventData,\n    ) -> dioxus_html::PointerData {\n        Synthetic::<web_sys::PointerEvent>::from(downcast_event(event).raw.clone()).into()\n    }\n\n    #[inline(always)]\n    fn convert_resize_data(\n        &self,\n        event: &dioxus_html::PlatformEventData,\n    ) -> dioxus_html::ResizeData {\n        Synthetic::<web_sys::ResizeObserverEntry>::from(downcast_event(event).raw.clone()).into()\n    }\n\n    #[inline(always)]\n    fn convert_scroll_data(\n        &self,\n        event: &dioxus_html::PlatformEventData,\n    ) -> dioxus_html::ScrollData {\n        Synthetic::new(downcast_event(event).raw.clone()).into()\n    }\n\n    #[inline(always)]\n    fn convert_selection_data(\n        &self,\n        event: &dioxus_html::PlatformEventData,\n    ) -> dioxus_html::SelectionData {\n        Synthetic::new(downcast_event(event).raw.clone()).into()\n    }\n\n    #[inline(always)]\n    fn convert_toggle_data(\n        &self,\n        event: &dioxus_html::PlatformEventData,\n    ) -> dioxus_html::ToggleData {\n        Synthetic::new(downcast_event(event).raw.clone()).into()\n    }\n\n    #[inline(always)]\n    fn convert_touch_data(&self, event: &dioxus_html::PlatformEventData) -> dioxus_html::TouchData {\n        Synthetic::<web_sys::TouchEvent>::from(downcast_event(event).raw.clone()).into()\n    }\n\n    #[inline(always)]\n    fn convert_transition_data(\n        &self,\n        event: &dioxus_html::PlatformEventData,\n    ) -> dioxus_html::TransitionData {\n        Synthetic::<web_sys::TransitionEvent>::from(downcast_event(event).raw.clone()).into()\n    }\n\n    #[inline(always)]\n    fn convert_visible_data(\n        &self,\n        event: &dioxus_html::PlatformEventData,\n    ) -> dioxus_html::VisibleData {\n        Synthetic::<web_sys::IntersectionObserverEntry>::from(downcast_event(event).raw.clone())\n            .into()\n    }\n\n    #[inline(always)]\n    fn convert_wheel_data(&self, event: &dioxus_html::PlatformEventData) -> dioxus_html::WheelData {\n        Synthetic::<web_sys::WheelEvent>::from(downcast_event(event).raw.clone()).into()\n    }\n}\n\n/// A extension trait for web-sys events that provides a way to get the event as a web-sys event.\npub trait WebEventExt {\n    /// The web specific event type\n    type WebEvent;\n\n    /// Try to downcast this event as a `web-sys` event.\n    fn try_as_web_event(&self) -> Option<Self::WebEvent>;\n\n    /// Downcast this event as a `web-sys` event.\n    #[inline(always)]\n    fn as_web_event(&self) -> Self::WebEvent\n    where\n        Self::WebEvent: 'static,\n    {\n        self.try_as_web_event().unwrap_or_else(|| {\n            panic!(\n                \"Error downcasting to `web-sys`, event should be a {}.\",\n                std::any::type_name::<Self::WebEvent>()\n            )\n        })\n    }\n}\n\nstruct GenericWebSysEvent {\n    raw: Event,\n    element: Element,\n}\n\n// todo: some of these events are being casted to the wrong event type.\n// We need tests that simulate clicks/etc and make sure every event type works.\npub(crate) fn virtual_event_from_websys_event(\n    event: web_sys::Event,\n    target: Element,\n) -> PlatformEventData {\n    PlatformEventData::new(Box::new(GenericWebSysEvent {\n        raw: event,\n        element: target,\n    }))\n}\n\npub(crate) fn load_document() -> Document {\n    web_sys::window()\n        .expect(\"should have access to the Window\")\n        .document()\n        .expect(\"should have access to the Document\")\n}\n\nmacro_rules! uncheck_convert {\n    ($t:ty) => {\n        impl From<Event> for Synthetic<$t> {\n            #[inline]\n            fn from(e: Event) -> Self {\n                let e: $t = e.unchecked_into();\n                Self::new(e)\n            }\n        }\n\n        impl From<&Event> for Synthetic<$t> {\n            #[inline]\n            fn from(e: &Event) -> Self {\n                let e: &$t = e.unchecked_ref();\n                Self::new(e.clone())\n            }\n        }\n    };\n    ($($t:ty),+ $(,)?) => {\n        $(uncheck_convert!($t);)+\n    };\n}\n\nuncheck_convert![\n    web_sys::CompositionEvent,\n    web_sys::KeyboardEvent,\n    web_sys::TouchEvent,\n    web_sys::PointerEvent,\n    web_sys::WheelEvent,\n    web_sys::AnimationEvent,\n    web_sys::TransitionEvent,\n    web_sys::MouseEvent,\n    web_sys::FocusEvent,\n];\n"
  },
  {
    "path": "packages/web/src/events/mounted.rs",
    "content": "use dioxus_html::{\n    geometry::euclid::{Point2D, Size2D},\n    MountedData,\n};\nuse wasm_bindgen::JsCast;\n\nuse super::{Synthetic, WebEventExt};\n\nimpl dioxus_html::RenderedElementBacking for Synthetic<web_sys::Element> {\n    fn get_scroll_offset(\n        &self,\n    ) -> std::pin::Pin<\n        Box<\n            dyn std::future::Future<\n                Output = dioxus_html::MountedResult<dioxus_html::geometry::PixelsVector2D>,\n            >,\n        >,\n    > {\n        let left = self.event.scroll_left();\n        let top = self.event.scroll_top();\n        let result = Ok(dioxus_html::geometry::PixelsVector2D::new(\n            left as f64,\n            top as f64,\n        ));\n        Box::pin(async { result })\n    }\n\n    fn get_scroll_size(\n        &self,\n    ) -> std::pin::Pin<\n        Box<\n            dyn std::future::Future<\n                Output = dioxus_html::MountedResult<dioxus_html::geometry::PixelsSize>,\n            >,\n        >,\n    > {\n        let width = self.event.scroll_width();\n        let height = self.event.scroll_height();\n        let result = Ok(dioxus_html::geometry::PixelsSize::new(\n            width as f64,\n            height as f64,\n        ));\n        Box::pin(async { result })\n    }\n\n    fn get_client_rect(\n        &self,\n    ) -> std::pin::Pin<\n        Box<\n            dyn std::future::Future<\n                Output = dioxus_html::MountedResult<dioxus_html::geometry::PixelsRect>,\n            >,\n        >,\n    > {\n        let rect = self.event.get_bounding_client_rect();\n        let result = Ok(dioxus_html::geometry::PixelsRect::new(\n            Point2D::new(rect.left(), rect.top()),\n            Size2D::new(rect.width(), rect.height()),\n        ));\n        Box::pin(async { result })\n    }\n\n    fn as_any(&self) -> &dyn std::any::Any {\n        &self.event\n    }\n\n    fn scroll_to(\n        &self,\n        input_options: dioxus_html::ScrollToOptions,\n    ) -> std::pin::Pin<Box<dyn std::future::Future<Output = dioxus_html::MountedResult<()>>>> {\n        let options = web_sys::ScrollIntoViewOptions::new();\n        options.set_behavior(match input_options.behavior {\n            dioxus_html::ScrollBehavior::Instant => web_sys::ScrollBehavior::Instant,\n            dioxus_html::ScrollBehavior::Smooth => web_sys::ScrollBehavior::Smooth,\n        });\n        options.set_block(match input_options.vertical {\n            dioxus_html::ScrollLogicalPosition::Start => web_sys::ScrollLogicalPosition::Start,\n            dioxus_html::ScrollLogicalPosition::Center => web_sys::ScrollLogicalPosition::Center,\n            dioxus_html::ScrollLogicalPosition::End => web_sys::ScrollLogicalPosition::End,\n            dioxus_html::ScrollLogicalPosition::Nearest => web_sys::ScrollLogicalPosition::Nearest,\n        });\n        options.set_inline(match input_options.horizontal {\n            dioxus_html::ScrollLogicalPosition::Start => web_sys::ScrollLogicalPosition::Start,\n            dioxus_html::ScrollLogicalPosition::Center => web_sys::ScrollLogicalPosition::Center,\n            dioxus_html::ScrollLogicalPosition::End => web_sys::ScrollLogicalPosition::End,\n            dioxus_html::ScrollLogicalPosition::Nearest => web_sys::ScrollLogicalPosition::Nearest,\n        });\n        self.event\n            .scroll_into_view_with_scroll_into_view_options(&options);\n\n        Box::pin(async { Ok(()) })\n    }\n\n    fn scroll(\n        &self,\n        coordinates: dioxus_html::geometry::PixelsVector2D,\n        behavior: dioxus_html::ScrollBehavior,\n    ) -> std::pin::Pin<Box<dyn std::future::Future<Output = dioxus_html::MountedResult<()>>>> {\n        let options = web_sys::ScrollToOptions::new();\n        options.set_top(coordinates.y);\n        options.set_left(coordinates.x);\n        match behavior {\n            dioxus_html::ScrollBehavior::Instant => {\n                options.set_behavior(web_sys::ScrollBehavior::Instant);\n            }\n            dioxus_html::ScrollBehavior::Smooth => {\n                options.set_behavior(web_sys::ScrollBehavior::Smooth);\n            }\n        }\n        self.event.scroll_with_scroll_to_options(&options);\n\n        Box::pin(async { Ok(()) })\n    }\n\n    fn set_focus(\n        &self,\n        focus: bool,\n    ) -> std::pin::Pin<Box<dyn std::future::Future<Output = dioxus_html::MountedResult<()>>>> {\n        #[derive(Debug)]\n        struct FocusError(wasm_bindgen::JsValue);\n\n        impl std::fmt::Display for FocusError {\n            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n                write!(f, \"failed to focus element {:?}\", self.0)\n            }\n        }\n\n        impl std::error::Error for FocusError {}\n\n        let result = self\n            .event\n            .dyn_ref::<web_sys::HtmlElement>()\n            .ok_or_else(|| {\n                dioxus_html::MountedError::OperationFailed(Box::new(FocusError(\n                    self.event.clone().into(),\n                )))\n            })\n            .and_then(|e| {\n                (if focus { e.focus() } else { e.blur() }).map_err(|err| {\n                    dioxus_html::MountedError::OperationFailed(Box::new(FocusError(err)))\n                })\n            });\n        Box::pin(async { result })\n    }\n}\n\nimpl WebEventExt for MountedData {\n    type WebEvent = web_sys::Element;\n\n    #[inline(always)]\n    fn try_as_web_event(&self) -> Option<web_sys::Element> {\n        self.downcast::<web_sys::Element>().cloned()\n    }\n}\n"
  },
  {
    "path": "packages/web/src/events/mouse.rs",
    "content": "use dioxus_html::{\n    geometry::{ClientPoint, ElementPoint, PagePoint, ScreenPoint},\n    input_data::{decode_mouse_button_set, MouseButton},\n    HasMouseData, InteractionElementOffset, InteractionLocation, Modifiers, ModifiersInteraction,\n    PointerInteraction,\n};\nuse web_sys::MouseEvent;\n\nuse super::{Synthetic, WebEventExt};\n\nimpl InteractionLocation for Synthetic<MouseEvent> {\n    fn client_coordinates(&self) -> ClientPoint {\n        ClientPoint::new(self.event.client_x().into(), self.event.client_y().into())\n    }\n\n    fn page_coordinates(&self) -> PagePoint {\n        PagePoint::new(self.event.page_x().into(), self.event.page_y().into())\n    }\n\n    fn screen_coordinates(&self) -> ScreenPoint {\n        ScreenPoint::new(self.event.screen_x().into(), self.event.screen_y().into())\n    }\n}\n\nimpl InteractionElementOffset for Synthetic<MouseEvent> {\n    fn element_coordinates(&self) -> ElementPoint {\n        ElementPoint::new(self.event.offset_x().into(), self.event.offset_y().into())\n    }\n}\n\nimpl ModifiersInteraction for Synthetic<MouseEvent> {\n    fn modifiers(&self) -> Modifiers {\n        let mut modifiers = Modifiers::empty();\n\n        if self.event.alt_key() {\n            modifiers.insert(Modifiers::ALT);\n        }\n        if self.event.ctrl_key() {\n            modifiers.insert(Modifiers::CONTROL);\n        }\n        if self.event.meta_key() {\n            modifiers.insert(Modifiers::META);\n        }\n        if self.event.shift_key() {\n            modifiers.insert(Modifiers::SHIFT);\n        }\n\n        modifiers\n    }\n}\n\nimpl PointerInteraction for Synthetic<MouseEvent> {\n    fn held_buttons(&self) -> dioxus_html::input_data::MouseButtonSet {\n        decode_mouse_button_set(self.event.buttons())\n    }\n\n    fn trigger_button(&self) -> Option<MouseButton> {\n        Some(MouseButton::from_web_code(self.event.button()))\n    }\n}\n\nimpl HasMouseData for Synthetic<MouseEvent> {\n    fn as_any(&self) -> &dyn std::any::Any {\n        &self.event\n    }\n}\n\nimpl WebEventExt for dioxus_html::MouseData {\n    type WebEvent = web_sys::MouseEvent;\n\n    #[inline(always)]\n    fn try_as_web_event(&self) -> Option<web_sys::MouseEvent> {\n        self.downcast::<web_sys::MouseEvent>().cloned()\n    }\n}\n"
  },
  {
    "path": "packages/web/src/events/pointer.rs",
    "content": "use dioxus_html::{\n    geometry::{ClientPoint, ElementPoint, PagePoint, ScreenPoint},\n    input_data::{decode_mouse_button_set, MouseButton},\n    HasPointerData, InteractionElementOffset, InteractionLocation, Modifiers, ModifiersInteraction,\n    PointerInteraction,\n};\nuse web_sys::PointerEvent;\n\nuse super::{Synthetic, WebEventExt};\n\nimpl HasPointerData for Synthetic<PointerEvent> {\n    fn pointer_id(&self) -> i32 {\n        self.event.pointer_id()\n    }\n\n    fn width(&self) -> f64 {\n        self.event.width() as _\n    }\n\n    fn height(&self) -> f64 {\n        self.event.height() as _\n    }\n\n    fn pressure(&self) -> f32 {\n        self.event.pressure()\n    }\n\n    fn tangential_pressure(&self) -> f32 {\n        self.event.tangential_pressure()\n    }\n\n    fn tilt_x(&self) -> i32 {\n        self.event.tilt_x()\n    }\n\n    fn tilt_y(&self) -> i32 {\n        self.event.tilt_y()\n    }\n\n    fn twist(&self) -> i32 {\n        self.event.twist()\n    }\n\n    fn pointer_type(&self) -> String {\n        self.event.pointer_type()\n    }\n\n    fn is_primary(&self) -> bool {\n        self.event.is_primary()\n    }\n\n    fn as_any(&self) -> &dyn std::any::Any {\n        &self.event\n    }\n}\n\nimpl InteractionLocation for Synthetic<PointerEvent> {\n    fn client_coordinates(&self) -> ClientPoint {\n        ClientPoint::new(self.event.client_x().into(), self.event.client_y().into())\n    }\n\n    fn screen_coordinates(&self) -> ScreenPoint {\n        ScreenPoint::new(self.event.screen_x().into(), self.event.screen_y().into())\n    }\n\n    fn page_coordinates(&self) -> PagePoint {\n        PagePoint::new(self.event.page_x().into(), self.event.page_y().into())\n    }\n}\n\nimpl InteractionElementOffset for Synthetic<PointerEvent> {\n    fn element_coordinates(&self) -> ElementPoint {\n        ElementPoint::new(self.event.offset_x().into(), self.event.offset_y().into())\n    }\n}\n\nimpl ModifiersInteraction for Synthetic<PointerEvent> {\n    fn modifiers(&self) -> Modifiers {\n        let mut modifiers = Modifiers::empty();\n\n        if self.event.alt_key() {\n            modifiers.insert(Modifiers::ALT);\n        }\n        if self.event.ctrl_key() {\n            modifiers.insert(Modifiers::CONTROL);\n        }\n        if self.event.meta_key() {\n            modifiers.insert(Modifiers::META);\n        }\n        if self.event.shift_key() {\n            modifiers.insert(Modifiers::SHIFT);\n        }\n\n        modifiers\n    }\n}\n\nimpl PointerInteraction for Synthetic<PointerEvent> {\n    fn held_buttons(&self) -> dioxus_html::input_data::MouseButtonSet {\n        decode_mouse_button_set(self.event.buttons())\n    }\n\n    fn trigger_button(&self) -> Option<MouseButton> {\n        Some(MouseButton::from_web_code(self.event.button()))\n    }\n}\n\nimpl WebEventExt for dioxus_html::PointerData {\n    type WebEvent = web_sys::PointerEvent;\n\n    #[inline(always)]\n    fn try_as_web_event(&self) -> Option<web_sys::PointerEvent> {\n        self.downcast::<web_sys::PointerEvent>().cloned()\n    }\n}\n"
  },
  {
    "path": "packages/web/src/events/resize.rs",
    "content": "use dioxus_html::{geometry::PixelsSize, HasResizeData, ResizeResult};\nuse wasm_bindgen::JsCast;\nuse web_sys::{CustomEvent, Event, ResizeObserverEntry};\n\nuse super::{Synthetic, WebEventExt};\n\nimpl From<Event> for Synthetic<ResizeObserverEntry> {\n    #[inline]\n    fn from(e: Event) -> Self {\n        <Synthetic<ResizeObserverEntry> as From<&Event>>::from(&e)\n    }\n}\n\nimpl From<&Event> for Synthetic<ResizeObserverEntry> {\n    #[inline]\n    fn from(e: &Event) -> Self {\n        let e: &CustomEvent = e.unchecked_ref();\n        let value = e.detail();\n        Self::new(value.unchecked_into::<ResizeObserverEntry>())\n    }\n}\n\nimpl HasResizeData for Synthetic<ResizeObserverEntry> {\n    fn as_any(&self) -> &dyn std::any::Any {\n        &self.event\n    }\n\n    fn get_border_box_size(&self) -> ResizeResult<PixelsSize> {\n        extract_first_size(self.event.border_box_size())\n    }\n\n    fn get_content_box_size(&self) -> ResizeResult<PixelsSize> {\n        extract_first_size(self.event.content_box_size())\n    }\n}\n\nimpl WebEventExt for dioxus_html::ResizeData {\n    type WebEvent = web_sys::ResizeObserverEntry;\n\n    #[inline(always)]\n    fn try_as_web_event(&self) -> Option<web_sys::ResizeObserverEntry> {\n        self.downcast::<web_sys::ResizeObserverEntry>().cloned()\n    }\n}\n\nfn extract_first_size(resize_observer_output: js_sys::Array) -> ResizeResult<PixelsSize> {\n    let first = resize_observer_output.get(0);\n    let size = first.unchecked_into::<web_sys::ResizeObserverSize>();\n\n    // inline_size matches the width of the element if its writing-mode is horizontal, the height otherwise\n    let inline_size = size.inline_size();\n    // block_size matches the height of the element if its writing-mode is horizontal, the width otherwise\n    let block_size = size.block_size();\n\n    Ok(PixelsSize::new(inline_size, block_size))\n}\n"
  },
  {
    "path": "packages/web/src/events/scroll.rs",
    "content": "use dioxus_html::HasScrollData;\nuse wasm_bindgen::JsCast;\nuse web_sys::{Document, Element, Event};\n\nuse super::{Synthetic, WebEventExt};\n\nimpl HasScrollData for Synthetic<Event> {\n    fn as_any(&self) -> &dyn std::any::Any {\n        &self.event\n    }\n\n    fn scroll_top(&self) -> f64 {\n        if let Some(target) = self.event.target().as_ref() {\n            if let Some(element) = target.dyn_ref::<Element>() {\n                return element.scroll_top() as f64;\n            } else if let Some(element) = target\n                .dyn_ref::<Document>()\n                .and_then(|document| document.document_element())\n            {\n                return element.scroll_top() as f64;\n            }\n        }\n        0f64\n    }\n\n    fn scroll_left(&self) -> f64 {\n        if let Some(target) = self.event.target().as_ref() {\n            if let Some(element) = target.dyn_ref::<Element>() {\n                return element.scroll_left() as f64;\n            } else if let Some(element) = target\n                .dyn_ref::<Document>()\n                .and_then(|document| document.document_element())\n            {\n                return element.scroll_left() as f64;\n            }\n        }\n        0f64\n    }\n\n    fn scroll_width(&self) -> i32 {\n        if let Some(target) = self.event.target().as_ref() {\n            if let Some(element) = target.dyn_ref::<Element>() {\n                return element.scroll_width();\n            } else if let Some(element) = target\n                .dyn_ref::<Document>()\n                .and_then(|document| document.document_element())\n            {\n                return element.scroll_width();\n            }\n        }\n        0\n    }\n\n    fn scroll_height(&self) -> i32 {\n        if let Some(target) = self.event.target().as_ref() {\n            if let Some(element) = target.dyn_ref::<Element>() {\n                return element.scroll_height();\n            } else if let Some(element) = target\n                .dyn_ref::<Document>()\n                .and_then(|document| document.document_element())\n            {\n                return element.scroll_height();\n            }\n        }\n        0\n    }\n\n    fn client_width(&self) -> i32 {\n        if let Some(target) = self.event.target().as_ref() {\n            if let Some(element) = target.dyn_ref::<Element>() {\n                return element.client_width();\n            } else if let Some(element) = target\n                .dyn_ref::<Document>()\n                .and_then(|document| document.document_element())\n            {\n                return element.client_width();\n            }\n        }\n        0\n    }\n\n    fn client_height(&self) -> i32 {\n        if let Some(target) = self.event.target().as_ref() {\n            if let Some(element) = target.dyn_ref::<Element>() {\n                return element.client_height();\n            } else if let Some(element) = target\n                .dyn_ref::<Document>()\n                .and_then(|document| document.document_element())\n            {\n                return element.client_height();\n            }\n        }\n        0\n    }\n}\n\nimpl WebEventExt for dioxus_html::ScrollData {\n    type WebEvent = web_sys::Event;\n\n    #[inline(always)]\n    fn try_as_web_event(&self) -> Option<Self::WebEvent> {\n        self.downcast::<web_sys::Event>().cloned()\n    }\n}\n"
  },
  {
    "path": "packages/web/src/events/selection.rs",
    "content": "use super::{Synthetic, WebEventExt};\nuse dioxus_html::HasSelectionData;\n\nimpl HasSelectionData for Synthetic<web_sys::Event> {\n    fn as_any(&self) -> &dyn std::any::Any {\n        &self.event\n    }\n}\n\nimpl WebEventExt for dioxus_html::SelectionData {\n    type WebEvent = web_sys::Event;\n\n    #[inline(always)]\n    fn try_as_web_event(&self) -> Option<Self::WebEvent> {\n        self.downcast::<web_sys::Event>().cloned()\n    }\n}\n"
  },
  {
    "path": "packages/web/src/events/toggle.rs",
    "content": "use super::{Synthetic, WebEventExt};\nuse dioxus_html::HasToggleData;\n\nimpl HasToggleData for Synthetic<web_sys::Event> {\n    fn as_any(&self) -> &dyn std::any::Any {\n        &self.event\n    }\n}\n\nimpl WebEventExt for dioxus_html::ToggleData {\n    type WebEvent = web_sys::Event;\n\n    #[inline(always)]\n    fn try_as_web_event(&self) -> Option<Self::WebEvent> {\n        self.downcast::<web_sys::Event>().cloned()\n    }\n}\n"
  },
  {
    "path": "packages/web/src/events/touch.rs",
    "content": "use dioxus_html::{\n    geometry::{ClientPoint, PagePoint, ScreenPoint},\n    HasTouchPointData, InteractionLocation, Modifiers, ModifiersInteraction, TouchPoint,\n};\nuse web_sys::{Touch, TouchEvent};\n\nuse super::{Synthetic, WebEventExt};\n\nimpl ModifiersInteraction for Synthetic<TouchEvent> {\n    fn modifiers(&self) -> Modifiers {\n        let mut modifiers = Modifiers::empty();\n\n        if self.event.alt_key() {\n            modifiers.insert(Modifiers::ALT);\n        }\n        if self.event.ctrl_key() {\n            modifiers.insert(Modifiers::CONTROL);\n        }\n        if self.event.meta_key() {\n            modifiers.insert(Modifiers::META);\n        }\n        if self.event.shift_key() {\n            modifiers.insert(Modifiers::SHIFT);\n        }\n\n        modifiers\n    }\n}\n\nimpl dioxus_html::events::HasTouchData for Synthetic<TouchEvent> {\n    fn touches(&self) -> Vec<TouchPoint> {\n        let touches = TouchEvent::touches(&self.event);\n        let mut result = Vec::with_capacity(touches.length() as usize);\n        for i in 0..touches.length() {\n            let touch = touches.get(i).unwrap();\n            result.push(TouchPoint::new(Synthetic::new(touch)));\n        }\n        result\n    }\n\n    fn touches_changed(&self) -> Vec<TouchPoint> {\n        let touches = self.event.changed_touches();\n        let mut result = Vec::with_capacity(touches.length() as usize);\n        for i in 0..touches.length() {\n            let touch = touches.get(i).unwrap();\n            result.push(TouchPoint::new(Synthetic::new(touch)));\n        }\n        result\n    }\n\n    fn target_touches(&self) -> Vec<TouchPoint> {\n        let touches = self.event.target_touches();\n        let mut result = Vec::with_capacity(touches.length() as usize);\n        for i in 0..touches.length() {\n            let touch = touches.get(i).unwrap();\n            result.push(TouchPoint::new(Synthetic::new(touch)));\n        }\n        result\n    }\n\n    fn as_any(&self) -> &dyn std::any::Any {\n        &self.event\n    }\n}\n\nimpl HasTouchPointData for Synthetic<Touch> {\n    fn identifier(&self) -> i32 {\n        self.event.identifier()\n    }\n\n    fn radius(&self) -> ScreenPoint {\n        ScreenPoint::new(self.event.radius_x().into(), self.event.radius_y().into())\n    }\n\n    fn rotation(&self) -> f64 {\n        self.event.rotation_angle() as f64\n    }\n\n    fn force(&self) -> f64 {\n        self.event.force() as f64\n    }\n\n    fn as_any(&self) -> &dyn std::any::Any {\n        &self.event\n    }\n}\n\nimpl InteractionLocation for Synthetic<Touch> {\n    fn client_coordinates(&self) -> ClientPoint {\n        ClientPoint::new(self.event.client_x().into(), self.event.client_y().into())\n    }\n\n    fn screen_coordinates(&self) -> ScreenPoint {\n        ScreenPoint::new(self.event.screen_x().into(), self.event.screen_y().into())\n    }\n\n    fn page_coordinates(&self) -> PagePoint {\n        PagePoint::new(self.event.page_x().into(), self.event.page_y().into())\n    }\n}\n\nimpl WebEventExt for dioxus_html::TouchData {\n    type WebEvent = web_sys::TouchEvent;\n\n    #[inline(always)]\n    fn try_as_web_event(&self) -> Option<web_sys::TouchEvent> {\n        self.downcast::<web_sys::TouchEvent>().cloned()\n    }\n}\n"
  },
  {
    "path": "packages/web/src/events/transition.rs",
    "content": "use dioxus_html::HasTransitionData;\nuse web_sys::TransitionEvent;\n\nuse super::Synthetic;\n\nimpl HasTransitionData for Synthetic<TransitionEvent> {\n    fn elapsed_time(&self) -> f32 {\n        self.event.elapsed_time()\n    }\n\n    fn property_name(&self) -> String {\n        self.event.property_name()\n    }\n\n    fn pseudo_element(&self) -> String {\n        self.event.pseudo_element()\n    }\n\n    fn as_any(&self) -> &dyn std::any::Any {\n        &self.event\n    }\n}\n"
  },
  {
    "path": "packages/web/src/events/visible.rs",
    "content": "use std::time::SystemTime;\n\nuse dioxus_html::{\n    geometry::{\n        euclid::{Point2D, Size2D},\n        PixelsRect,\n    },\n    HasVisibleData, VisibleData, VisibleError, VisibleResult,\n};\nuse wasm_bindgen::JsCast;\nuse web_sys::{CustomEvent, DomRectReadOnly, Event, IntersectionObserverEntry};\n\nuse super::{Synthetic, WebEventExt};\n\nimpl From<Event> for Synthetic<IntersectionObserverEntry> {\n    #[inline]\n    fn from(e: Event) -> Self {\n        <Synthetic<IntersectionObserverEntry> as From<&Event>>::from(&e)\n    }\n}\n\nimpl From<&Event> for Synthetic<IntersectionObserverEntry> {\n    #[inline]\n    fn from(e: &Event) -> Self {\n        let e: &CustomEvent = e.unchecked_ref();\n        let value = e.detail();\n        Self::new(value.unchecked_into::<IntersectionObserverEntry>())\n    }\n}\nfn dom_rect_ro_to_pixel_rect(dom_rect: &DomRectReadOnly) -> PixelsRect {\n    PixelsRect::new(\n        Point2D::new(dom_rect.x(), dom_rect.y()),\n        Size2D::new(dom_rect.width(), dom_rect.height()),\n    )\n}\n\nimpl HasVisibleData for Synthetic<IntersectionObserverEntry> {\n    /// Get the bounds rectangle of the target element\n    fn get_bounding_client_rect(&self) -> VisibleResult<PixelsRect> {\n        Ok(dom_rect_ro_to_pixel_rect(\n            &self.event.bounding_client_rect(),\n        ))\n    }\n\n    /// Get the ratio of the intersectionRect to the boundingClientRect\n    fn get_intersection_ratio(&self) -> VisibleResult<f64> {\n        Ok(self.event.intersection_ratio())\n    }\n\n    /// Get the rect representing the target's visible area\n    fn get_intersection_rect(&self) -> VisibleResult<PixelsRect> {\n        Ok(dom_rect_ro_to_pixel_rect(&self.event.intersection_rect()))\n    }\n\n    /// Get if the target element intersects with the intersection observer's root\n    fn is_intersecting(&self) -> VisibleResult<bool> {\n        Ok(self.event.is_intersecting())\n    }\n\n    /// Get the rect for the intersection observer's root\n    fn get_root_bounds(&self) -> VisibleResult<PixelsRect> {\n        match self.event.root_bounds() {\n            Some(root_bounds) => Ok(dom_rect_ro_to_pixel_rect(&root_bounds)),\n            None => Err(VisibleError::NotSupported),\n        }\n    }\n\n    /// Get a timestamp indicating the time at which the intersection was recorded\n    fn get_time(&self) -> VisibleResult<SystemTime> {\n        let ms_since_epoch = self.event.time();\n        let rounded = ms_since_epoch.round() as u64;\n        let duration = std::time::Duration::from_millis(rounded);\n        Ok(SystemTime::UNIX_EPOCH + duration)\n    }\n\n    fn as_any(&self) -> &dyn std::any::Any {\n        self\n    }\n}\n\nimpl WebEventExt for VisibleData {\n    type WebEvent = IntersectionObserverEntry;\n\n    #[inline(always)]\n    fn try_as_web_event(&self) -> Option<IntersectionObserverEntry> {\n        self.downcast::<CustomEvent>()\n            .and_then(|e| e.detail().dyn_into::<IntersectionObserverEntry>().ok())\n    }\n}\n"
  },
  {
    "path": "packages/web/src/events/wheel.rs",
    "content": "use dioxus_html::{\n    geometry::{ClientPoint, ElementPoint, PagePoint, ScreenPoint},\n    input_data::{decode_mouse_button_set, MouseButton},\n    HasMouseData, HasWheelData, InteractionElementOffset, InteractionLocation, Modifiers,\n    ModifiersInteraction, PointerInteraction,\n};\nuse web_sys::WheelEvent;\n\nuse super::{Synthetic, WebEventExt};\n\nimpl HasWheelData for Synthetic<WheelEvent> {\n    fn delta(&self) -> dioxus_html::geometry::WheelDelta {\n        dioxus_html::geometry::WheelDelta::from_web_attributes(\n            self.event.delta_mode(),\n            self.event.delta_x(),\n            self.event.delta_y(),\n            self.event.delta_z(),\n        )\n    }\n\n    fn as_any(&self) -> &dyn std::any::Any {\n        &self.event\n    }\n}\n\nimpl HasMouseData for Synthetic<WheelEvent> {\n    fn as_any(&self) -> &dyn std::any::Any {\n        &self.event\n    }\n}\n\nimpl InteractionLocation for Synthetic<WheelEvent> {\n    fn client_coordinates(&self) -> ClientPoint {\n        ClientPoint::new(self.event.client_x().into(), self.event.client_y().into())\n    }\n\n    fn screen_coordinates(&self) -> ScreenPoint {\n        ScreenPoint::new(self.event.screen_x().into(), self.event.screen_y().into())\n    }\n\n    fn page_coordinates(&self) -> PagePoint {\n        PagePoint::new(self.event.page_x().into(), self.event.page_y().into())\n    }\n}\n\nimpl InteractionElementOffset for Synthetic<WheelEvent> {\n    fn element_coordinates(&self) -> ElementPoint {\n        ElementPoint::new(self.event.offset_x().into(), self.event.offset_y().into())\n    }\n}\n\nimpl ModifiersInteraction for Synthetic<WheelEvent> {\n    fn modifiers(&self) -> Modifiers {\n        let mut modifiers = Modifiers::empty();\n\n        if self.event.alt_key() {\n            modifiers.insert(Modifiers::ALT);\n        }\n        if self.event.ctrl_key() {\n            modifiers.insert(Modifiers::CONTROL);\n        }\n        if self.event.meta_key() {\n            modifiers.insert(Modifiers::META);\n        }\n        if self.event.shift_key() {\n            modifiers.insert(Modifiers::SHIFT);\n        }\n\n        modifiers\n    }\n}\n\nimpl PointerInteraction for Synthetic<WheelEvent> {\n    fn held_buttons(&self) -> dioxus_html::input_data::MouseButtonSet {\n        decode_mouse_button_set(self.event.buttons())\n    }\n\n    fn trigger_button(&self) -> Option<MouseButton> {\n        Some(MouseButton::from_web_code(self.event.button()))\n    }\n}\n\nimpl WebEventExt for dioxus_html::WheelData {\n    type WebEvent = web_sys::WheelEvent;\n\n    #[inline(always)]\n    fn try_as_web_event(&self) -> Option<Self::WebEvent> {\n        self.downcast::<web_sys::WheelEvent>().cloned()\n    }\n}\n"
  },
  {
    "path": "packages/web/src/files.rs",
    "content": "use dioxus_core::AnyhowContext;\nuse dioxus_html::{bytes::Bytes, FileData, NativeFileData};\nuse futures_channel::oneshot;\nuse js_sys::Uint8Array;\nuse send_wrapper::SendWrapper;\nuse std::{pin::Pin, prelude::rust_2024::Future};\nuse wasm_bindgen::{prelude::Closure, JsCast};\nuse web_sys::{File, FileList, FileReader};\n\n/// A file representation for the web platform\n#[derive(Clone)]\npub struct WebFileData {\n    file: File,\n    reader: FileReader,\n}\n\nunsafe impl Send for WebFileData {}\nunsafe impl Sync for WebFileData {}\n\nimpl WebFileData {\n    /// Create a new WebFileData from a web_sys::File\n    pub fn new(file: File, reader: FileReader) -> Self {\n        Self { file, reader }\n    }\n}\n\nimpl NativeFileData for WebFileData {\n    fn name(&self) -> String {\n        self.file.name()\n    }\n\n    fn size(&self) -> u64 {\n        self.file.size() as u64\n    }\n\n    fn last_modified(&self) -> u64 {\n        self.file.last_modified() as u64\n    }\n\n    fn read_bytes(\n        &self,\n    ) -> Pin<Box<dyn Future<Output = Result<Bytes, dioxus_core::CapturedError>> + 'static>> {\n        let file_reader = self.reader.clone();\n        let file_reader_ = self.reader.clone();\n        let file = self.file.clone();\n        Box::pin(async move {\n            let (rx, tx) = oneshot::channel();\n            let on_load: Closure<dyn FnMut()> = Closure::new({\n                let mut rx = Some(rx);\n                move || {\n                    let result = file_reader.result();\n                    let _ = rx\n                        .take()\n                        .expect(\"multiple files read without refreshing the channel\")\n                        .send(result);\n                }\n            });\n\n            file_reader_.set_onload(Some(on_load.as_ref().unchecked_ref()));\n            on_load.forget();\n            file_reader_\n                .read_as_array_buffer(&file)\n                .ok()\n                .context(\"Failed to read file\")?;\n\n            let js_val = tx.await?.ok().context(\"Failed to read file\")?;\n            let as_u8_arr = Uint8Array::new(&js_val);\n            let as_u8_vec = as_u8_arr.to_vec().into();\n            Ok(as_u8_vec)\n        })\n    }\n\n    fn read_string(\n        &self,\n    ) -> Pin<Box<dyn Future<Output = Result<String, dioxus_core::CapturedError>> + 'static>> {\n        let file_reader = self.reader.clone();\n        let file_reader_ = self.reader.clone();\n        let file = self.file.clone();\n        Box::pin(async move {\n            let (rx, tx) = oneshot::channel();\n            let on_load: Closure<dyn FnMut()> = Closure::new({\n                let mut rx = Some(rx);\n                move || {\n                    let result = file_reader.result();\n                    let _ = rx\n                        .take()\n                        .expect(\"multiple files read without refreshing the channel\")\n                        .send(result);\n                }\n            });\n\n            file_reader_.set_onload(Some(on_load.as_ref().unchecked_ref()));\n            on_load.forget();\n            file_reader_\n                .read_as_text(&file)\n                .ok()\n                .context(\"Failed to read file\")?;\n\n            let js_val = tx.await?.ok().context(\"Failed to read file\")?;\n            let as_string = js_val.as_string().context(\"Failed to read file\")?;\n            Ok(as_string)\n        })\n    }\n\n    /// we'd like to use `blob` to readable stream here, but we cannot.\n    ///\n    /// We just read the entire file into memory and return it as a single chunk.\n    /// This is not super great, especially given the wasm <-> js boundary duplication cost.\n    ///\n    /// For more efficient streaming of byte data, consider using the dedicated FileStream type which\n    /// goes directly from `File` to fetch request body without going through Rust.\n    ///\n    /// We should maybe update these APIs to use our own custom `ByteBuffer` type to avoid going through `Vec<u8>`?\n    fn byte_stream(\n        &self,\n    ) -> Pin<\n        Box<\n            dyn futures_util::Stream<Item = Result<Bytes, dioxus_core::CapturedError>>\n                + 'static\n                + Send,\n        >,\n    > {\n        let file = self.file.dyn_ref::<web_sys::Blob>().unwrap().clone();\n        Box::pin(SendWrapper::new(futures_util::stream::once(async move {\n            let array_buff = wasm_bindgen_futures::JsFuture::from(file.array_buffer())\n                .await\n                .unwrap();\n            let as_uint_array = array_buff.dyn_into::<Uint8Array>().unwrap();\n            Ok(as_uint_array.to_vec().into())\n        })))\n    }\n\n    fn inner(&self) -> &dyn std::any::Any {\n        &self.file\n    }\n\n    fn path(&self) -> std::path::PathBuf {\n        let key = wasm_bindgen::JsValue::from_str(\"webkitRelativePath\");\n\n        if let Ok(value) = js_sys::Reflect::get(&self.file, &key) {\n            if let Some(path_str) = value.as_string() {\n                if !path_str.is_empty() {\n                    return std::path::PathBuf::from(path_str);\n                }\n            }\n        }\n\n        std::path::PathBuf::from(self.file.name())\n    }\n\n    fn content_type(&self) -> Option<String> {\n        let type_ = self.file.type_();\n        if type_.is_empty() {\n            None\n        } else {\n            Some(type_)\n        }\n    }\n}\n\n/// A file engine for the web platform\n#[derive(Clone)]\npub(crate) struct WebFileEngine {\n    file_list: FileList,\n}\n\nimpl WebFileEngine {\n    /// Create a new file engine from a file list\n    pub fn new(file_list: FileList) -> Self {\n        Self { file_list }\n    }\n\n    fn len(&self) -> usize {\n        self.file_list.length() as usize\n    }\n\n    fn get(&self, index: usize) -> Option<File> {\n        self.file_list.item(index as u32)\n    }\n\n    pub fn to_files(&self) -> Vec<FileData> {\n        (0..self.len())\n            .filter_map(|i| self.get(i))\n            .map(|file| {\n                FileData::new(WebFileData {\n                    file,\n                    reader: FileReader::new().unwrap(),\n                })\n            })\n            .collect()\n    }\n}\n\n/// Helper trait for extracting the underlying `web_sys::File` from a `FileData`\npub trait WebFileExt {\n    /// returns web_sys::File\n    fn get_web_file(&self) -> Option<web_sys::File>;\n}\n\nimpl WebFileExt for FileData {\n    fn get_web_file(&self) -> Option<web_sys::File> {\n        self.inner().downcast_ref::<web_sys::File>().cloned()\n    }\n}\n"
  },
  {
    "path": "packages/web/src/history.rs",
    "content": "use wasm_bindgen::{prelude::Closure, JsCast, JsValue};\nuse web_sys::{window, Event, History, ScrollRestoration, Window};\n\n/// A [`dioxus_history::History`] provider that integrates with a browser via the [History API](https://developer.mozilla.org/en-US/docs/Web/API/History_API).\n///\n/// # Prefix\n/// This [`dioxus_history::History`] supports a prefix, which can be used for web apps that aren't located\n/// at the root of their domain.\n///\n/// Application developers are responsible for ensuring that right after the prefix comes a `/`. If\n/// that is not the case, this [`dioxus_history::History`] will replace the first character after the prefix\n/// with one.\n///\n/// Application developers are responsible for not rendering the router if the prefix is not present\n/// in the URL. Otherwise, if a router navigation is triggered, the prefix will be added.\npub struct WebHistory {\n    do_scroll_restoration: bool,\n    history: History,\n    prefix: Option<String>,\n    window: Window,\n}\n\nimpl Default for WebHistory {\n    fn default() -> Self {\n        Self::new(None, true)\n    }\n}\n\nimpl WebHistory {\n    /// Create a new [`WebHistory`].\n    ///\n    /// If `do_scroll_restoration` is [`true`], [`WebHistory`] will take control of the history\n    /// state. It'll also set the browsers scroll restoration to `manual`.\n    pub fn new(prefix: Option<String>, do_scroll_restoration: bool) -> Self {\n        let myself = Self::new_inner(prefix, do_scroll_restoration);\n\n        let current_route = dioxus_history::History::current_route(&myself);\n        let current_route_str = current_route.to_string();\n        let prefix_str = myself.prefix.as_deref().unwrap_or(\"\");\n        let current_url = format!(\"{prefix_str}{current_route_str}\");\n        let state = myself.create_state();\n        let _ = replace_state_with_url(&myself.history, &state, Some(&current_url));\n\n        myself\n    }\n\n    fn new_inner(prefix: Option<String>, do_scroll_restoration: bool) -> Self {\n        let window = window().expect(\"access to `window`\");\n        let history = window.history().expect(\"`window` has access to `history`\");\n\n        if do_scroll_restoration {\n            history\n                .set_scroll_restoration(ScrollRestoration::Manual)\n                .expect(\"`history` can set scroll restoration\");\n        }\n\n        let prefix = prefix\n            // If there isn't a base path, try to grab one from the CLI\n            .or_else(dioxus_cli_config::web_base_path)\n            // Normalize the prefix to start and end with no slashes\n            .as_ref()\n            .map(|prefix| prefix.trim_matches('/'))\n            // If the prefix is empty, don't add it\n            .filter(|prefix| !prefix.is_empty())\n            // Otherwise, start with a slash\n            .map(|prefix| format!(\"/{prefix}\"));\n\n        Self {\n            do_scroll_restoration,\n            history,\n            prefix,\n            window,\n        }\n    }\n\n    fn scroll_pos(&self) -> ScrollPosition {\n        if self.do_scroll_restoration {\n            ScrollPosition::of_window(&self.window)\n        } else {\n            Default::default()\n        }\n    }\n\n    fn create_state(&self) -> [f64; 2] {\n        let scroll = self.scroll_pos();\n        [scroll.x, scroll.y]\n    }\n\n    fn handle_nav(&self) {\n        if self.do_scroll_restoration {\n            self.window.scroll_to_with_x_and_y(0.0, 0.0)\n        }\n    }\n\n    fn route_from_location(&self) -> String {\n        let location = self.window.location();\n        let path = location.pathname().unwrap_or_else(|_| \"/\".into())\n            + &location.search().unwrap_or(\"\".into())\n            + &location.hash().unwrap_or(\"\".into());\n        let mut path = match self.prefix {\n            None => &path,\n            Some(ref prefix) => path.strip_prefix(prefix).unwrap_or(prefix),\n        };\n        // If the path is empty, parse the root route instead\n        if path.is_empty() {\n            path = \"/\"\n        }\n        path.to_string()\n    }\n\n    fn full_path(&self, state: &String) -> String {\n        match &self.prefix {\n            None => state.to_string(),\n            Some(prefix) => format!(\"{prefix}{state}\"),\n        }\n    }\n}\n\nimpl dioxus_history::History for WebHistory {\n    fn current_route(&self) -> String {\n        self.route_from_location()\n    }\n\n    fn current_prefix(&self) -> Option<String> {\n        self.prefix.clone()\n    }\n\n    fn go_back(&self) {\n        let _ = self.history.back();\n    }\n\n    fn go_forward(&self) {\n        let _ = self.history.forward();\n    }\n\n    fn push(&self, state: String) {\n        if state == self.current_route() {\n            // don't push the same state twice\n            return;\n        }\n\n        let w = window().expect(\"access to `window`\");\n        let h = w.history().expect(\"`window` has access to `history`\");\n\n        // update the scroll position before pushing the new state\n        update_scroll(&w, &h);\n\n        if push_state_and_url(&self.history, &self.create_state(), self.full_path(&state)).is_ok() {\n            self.handle_nav();\n        }\n    }\n\n    fn replace(&self, state: String) {\n        if replace_state_with_url(\n            &self.history,\n            &self.create_state(),\n            Some(&self.full_path(&state)),\n        )\n        .is_ok()\n        {\n            self.handle_nav();\n        }\n    }\n\n    fn external(&self, url: String) -> bool {\n        self.window.location().set_href(&url).is_ok()\n    }\n\n    fn updater(&self, callback: std::sync::Arc<dyn Fn() + Send + Sync>) {\n        let w = self.window.clone();\n        let h = self.history.clone();\n        let d = self.do_scroll_restoration;\n\n        let function = Closure::wrap(Box::new(move |_| {\n            (*callback)();\n            if d {\n                if let Some([x, y]) = get_current(&h) {\n                    ScrollPosition { x, y }.scroll_to(w.clone())\n                }\n            }\n        }) as Box<dyn FnMut(Event)>);\n        self.window\n            .add_event_listener_with_callback(\n                \"popstate\",\n                &function.into_js_value().unchecked_into(),\n            )\n            .unwrap();\n    }\n}\n\n/// A [`dioxus_history::History`] provider that integrates with a browser via the [History API](https://developer.mozilla.org/en-US/docs/Web/API/History_API)\n/// but uses the url fragment for the route. This allows serving as a single html file or on a single url path.\npub struct HashHistory {\n    do_scroll_restoration: bool,\n    history: History,\n    pathname: String,\n    window: Window,\n}\n\nimpl Default for HashHistory {\n    fn default() -> Self {\n        Self::new(true)\n    }\n}\n\nimpl HashHistory {\n    /// Create a new [`HashHistory`].\n    ///\n    /// If `do_scroll_restoration` is [`true`], [`HashHistory`] will take control of the history\n    /// state. It'll also set the browsers scroll restoration to `manual`.\n    pub fn new(do_scroll_restoration: bool) -> Self {\n        let myself = Self::new_inner(do_scroll_restoration);\n\n        let current_route = dioxus_history::History::current_route(&myself);\n        let current_route_str = current_route.to_string();\n        let pathname_str = &myself.pathname;\n        let current_url = format!(\"{pathname_str}#{current_route_str}\");\n        let state = myself.create_state();\n        let _ = replace_state_with_url(&myself.history, &state, Some(&current_url));\n\n        myself\n    }\n\n    fn new_inner(do_scroll_restoration: bool) -> Self {\n        let window = window().expect(\"access to `window`\");\n        let history = window.history().expect(\"`window` has access to `history`\");\n        let pathname = window.location().pathname().unwrap();\n\n        if do_scroll_restoration {\n            history\n                .set_scroll_restoration(ScrollRestoration::Manual)\n                .expect(\"`history` can set scroll restoration\");\n        }\n\n        Self {\n            do_scroll_restoration,\n            history,\n            pathname,\n            window,\n        }\n    }\n\n    fn scroll_pos(&self) -> ScrollPosition {\n        if self.do_scroll_restoration {\n            ScrollPosition::of_window(&self.window)\n        } else {\n            Default::default()\n        }\n    }\n\n    fn create_state(&self) -> [f64; 2] {\n        let scroll = self.scroll_pos();\n        [scroll.x, scroll.y]\n    }\n\n    fn full_path(&self, state: &String) -> String {\n        format!(\"{}#{state}\", self.pathname)\n    }\n\n    fn handle_nav(&self) {\n        if self.do_scroll_restoration {\n            self.window.scroll_to_with_x_and_y(0.0, 0.0)\n        }\n    }\n}\n\nimpl dioxus_history::History for HashHistory {\n    fn current_route(&self) -> String {\n        let location = self.window.location();\n\n        let hash = location.hash().unwrap();\n        if hash.is_empty() {\n            // If the path is empty, parse the root route instead\n            \"/\".to_owned()\n        } else {\n            hash.trim_start_matches(\"#\").to_owned()\n        }\n    }\n\n    fn current_prefix(&self) -> Option<String> {\n        Some(format!(\"{}#\", self.pathname))\n    }\n\n    fn go_back(&self) {\n        let _ = self.history.back();\n    }\n\n    fn go_forward(&self) {\n        let _ = self.history.forward();\n    }\n\n    fn push(&self, state: String) {\n        if state == self.current_route() {\n            // don't push the same state twice\n            return;\n        }\n\n        let w = window().expect(\"access to `window`\");\n        let h = w.history().expect(\"`window` has access to `history`\");\n\n        // update the scroll position before pushing the new state\n        update_scroll(&w, &h);\n\n        if push_state_and_url(&self.history, &self.create_state(), self.full_path(&state)).is_ok() {\n            self.handle_nav();\n        }\n    }\n\n    fn replace(&self, state: String) {\n        if replace_state_with_url(\n            &self.history,\n            &self.create_state(),\n            Some(&self.full_path(&state)),\n        )\n        .is_ok()\n        {\n            self.handle_nav();\n        }\n    }\n\n    fn external(&self, url: String) -> bool {\n        self.window.location().set_href(&url).is_ok()\n    }\n\n    fn updater(&self, callback: std::sync::Arc<dyn Fn() + Send + Sync>) {\n        let w = self.window.clone();\n        let h = self.history.clone();\n        let d = self.do_scroll_restoration;\n\n        let function = Closure::wrap(Box::new(move |_| {\n            (*callback)();\n            if d {\n                if let Some([x, y]) = get_current(&h) {\n                    ScrollPosition { x, y }.scroll_to(w.clone())\n                }\n            }\n        }) as Box<dyn FnMut(Event)>);\n        self.window\n            .add_event_listener_with_callback(\n                \"popstate\",\n                &function.into_js_value().unchecked_into(),\n            )\n            .unwrap();\n    }\n}\n\n#[derive(Clone, Copy, Debug, Default)]\npub(crate) struct ScrollPosition {\n    pub x: f64,\n    pub y: f64,\n}\n\nimpl ScrollPosition {\n    pub(crate) fn of_window(window: &Window) -> Self {\n        Self {\n            x: window.scroll_x().unwrap_or_default(),\n            y: window.scroll_y().unwrap_or_default(),\n        }\n    }\n\n    pub(crate) fn scroll_to(&self, window: Window) {\n        let Self { x, y } = *self;\n        let f = Closure::wrap(\n            Box::new(move || window.scroll_to_with_x_and_y(x, y)) as Box<dyn FnMut()>\n        );\n        web_sys::window()\n            .expect(\"should be run in a context with a `Window` object (dioxus cannot be run from a web worker)\")\n            .request_animation_frame(&f.into_js_value().unchecked_into())\n            .expect(\"should register `requestAnimationFrame` OK\");\n    }\n}\n\npub(crate) fn replace_state_with_url(\n    history: &History,\n    value: &[f64; 2],\n    url: Option<&str>,\n) -> Result<(), JsValue> {\n    let position = js_sys::Array::new();\n    position.push(&JsValue::from(value[0]));\n    position.push(&JsValue::from(value[1]));\n    history.replace_state_with_url(&position, \"\", url)\n}\n\npub(crate) fn push_state_and_url(\n    history: &History,\n    value: &[f64; 2],\n    url: String,\n) -> Result<(), JsValue> {\n    let position = js_sys::Array::new();\n    position.push(&JsValue::from(value[0]));\n    position.push(&JsValue::from(value[1]));\n    history.push_state_with_url(&position, \"\", Some(&url))\n}\n\npub(crate) fn get_current(history: &History) -> Option<[f64; 2]> {\n    use wasm_bindgen::JsCast;\n    history.state().ok().and_then(|state| {\n        let state = state.dyn_into::<js_sys::Array>().ok()?;\n        let x = state.get(0).as_f64()?;\n        let y = state.get(1).as_f64()?;\n        Some([x, y])\n    })\n}\n\nfn update_scroll(window: &Window, history: &History) {\n    let scroll = ScrollPosition::of_window(window);\n    let _ = replace_state_with_url(history, &[scroll.x, scroll.y], None);\n}\n"
  },
  {
    "path": "packages/web/src/hydration/hydrate.rs",
    "content": "//! When hydrating streaming components:\n//! 1. Just hydrate the template on the outside\n//! 2. As we render the virtual dom initially, keep track of the server ids of the suspense boundaries\n//! 3. Register a callback for dx_hydrate(id, data) that takes some new data, reruns the suspense boundary with that new data and then rehydrates the node\n\nuse crate::dom::WebsysDom;\nuse dioxus_core::{\n    AttributeValue, DynamicNode, ElementId, ScopeId, ScopeState, SuspenseBoundaryProps,\n    SuspenseContext, TemplateNode, VNode, VirtualDom,\n};\nuse dioxus_fullstack_core::HydrationContext;\nuse futures_channel::mpsc::UnboundedReceiver;\nuse std::fmt::Write;\nuse RehydrationError::*;\n\nuse super::SuspenseMessage;\n\n#[derive(Debug)]\n#[non_exhaustive]\npub(crate) enum RehydrationError {\n    /// The client tried to rehydrate a vnode before the dom was built\n    VNodeNotInitialized,\n    /// The client tried to rehydrate a suspense boundary that was not mounted on the server\n    SuspenseHydrationIdNotFound,\n    /// The client tried to rehydrate a dom id that was not found on the server\n    ElementNotFound,\n}\n\n#[derive(Debug)]\nstruct SuspenseHydrationIdsNode {\n    /// The scope id of the suspense boundary\n    scope_id: ScopeId,\n    /// Children of this node\n    children: Vec<SuspenseHydrationIdsNode>,\n}\n\nimpl SuspenseHydrationIdsNode {\n    fn new(scope_id: ScopeId) -> Self {\n        Self {\n            scope_id,\n            children: Vec::new(),\n        }\n    }\n\n    fn traverse(&self, path: &[u32]) -> Option<&Self> {\n        match path {\n            [] => Some(self),\n            [id, rest @ ..] => self.children.get(*id as usize)?.traverse(rest),\n        }\n    }\n\n    fn traverse_mut(&mut self, path: &[u32]) -> Option<&mut Self> {\n        match path {\n            [] => Some(self),\n            [id, rest @ ..] => self.children.get_mut(*id as usize)?.traverse_mut(rest),\n        }\n    }\n}\n\n/// Streaming hydration happens in waves. The server assigns suspense hydrations ids based on the order\n/// the suspense boundaries are discovered in which should be consistent on the client and server.\n///\n/// This struct keeps track of the order the suspense boundaries are discovered in on the client so we can map the id in the dom to the scope we need to rehydrate.\n///\n/// Diagram: <https://excalidraw.com/#json=4NxmW90g0207Y62lESxfF,vP_Yn6j7k23utq2HZIsuiw>\n#[derive(Default, Debug)]\npub(crate) struct SuspenseHydrationIds {\n    /// A dense mapping from traversal order to the scope id of the suspense boundary\n    /// The suspense boundary may be unmounted if the component was removed after partial hydration on the client\n    children: Vec<SuspenseHydrationIdsNode>,\n    current_path: Vec<u32>,\n}\n\nimpl SuspenseHydrationIds {\n    /// Add a suspense boundary to the list of suspense boundaries. This should only be called on the root scope after the first rebuild (which happens on the server) and on suspense boundaries that are resolved from the server.\n    /// Running this on a scope that is only created on the client may cause hydration issues.\n    fn add_suspense_boundary(&mut self, id: ScopeId) {\n        match self.current_path.as_slice() {\n            // This is a root node, add the new node\n            [] => {\n                self.children.push(SuspenseHydrationIdsNode::new(id));\n            }\n            // This isn't a root node, traverse into children and add the new node\n            [first_index, rest @ ..] => {\n                let child_node = self.children[*first_index as usize]\n                    .traverse_mut(rest)\n                    .unwrap();\n                child_node.children.push(SuspenseHydrationIdsNode::new(id));\n            }\n        }\n    }\n\n    /// Get the scope id of the suspense boundary from the id in the dom\n    fn get_suspense_boundary(&self, path: &[u32]) -> Option<ScopeId> {\n        let root = self.children.get(path[0] as usize)?;\n        root.traverse(&path[1..]).map(|node| node.scope_id)\n    }\n}\n\nimpl WebsysDom {\n    pub fn rehydrate_streaming(&mut self, message: SuspenseMessage, dom: &mut VirtualDom) {\n        if let Err(err) = self.rehydrate_streaming_inner(message, dom) {\n            tracing::error!(\"Rehydration failed. {:?}\", err);\n        }\n    }\n\n    fn rehydrate_streaming_inner(\n        &mut self,\n        message: SuspenseMessage,\n        dom: &mut VirtualDom,\n    ) -> Result<(), RehydrationError> {\n        let SuspenseMessage {\n            suspense_path,\n            data,\n            #[cfg(debug_assertions)]\n            debug_types,\n            #[cfg(debug_assertions)]\n            debug_locations,\n        } = message;\n\n        let document = web_sys::window().unwrap().document().unwrap();\n        // Before we start rehydrating the suspense boundary we need to check that the suspense boundary exists. It may have been removed on the client.\n        let resolved_suspense_id = path_to_resolved_suspense_id(&suspense_path);\n        let resolved_suspense_element = document\n            .get_element_by_id(&resolved_suspense_id)\n            .ok_or(RehydrationError::ElementNotFound)?;\n\n        // First convert the dom id into a scope id based on the discovery order of the suspense boundaries.\n        // This may fail if the id is not parsable, or if the suspense boundary was removed after partial hydration on the client.\n        let id = self\n            .suspense_hydration_ids\n            .get_suspense_boundary(&suspense_path)\n            .ok_or(RehydrationError::SuspenseHydrationIdNotFound)?;\n\n        // Push the new nodes onto the stack\n        let mut current_child = resolved_suspense_element.first_child();\n        let mut children = Vec::new();\n        while let Some(node) = current_child {\n            children.push(node.clone());\n            current_child = node.next_sibling();\n            self.interpreter.base().push_root(node);\n        }\n\n        #[cfg(not(debug_assertions))]\n        let debug_types = None;\n        #[cfg(not(debug_assertions))]\n        let debug_locations = None;\n\n        let server_data = HydrationContext::from_serialized(&data, debug_types, debug_locations);\n        // If the server serialized an error into the suspense boundary, throw it on the client so that it bubbles up to the nearest error boundary\n        if let Some(error) = server_data.error_entry().get().ok().flatten() {\n            dom.in_runtime(|| dom.runtime().throw_error(id, error));\n        }\n        server_data.in_context(|| {\n            // rerun the scope with the new data\n            SuspenseBoundaryProps::resolve_suspense(\n                id,\n                dom,\n                self,\n                |to| {\n                    // Switch to only writing templates\n                    to.skip_mutations = true;\n                },\n                children.len(),\n            );\n            self.skip_mutations = false;\n        });\n\n        // Flush the mutations that will swap the placeholder nodes with the resolved nodes\n        self.flush_edits();\n\n        // Remove the streaming div\n        resolved_suspense_element.remove();\n\n        let Some(root_scope) = dom.get_scope(id) else {\n            // If the scope was removed on the client, we may not be able to rehydrate it, but this shouldn't cause an error\n            return Ok(());\n        };\n\n        // As we hydrate the suspense boundary, set the current path to the path of the suspense boundary\n        self.suspense_hydration_ids\n            .current_path\n            .clone_from(&suspense_path);\n        self.start_hydration_at_scope(root_scope, dom, children)?;\n\n        Ok(())\n    }\n\n    fn start_hydration_at_scope(\n        &mut self,\n        scope: &ScopeState,\n        dom: &VirtualDom,\n        under: Vec<web_sys::Node>,\n    ) -> Result<(), RehydrationError> {\n        let mut ids = Vec::new();\n        let mut to_mount = Vec::new();\n\n        // Recursively rehydrate the nodes under the scope\n        self.rehydrate_scope(scope, dom, &mut ids, &mut to_mount)?;\n\n        self.interpreter.base().hydrate(ids, under);\n\n        #[cfg(feature = \"mounted\")]\n        for id in to_mount {\n            self.send_mount_event(id);\n        }\n\n        Ok(())\n    }\n\n    pub fn rehydrate(\n        &mut self,\n        vdom: &VirtualDom,\n    ) -> Result<UnboundedReceiver<SuspenseMessage>, RehydrationError> {\n        let (mut tx, rx) = futures_channel::mpsc::unbounded();\n        let closure =\n            move |path: Vec<u32>,\n                  data: js_sys::Uint8Array,\n                  #[allow(unused)] debug_types: Option<Vec<String>>,\n                  #[allow(unused)] debug_locations: Option<Vec<String>>| {\n                let data = data.to_vec();\n                _ = tx.start_send(SuspenseMessage {\n                    suspense_path: path,\n                    data,\n                    #[cfg(debug_assertions)]\n                    debug_types,\n                    #[cfg(debug_assertions)]\n                    debug_locations,\n                });\n            };\n        let closure = wasm_bindgen::closure::Closure::new(closure);\n        dioxus_interpreter_js::minimal_bindings::register_rehydrate_chunk_for_streaming_debug(\n            &closure,\n        );\n        closure.forget();\n\n        // Rehydrate the root scope that was rendered on the server. We will likely run into suspense boundaries.\n        // Any suspense boundaries we run into are stored for hydration later.\n        self.start_hydration_at_scope(vdom.base_scope(), vdom, vec![self.root.clone()])?;\n\n        Ok(rx)\n    }\n\n    fn rehydrate_scope(\n        &mut self,\n        scope: &ScopeState,\n        dom: &VirtualDom,\n        ids: &mut Vec<u32>,\n        to_mount: &mut Vec<ElementId>,\n    ) -> Result<(), RehydrationError> {\n        // If this scope is a suspense boundary that is pending, add it to the list of pending suspense boundaries\n        if let Some(suspense) =\n            SuspenseContext::downcast_suspense_boundary_from_scope(&dom.runtime(), scope.id())\n        {\n            if suspense.has_suspended_tasks() {\n                self.suspense_hydration_ids\n                    .add_suspense_boundary(scope.id());\n            }\n        }\n\n        self.rehydrate_vnode(dom, scope.root_node(), ids, to_mount)\n    }\n\n    fn rehydrate_vnode(\n        &mut self,\n        dom: &VirtualDom,\n        vnode: &VNode,\n        ids: &mut Vec<u32>,\n        to_mount: &mut Vec<ElementId>,\n    ) -> Result<(), RehydrationError> {\n        for (i, root) in vnode.template.roots.iter().enumerate() {\n            self.rehydrate_template_node(\n                dom,\n                vnode,\n                root,\n                ids,\n                to_mount,\n                Some(vnode.mounted_root(i, dom).ok_or(VNodeNotInitialized)?),\n            )?;\n        }\n        Ok(())\n    }\n\n    fn rehydrate_template_node(\n        &mut self,\n        dom: &VirtualDom,\n        vnode: &VNode,\n        node: &TemplateNode,\n        ids: &mut Vec<u32>,\n        to_mount: &mut Vec<ElementId>,\n        root_id: Option<ElementId>,\n    ) -> Result<(), RehydrationError> {\n        match node {\n            TemplateNode::Element {\n                children, attrs, ..\n            } => {\n                let mut mounted_id = root_id;\n                for attr in *attrs {\n                    if let dioxus_core::TemplateAttribute::Dynamic { id } = attr {\n                        let attributes = &*vnode.dynamic_attrs[*id];\n                        let id = vnode\n                            .mounted_dynamic_attribute(*id, dom)\n                            .ok_or(VNodeNotInitialized)?;\n                        // We always need to hydrate the node even if the attributes are empty so we have\n                        // a mount for the node later. This could be spread attributes that are currently empty,\n                        // but will be filled later\n                        mounted_id = Some(id);\n                        for attribute in attributes {\n                            let value = &attribute.value;\n                            if let AttributeValue::Listener(_) = value {\n                                if attribute.name == \"onmounted\" {\n                                    to_mount.push(id);\n                                }\n                            }\n                        }\n                    }\n                }\n                if let Some(id) = mounted_id {\n                    ids.push(id.0 as u32);\n                }\n                if !children.is_empty() {\n                    for child in *children {\n                        self.rehydrate_template_node(dom, vnode, child, ids, to_mount, None)?;\n                    }\n                }\n            }\n            TemplateNode::Dynamic { id } => self.rehydrate_dynamic_node(\n                dom,\n                &vnode.dynamic_nodes[*id],\n                *id,\n                vnode,\n                ids,\n                to_mount,\n            )?,\n            TemplateNode::Text { .. } => {\n                if let Some(id) = root_id {\n                    ids.push(id.0 as u32);\n                }\n            }\n        }\n        Ok(())\n    }\n\n    fn rehydrate_dynamic_node(\n        &mut self,\n        dom: &VirtualDom,\n        dynamic: &DynamicNode,\n        dynamic_node_index: usize,\n        vnode: &VNode,\n        ids: &mut Vec<u32>,\n        to_mount: &mut Vec<ElementId>,\n    ) -> Result<(), RehydrationError> {\n        match dynamic {\n            dioxus_core::DynamicNode::Text(_) | dioxus_core::DynamicNode::Placeholder(_) => {\n                ids.push(\n                    vnode\n                        .mounted_dynamic_node(dynamic_node_index, dom)\n                        .ok_or(VNodeNotInitialized)?\n                        .0 as u32,\n                );\n            }\n            dioxus_core::DynamicNode::Component(comp) => {\n                let scope = comp\n                    .mounted_scope(dynamic_node_index, vnode, dom)\n                    .ok_or(VNodeNotInitialized)?;\n                self.rehydrate_scope(scope, dom, ids, to_mount)?;\n            }\n            dioxus_core::DynamicNode::Fragment(fragment) => {\n                for vnode in fragment {\n                    self.rehydrate_vnode(dom, vnode, ids, to_mount)?;\n                }\n            }\n        }\n        Ok(())\n    }\n}\n\nfn write_comma_separated(id: &[u32], into: &mut String) {\n    let mut iter = id.iter();\n    if let Some(first) = iter.next() {\n        write!(into, \"{first}\").unwrap();\n    }\n    for id in iter {\n        write!(into, \",{id}\").unwrap();\n    }\n}\n\nfn path_to_resolved_suspense_id(path: &[u32]) -> String {\n    let mut resolved_suspense_id_formatted = String::from(\"ds-\");\n    write_comma_separated(path, &mut resolved_suspense_id_formatted);\n    resolved_suspense_id_formatted.push_str(\"-r\");\n    resolved_suspense_id_formatted\n}\n"
  },
  {
    "path": "packages/web/src/hydration/mod.rs",
    "content": "#[cfg(feature = \"hydrate\")]\nmod hydrate;\n\n#[cfg(feature = \"hydrate\")]\n#[allow(unused)]\npub use hydrate::*;\n\n/// The message sent from the server to the client to hydrate a suspense boundary\n#[derive(Debug)]\npub(crate) struct SuspenseMessage {\n    #[cfg(feature = \"hydrate\")]\n    /// The path to the suspense boundary. Each element in the path is an index into the children of the suspense boundary (or the root node) in the order they are first created\n    suspense_path: Vec<u32>,\n    #[cfg(feature = \"hydrate\")]\n    /// The data to hydrate the suspense boundary with\n    data: Vec<u8>,\n    #[cfg(feature = \"hydrate\")]\n    #[cfg(debug_assertions)]\n    /// The type names of the data\n    debug_types: Option<Vec<String>>,\n    #[cfg(feature = \"hydrate\")]\n    #[cfg(debug_assertions)]\n    /// The location of the data in the source code\n    debug_locations: Option<Vec<String>>,\n}\n"
  },
  {
    "path": "packages/web/src/js/eval.js",
    "content": "class Channel{pending;waiting;constructor(){this.pending=[],this.waiting=[]}send(data){if(this.waiting.length>0){this.waiting.shift()(data);return}this.pending.push(data)}async recv(){return new Promise((resolve,_reject)=>{if(this.pending.length>0){resolve(this.pending.shift());return}this.waiting.push(resolve)})}}class WeakDioxusChannel{inner;constructor(channel){this.inner=new WeakRef(channel)}rustSend(data){let channel=this.inner.deref();if(channel)channel.rustSend(data)}async rustRecv(){let channel=this.inner.deref();if(channel)return await channel.rustRecv()}}class DioxusChannel{weak(){return new WeakDioxusChannel(this)}}globalThis.__nextChannelId=0;globalThis.__channels=[];class WebDioxusChannel extends DioxusChannel{js_to_rust;rust_to_js;owner;id;constructor(owner){super();this.owner=owner,this.js_to_rust=new Channel,this.rust_to_js=new Channel,this.id=globalThis.__nextChannelId,globalThis.__channels[this.id]=this,globalThis.__nextChannelId+=1}weak(){return new WeakDioxusChannel(this)}async recv(){return await this.rust_to_js.recv()}send(data){this.js_to_rust.send(data)}rustSend(data){this.rust_to_js.send(data)}async rustRecv(){return await this.js_to_rust.recv()}close(){globalThis.__channels[this.id]=null}}export{WebDioxusChannel,WeakDioxusChannel};\n"
  },
  {
    "path": "packages/web/src/js/hash.txt",
    "content": "[1857573420427678443]"
  },
  {
    "path": "packages/web/src/launch.rs",
    "content": "//! This module contains the `launch` function, which is the main entry point for dioxus web\npub use crate::Config;\nuse dioxus_core::{Element, VirtualDom};\nuse std::any::Any;\n\n/// Launch the web application with the given root component, context and config\n///\n/// For a builder API, see `LaunchBuilder` defined in the `dioxus` crate.\npub fn launch(\n    root: fn() -> Element,\n    contexts: Vec<Box<dyn Fn() -> Box<dyn Any> + Send + Sync>>,\n    platform_config: Vec<Box<dyn Any>>,\n) {\n    let mut vdom = VirtualDom::new(root);\n    for context in contexts {\n        vdom.insert_any_root_context(context());\n    }\n\n    let platform_config = *platform_config\n        .into_iter()\n        .find_map(|cfg| cfg.downcast::<Config>().ok())\n        .unwrap_or_default();\n    launch_virtual_dom(vdom, platform_config)\n}\n\n/// Launch the web application with a prebuild virtual dom\n///\n/// For a builder API, see `LaunchBuilder` defined in the `dioxus` crate.\npub fn launch_virtual_dom(vdom: VirtualDom, platform_config: Config) {\n    wasm_bindgen_futures::spawn_local(async move {\n        crate::run(vdom, platform_config).await;\n    });\n}\n\n/// Launch the web application with the given root component and config\npub fn launch_cfg(root: fn() -> Element, platform_config: Config) {\n    launch(root, Vec::new(), vec![Box::new(platform_config)])\n}\n"
  },
  {
    "path": "packages/web/src/lib.rs",
    "content": "#![doc(html_logo_url = \"https://avatars.githubusercontent.com/u/79236386\")]\n#![doc(html_favicon_url = \"https://avatars.githubusercontent.com/u/79236386\")]\n#![deny(missing_docs)]\n\n//! # Dioxus Web\n\npub use crate::cfg::Config;\nuse crate::hydration::SuspenseMessage;\nuse dioxus_core::{ScopeId, VirtualDom};\nuse dom::WebsysDom;\nuse futures_util::{pin_mut, select, FutureExt, StreamExt};\n\nmod cfg;\nmod dom;\n\nmod events;\npub mod launch;\nmod mutations;\npub use events::*;\n\n#[cfg(feature = \"document\")]\nmod document;\n#[cfg(feature = \"document\")]\nmod history;\n#[cfg(feature = \"document\")]\npub use document::WebDocument;\n#[cfg(feature = \"document\")]\npub use history::{HashHistory, WebHistory};\n\nmod files;\npub use files::*;\n\nmod data_transfer;\npub use data_transfer::*;\n\n#[cfg(all(feature = \"devtools\", debug_assertions))]\nmod devtools;\n\nmod hydration;\n#[allow(unused)]\npub use hydration::*;\n\n/// Runs the app as a future that can be scheduled around the main thread.\n///\n/// Polls futures internal to the VirtualDOM, hence the async nature of this function.\n///\n/// # Example\n///\n/// ```ignore, rust\n/// let app_fut = dioxus_web::run_with_props(App, RootProps { name: String::from(\"foo\") });\n/// wasm_bindgen_futures::spawn_local(app_fut);\n/// ```\npub async fn run(mut virtual_dom: VirtualDom, web_config: Config) -> ! {\n    #[cfg(all(feature = \"devtools\", debug_assertions))]\n    let mut hotreload_rx = devtools::init(&web_config);\n\n    #[cfg(feature = \"document\")]\n    if let Some(history) = web_config.history.clone() {\n        virtual_dom.in_scope(ScopeId::ROOT, || dioxus_core::provide_context(history));\n    }\n\n    #[cfg(feature = \"document\")]\n    virtual_dom.in_runtime(document::init_document);\n\n    let runtime = virtual_dom.runtime();\n\n    // If the hydrate feature is enabled, launch the client with hydration enabled\n    let should_hydrate = web_config.hydrate || cfg!(feature = \"hydrate\");\n\n    let mut websys_dom = WebsysDom::new(web_config, runtime);\n\n    let mut hydration_receiver: Option<futures_channel::mpsc::UnboundedReceiver<SuspenseMessage>> =\n        None;\n\n    if should_hydrate {\n        // If we are hydrating, then the hotreload message might actually have a patch for us to apply.\n        // Let's wait for a moment to see if we get a hotreload message before we start hydrating.\n        // That way, the hydration will use the same functions that the server used to serialize the data.\n        #[cfg(all(feature = \"devtools\", debug_assertions))]\n        loop {\n            let mut timeout = gloo_timers::future::TimeoutFuture::new(100).fuse();\n            futures_util::select! {\n                msg = hotreload_rx.next() => {\n                    if let Some(msg) = msg {\n                        if msg.for_build_id == Some(dioxus_cli_config::build_id()) {\n                            dioxus_devtools::apply_changes(&virtual_dom, &msg);\n                        }\n                    }\n                }\n                _ = &mut timeout => {\n                    break;\n                }\n            }\n        }\n\n        #[cfg(feature = \"hydrate\")]\n        {\n            use dioxus_fullstack_core::HydrationContext;\n\n            websys_dom.skip_mutations = true;\n            // Get the initial hydration data from the client\n            #[wasm_bindgen::prelude::wasm_bindgen(inline_js = r#\"\n                export function get_initial_hydration_data() {\n                    const decoded = atob(window.initial_dioxus_hydration_data);\n                    return Uint8Array.from(decoded, (c) => c.charCodeAt(0))\n                }\n                export function get_initial_hydration_debug_types() {\n                    return window.initial_dioxus_hydration_debug_types;\n                }\n                export function get_initial_hydration_debug_locations() {\n                    return window.initial_dioxus_hydration_debug_locations;\n                }\n            \"#)]\n            extern \"C\" {\n                fn get_initial_hydration_data() -> js_sys::Uint8Array;\n                fn get_initial_hydration_debug_types() -> Option<Vec<String>>;\n                fn get_initial_hydration_debug_locations() -> Option<Vec<String>>;\n            }\n            let hydration_data = get_initial_hydration_data().to_vec();\n\n            // If we are running in debug mode, also get the debug types and locations\n            #[cfg(debug_assertions)]\n            let debug_types = get_initial_hydration_debug_types();\n            #[cfg(not(debug_assertions))]\n            let debug_types = None;\n            #[cfg(debug_assertions)]\n            let debug_locations = get_initial_hydration_debug_locations();\n            #[cfg(not(debug_assertions))]\n            let debug_locations = None;\n\n            let server_data =\n                HydrationContext::from_serialized(&hydration_data, debug_types, debug_locations);\n            // If the server serialized an error into the root suspense boundary, throw it into the root scope\n            if let Some(error) = server_data.error_entry().get().ok().flatten() {\n                virtual_dom.in_runtime(|| virtual_dom.runtime().throw_error(ScopeId::APP, error));\n            }\n            server_data.in_context(|| {\n                virtual_dom.in_scope(ScopeId::ROOT, || {\n                    // Provide a hydration compatible create error boundary method\n                    dioxus_core::provide_create_error_boundary(\n                        dioxus_fullstack_core::init_error_boundary,\n                    );\n                    #[cfg(feature = \"document\")]\n                    document::init_fullstack_document();\n                });\n                virtual_dom.rebuild(&mut websys_dom);\n            });\n            websys_dom.skip_mutations = false;\n\n            let rx = websys_dom.rehydrate(&virtual_dom).unwrap();\n            hydration_receiver = Some(rx);\n\n            #[cfg(feature = \"mounted\")]\n            {\n                // Flush any mounted events that were queued up while hydrating\n                websys_dom.flush_queued_mounted_events();\n            }\n        }\n        #[cfg(not(feature = \"hydrate\"))]\n        {\n            panic!(\"Hydration is not enabled. Please enable the `hydrate` feature.\");\n        }\n    } else {\n        virtual_dom.rebuild(&mut websys_dom);\n\n        websys_dom.flush_edits();\n    }\n\n    loop {\n        // if virtual dom has nothing, wait for it to have something before requesting idle time\n        // if there is work then this future resolves immediately.\n        #[cfg(all(feature = \"devtools\", debug_assertions))]\n        let template;\n        #[allow(unused)]\n        let mut hydration_work: Option<SuspenseMessage> = None;\n\n        {\n            let work = virtual_dom.wait_for_work().fuse();\n            pin_mut!(work);\n\n            let mut hydration_receiver_iter = futures_util::stream::iter(&mut hydration_receiver)\n                .fuse()\n                .flatten();\n            let mut rx_hydration = hydration_receiver_iter.select_next_some();\n\n            #[cfg(all(feature = \"devtools\", debug_assertions))]\n            #[allow(unused)]\n            {\n                let mut devtools_next = hotreload_rx.select_next_some();\n                select! {\n                    _ = work => {\n                        template = None;\n                    },\n                    new_template = devtools_next => {\n                        template = Some(new_template);\n                    },\n                    hydration_data = rx_hydration => {\n                        template = None;\n                        #[cfg(feature = \"hydrate\")]\n                        {\n                            hydration_work = Some(hydration_data);\n                        }\n                    },\n                }\n            }\n\n            #[cfg(not(all(feature = \"devtools\", debug_assertions)))]\n            #[allow(unused)]\n            {\n                select! {\n                    _ = work => {},\n                    hyd = rx_hydration => {\n                        #[cfg(feature = \"hydrate\")]\n                        {\n                            hydration_work = Some(hyd);\n                        }\n                    }\n                }\n            }\n        }\n\n        #[cfg(all(feature = \"devtools\", debug_assertions))]\n        if let Some(hr_msg) = template {\n            // Replace all templates\n            dioxus_devtools::apply_changes(&virtual_dom, &hr_msg);\n\n            if !hr_msg.assets.is_empty() {\n                crate::devtools::invalidate_browser_asset_cache();\n            }\n\n            if hr_msg.for_build_id == Some(dioxus_cli_config::build_id()) {\n                devtools::show_toast(\n                    \"Hot-patch success!\",\n                    &format!(\"App successfully patched in {} ms\", hr_msg.ms_elapsed),\n                    devtools::ToastLevel::Success,\n                    std::time::Duration::from_millis(2000),\n                    false,\n                );\n            }\n        }\n\n        #[cfg(feature = \"hydrate\")]\n        if let Some(hydration_data) = hydration_work {\n            websys_dom.rehydrate_streaming(hydration_data, &mut virtual_dom);\n        }\n\n        // Todo: This is currently disabled because it has a negative impact on response times for events but it could be re-enabled for tasks\n        // Jank free rendering\n        //\n        // 1. wait for the browser to give us \"idle\" time\n        // 2. During idle time, diff the dom\n        // 3. Stop diffing if the deadline is exceeded\n        // 4. Wait for the animation frame to patch the dom\n\n        // wait for the mainthread to schedule us in\n        // let deadline = work_loop.wait_for_idle_time().await;\n\n        // run the virtualdom work phase until the frame deadline is reached\n        virtual_dom.render_immediate(&mut websys_dom);\n\n        // wait for the animation frame to fire so we can apply our changes\n        // work_loop.wait_for_raf().await;\n\n        websys_dom.flush_edits();\n    }\n}\n"
  },
  {
    "path": "packages/web/src/mutations.rs",
    "content": "use crate::dom::WebsysDom;\nuse dioxus_core::{\n    AttributeValue, ElementId, Template, TemplateAttribute, TemplateNode, WriteMutations,\n};\nuse dioxus_core_types::event_bubbles;\nuse dioxus_interpreter_js::minimal_bindings;\nuse wasm_bindgen::JsCast;\nuse wasm_bindgen::JsValue;\n\nimpl WebsysDom {\n    pub(crate) fn create_template_node(&self, v: &TemplateNode) -> web_sys::Node {\n        use TemplateNode::*;\n        match v {\n            Element {\n                tag,\n                namespace,\n                attrs,\n                children,\n                ..\n            } => {\n                let el = match namespace {\n                    Some(ns) => self.document.create_element_ns(Some(ns), tag).unwrap(),\n                    None => self.document.create_element(tag).unwrap(),\n                };\n                for attr in *attrs {\n                    if let TemplateAttribute::Static {\n                        name,\n                        value,\n                        namespace,\n                    } = attr\n                    {\n                        minimal_bindings::setAttributeInner(\n                            el.clone().into(),\n                            name,\n                            JsValue::from_str(value),\n                            *namespace,\n                        );\n                    }\n                }\n                for child in *children {\n                    let _ = el.append_child(&self.create_template_node(child));\n                }\n                el.dyn_into().unwrap()\n            }\n            Text { text } => self.document.create_text_node(text).dyn_into().unwrap(),\n            Dynamic { .. } => {\n                let placeholder = self.document.create_comment(\"placeholder\");\n                placeholder.dyn_into().unwrap()\n            }\n        }\n    }\n\n    pub fn flush_edits(&mut self) {\n        self.interpreter.flush();\n\n        // Now that we've flushed the edits and the dom nodes exist, we can send the mounted events.\n        #[cfg(feature = \"mounted\")]\n        self.flush_queued_mounted_events();\n    }\n\n    #[cfg(feature = \"mounted\")]\n    pub(crate) fn flush_queued_mounted_events(&mut self) {\n        for id in self.queued_mounted_events.drain(..) {\n            let node = self.interpreter.base().get_node(id.0 as u32);\n            if let Some(element) = node.dyn_ref::<web_sys::Element>() {\n                let event = dioxus_core::Event::new(\n                    std::rc::Rc::new(dioxus_html::PlatformEventData::new(Box::new(\n                        element.clone(),\n                    ))) as std::rc::Rc<dyn std::any::Any>,\n                    false,\n                );\n                let name = \"mounted\";\n                self.runtime.handle_event(name, event, id)\n            }\n        }\n    }\n\n    #[cfg(feature = \"mounted\")]\n    pub(crate) fn send_mount_event(&mut self, id: ElementId) {\n        self.queued_mounted_events.push(id);\n    }\n\n    #[inline]\n    fn skip_mutations(&self) -> bool {\n        #[cfg(feature = \"hydrate\")]\n        {\n            self.skip_mutations\n        }\n        #[cfg(not(feature = \"hydrate\"))]\n        {\n            false\n        }\n    }\n}\n\nimpl WriteMutations for WebsysDom {\n    fn append_children(&mut self, id: ElementId, m: usize) {\n        if self.skip_mutations() {\n            return;\n        }\n        self.interpreter.append_children(id.0 as u32, m as u16)\n    }\n\n    fn assign_node_id(&mut self, path: &'static [u8], id: ElementId) {\n        if self.skip_mutations() {\n            return;\n        }\n        self.interpreter\n            .assign_id(path.as_ptr() as u32, path.len() as u8, id.0 as u32)\n    }\n\n    fn create_placeholder(&mut self, id: ElementId) {\n        if self.skip_mutations() {\n            return;\n        }\n        self.interpreter.create_placeholder(id.0 as u32)\n    }\n\n    fn create_text_node(&mut self, value: &str, id: ElementId) {\n        if self.skip_mutations() {\n            return;\n        }\n        self.interpreter.create_text_node(value, id.0 as u32)\n    }\n\n    fn load_template(&mut self, template: Template, index: usize, id: ElementId) {\n        if self.skip_mutations() {\n            return;\n        }\n        let tmpl_id = self.templates.get(&template).cloned().unwrap_or_else(|| {\n            let mut roots = vec![];\n            for root in template.roots {\n                roots.push(self.create_template_node(root))\n            }\n            let id = self.templates.len() as u16;\n            self.templates.insert(template, id);\n            self.interpreter.base().save_template(roots, id);\n            id\n        });\n\n        self.interpreter\n            .load_template(tmpl_id, index as u16, id.0 as u32)\n    }\n\n    fn replace_node_with(&mut self, id: ElementId, m: usize) {\n        if self.skip_mutations() {\n            return;\n        }\n        self.interpreter.replace_with(id.0 as u32, m as u16)\n    }\n\n    fn replace_placeholder_with_nodes(&mut self, path: &'static [u8], m: usize) {\n        if self.skip_mutations() {\n            return;\n        }\n        self.interpreter\n            .replace_placeholder(path.as_ptr() as u32, path.len() as u8, m as u16)\n    }\n\n    fn insert_nodes_after(&mut self, id: ElementId, m: usize) {\n        if self.skip_mutations() {\n            return;\n        }\n        self.interpreter.insert_after(id.0 as u32, m as u16)\n    }\n\n    fn insert_nodes_before(&mut self, id: ElementId, m: usize) {\n        if self.skip_mutations() {\n            return;\n        }\n        self.interpreter.insert_before(id.0 as u32, m as u16)\n    }\n\n    fn set_attribute(\n        &mut self,\n        name: &'static str,\n        ns: Option<&'static str>,\n        value: &AttributeValue,\n        id: ElementId,\n    ) {\n        if self.skip_mutations() {\n            return;\n        }\n        match value {\n            AttributeValue::Text(txt) => {\n                self.interpreter\n                    .set_attribute(id.0 as u32, name, txt, ns.unwrap_or_default())\n            }\n            AttributeValue::Float(f) => self.interpreter.set_attribute(\n                id.0 as u32,\n                name,\n                &f.to_string(),\n                ns.unwrap_or_default(),\n            ),\n            AttributeValue::Int(n) => self.interpreter.set_attribute(\n                id.0 as u32,\n                name,\n                &n.to_string(),\n                ns.unwrap_or_default(),\n            ),\n            AttributeValue::Bool(b) => self.interpreter.set_attribute(\n                id.0 as u32,\n                name,\n                if *b { \"true\" } else { \"false\" },\n                ns.unwrap_or_default(),\n            ),\n            AttributeValue::None => {\n                self.interpreter\n                    .remove_attribute(id.0 as u32, name, ns.unwrap_or_default())\n            }\n            _ => unreachable!(),\n        }\n    }\n\n    fn set_node_text(&mut self, value: &str, id: ElementId) {\n        if self.skip_mutations() {\n            return;\n        }\n        self.interpreter.set_text(id.0 as u32, value)\n    }\n\n    fn create_event_listener(&mut self, name: &'static str, id: ElementId) {\n        if self.skip_mutations() {\n            return;\n        }\n        // mounted events are fired immediately after the element is mounted.\n        if name == \"mounted\" {\n            #[cfg(feature = \"mounted\")]\n            self.send_mount_event(id);\n            return;\n        }\n\n        self.interpreter\n            .new_event_listener(name, id.0 as u32, event_bubbles(name) as u8);\n    }\n\n    fn remove_event_listener(&mut self, name: &'static str, id: ElementId) {\n        if self.skip_mutations() {\n            return;\n        }\n        if name == \"mounted\" {\n            return;\n        }\n\n        self.interpreter\n            .remove_event_listener(name, id.0 as u32, event_bubbles(name) as u8);\n    }\n\n    fn remove_node(&mut self, id: ElementId) {\n        if self.skip_mutations() {\n            return;\n        }\n        self.interpreter.remove(id.0 as u32)\n    }\n\n    fn push_root(&mut self, id: ElementId) {\n        if self.skip_mutations() {\n            return;\n        }\n        self.interpreter.push_root(id.0 as u32)\n    }\n}\n"
  },
  {
    "path": "packages/web/src/ts/eval.ts",
    "content": "import {\n  DioxusChannel,\n  Channel,\n  WeakDioxusChannel,\n} from \"../../../document/src/ts/eval\";\n\nglobalThis.__nextChannelId = 0;\nglobalThis.__channels = [];\n\nexport { WeakDioxusChannel };\nexport class WebDioxusChannel extends DioxusChannel {\n  js_to_rust: Channel;\n  rust_to_js: Channel;\n  owner: any;\n  id: number;\n\n  constructor(owner: any) {\n    super();\n    this.owner = owner;\n    this.js_to_rust = new Channel();\n    this.rust_to_js = new Channel();\n\n    this.id = globalThis.__nextChannelId;\n    globalThis.__channels[this.id] = this;\n    globalThis.__nextChannelId += 1;\n  }\n\n  // Return a weak reference to this channel\n  weak(): WeakDioxusChannel {\n    return new WeakDioxusChannel(this);\n  }\n\n  // Receive message from Rust\n  async recv() {\n    return await this.rust_to_js.recv();\n  }\n\n  // Send message to rust.\n  send(data: any) {\n    this.js_to_rust.send(data);\n  }\n\n  // Send data from rust to javascript\n  rustSend(data: any) {\n    this.rust_to_js.send(data);\n  }\n\n  // Receive data sent from javascript in rust\n  async rustRecv(): Promise<any> {\n    return await this.js_to_rust.recv();\n  }\n\n  // Close the channel, dropping it.\n  close(): void {\n    globalThis.__channels[this.id] = null;\n  }\n}\n"
  }
]